M[A[M[A[B]]]] - how to resolve monad nesting? (transformers?)

77 views
Skip to first unread message

Timothy Perrett

unread,
Feb 3, 2013, 2:02:41 AM2/3/13
to sca...@googlegroups.com
Hey again guys,

So I have a somewhat awkward issue that I know can be resolved but I just cant find the right parts to get the output I want. Given the following (a simplification of the thing at hand):

  val f0: Future[Validation[String,String]] = 
    Promise.successful(Success("foo"))

  val f1: String => Future[Validation[String,String]] = 
    s => Promise.successful(Success("bar"))

  val f2: String => Future[Validation[String,String]] = 
    s => Promise.successful(Success("baz"))

  val q: Future[Validation[String, Future[Validation[String, String]]]] = 
    for { // M[+_] is Future
      a <- f0 // a is Validation[A,B]
    } yield for {
      x <- a // x is the String needed to make next call 
    } yield for {
      y <- f1(x)
      z <- f2(x)
    } yield (y |@| z){ case (_,b) => b }

In essence, the latter two calls are dependant on the first, and there could be further calls later which are similarly dependant. Seems like I cant use kliesli composition here because the same problem exists with being "stuck" with the validation and yielding a V[F[V[A]]] as when using map/flatMap. There's something here using applicatives it seems (as that worked splendidly for my other use case) but i cant seem to find the right path with it... or maybe i'm totally off base and it requires some transformer magic?

Any help greatly appreciated 

Cheers, Tim 

Stefan Hoeck

unread,
Feb 3, 2013, 2:37:56 AM2/3/13
to sca...@googlegroups.com
Hey Tim

You are trying to use Validation as a Monad here, which is not what it was meant for. If you change the return type of f0, f1, and f2 to
EitherT[Future,String,String], you can use the results directly in a for-comprehension:

  val f1: EitherT[Future,String,String] = EitherT(Promise.successful("foo".right))

  def f2 (s: String): EitherT[Future,String,String] = ...

  def f3 (s: String): EitherT[Future,String,String] = ...

  val q: EitherT[Future,String,String] = for {
    x <- f1 //x is a String
    y <- f2(x)
    z <- f3(x)
  } yield z

Note, that in this case the errors of the calls to f2 and f3 will not be accumulated. You can achieve error accumulation by first
transferring the EitherT[Future,String,String] to a Future[Validation[String,String]] and then using its applicative functor. In that
case it might make more sense, to accumulate errors in a NonEmptyList. Otherwise you will just concatenate the error Strings.
Please tell me, if you need more help with the interconversion between \/ and Validation.

As an alternative, if you want to stick to your version of the code, you can transfer a Validation[String,Future[A]] to
a Future[Validation[String,A]] via .sequence:

  val r: Future[Future[Validation[String,Validation[String,String]]]] =
    q map { _.sequence }

In the end this should lead to a Future[Future[Validation[String,Validation[String,String]]]]
which you can the flatten further (in case of Future via join, in case of Validation via fold).

Cheers, Stefan

Timothy Perrett

unread,
Feb 3, 2013, 1:38:50 PM2/3/13
to sca...@googlegroups.com
Hey Stefan,

Thanks for the great reply - very helpful. Funnily enough my initial version of this code used \/ but I couldn't get it working how I wanted; seems I should have been using EitherT instead! If I understand what you wrote correctly, if the LHS of the generator is String (in the case of x <- f0), then somehow the EitherT gets at the result of the future (presumably by map internally)? Nonetheless, its pretty clever that it can combine monad behaviour together. 

With regard to the error accumulation, I see that \/ has the 'validation' method to convert to Validation[A,B], and i'm assuming that there's some way to stay within the EitherT and accumulate errors by the usual applicative functor route? Calling validation on the EitherT gets me back to Future[ValidationNEL[String,String]] again?

Cheers, Tim 

Timothy Perrett

unread,
Feb 3, 2013, 1:53:44 PM2/3/13
to sca...@googlegroups.com
Is this the kind of thing you meant? 

    val f0: EitherT[Future, NonEmptyList[String], String] = 
      EitherT(Promise.successful("foo".right))

    val f1: String => EitherT[Future, NonEmptyList[String], String] = 
     s => EitherT(Promise.successful(NonEmptyList("bar").left))

    val f2: String => EitherT[Future, NonEmptyList[String], String] = 
     s => EitherT(Promise.successful(NonEmptyList("baz").left))

    val q: EitherT[Future, NonEmptyList[String], String] = for {
      x <- f0 //x is a String
      y <- f1(x) +++ f2(x)
    } yield y

    val w: Future[NonEmptyList[String] \/ String] = q.run

Thanks, Tim 

Stefan Hoeck

unread,
Feb 3, 2013, 2:12:53 PM2/3/13
to sca...@googlegroups.com
Hey Tim

y <- f1(x) +++ f2(x) will concatenate the result Strings of f1 and f2 if both are successful. This is (from your first post)
not what you want. So you will have to run the calculation with f1 and f2 in a separate function:

  (f1 and f2 both return Future[ValidationNEL[String,String]])

  // errors are accumulated
  def f1And2(s: String): Future[ValidationNEL[String,String]] = (f1(s) |@| f2(s)) { case (_,b) => b }

  // helper method that needs to be defined only once
  def toEitherT[A] (v: Future[ValidationNEL[String,A]]): EitherT[Future,NonEmptyList[String],A] =
    EitherT(v map { _.disjunction })


   val q: EitherT[Future, NonEmptyList[String], String] = for {
      x <- f0 //x is a String
      y <- toEitherT(f1And2(x))
    } yield y

    val w: Future[NonEmptyList[String] \/ String] = q.run

So, going from Future[ValidationNEL[String,String]] to EitherT[...] is a bit cumbersome (there is probably a nicer way
to do it), but it takes a helper function that has to be defined only once. Afterwards you can freely switch between
error accumulation using Validation and the monadic behavior of EitherT.

Cheers, Stefan

Timothy Perrett

unread,
Feb 3, 2013, 2:27:39 PM2/3/13
to sca...@googlegroups.com
Hey Stefan,

Thanks for that - insightful. However, assuming a NonEmptyList on the LHS of the \/, based on the ScalaDoc, +++ does seem to aggregate the errors? I made a few samples and it seems to do what I want. Is there some subtle difference i'm missing between +++ and chaining Validations with |@| ?

Cheers, Tim 

Timothy Perrett

unread,
Feb 3, 2013, 2:45:48 PM2/3/13
to sca...@googlegroups.com
Err, ignore my last post - i'm still digesting what you wrote and was only thinking in the error case. I shall let it soak a little more before posting again :-) 

Stefan Hoeck

unread,
Feb 3, 2013, 3:43:24 PM2/3/13
to sca...@googlegroups.com
In your particular example, you CAN do the following:


    val q: EitherT[Future, NonEmptyList[String], String] = for {
      x <- f0 //x is a String
      y <- f1(x).as("") +++ f2(x)
    } yield y

    val w: Future[NonEmptyList[String] \/ String] = q.run

Since you are only interested in the result of the second calculation (f2), you can used Monoid zero as the
result of f1 (which is, in case of a String, the empty String ""). But since this is only example code, you still
might need to switch between Validation and EitherT in your application.

Cheers, Stefan

Timothy Perrett

unread,
Feb 3, 2013, 4:08:04 PM2/3/13
to sca...@googlegroups.com
But of course, you're right - I had a think and I probably do need both cases depending on the context of the usage. Thanks for the mails though - really do appreciate it :-)

Cheers
Reply all
Reply to author
Forward
0 new messages