2012-03-27 19 views
9

Ammetto che il titolo non è molto esplicito: mi dispiace per quello.Scalaz: convalida di una comprensione e registrazione

Si supponga Ho un per-di comprensione:

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

Validation1, Validation2 e Validation3 fare qualche controllo (per esempio "età> 18") e l'uso fallire/successo; quindi se qualcosa è sbagliato, la comprensione incomprensibile e io ricevo il motivo nella parte del fallimento del risultato, altrimenti ottengo il valore atteso nella parte di successo. Fin qui, tutto bene e niente di molto difficile.

Ma Validation1, Validation2, Validation3 hanno successo se il loro input soddisfa alcune regole (ad esempio "il ragazzo può votare perché la sua età è maggiore di 18 e la sua nazionalità è francese"). quello che voglio è tenere traccia delle regole che vengono applicate per poterle mostrare alla fine.

È chiaramente un caso di utilizzo della registrazione. ma esita sul modo per farlo:

  1. avere un oggetto "logger", che è accessibile da qualsiasi funzione (Validation1, 2 e 3, ma anche il chiamante che vuole visualizzare il contenuto del registro)

  2. Fai il logger un parametro di Validation1, 2 e 3

  3. Attendere il capitolo pertinente di "programmazione funzionale in Scala" :)

  4. Altro?

grazie per il vostro consigli

Modificato il 10 aprile

Così, supponiamo che io voglio calcolare la funzione: x -> 1/sqrt (x)

In primo luogo, Calcolo sqrt (x) controllando che x> 0 e poi prendo l'inverso se non zero.

con scalaz.Validation, è semplice:

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 

Ora, se i successi SQUAREROOT, voglio registrare il successsquaretoot corda e se sucesses inverse, voglio registrare il successinverse stringa in modo che la funzione resultat accumula le stringhe sia in caso di successo

ho iniziato con ValidationT come Yo Otto suggerito:

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))) 

Ma io non riesco a trovare h dobbiamo combinarli in una comprensione preliminare. Inoltre, per ottenere il risultato di uno di questi, devo scrivere: squareroot2 (4) .run.Corro che sembra strano e nel modo che ho scritto, anche in caso di guasto della stringhe successsquareroot viene registrato:

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

stampe: (SQUAREROOT ok, Fallimento (Non può prendere radice quadrata di un numero negativo))

Grazie! Benoit

Modificato il 12 aprile

Così Yo Otto suggerito questo frammento:

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 

e mi ha avvertito che alcune annotazioni di tipo era necessario. Effettivamente, il ritorno di squareroot e inverso è piuttosto brutto: è una convalida di qualcosa che ho avuto difficoltà a capire!

Quindi, ho dovuto specificare esplicitamente il tipo di ritorno: def inverso (x: Double): ValidationT [?, E, A] dove "E" è String e "A" è Double (che era facile!). Ma per quanto riguarda il primo? Deve essere una monade (per quanto ho capito) e ho scelto il più semplice: Id (che è Identità).

Così ora abbiamo:

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)  

Ma la for-comprensione non compila perché "y" non è un doppio, ma un WriterT [Id, String, Double] Inoltre, il primo messaggio registrato ("Squareroot ok") è "perso".

Alla fine, ho fatto così:

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) 

che dà:

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) 

Cool! Sarebbe meglio usare una List [String] per il writer, ma penso di essere sulla buona strada!

E ora, posso pensare a mie vacanze (domani!) :)

Redatta il 14 maggio

bene, il codice non si compila, ma l'errore è in Yo Eight della scorsa suggerimento (nota che non è più un'offesa Yo Eight che è un modello di gentilezza!). Vi sottopongo il codice completo e l'errore:

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) 
    } 



} 

Ecco la sessione del terminale:

[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 

Credo che ci sia qualcosa che ho perso fin dall'inizio che potrebbe spiegare il motivo per cui io sono incollato per alcune settimane!

Benoit

Modificato il 15 maggio

Compilare il codice, ho un primo errore:

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

Dopo alcuni tentativi, ho riscritto l'importazione in questo modo:

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

C'è sti ll un errore:

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 

Questo errore era presente con il codice che hai scritto il 14 maggio Ovviamente, è difficile capire che cosa esattamente iimport con scalaz-sette. Usando la versione 6, le cose sembravano più semplici: basta importare scalaz._ e Scalaz._

Mi sento come una "casalinga disperata" :) (sì, sono d'accordo, non è molto astuto ma è rilassante!)

Benoit

23 maggio

Ouf! Funziona in modo efficace con l'ultima versione di scalaz-seven: nota che ho dovuto costruirla invece di scaricare un'istantanea.

è fantastico!

Per coloro che sono interessati, ecco l'output:

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 Otto, se per caso ci incontriamo un giorno, ti pagherò una birra!

Benoit

+0

Penso che la tua versione di scalaz-seven non sia buona. Il codice funziona correttamente con l'ultima versione del ramo scalaz-seven –

+0

Ho perso il tuo post. Questo è quello che ho indovinato. Sto usando l'istantanea del 14 aprile. Avrei dovuto avere il tempo di provare l'ultima versione questa sera – bhericher

risposta

7

Al fine di accedere durante il calcolo della monade, è necessario utilizzare un'istanza di Writer monade. Poiché monad non si compone e si desidera mantenere l'effetto "Validazione", è necessario utilizzare un Validation Monad Transformer. Non so quale versione di ScalaZ stai usando ma Scalaz7 (branch scalaz-seven) fornisce tale trasformatore monad (vale a dire ValidationT).

così otteniamo:

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

con W il tipo di logger

Secondo la modifica, ecco come lo farò

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) 

Ed ora, come usarlo in a comprensione

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

Quei frammenti potrebbero avere bisogno di più annotazioni di tipo

Redatta il 13 aprile

Ecco l'annotazioni di tipo corretti per i vostri metodi:

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] 

In questo modo, è possibile definire il metodo resultat come questo :

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 

Tu potrebbe anche usare List [String] come un tipo di registro perché è un monoide

BTW, parlo francese se può aiutare :-)

Modifica il 14 maggio

Il problema era : Il compilatore non può risolvere

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

perché WriterT ha bisogno di un'istanza di Monoid [String] e Pointed [Id].

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

Ecco il codice eseguibile 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) 
} 

Modifica il 14 agosto

Questo codice non è più valido scalaz-sette. ValidationT è stato rimosso poiché Validation non è una monade. Speriamo che sia possibile utilizzare EitherT. Inoltre, è stata aggiunta una nuova classe di caratteri MonadWriter/ListenableMonadWriter per alleviare le annotazioni di 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

Grazie Yo Eight: Io uso Scalaz 6.0.4 e controllerò cosa fornisce. Ad ogni modo, capisco l'idea. – bhericher

+0

Sono incollato! Se sostituisco Validation1, Validation2 e Validation3 di ValidationT [...] (Writer ("blabla", Validation1 (x)) ecc ..., riesco a concatenare correttamente le 3 convalide, ma non vedo come accumulare i log ... – bhericher

+1

Il log viene "accumulato" implicitamente quando viene chiamato Writer.flatMap (è richiesta un'istanza di Monoid [W]). Writer.flatMap viene chiamato implicitamente quando viene chiamato ValidationT.flatMap. –