Async computation with Validation in Scala using S

2019-03-16 04:44发布

Being writing a completely async library to access a remote service (using Play2.0), I'm using Promise and Validation to create non-blocking call, which has a type presenting fail and valid result at once.

Promise comes from Play2-scala, where Validation comes from scalaz.

So here is the type of examples of such functions

  • f :: A => Promise[Validation[E, B]]
  • g :: B => Promise[Validation[E, C]]

So far, so good, now if I want to compose them, I can simple use the fact that Promise present a flatMap, so I can do it with a for-comprehension

for (
   x <- f(a);
   y <- g(b)
) yield y

Ok, I took a shortcut to my problem here because I didn't reused the Validation results within the for-comprehension. So if I want to reuse x in g, here is how I could do

for (
   x <- f(a); // x is a Validation
   y <- x.fold(
      fail => Promise.pure(x),
      ok => g(ok)
   )
) yield y

Fair enough, but this kind of boilerplate will go to pollute my code over and over again. The problem here is that I've a kind of two-levels Monadic structure like M[N[_]].

At this stage, is there any structure in f° programming that enables working with such structure by skipping easily the secong level:

for (
   x <- f(a); //x is a B
   y <- g(b) 
) yield y

Now, below is how I achieved something similar.

I created kind of Monadic structure that wraps the two level in one, let say ValidationPromised which pimped the Promise type with two methods:

def /~> [EE >: E, B](f: Validation[E, A] => ValidationPromised[EE, B]): ValidationPromised[EE, B] = 
    promised flatMap { valid => 
        f(valid).promised
    }

def /~~>[EE >: E, B](f: A => ValidationPromised[EE, B]): ValidationPromised[EE, B] = 
    promised flatMap { valid => 
        valid.fold (
            bad => Promise.pure(KO(bad)),
            good => f(good).promised
        )
    }

This allows me to do such things

      endPoint.service /~~>                                   //get the service
      (svc =>                                                 //the service
        svc.start /~~> (st =>                                 //get the starting elt
          svc.create(None) /~~>                               //svc creates a new elt
          (newE =>                                            //the created one
            newEntry.link(st, newE) /~~>                      //link start and the new
            (lnk => Promise.pure(OK((st, lnk, newE))))        //returns a triple => hackish 
          ) 
        )
      )

As we can see /~~> is pretty similar to flatMap but skips one level. The problem is the verbosity (that's why "for-comprehension" exists in Scala and "do" in Haskell).

Another point, I've the /~> that stands like a map also but works on the second level (instead of the Valid type -- third level)

So my second question is corollary to the former... Am I approching a sustainable solution with this construction ?

sorry to be that long

1条回答
ゆ 、 Hurt°
2楼-- · 2019-03-16 05:36

The concept you are looking for here is monad transformers. In brief, monad transformers compensate for monads not composing by allowing you to "stack" them.

You didn't mention the version of Scalaz you are using, but if you look in the scalaz-seven branch, you'll find ValidationT. This can be used to wrap any F[Validation[E, A]] into a ValidationT[F, E, A], where in your case F = Promise. If you change f and g to return ValidationT, then you can leave your code as

for {
  x ← f(a)
  y ← g(b)
} yield y

This will give you a ValidationT[Promise, E, B] as a result.

查看更多
登录 后发表回答