2012-02-23 31 views
11

Al aprender Scalaz 6, intento escribir lectores de tipo seguro que devuelvan las validaciones. Aquí están mis nuevos tipos:Cómo componer la función a los aplicativos con scalaz

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

y tengo dos funciones que crean mapa lectores de enteros y cadenas (*):

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

se tenga lo siguiente mapa:

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

I puede escribir dos lectores para recuperar el nombre y la edad:

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

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

Todo funciona bien, pero ahora quiero componer tanto a los lectores a construir una Boy ejemplo:

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

Mi mejor toma es:

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

Funciona como se esperaba, pero la expresión es torpe con dos niveles de constructores aplicativos. ¿Hay alguna forma de que funcione la siguiente sintaxis?

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

(*) completa y la aplicación ejecutable en: https://gist.github.com/1891147


Actualización: Aquí es el mensaje de error del compilador que consigo cuando se trata de la línea por encima o por Daniel sugerencia:

[error] ***/MapReader.scala:114: type mismatch; 
[error] found : scalaz.Validation[scalaz.NonEmptyList[String],String] 
[error] required: String 
[error] val boy = (name |@| age) { Boy(_,_) } 
[error]         ^
+0

Voy a publicar una respuesta más tarde, pero como una sugerencia, recuerde que 'Applicative [G]' and 'Applicative [F]' implica 'Applicative [[x] F [G [x]]'. En scalaz 7, 'Applicative # Componer' es testigo de este hecho. Trabaje directamente con las clases de tipos para comenzar, en lugar de utilizar la sintaxis '| @ |'. – retronym

+0

Gracias, pero todavía no lo entiendo, así que esperaré su respuesta. Tenga en cuenta que estoy usando scalaz 6 (pregunta actualizada). – paradigmatic

+0

@paradigmatic ¿Ha intentado usar 'apply' explícitamente?Al igual que '(name | @ | age) apply {Boy (_, _)}'? –

Respuesta

5

¿Qué tal esto?

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

o utilizando un alias de tipo:

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

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

Esto se basa en el mensaje de error siguiente en la consola:

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]) => ? 

Así que sólo se levantan Boy.apply para tomar el tipo requerido.

+0

Gracias. No pensé en levantar objetos. Sin embargo, no estoy seguro de que la sopa tipo lambda sea más legible que usar dos constructores aplicativos en cascada. ¿Sabes si hay una forma de que el constructor se levante mediante conversiones implícitas? – paradigmatic

+0

@paradigmatic, creo que un alias de tipo lo hace más legible. También lo prefiero a una conversión implícita hipotética por dos razones: (1) me dice el tipo de '(Boy (_, _)). Lift [VNELStr]' lambda - No pude entender el tipo en su versión. (2) por el mismo motivo, no quiero cambiar implícitamente 'Int' a' Opción [Int] 'Creo que la conversión implícita del lampda evita el uso del sistema de tipos. – huynhjl

+0

@huynjl Me gusta el tipo de alias y veo tu punto. Gracias. – paradigmatic

2

Tenga en cuenta que desde Reader y Validation (con un semigrupo E) son ambos aplicables, su composición también es aplicable. Usando scalaz 7 esto se puede expresar como:

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] 

Ahora podemos utilizar una sola operación |@| en el Aplicativo compuesto:

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")) 
+0

Acerca de la ambigua resolución implícita, abrí http://stackoverflow.com/questions/11913128/scalaz-7-why-using-type-alias-results-in-ambigous-typeclass-resolution-for-rea – ron

+0

Lamentablemente necesita para importar 'scalaz.Id._' para obtener la instancia de identidad en el alcance. Pero entonces deberías poder usar 'Reader [Int, A]'. – retronym

Cuestiones relacionadas