2015-07-28 2 views
2

Это является продолжением моей предыдущей вопрос: Sequencing both Scalaz WriterT and Either with for-yieldНастройка состава будущего, будь то и писатель в Scalaz

Следующий блок кода является примером последовательности Future, Either и Writer с использованием Монадой трансформаторов EitherT и WriterT; следующий вопрос: как тонко изменить поведение этого пакета трансформаторов.

import scalaz._, Scalaz._ 

class Example[F[_], L] (val logFn: (String) => L)(implicit val f: Monad[F], l: Monoid[L]) 
{ 
    type T = Throwable 
    type EF[α] = EitherT[F, T, α] 
    type WEF[α] = WriterT[EF, L, α] 

    private def unreliableInt (i: Int): T Either Int = new java.util.Random().nextBoolean match { 
    case false => Right (i) 
    case true => Left (new Exception (":-(")) 
    } 

    private def fn (i: Int): WEF[Int] = WriterT.put[EF, L, Int](EitherT.fromEither[F, T, Int](f.point (unreliableInt (i))))(l.zero) 

    private def log (msg: String): WEF[Unit] = WriterT.put[EF, L, Unit](EitherT.right[F, T, Unit](f.point (())))(logFn (msg)) 

    private def foo(): WEF[Int] = for { 
    _ <- log ("Start") 
    x <- fn (18) 
    _ <- log ("Middle") 
    y <- fn (42) 
    _ <- log ("End") 
    } yield x + y 

    def bar(): F[(Option[Int], L)] = { 
    val barWEF: WEF[Int] = foo() 

    // Pull out the logs. 
    val logsEF: EF[L] = barWEF.written 
    val logsF: F[L] = logsEF.toEither.map { 
     case Right (x) => x 
     case Left (e) => logFn(s"Not the logs we are looking for ${e.getMessage}") 
    } 

    // Pull out the value. 
    val resEF: EF[Int] = barWEF.value 
    val resF: F[Option[Int]] = resEF.run.map { 
     case \/- (r) => r.some 
     case -\/ (ex) => None 
    } 

    for { 
     logs <- logsF 
     response <- resF 
    } yield (response, logs) 
    } 
} 

object Program 
{ 
    def main (args : Array[String]) = { 
    import scala.concurrent._ 
    import scala.concurrent.duration._ 
    import ExecutionContext.Implicits.global 

    type L = List[String] 
    type F[α] = Future[α] 

    implicit val l: Monoid[L] = new Monoid[L] { def zero = Nil; def append (f1: L, f2: => L) = f1 ::: f2 } 
    implicit val f: Monad[F] = scalaz.std.scalaFuture.futureInstance 

    def createLog (s: String) = s :: Nil 
    val example = new Example[F, L] (createLog) 
    val result = Await.result (example.bar(), 5 seconds) 
    println ("Context logs attached:" + result._2.foldLeft ("") { (a, x) => a + "\n$ " + s"$x"}) 
    println ("Result:" + result._1) 
    } 
} 

Функция foo не ведет себя, как мне это нужно для; функция bar и функция main иллюстрируют проблему.

желаемое поведение таково, что main всегда будет печатать один из следующих результатов:

Context logs attached: 
$ Start 
Result:None 

или

Context logs attached: 
$ Start 
$ Middle 
Result:None 

или

Context logs attached: 
$ Start 
$ Middle 
$ End 
Result:Some(60) 

main функция должна, однако, никогда не печатайте следующее:

