2012-03-27 25 views
9

Admito que el título no es muy explícito: lo siento por eso.Scalaz: validación en una comprensión y registro

Supongamos que tengo una para-comprensión:

for {v1<-Validation1(input) 
    v2<-Validation2(v1) 
    v3<-Validation3(v2) 
} yield result 

Validation1, Validation2 y Validation3 hacer algunas comprobaciones (por ejemplo "la edad> 18") y el uso fracaso/éxito; entonces, si algo está mal, la comprensión forzada aborta y obtengo la razón en la parte de falla del resultado, de lo contrario obtengo el valor esperado en la parte de éxito. Hasta ahora, tan bueno y nada muy difícil.

Pero Validation1, Validation2, Validation3 son exitosos si su entrada cumple algunas reglas (por ejemplo, "el hombre puede votar porque su edad es mayor a 18 y su nacionalidad es francesa"). lo que quiero es mantener un rastro de las reglas que se aplican para poder mostrarlas al final.

Es claramente un caso de uso del registro. pero yo dude sobre la manera de hacerlo:

  1. tener un objeto "registrador" que es accesible por cualquier función (Validation1, 2 y 3, pero también la persona que llama que quiere mostrar el contenido del registro)

  2. Hacer el registrador de un parámetro de Validation1, 2 y 3

  3. Espere el capítulo pertinente de la "programación funcional en Scala" :)

  4. Otro?

gracias por sus consejos

Editado el 10 de abril

Así, supongamos que quiero para calcular la función: x -> 1/sqrt (x)

En primer lugar, Calculo sqrt (x) comprobando que x> 0 y luego tomo el inverso si no es cero.

con scalaz.Validation, es simple:

val failsquareroot= "Can't take squareroot of negative number" 
val successsquareroot= "Squareroot ok" 
val failinverse="Can't take inverse of zero" 
val successinverse= "Inverse ok" 

def squareroot(x:Double)=if (x < 0) failsquareroot.fail else sqrt(x).success 
def inverse(x:Double)= if (x == 0) failinverse.fail else (1/x).success 
def resultat(x:Double)= for { 
    y <- squareroot(x) 
    z<-inverse(y) 
} yield z 

Ahora, si éxitos squareRoot, quiero registrar el successsquaretoot cadena de Éxitos y si inversas, quiero registrar el successinverse cadena para que la función resultat acumula las cuerdas tanto en caso de éxito

empecé con ValidationT como yo Ocho sugirió:

