Skip to content

Instantly share code, notes, and snippets.

@krasserm
Created December 17, 2010 18:00
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save krasserm/745386 to your computer and use it in GitHub Desktop.
Save krasserm/745386 to your computer and use it in GitHub Desktop.
import scalaz._
object ValidationKleisli extends Application {
import Scalaz._
// ------------------------------------------------------------------------------------------------
// Monadic use of scalaz.Validation to sequence dependent computations
// e.g. processors in a route, see also Kleisli composition of processors in scalaz-camel: http://goo.gl/CUIlQ
// ------------------------------------------------------------------------------------------------
case class Message(b: String)
type ValidationMonad[B] = PartialApply1Of2[Validation, Exception]#Apply[B]
implicit def toKleisli(f: Message => ValidationMonad[Message]) = kleisli[ValidationMonad, Message, Message](f)
val f = (m: Message) => Failure(new Exception("blah"))
val p = (s:String) => (m: Message) => {
print("%s " format s) // track calls
Message("%s-x" format m.b).success
}
println(p("a") >=> p("b") >=> p("c") apply Message("a")) // a b c Success(Message(a-x-x-x))
println(p("a") >=> p("b") >=> f apply Message("a")) // a b Failure(java.lang.Exception: blah)
println(p("a") >=> f >=> p("b") apply Message("a")) // a Failure(java.lang.Exception: blah)
println(f >=> p("a") >=> p("b") apply Message("a")) // Failure(java.lang.Exception: blah)
}
@retronym
Copy link

Yep, that's monadic, using the Kleisli composition for the Monad[({lam[a]=Validation[Exception,a]})#lam]

for (a <- p("a"); b <- p("b"); c <- p("c")} yield (a, b, c) is also monadic.

On the other hand, this is applicative, won't stop at the first failure, and accumulate one or more errors into a NonEmptyList[Exception]

(p("a").liftFailNel |@| p("b").liftFailNel |@| p("c").liftFailNel) { (_, _ _) }

@retronym
Copy link

In the monadic computation, the results of the first step can be fed into the second, so if it fails the second can't happen. In the applicative case, each part is independent, and the results are combined.

This property is of particular interest when using scalaz.concurrent.Promise. If you have independent subcomputations that should be run in parallel, you should structure them in the applicative style, rather than the monadic style. Otherwise, the second part must wait for the first to finish, even if it doesn't depend it's results.

For this reason, its a shame that monadic computations are given such special treatment in the language, with for-comprehension syntactic sugar for flatMap/map. Applicative computations are more ubiquitous, and would benefit from syntactic support.

This could be added to Scala, perhaps with an annotation:

(for @applicative) {
a <- p("a")
b <- p("b") // would be an error to refer to a
} yield (a, b)

The desugaring of this could use the equivalent of scalaz.Applicative, or fall back to a usage of map/flatMap that runs the second line regardless of the result of the first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment