1

I have a Scala case class containing command-line configuration information:

case class Config(emailAddress: Option[String],
                  firstName: Option[String]
                  lastName: Option[String]
                  password: Option[String])

I am writing a validation function that checks that each of the values is a Some:

def validateConfig(config: Config): Try[Config] = {
  if (config.emailAddress.isEmpty) {
    Failure(new IllegalArgumentException("Email Address")
  } else if (config.firstName.isEmpty) {
    Failure(new IllegalArgumentException("First Name")
  } else if (config.lastName.isEmpty) {
    Failure(new IllegalArgumentException("Last Name")
  } else if (config.password.isEmpty) {
    Failure(new IllegalArgumentException("Password")
  } else {
    Success(config)
  }
}

but if I understand monads from Haskell, it seems that I should be able to chain the validations together (pseudo syntax):

def validateConfig(config: Config): Try[Config] = {
  config.emailAddress.map(Success(config)).
    getOrElse(Failure(new IllegalArgumentException("Email Address")) >>
  config.firstName.map(Success(config)).
    getOrElse(Failure(new IllegalArgumentException("First Name")) >>
  config.lastName.map(Success(config)).
    getOrElse(Failure(new IllegalArgumentException("Last Name")) >>
  config.password.map(Success(config)).
    getOrElse(Failure(new IllegalArgumentException("Password"))
}

If any of the config.XXX expressions returns Failure, the whole thing (validateConfig) should fail, otherwise Success(config) should be returned.

Is there some way to do this with Try, or maybe some other class?

Ralph
  • 31,584
  • 38
  • 145
  • 282
  • 1
    My [answer here](http://stackoverflow.com/a/12309023/334519) has some examples and discussion (but using Scalaz). – Travis Brown Sep 12 '13 at 16:30
  • 2
    Might I ask a silly question? If all these things are required, as per your validations, then why have them optional on the case class in the first place? – cmbaxter Sep 12 '13 at 21:18
  • @cmbaxter: my thought exactly... – Erik Kaplun Sep 12 '13 at 22:31
  • I am using scopt (https://github.com/scopt/scopt) for command line parsing. The `Config` case class is immutable and "changed" by copying. It starts with default values for each field and these are changed as options are parsed. I need some way in the default to indicate that an option has not been set; `Option[String]` seems much better than `null`. I am not using scopt's `required` functionality because I want to facilitate testing of individual command line options. – Ralph Sep 13 '13 at 11:19
  • It really sounds as if putting the options into a collection (maybe a map) along with their constraints (as closures, most likely) and manipulating them with for comprehensions is a better match. You can find many explanations online of how for comprehensions are monadic. Here's one: http://debasishg.blogspot.co.uk/2008/03/monads-another-way-to-abstract.html – itsbruce Sep 16 '13 at 18:31
  • But you might like this better: http://stackoverflow.com/questions/10857973/scala-return-has-its-place I think it is demonstrating what you want to do, only with a more suitable example. – itsbruce Sep 16 '13 at 22:26

3 Answers3

9

It's pretty straightforward to convert each Option to an instance of the right projection of Either:

def validateConfig(config: Config): Either[String, Config] = for {
  _ <- config.emailAddress.toRight("Email Address").right
  _ <- config.firstName.toRight("First Name").right
  _ <- config.lastName.toRight("Last Name").right
  _ <- config.password.toRight("Password").right
} yield config

Either isn't a monad in the standard library's terms, but its right projection is, and will provide the behavior you want in the case of failure.

If you'd prefer to end up with a Try, you could just convert the resulting Either:

import scala.util._

val validate: Config => Try[Config] = (validateConfig _) andThen (
  _.fold(msg => Failure(new IllegalArgumentException(msg)), Success(_))
)

I wish that the standard library provided a nicer way to make this conversion, but it doesn't.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • 1
    Do people actually use this sort of code in real life? If yes, is this supposed to be readable and/or maintainable? (This is a sincere question—I might well be missing something important here) – Erik Kaplun Sep 12 '13 at 22:32
  • 1
    @ErikAllik: I use stuff like my `validateConfig` here all the time, and personally find it a lot clearer than e.g. the big blocks of conditionals in the question. The conversion to `Try` is ugly, but could be made more generic—it's the kind of thing that should be supported by the standard library, and even though it's not, you only need to define the key pieces once. – Travis Brown Sep 12 '13 at 22:47
  • 1
    @ErikAllik Wandered back to this question and saw this, which is the most appropriate answer in terms of what the OP is trying to achieve. It is essentially the most minimal possible Monadic implementation of a chain of responsibility. I have no idea why Ralph thinks his version is better, though. On this evidence, he really doesn't grok monads. – itsbruce Oct 02 '13 at 12:48
1

It's a case class, so why aren't you doing this with pattern matching?

def validateConfig(config: Config): Try[Config] = config match {
  case Config(None, _, _, _) => Failure(new IllegalArgumentException("Email Address")
  case Config(_, None, _, _) => Failure(new IllegalArgumentException("First Name")
  case Config(_, _, None, _) => Failure(new IllegalArgumentException("Last Name")
  case Config(_, _, _, None) => Failure(new IllegalArgumentException("Password")
  case _ => Success(config)
}

In your simple example, my priority would be to forget monads and chaining, just get rid of that nasty if...else smell.

However, while a case class works perfectly well for a short list, for a large number of configuration options, this becomes tedious and the risk of error increases. In this case, I would consider something like this:

  1. Add a method that returns a key->value map of the configuration options, using the option names as the keys.
  2. Have the Validate method check if any value in the map is None
  3. If no such value, return success.
  4. If at least one value matches, return that value name with the error.

So assuming that somewhere is defined

type OptionMap = scala.collection.immutable.Map[String, Option[Any]]

and the Config class has a method like this:

def optionMap: OptionMap = ...

then I would write Config.validate like this:

def validate: Either[List[String], OptionMap] = {
  val badOptions = optionMap collect { case (s, None) => s }
  if (badOptions.size > 0)
    Left(badOptions)
  else
    Right(optionMap)
}

So now Config.validate returns either a Left containing the name of all the bad options or a Right containing the full map of options and their values. Frankly, it probably doesn't matter what you put in the Right.

Now, anything that wants to validate a Config just calls Config.validate and examines the result. If it's a Left, it can throw an IllegalArgumentException containing one or more of the names of bad options. If it's a Right, it can do whatever it wanted to do, knowing the Config is valid.

So we could rewrite your validateConfig function as

def validateConfig(config: Config): Try[Config] = config.validate match {
  case Left(l) => Failure(new IllegalArgumentException(l.toString))
  case _ => Success(config)
}

Can you see how much more functional and OO this is getting?

  • No imperative chain of if...else
  • The Config object validates itself
  • The consequences of a Config object being invalid are left to the larger program.

I think a real example would be more complex yet, though. You are validating options by saying "Does it contain Option[String] or None?" but not checking the validity of the string itself. Really, I think your Config class should contain a map of options where the name maps to the value and to an anonymous function that validates the string. I could describe how to extend the above logic to work with that model, but I think I'll leave that as an exercise for you. I will give you a hint: you might want to return not just the list of failed options, but also the reason for failure in each case.

Oh, by the way... I hope none of the above implies that I think you should actually store the options and their values as an optionMap inside the object. I think it's useful to be able to retrieve them like that, but I wouldn't ever encourage such exposure of the actual internal representation ;)

Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111
itsbruce
  • 4,825
  • 26
  • 35
0

Here's a solution that I came up with after some searching and scaladocs reading:

def validateConfig(config: Config): Try[Config] = {
  for {
    _ <- Try(config.emailAddress.
             getOrElse(throw new IllegalArgumentException("Email address missing")))
    _ <- Try(config.firstName.
             getOrElse(throw new IllegalArgumentException("First name missing")))
    _ <- Try(config.lastName.
             getOrElse(throw new IllegalArgumentException("Last name missing")))
    _ <- Try(config.password.
             getOrElse(throw new IllegalArgumentException("Password missing")))
  } yield config
}

Similar to Travis Brown's answer.

Ralph
  • 31,584
  • 38
  • 145
  • 282
  • Why do you want to chain them like this? It's not a solution that matches the given problem well at all. Is it that you want to experiment with the technique? – itsbruce Sep 12 '13 at 20:30
  • "Is it that you want to experiment with the technique?" -- partly. I am also trying to accommodate other validations (e.g.: is the TCP port in range?) The monadic approach will easily allow that without make a special case. – Ralph Sep 12 '13 at 21:01
  • 1
    But writing out each check in sequence makes *everything* a special case and the class itself almost impossible to extend cleanly. Nothing about that is good FP style. You can cater for arbitrary checks just by storing a closure with each config option. – itsbruce Sep 16 '13 at 09:34