简体   繁体   中英

Function chaining in scala

I can't seem to figure out how to chain together these functions, any help or advice would be appreciated.

// Generic approach to adding flags to a command string
trait UpdateCommandString {
  def update[T](option: Option[T], flagName: String)(implicit command: String): String = {
    if (option.isEmpty)
      command
    else if (option.get.isInstanceOf[Boolean]) {
      if (option.get.asInstanceOf[Boolean])
        s"$command $flagName"
      command
    } else
      s"$command $flagName ${option.get.asInstanceOf[String]}"        
  }
}

// One example of flags (the program I'm using has literally 50+ flags
// so there will be a number of case classes that group them into related
// sets)
case class Flags(cache: Option[String] = None,
  errorlog: Option[String] = None,
  accesslog: Option[String] = None,
  verbose: Option[Boolean] = Some(false),
  auth: Option[Boolean] = Some(false)) extends UpdateCommandString {

  def applyToCommand(implicit command: String): String = {
    // These seem to apply separately, but I want to chain 
    // them together!
    update(cache, "-cache")
    update(errorlog, "-error")
    update(accesslog, "-access")
    update(auth, "-do-auth")
  }
}

// An example of what I'm trying to do
// Given a base command string and a bunch of case classes to apply
// to that string, I'd like to be able to call applyToCommand and 
// get back the modified command string
var command = "run_system"
val f = Flags(Some("asdfasdf"), None, None, Some(true), Some(false))
command = f.applyToCommand(command)

I would recommend a complete redesign of your current approach.

Every member of your Flags class should be it's own case class, extending a common Flag class.

So you can define functions to combine different flags to one configuration. This configuration can than, in a final step, be used to build your result string.

abstract class Flag(name: String, parameter : Option[String])
case class Cache(parameter : Option[String]) extends Flag("-cache", parameter)
case class ErrorLog(parameter : Option[String]) extends Flag("-errorlog", parameter)
//...

type Config = List[Flag]

def applyToCommand(commandName : String, config : Config) = {
  def buildString(f:Flag) = 
    s" $f.name${f.parameter.map(" " ++ _).getOrElse("")}"

  val flagsString = config.map(buildString).mkString("")
  s"$commandName" ++ flagString
}

//Now you can it simply use it as I described above
val config = List(Cache(Some("asdf")), ErrorLog(None))
applyToCommand("run_system", config)

This makes your code more flexible and easier to refactor.

At last here are some advises how you could modify this design to better fit your needs:

  • If you need to group your flags, you can put them in objects or separate files. Or if you want to change their behavior based on the group you can enhance the class hierarchy and add an intermediate layer.

  • You can move the parameter from Flag down to the case classes, so every Flag can define if it needs parameters, if yes how many and if those are optional or not.

  • You could also implement buildString at the case classes so every flag can decide how to format it self.

  • If you want do add new Flags you simply add a new class and that's it, no need to add anything to an unrelated class.

As explained @bmaderbacher, I think you should separate the different flags in the different case class.

But to answer your question, you should modify applyToCommand :

def applyToCommand(implicit command: String): String = {
   var s = update(cache, "-cache")(command)
   s = update(errorlog, "-error")(s)
   s = update(accesslog, "-access")(s)
   s = update(auth, "-do-auth")(s)
   s
}

At this point it should be clear that you didn't make the right choice for your Flag class.

I'll do something like that:

trait Flag {
   def toString: String
}

case class Command(value: String) {
   def add(flag: Flag) = Command(value + ' ' + flag.toString)
   def +(flag: Flag) = add(flag) 
}

case class Cache(size: Int) extends Flag {
   def toString = s"--cache $size"
}

case object Auth extends Flag {
   def toString = "--auth"
}

Now you can do something like:

val command = Command("run") + Cache(500) + Auth

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM