Скалаз: проверка в целях понимания и ведения журнала - PullRequest
9 голосов
/ 27 марта 2012

Я допускаю, что название не очень явное: извините за это.

Предположим, у меня есть для понимания:

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

Валидация 1, Валидация 2 и Валидация 3 выполняют некоторую проверку (например, «возраст> 18») и используют сбой / успех; поэтому, если что-то не так, из-за невозможности понимания я получаю причину в части неудачи результата, иначе я получаю ожидаемое значение в части успеха. Пока все хорошо и ничего сложного.

Но Валидация 1, Валидация 2, Валидация 3 успешны, если их вклад удовлетворяет некоторым правилам (например, «парень может голосовать, потому что его возраст больше 18 лет, а его гражданство - француз»). я хочу отслеживать, какие правила применяются, чтобы их можно было отображать в конце.

Это явно случай использования логирования. но я не решаюсь сделать это:

  1. Имейте объект "регистратор", который доступен любой функции (Validation1, 2 и 3, но также вызывающему, который хочет отобразить содержание журнала)

  2. Сделать регистратор параметром Validation1, 2 и 3

  3. Дождитесь соответствующей главы «Функциональное программирование в Scala»:)

  4. Другое

Спасибо за советы

Отредактировано 10 апреля

Итак, предположим, что я хочу вычислить функцию: x -> 1 / sqrt (x)

Сначала я вычисляю sqrt (x), проверяя, что x> 0, а затем беру обратное, если не ноль.

со скалязом. Валидация, это просто:

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

Теперь, если квадратный корень успешно, я хочу записать строку successsquaretoot, а если обратный успех, я хочу записать строку successinverse так, чтобы функция resultat накапливала обе строки в случае успеха

Я начал с ValidationT, так как Yo Eight предложил:

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

Но я не могу найти, как объединить их для понимания. Кроме того, чтобы получить результат одного из них, мне нужно написать: squareroot2 (4) .run.run что странно и так, как я написал, даже в случае сбоя записывается строка successsquareroot:

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

печатает: (квадратный корень в порядке, ошибка (невозможно получить квадратный корень отрицательного числа))

Спасибо! Benoit

Отредактировано 12 апреля

Итак, Йо Восьми предложил этот фрагмент:

 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

и он предупредил меня, что нужны аннотации некоторых типов. В действительности, возвращаемое значение квадратного корня и обратное довольно уродливо: это проверка того, что мне было трудно понять!

Итак, я должен был явно указать тип возвращаемого значения: def inverse (x: Double): ValidationT [?, E, A] где «E» - это строка, а «A» - двойная (это было легко!). Но как насчет первого? Это должна быть монада (насколько я понимаю), и я выбрал самое простое: Id (то есть Identity).

Так что теперь у нас есть:

   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)     

Но для понимания не компилируется, потому что "y" не Double, а WriterT [Id, String, Double] Кроме того, первое зарегистрированное сообщение ("Squareroot ok") "потеряно".

В конце концов, я так и сделал:

   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)

, что дает:

  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! Я бы лучше использовал List [String] для Writer, но я думаю, что я на правильном пути!

А теперь я могу подумать о моих выходных (завтра!):)

Отредактировано 14 мая

хорошо, код не компилируется, но ошибка в последнем предложении Yo Eight (обратите внимание, что Yo Eight снова не оскорбляет, кто образец доброты!). Я отправляю вам полный код и ошибку:

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



}

Вот сеанс терминала:

benoit@benoit-laptop:~$ cd scala
benoit@benoit-laptop:~/scala$ scala -version
Scala code runner version 2.9.2 -- Copyright 2002-2011, LAMP/EPFL
benoit@benoit-laptop:~/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

Полагаю, что с самого начала я что-то упустил, и это может объяснить, почему я застрял на несколько недель!

Benoit

Отредактировано 15 мая

При компиляции вашего кода у меня появляется первая ошибка:

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

После некоторых попыток я переписал импорт следующим образом:

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

Есть еще одна ошибка:

 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

Эта ошибка присутствовала в коде, который вы написали 14 мая. Очевидно, что сложно понять, что именно импортировать с помощью scalaz-seven. С версией 6 все выглядело проще: нужно было просто импортировать scalaz._ и Scalaz._

Я чувствую себя "отчаянным домохозяином" :) (да, я согласен, это не очень проницательно, но это расслабляет!)

Benoit

23 мая

Ouf! Он эффективно работает с последней версией scalaz-seven: обратите внимание, что мне пришлось собирать его, а не загружать снимок.

это здорово!

Для тех, кто заинтересован, вот вывод:

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

Эй, восемь, если случайно мы встретимся однажды, я заплачу тебе пиво!

Benoit

1 Ответ

7 голосов
/ 28 марта 2012

Для регистрации во время монадических вычислений вы должны использовать экземпляр монады Writer.Поскольку монада не скомпонована и вы хотите сохранить эффект «Валидация», вам следует использовать Валидационный монадный трансформатор.Я не знаю, какую версию ScalaZ вы используете, но Scalaz7 (ветвь scalaz-seven) предоставляет такой монадный преобразователь (а именно ValidationT).

, поэтому мы получаем:

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

сW тип вашего регистратора

Согласно вашему редактированию , вот как я это сделаю

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

Эти фрагменты могут нуждаться в большем количестве аннотаций типов

Отредактировано 13 апреля

Вот правильные аннотации типов для ваших методов:

 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]

Таким образом, вы можете определить метод resultat следующим образом:

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

Вы также можете использовать List [String] в качестве типа журнала, потому что это моноид

BTWЯ говорю по-французски, если это может помочь: -)

Изменить 14 мая

Проблема была: Компилятор не может разрешить

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

, поскольку WriterT нужен экземпляр Monoid [String] и Pointed [Id].

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

Вот полный исполняемый код

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

Редактировать 14 августа

Этот код не являетсябольше действует в скалязе-семь.ValidationT был удален, так как Validation не является монадой.Надеюсь, вместо этого можно использовать EitherT.Кроме того, был добавлен новый класс типов MonadWriter / ListenableMonadWriter для облегчения этих аннотаций типов.

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