def squareroot2(x:Double)=ValidationT[({type f[x] = Writer[String,x]})#f, String,Double](Writer(successsquareroot,squareroot(x))) 
def inverse2(x:Double)=ValidationT[({type f[x] = Writer[String,x]})#f, String,Double](Writer(successinverse,inverse(x))) 

Pero no puedo encontrar h cómo combinarlos en una comprensión forzosa. Además, para obtener el resultado de uno de ellos, tengo que escribir: squareroot2 (4) .run.corro que parece extraño y en la forma en que lo escribí, incluso en caso de fallo se registra el cadenas successsquareroot:

println(squareroot2(-1).run.run) 

impresiones: (squareRoot bien, Fracaso (No se puede tomar raíz cuadrada de un número negativo))

¡Gracias! Benoit

Editado día 12 Abril

Así Yo Ocho sugirió este fragmento:

def squareroot(x:Double) = if (x < 0) failureT("Can't take squareroot of negative number") else successT(sqrt(x)) 

def inverse(x:Double) = if (x == 0) failureT("Can't take inverse of zero ") else successT(1/x) 

for { 
    y <- squareroot(x).flatMapF(i => Writer("Squareroot ok", i)) 
    z <- inverse(y).flatMapF(i => Writer("Inverse ok", i)) 
} yield z 

y me advirtió que algunas anotaciones de tipo era necesario. Efectivamente, el tpye de retorno de raíz cuadrada e inversa es bastante feo: ¡es una Validación de algo que tuve dificultades para entender!

Entonces, tuve que especificar el tipo de devolución explícitamente: def inversa (x: Doble): ValidaciónT [?, E, A] donde "E" es Cadena y "A" es Doble (¡eso fue fácil!). Pero, ¿y el primero? Debe ser una mónada (hasta donde yo entiendo) y elegí la más simple: Id (es decir, Identidad).

así que ahora tenemos:

def squareroot(x:Double):ValidationT[Id,String,Double]=if (x < 0) failureT(failsquareroot) else successT(sqrt(x)) 
    def inverse(x:Double):ValidationT[Id,String,Double]=if (x == 0) failureT(failinverse)else successT(1/x)  

Pero el para-comprensión no se compila porque "y" no es un doble, pero un [, cadena, doble Id] Por otra parte, el primer mensaje registrado WriterT ("Squareroot ok") está "perdido".

el tiempo, me gustó que:

def resultat(x:Double) = for { 
     y <- squareroot(x).flatMapF(i => Writer("Squareroot ok", i)) 
     z <- inverse(y.run._2).flatMapF(i => Writer(y.run._1 + ", Inverse ok", i)) 
    } yield z.run //Note that writing "z.run.run" doesn't compile 

    println("0 : " + resultat(0.0).run) 
    println("-1 : " +resultat(-1.0).run) 
    println("4 : " + resultat(4).run) 

que da:

0 : Failure(Can't take inverse of zero) 
    -1 : Failure(Can't take squareroot of negative number) 
    4 : Success((Squareroot ok, Inverse ok,0.5) 

fresco! Sería mejor usar una lista [String] para el escritor, ¡pero creo que estoy en el buen camino!

Y ahora, se me ocurre para mis vacaciones (mañana!) :)

Editado el 14 de mayo

así, el código no se compila, pero el error está en Yo Ocho de última sugerencia (Tenga en cuenta que no es una ofensa otra vez Yo Ocho que es un modelo de bondad!). Os presento el código completo y el error:

import scala.math._ 
import scalaz._ 
import Scalaz._ 

object validlog extends ValidationTFunctions { 



val failsquareroot= "Can't take squareroot of negative number" 
val successsquareroot= "Squareroot ok" 
val failinverse="Can't take inverse of zero" 
val successinverse= "Inverse ok" 

case class MyId[A](v: A) 

implicit val myIdPointed = new Pointed[MyId]{ 
    def point[A](v: => A) = MyId(v) 

} 

implicit def unId[A](my: MyId[A]): A = my.v 

def squareroot(x:Double):ValidationT[({type f[x] = WriterT[MyId,String, x]})#f,String,Double]=if (x < 0) failureT[({type f[x] = WriterT[MyId,String, x]})#f,String,Double](failsquareroot) else successT[({type f[x] = WriterT[MyId,String, x]})#f,String,Double](sqrt(x)) 

def inverse(x:Double):ValidationT[({type f[x] = WriterT[MyId, String, x]})#f,String,Double]=if (x == 0) failureT[({type f[x] = WriterT[MyId,String, x]})#f,String,Double](failinverse) else successT[({type f[x] = WriterT[MyId,String, x]})#f,String,Double](1/x) 


    /* def resultat(x:Double) = for { 
     y <- squareroot(x).flatMapF(i => Writer(", Squareroot ok", i)) 
     z <- inverse(y).flatMapF(i => Writer(", Inverse ok", i)) 
    } yield z */ 

    def main(args: Array[String]): Unit = { 
    println(inverse(0.0).run) 
    println(inverse(0.5).run) 
    println(squareroot(-1.0).run) 
    println(inverse(4.0).run) 
    } 



} 

Aquí es la sesión de terminal:

[email protected]:~$ cd scala 
[email protected]:~/scala$ scala -version 
Scala code runner version 2.9.2 -- Copyright 2002-2011, LAMP/EPFL 
[email protected]:~/scala$ scala -cp ./scalaz7/scalaz-core_2.9.2-7.0-SNAPSHOT.jar validlog.scala 
/home/benoit/scala/validlog.scala:15: error: object creation impossible, since method map in trait Functor of type [A, B](fa: Main.MyId[A])(f: A => B)Main.MyId[B] is not defined 
implicit val myIdPointed = new Pointed[MyId]{ 
         ^
    one error found 

supongo que es algo que he echado de menos desde el principio que podría explicar por qué estoy pegado por algunas semanas!

Benoit

Modificado el 15 de mayo

La compilación del código, que tienen un primer error:

could not find implicit value for parameter F: scalaz.Pointed[Main.$anon.ValidationTExample.WriterAlias] 

Después de algunos intentos, Reescribí la importación de esta manera:

import scalaz.Writer 
import scalaz.std.string._ 
import scalaz.Id._ 
import scalaz.WriterT 
import scalaz.ValidationT 
import scala.Math._ 

Hay sti ll un error: se hizo presente con el código que escribió el 14 de mayo Obviamente

error: could not find implicit value for parameter F: scalaz.Monad[[x]scalaz.WriterT[[+X]X,String,x]] 
    y <- squareroot(x).flatMapF(i => Writer("Squareroot ok", i)) 
         ^
one error found 

Este error, es difícil entender lo que iimport exactamente con scalaz y siete. Usando la versión 6, todo parecía más simple: uno solo tenía que importar scalaz._ y Scalaz._

Me siento como un "escritor de casa desesperado" :) (sí, estoy de acuerdo, no es muy astuto pero es relajante!)

Benoit

23 de de mayo de

Ouf! Funciona efectivamente con la última versión de scalaz-seven: tenga en cuenta que tuve que compilarlo en lugar de descargar una instantánea.

¡eso es genial!

Para aquellos que estén interesados, aquí está la salida:

0 : (Squareroot ok,Failure(Can't take inverse of zero)) 
-1 : (,Failure(Can't take squareroot of negative number)) 
4 : (Squareroot ok, Inverse ok,Success(0.5)) 

Yo Ocho, si por casualidad nos encontramos un día, te voy a pagar una cerveza!

Benoit

+0

Creo que su versión de scalaz-seven no es buena. El código se está ejecutando correctamente con la última versión de scalaz-seven branch –

+0

Me perdí tu publicación. Eso es lo que supuse. Estoy usando la instantánea del 14 de abril. Debería haber tenido tiempo para probar con la última versión esta noche – bhericher

Respuesta

7

Con el fin de iniciar la sesión durante el cálculo monádico, usted tiene que utilizar una instancia de mónada Writer. Como la mónada no se compone y desea mantener el efecto de "Validación", debe usar un Transformador de mónada de validación. No sé qué versión de ScalaZ está utilizando, pero Scalaz7 (rama scalaz-seven) proporciona dicho transformador de mónada (es decir, ValidationT).

por lo que tenemos:

ValidationT[({type f[x] = Writer[W, x]})#f, A] 

con W del tipo de su registrador

De acuerdo con tu edición, así es como lo voy a hacer

def squareroot(x:Double) = if (x < 0) failureT("Can't take squareroot of negative number") else successT(sqrt(x)) 

def inverse(x:Double) = if (x == 0) failureT("Can't take inverse of zero ") else successT(1/x) 

Y ahora, cómo usarlo en una comprensión forzosa

for { 
    y <- squareroot(x).flatMapF(i => Writer("Squareroot ok", i)) 
    z <- inverse(y).flatMapF(i => Writer("Inverse ok", i)) 
} yield z 

Esos fragmentos podrían necesitar más anotaciones de tipo

Editado el 13 de abril

Aquí está la anotaciones de tipo correctos para sus métodos:

def squareroot(x:Double):ValidationT[({type f[x] = Writer[String, x]})#f,String,Double] 
def inverse(x:Double):ValidationT[{type f[x] = Writer[String, x]})#f,String,Double] 

De esta manera, se puede definir el método resultat como esto :

def resultat(x:Double) = for { 
    y <- squareroot(x).flatMapF(i => Writer(", Squareroot ok", i)) 
    z <- inverse(y).flatMapF(i => Writer(", Inverse ok", i)) 
} yield z 

Usted También podría utilizar la Lista [String] como un tipo de registro, porque es un monoide

Por cierto, hablo francés si puede ayudar :-)

Editar el 14 de mayo

El problema era : El compilador no puede resolver

implicitly[Pointed[({ type f[x] = Writer[String, x] })#f]] 

porque WriterT necesita una instancia de Monoid [String] y señaló [Id].

import std.string._ // this import all string functions and instances 
import Id._   // this import all Id functions and instances 

Este es el código ejecutable completo

import scalaz._ 
import std.string._ 
import Id._ 
import scalaz.WriterT 
import scalaz.ValidationT 
import scala.Math._ 

object ValidationTExample extends Application { 
    type ValidationTWriterAlias[W, A] = ValidationT[({type f[x] = Writer[W, x]})#f, W, A] 
    type WriterAlias[A] = Writer[String, A] 

    def squareroot(x:Double): ValidationTWriterAlias[String, Double] = 
    if (x < 0) ValidationT.failureT[WriterAlias, String, Double]("Can't take squareroot of negative number") 
    else ValidationT.successT[WriterAlias, String, Double](sqrt(x)) 

    def inverse(x:Double): ValidationTWriterAlias[String, Double] = 
    if (x == 0) ValidationT.failureT[WriterAlias, String, Double]("Can't take inverse of zero ") 
    else ValidationT.successT[WriterAlias, String, Double](1/x) 

    def resultat(x:Double) = for { 
    y <- squareroot(x).flatMapF(i => Writer("Squareroot ok", i)) 
    z <- inverse(y).flatMapF(i => Writer(", Inverse ok", i)) 
    } yield z 

    println("0 : " + resultat(0.0).run.run) 
    println("-1 : " + resultat(-1.0).run.run) 
    println("4 : " + resultat(4).run.run) 
} 

Editar el 14 de agosto

Este código ya no es válida en scalaz y siete. ValidationT se ha eliminado ya que Validation no es una mónada. Con suerte, EitherT se puede usar en su lugar. Además, se ha agregado una nueva clase de tipo MonadWriter/ListenableMonadWriter para aliviar esas anotaciones tipo.

import scalaz._ 
import std.string._ 
import syntax.monadwriter._ 
import scala.Math._ 

object EitherTExample extends Application { 
    implicit val monadWriter = EitherT.monadWriter[Writer, String, String] 

    def squareroot(x: Double) = 
    if (x < 0) 
     monadWriter.left[Double]("Can't take squareroot of negative number") 
    else 
     monadWriter.right[Double](sqrt(x)) 

    def inverse(x: Double) = 
    if (x == 0) 
     monadWriter.left[Double]("Can't take inverse of zero") 
    else 
     monadWriter.right[Double](1/x) 

    def resultat(x: Double) = for { 
    y <- squareroot(x) :++> "Squareroot ok" 
    z <- inverse(y) :++> ", Inverse ok" 
    } yield z 

    println("0 : " + resultat(0.0).run.run) 
    println("-1 : " + resultat(-1.0).run.run) 
    println("4 : " + resultat(4).run.run) 
} 
+0

Gracias Yo Ocho: uso Scalaz 6.0.4 y comprobaré lo que proporciona. De todos modos, entiendo la idea. – bhericher

+0

¡Estoy pegado! Si reemplazo Validation1, Validation2 y Validation3 por ValidationT [...] (Writer ("blabla", Validation1 (x)) etc ..., encadenó con éxito las 3 validaciones, pero no veo cómo acumular los registros ... – bhericher

+1

El registro se "acumula" implícitamente cuando se llama a Writer.flatMap (se requiere una instancia de Monoid [W]). Writer.flatMap se invoca implícitamente cuando se llama a ValidationT.flatMap. –