Context logs attached: 
$ Not the logs we are looking for :-(
Result:None 

Но это именно то, что он делает. Когда оба fn1 и fn2 успешны, foo ведет себя как требуется, и main распечатывает все журналы. Если оба или оба fn1 или fn2 возвращают номер Left, функция bar не возвращает никаких журналов, и основная часть печатает только исключение. Theres никак не увидеть как далеко оно получило в журналах.

кажется, что именно этот стек трансформаторов ведет себя таким образом, что если когда-либо есть в последовательности с -\/, контекст каротаж просто отображенной из ...

Глядя на код Scalaz для WriterT этого выглядит скорее всего, будет так:

final case class WriterT[F[_], W, A](run: F[(W, A)]) 

WriterT случай класс, только член run. В отношении этого примера run является кортежем нашего контекста ведения журнала (A) и нашего результата, оба завернуты в те же EitherT (F). W и A связаны данными по типу, так что оба они оба находятся внутри левого или обоих внутри справа.

Я могу предположить, что мне нужно настроенную версию WriterT, которая ведет себя немного по-другому, сохраняя свои данные немного, как это, предоставляя доступ к писателю части только в свежем Applicative[F].point:

final case class WriterT[F[_], W, A](wF: F[W], vF:F[A]) { 
    def run: F[(W, A)] = for { 
    w <- wF 
    v <- vF 
    } yield (w, v) 
} 

Хотя я» m не совсем уверен, что создание собственного класса классов WriterT было бы целесообразным подходом к решению этой проблемы и достижению моего желаемого поведения.

Какие у меня варианты?

ответ

5

Эта статья: Composing monadic effects объясняет проблему.

Итак ...

type MyMonad e w a = ErrorT e (Writer w) А изоморфна (Either e a, w)

type MyMonad e w a = WriterT w (Either e) a изоморфна Either r (a, w)

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

import scalaz._, Scalaz._ 

class Example[F[_], L] (val logFn: (String) => L)(implicit val f: Monad[F], l: Monoid[L]) 
{ 
    type T = Throwable 
    type WF[α] = WriterT[F, L, α] 
    type EWF[α] = EitherT[WF, T, α] 

    private def unreliableInt (i: Int): T Either Int = { 
    new java.util.Random().nextBoolean match { 
     case false => Right (i) 
     case true => Left (new Exception (":-(")) 
    } 
    } 

    private def fn (i: Int): EWF[Int] = unreliableInt (i) match { 
    case Left (left) => EitherT.left [WF, T, Int] (WriterT.put[F, L, T] (f.point (left))(l.zero)) 
    case Right (right) => EitherT.right [WF, T, Int] (WriterT.put[F, L, Int] (f.point (right))(l.zero)) 
    } 

    private def log (msg: String): EWF[Unit] = { EitherT.right[WF, T, Unit](WriterT.put[F, L, Unit] (f.point (()))(logFn (msg))) } 

    private def foo(): EWF[Int] = for { 
    a <- log ("Start") 
    x <- fn (18) 
    b <- log ("Middle") 
    y <- fn (42) 
    c <- log ("End") 
    } yield x + y 

    def bar(): F[(Option[Int], L)] = { 
    val barEWF: EWF[Int] = foo() 

    // Pull out the logs. 
    val logsF: F[L] = barEWF.run.written 

    // Pull out the value. 
    val resF: F[Option[Int]] = barEWF.run.value.map { 
     case \/- (r) => r.some 
     case -\/ (ex) => None 
    } 

    for { 
     logs <- logsF 
     response <- resF 
    } yield (response, logs) 
    } 
} 

object Program 
{ 
    def main (args : Array[String]) = { 
    import scala.concurrent._ 
    import scala.concurrent.duration._ 
    import ExecutionContext.Implicits.global 

    type L = List[String] 
    type F[α] = Future[α] 

    implicit val l: Monoid[L] = new Monoid[L] { def zero = Nil; def append (f1: L, f2: => L) = f1 ::: f2 } 
    implicit val f: Monad[F] = scalaz.std.scalaFuture.futureInstance 

    def createLog (s: String) = s :: Nil 
    val example = new Example[F, L] (createLog) 
    val result = Await.result (example.bar(), 5 seconds) 
    println ("Context logs attached:" + result._2.foldLeft ("") { (a, x) => a + "\n$ " + s"$x"}) 
    println ("Result:" + result._1) 
    } 
} 
+0

Но вы не будете получать журналы, если будущее истечет! –

Смежные вопросы