A nicer way to compose functions thru a validation

340 views
Skip to first unread message

Chris Marshall

unread,
Nov 26, 2010, 10:04:54 AM11/26/10
to sca...@googlegroups.com
If I have 3 functions:

f : X => ValidationNEL[E, A]
g : A => ValidationNEL[E, B]
h : B => ValidationNEL[E, C]

I would like to compose them :

(f andThen g andThen h) apply x

But of course I can't: the nearest I can get is

((f(x) map g) join) map h join.

Is there a nicer/clearer way of composing the functions without the explicit joins?

Cheers, Chris

scala> val f = (_ : String).parseInt
f: (String) => scalaz.Validation[NumberFormatException,Int] = <function1>

scala> val f = (_ : String).parseInt.liftFailNel 
f: (String) => scalaz.Validation[scalaz.NonEmptyList[NumberFormatException],Int] = <function1>

scala> val g = (i : Int) => if (i == 0) false.success else if (i == 1) true.success else (new NumberFormatException("Not a bool: " + i).failNel)
g: (Int) => scalaz.Validation[scalaz.NonEmptyList[java.lang.NumberFormatException],Boolean] = <function1>

scala> (f("2") map g join) map h join
res7: scalaz.Validation[scalaz.NonEmptyList[NumberFormatException],Float] = Failure(NonEmptyList(java.lang.NumberFormatException: Not a bool: 2))

Jason Zaugg

unread,
Nov 26, 2010, 10:19:36 AM11/26/10
to sca...@googlegroups.com
Perhaps what you are after is Kleisli composition, a way to chain functions of (A => M[B]) where [M: Monad].

  scala> val a = (x: Int) => (x * 2).some
  a: (Int) => Option[Int] = <function1>

  scala> kleisli(a) >=> kleisli(a) >=> kleisli(a)
  res4: scalaz.Kleisli[Option,Int,Int] = scalaz.Kleislis$$anon$1@f892cc

  scala> res4(1)
  res5: Option[Int] = Some(8)

It's not so pretty with Validation, because partially applied type constructors aren't inferred:

  scala> val b = (x: Int) => (x * 2).success[String]
  b: (Int) => scalaz.Validation[String,Int] = <function1>

  scala> val kb = kleisli[PartialApply1Of2[Validation, String]#Apply, Int, Int](a)
  kb: scalaz.Kleisli[[B]scalaz.Validation[java.lang.String,B],Int,Int] = scalaz.Kleislis$$anon$1@9acfb6

  scala> kb >=> kb >=> kb
  res10: scalaz.Kleisli[[B]scalaz.Validation[java.lang.String,B],Int,Int] = scalaz.Kleislis$$anon$1@ae94ab

  scala> res10(1)
  res11: scalaz.Validation[java.lang.String,Int] = Success(8)

-jason

--
You received this message because you are subscribed to the Google Groups "scalaz" group.
To post to this group, send email to sca...@googlegroups.com.
To unsubscribe from this group, send email to scalaz+un...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/scalaz?hl=en.

Chris Marshall

unread,
Nov 26, 2010, 10:51:37 AM11/26/10
to sca...@googlegroups.com
Thanks Jason - that's awesome

Is it possible to add a "kleisli1Of2" method somewhere, to help with this stuff. This seems to work:

scala> def kleisli1Of2[M[_,_], A, B, S](f : A => M[S,B]) = kleisli[PartialApply1Of2[M, S]#Apply, A, B](f)
kleisli1Of2: [M[_,_],A,B,S](f: (A) => M[S,B])scalaz.Kleisli[[B]M[S,B],A,B]

So this then gives me.

scala> kleisli1Of2(f) >=> kleisli1Of2(g) >=> kleisli1Of2(h)
res7: scalaz.Kleisli[[B]scalaz.Validation[scalaz.NonEmptyList[NumberFormatException],B],String,Float] = scalaz.Kleislis$$anon$1@3ef91c8f

scala> res7("1")
res8: scalaz.Validation[scalaz.NonEmptyList[NumberFormatException],Float] = Success(3.0)

scala> res7("0")
res9: scalaz.Validation[scalaz.NonEmptyList[NumberFormatException],Float] = Failure(NonEmptyList(java.lang.NumberFormatException: FALSE))

scala> res7("2")
res10: scalaz.Validation[scalaz.NonEmptyList[NumberFormatException],Float] = Failure(NonEmptyList(java.lang.NumberFormatException: Not a bool: 2))

scala> res7("a")
res11: scalaz.Validation[scalaz.NonEmptyList[NumberFormatException],Float] = Failure(NonEmptyList(java.lang.NumberFormatException: For input string: "a"))

Perhaps they should be kleisli1Of2Apply and kleisli1Of2Flip? 

Chris

Jason Zaugg

unread,
Nov 26, 2010, 11:51:06 AM11/26/10
to sca...@googlegroups.com
There are dozens of other methods that would warrant the same treatment, I don't really want to give this one special status. 

So I'd say this method, useful as it is in this case, should stay in your own code for now.

-jason

Richard Wallace

unread,
Dec 7, 2010, 10:23:58 PM12/7/10
to sca...@googlegroups.com
That is awesome. That's exactly what I've been looking for too when
trying to handle some input validation. There is one thing that I'm
having a problem with. The examples that Chris gives work fine, but
if I try something like

scala> (kleisli1Of2(f) >=> kleisli1Of2(g) >=> kleisli1Of2(h))("1")

I get an error saying

<console>:15: error: type mismatch;
found : java.lang.String("1")
required: scalaz.Bind[[B]scalaz.Validation[scalaz.NonEmptyList[NumberFormatException],B]]
(kleisli1Of2(f) >=> kleisli1Of2(g) >=> kleisli1Of2(h))("1")
^

Assigning to a val and then invoking that works, but for some reason
the direction invocation fails. Any idea why?

Thanks,
Rich

Runar Oli

unread,
Dec 7, 2010, 10:43:58 PM12/7/10
to sca...@googlegroups.com, sca...@googlegroups.com
Kleisli takes an additional argument implicitly, of type Bind[M] for the given monad M. You are passing a String explicitly where this implicit argument is expected. When you assign to a val, the monad is implicitly passed. And what is assigned to your Val is an object with an "apply" method.

Try calling k.apply("1") directly.

Reply all
Reply to author
Forward
0 new messages