11

While learning Scalaz 6, I'm trying to write type-safe readers returning validations. Here are my new types:

type ValidReader[S,X] = (S) => Validation[NonEmptyList[String],X]
type MapReader[X] = ValidReader[Map[String,String],X]

and I have two functions creating map-readers for ints and strings (*):

def readInt( k: String ): MapReader[Int] = ...
def readString( k: String ): MapReader[String] = ...

Given the following map:

val data = Map( "name" -> "Paul", "age" -> "8" )

I can write two readers to retrieve the name and age:

val name = readString( "name" )
val age = readInt( "age" )

println( name(data) ) //=> Success("Paul")
println( age(data) )  //=> Success(8)

Everything works fine, but now I want to compose both readers to build a Boy instance:

case class Boy( name: String, age: Int )

My best take is:

  val boy = ( name |@| age ) {
    (n,a) => ( n |@| a ) { Boy(_,_) }
  }
  println( boy(data) ) //=> Success(Boy(Paul,8))

It works as expected, but the expression is awkward with two levels of applicative builders. Is there a way, to get the following syntax to work ?

  val boy = ( name |@| age ) { Boy(_,_) }

(*) Full and runnable implementation in: https://gist.github.com/1891147


Update: Here is the compiler error message that I get when trying the line above or Daniel suggestion:

[error] ***/MapReader.scala:114: type mismatch;
[error]  found   : scalaz.Validation[scalaz.NonEmptyList[String],String]
[error]  required: String
[error]   val boy = ( name |@| age ) { Boy(_,_) }
[error]                                    ^
paradigmatic
  • 40,153
  • 18
  • 88
  • 147
  • I'll post an answer later, but as a hint, remember that `Applicative[G]` and `Applicative[F]` implies `Applicative[[x]F[G[x]]`. In scalaz 7, `Applicative#compose` witnesses this fact. Work directly with the type classes to begin with, rather than using the `|@|` syntax. – retronym Feb 23 '12 at 07:58
  • Thanks, but I still don't get it, so I'll wait for your answer. Note that I am using scalaz 6 (question updated). – paradigmatic Feb 23 '12 at 08:05
  • @paradigmatic Have you tried using `apply` explicitly? Like `( name |@| age) apply { Boy(_, _) }`? – Daniel C. Sobral Feb 23 '12 at 13:59
  • @DanielC.Sobral Yes, it doesn't work either (I get the same compile error). – paradigmatic Feb 23 '12 at 14:59
  • @DanielC.Sobral I update the question to include the compiler error message. – paradigmatic Feb 23 '12 at 15:35

2 Answers2

5

How about this?

val boy = (name |@| age) {
  (Boy.apply _).lift[({type V[X]=ValidationNEL[String,X]})#V]
}

or using a type alias:

type VNELStr[X] = ValidationNEL[String,X]

val boy = (name |@| age) apply (Boy(_, _)).lift[VNELStr]

This is based on the following error message at the console:

scala> name |@| age apply Boy.apply
<console>:22: error: type mismatch;
 found   : (String, Int) => MapReader.Boy
 required: (scalaz.Validation[scalaz.NonEmptyList[String],String], 
            scalaz.Validation[scalaz.NonEmptyList[String],Int]) => ?

So I just lifted Boy.apply to take the required type.

huynhjl
  • 41,520
  • 14
  • 105
  • 158
  • Thanks. I did not think about lifting. However, I'm not sure that the lambda type soup is more readable than using two applicative builders in cascade. You know if there is a way to have the constructor lifted through implicit conversions ? – paradigmatic Feb 24 '12 at 05:37
  • @paradigmatic, I think a type alias makes it the most readable. Also I prefer it to an hypothetical implicit conversion for two reasons: (1) it tells me the type of the `(Boy(_, _)).lift[VNELStr]` lambda - I couldn't figure out the type in your version. (2) for the same reason I don't want to implicitly change `Int` to `Option[Int]` I think implicitly converting the lampda takes away from using the type system. – huynhjl Feb 24 '12 at 15:22
  • In scalaz 7, it appears that this #lift method is present on Function2, but not on Function3 and up? Is this on purpose? – ms-tg Jun 05 '13 at 19:48
2

Note that since Reader and Validation (with a semigroup E) are both Applicative, their composition is also Applicative. Using scalaz 7 this can be expressed as:

import scalaz.Reader
import scalaz.Reader.{apply => toReader}
import scalaz.{Validation, ValidationNEL, Applicative, Kleisli, NonEmptyList}

//type IntReader[A] = Reader[Int, A] // has some ambigous implicit resolution problem
type IntReader[A] = Kleisli[scalaz.IdInstances#Id, Int, A]
type ValNEL[A] = ValidationNEL[Throwable, A]

val app = Applicative[IntReader].compose[ValNEL]

Now we can use a single |@| operation on the composed Applicative:

val f1 = toReader((x: Int) => Validation.success[NonEmptyList[Throwable], String](x.toString))
val f2 = toReader((x: Int) => Validation.success[NonEmptyList[Throwable], String]((x+1).toString))

val f3 = app.map2(f1, f2)(_ + ":" + _)

f3.run(5) should be_==(Validation.success("5:6"))
ron
  • 9,262
  • 4
  • 40
  • 73
  • About the ambigous implicit resolution, I opened http://stackoverflow.com/questions/11913128/scalaz-7-why-using-type-alias-results-in-ambigous-typeclass-resolution-for-rea – ron Aug 11 '12 at 08:17
  • Unfortunately you need to import `scalaz.Id._` to get the identity instance in scope. But then you should be able to use `Reader[Int, A]`. – retronym Aug 11 '12 at 08:37