2012-03-27 9 views
9

Ich gebe zu, dass der Titel nicht sehr explizit ist: Entschuldigung dafür.Scalaz: Validierung in einem Verständnis und Logging

Angenommen, ich habe einen für Verständnis:

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

Validation1, Validation2 und Validation3 einiger Kontrollen zu tun (zum Beispiel "Alter> 18") und die Verwendung scheitern/Erfolg; Wenn also etwas nicht stimmt, bricht das Verständnis ab und ich bekomme den Grund für den fehlgeschlagenen Teil des Ergebnisses, sonst bekomme ich den erwarteten Wert im Erfolgsteil. So weit, so gut und nichts sehr schwer.

Aber Validation1, Validation2, Validation3 sind erfolgreich, wenn ihre Eingabe einige Regeln erfüllt (z. B .: "Der Typ kann wählen, weil sein Alter größer als 18 ist und seine Nationalität Französisch ist"). Was ich möchte, ist, die Regeln zu verfolgen, die angewendet werden, um sie am Ende anzeigen zu können.

Es ist eindeutig ein Anwendungsfall der Protokollierung. aber ich zögere, auf dem Weg, es zu tun:

  1. Haben Sie ein Objekt „Logger“, die von jeder Funktion (Validation1, 2 und 3, sondern auch den Anrufer, der den Inhalt des Protokolls anzuzeigen will) zugänglich ist

  2. der Logger Stellen ein Parameter Validation1, 2 und 3

  3. Warten Sie auf die betreffenden Kapitel von "Funktionale Programmierung in Scala" :)

  4. Andere? > 1/sqrt (x)

    Zuerst - x: Also, ich nehme an, die Funktion berechnet werden soll

Vielen Dank für Ihre Ratschläge

auf 10

April Bearbeitet Ich berechne sqrt (x), indem ich überprüfe, dass x> 0 und dann nehme ich die inverse wenn nicht null.

mit scalaz.Validation, es ist ganz einfach:

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 

Wenn nun squareroot Erfolge, ich möchte die Zeichenfolge successsquaretoot protokollieren und wenn inverse sucesses, möchte ich die Zeichenfolge successinverse anmelden, so dass die Funktion resultat akkumuliert die beide Strings im Erfolgsfall

ich mit ValidationT begann als Yo Acht vorgeschlagen:

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

Aber ich kann nicht h finden um sie zum Verständnis zu kombinieren. Um das Ergebnis von einem von ihnen zu erhalten, muss ich schreiben: squareroot2 (4) .run.laufen die seltsam scheint und in der Art, wie ich schrieb es, auch im Falle des Scheiterns wird die Saiten successsquareroot protokolliert:

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

druckt: (SquareRoot ok, Failure (kann nicht squareroot negative Zahl nehmen))

Vielen Dank! Benoit

Herausgegeben am 12. April

So Yo Acht vorgeschlagen, diesen Auszug:

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 

und er warnte mich, dass einige Typenannotationen notwendig war. Effektiv ist die Rückgabe von Quadratwurzel und Inverse ziemlich hässlich: Es ist eine Validierung von etwas, das ich schwer zu verstehen hatte!

Also, ich musste den Rückgabetyp explizit angeben: def inverse (x: Double): ValidierungT [?, E, A] wo "E" ist String und "A" ist Double (das war einfach!). Aber was ist mit dem ersten? Es muss eine Monade sein (soweit ich das verstehe) und ich wählte das Einfachste: Id (das ist Identität).

So, jetzt haben wir:

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)  

Aber die für Verständnis nicht kompilieren, weil "y" kein Double ist aber ein WriterT [Id, String, Double] Darüber hinaus ist die erste Nachricht protokolliert ("Quadratwurzel ok") ist "verloren".

Schließlich habe ich wie folgt aus:

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) 

abkühle

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) 

die gibt! Ich wäre besser, eine Liste [String] für den Schriftsteller zu verwenden, aber ich denke, dass ich auf dem guten Weg bin!

Und jetzt kann ich meinen Urlaub (morgen!) Denke :)

Herausgegeben am 14. Mai

gut, wird der Code nicht kompiliert werden, aber der Fehler ist in Yo Acht letzten Vorschlag (Beachten Sie, dass es nicht ein Verstoß ist wieder Yo Acht, die ein Modell der Freundlichkeit ist!). Ich gebe Ihnen den vollständigen Code und den Fehler:

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



} 

Hier ist die Sitzung des 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 

Ich denke, es ist etwas, das ich von Anfang verpasst haben, die erklären könnte, warum ich bin für einige Wochen geklebt!

Benoit

Herausgegeben am 15. Mai

Ihren Code kompilieren, habe ich einen ersten Fehler:

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

Nach einigen Versuchen habe ich den Import auf diese Weise neu geschrieben:

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

Es gibt Sti ll einen Fehler:

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 

Dieser Fehler war mit dem Code, den Sie am 14. Mai Offensichtlich schrieben, ist es schwierig zu verstehen, was genau IImport mit scalaz-seven. Mit der Version 6 sahen die Dinge einfacher aus: man musste nur scalaz._ und Scalaz._

Ich fühle mich wie ein "verzweifelt Hausschreiber" :) (Ja, ich stimme zu, es ist nicht sehr klug, aber es ist entspannend!)

Benoit

23. Mai

Ouf! Es funktioniert effektiv mit der letzten Version von scalaz-seven: Beachten Sie, dass ich es erstellen musste, anstatt einen Snapshot herunterzuladen.

das ist großartig!

Für diejenigen, die interessiert sind, hier ist die Ausgabe:

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 Acht, wenn zufällig wir eines Tages treffen, ich werde Sie ein Bier bezahlen!

Benoit

+0

Ich denke, Ihre Version von Scalaz-sieben ist nicht gut. Der Code läuft korrekt mit der letzten Version von Scalaz-sieben Zweig –

+0

Ich habe Ihren Beitrag verpasst. Das ist, was ich vermutete. Ich benutze den Schnappschuss vom 14. April. Ich hätte Zeit, heute Abend mit der letzten Version zu testen. – bhericher

Antwort

7

Um bei der monadischen Berechnung anmelden, haben Sie eine Instanz von Writer Monade zu verwenden. Da monad nicht komponiert und Sie den "Validation" -Effekt beibehalten möchten, sollten Sie einen Validation Monad Transformer verwenden. Ich weiß nicht, welche Version von ScalaZ Sie verwenden, aber Scalaz7 (Branch Scalaz-Seven) bietet solche Monade-Transformer (nämlich ValidationT).

so erhalten wir:

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

mit W Typ Ihres Loggers

nach Ihren bearbeiten, hier ist, wie ich es

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

Und jetzt, wie man es für ein Verständnis verwendet

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

Diese Schnipsel könnten mehr Typenannotationen benötigen

am 13. April Herausgegeben

Hier ist der richtige Typ Anmerkungen für Ihre Methoden:

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] 

Auf diese Weise können Sie resultat Methode wie folgt definieren :

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 

Sie Liste [Zeichenfolge] als Protokolltyp könnte auch verwendet werden, weil es ein Monoid ist

BTW, ich spreche Französisch, wenn es :-)

bearbeiten am 14. Mai

helfen kann

Das Problem war, : Der Compiler kann

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

nicht auflösen, da WriterT eine Instanz von Monoid [String] und Pointed [Id] benötigt. Hier

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

ist der vollständige ausführbare Code

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

bearbeiten am 14. August

Dieser Code ist nicht mehr gültig in scalaz-seven. ValidationT wurde entfernt, da Validation keine Monade ist. Hoffentlich kann EatleT stattdessen verwendet werden. Außerdem wurde eine neue Klassenklasse MonadWriter/ListenableMonadWriter hinzugefügt, um diese Art von Anmerkungen zu mindern.

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

Danke Yo Eight: Ich benutze Scalaz 6.0.4 und werde prüfen, was es bietet. Jedenfalls verstehe ich die Idee. – bhericher

+0

Ich bin geklebt! Wenn ich Validation1, Validation2 und Validation3 durch ValidationT [...] (Writer ("blabla", Validation1 (x)) usw.) ersetze, kettet ich erfolgreich die 3 Validierungen, aber ich sehe nicht, wie man die Logs anhäuft ... – bhericher

+1

Das Protokoll wird implizit "akkumuliert", wenn Writer.flatMap aufgerufen wird (eine Instanz von Monoid [W] ist erforderlich). Writer.flatMap wird implizit aufgerufen, wenn ValidationT.flatMap aufgerufen wird. –