2017-04-11 60 views
4

我正在玩scalaz的monad變形金剛。我正在試圖將一個Writer與底層的Id monad堆疊在一起。爲了組合它們,我使用了MonadReaderMonadWriter類型類。如何在scalaz中堆疊ReaderT和WriterT變換器?

我設法編譯和運行下面的代碼示例而不作家(即與Reader單子,即ReaderT[Id.Id, String, A])。當添加WriterT到堆棧中,我得到的編譯錯誤:

Gist.scala:10: could not find implicit value for parameter F: scalaz.MonadReader[Gist.R,String] 
    val MR = MonadReader[R, String] 
        ^

我怎樣才能獲得的MonadReader實例爲我的變壓器棧?我必須使用ReaderWriterStateT還是有另一種方式?

全碼:

import scalaz.{Id, MonadListen, MonadReader, ReaderT, WriterT} 

object Gist { 
    import scalaz.std.list._ 
    import scalaz.syntax.monad._ 

    type P[A] = ReaderT[Id.Id, String, A] 
    type R[A] = WriterT[P, List[String], A] 

    val MR = MonadReader[R, String] 
    val MW = MonadListen[R, List[String]] 

    def apply: R[String] = MR.ask >>= { greeting => 
    MW.tell(List(s"greeting $greeting")) >>= { _ => 
     MW.point(s"Hello $greeting") 
    } 
    } 
} 

回答

5

我不完全知道爲什麼Scalaz不提供此實例(或類似的單子變壓器MonadReader情況下),但我猜的答案有事情做與事實上,WriterTInstanceN已經goes past 11MonadReader增加只會讓事情更加混亂。

你可以在Scalaz的GitHub問題中挖掘一些問題(甚至可以在IRC頻道上詢問是否有胃),但我不確定答案的重要性。

你可以很直接地端口從Haskell的mtl實例:

instance (Monoid w, MonadReader r m) => MonadReader r (Strict.WriterT w m) where 
    ask = lift ask 
    local = Strict.mapWriterT . local 
    reader = lift . reader 

從而轉化爲斯卡拉看起來是這樣的:

import scalaz.{ MonadReader, MonadTrans, Monoid, WriterT } 
import scalaz.syntax.monad._ 

implicit def monadReaderForWriterT[F[_], I, W](implicit 
    F: MonadReader[F, I], 
    W: Monoid[W] 
): MonadReader[WriterT[F, W, ?], I] = new MonadReader[WriterT[F, W, ?], I] { 
    def ask: WriterT[F, W, I] = MonadTrans[WriterT[?[_], W, ?]].liftM(F.ask) 

    def local[A](f: I => I)(fa: WriterT[F, W, A]): WriterT[F, W, A] = 
    fa.mapT(F.local(f)) 

    def point[A](a: => A): WriterT[F, W, A] = a.point[WriterT[F, W, ?]] 
    def bind[A, B](fa: WriterT[F, W, A])(
    f: A => WriterT[F, W, B] 
): WriterT[F, W, B] = fa.flatMap(f) 
} 

請注意,我使用kind-projector,因爲類型拉姆達版本會更像Haskell版本的四到五倍而不是三倍。

一旦你定義這種情況下,你可以寫:

import scalaz.{ Id, MonadListen, ReaderT } 
import scalaz.std.list._ 

type P[A] = ReaderT[Id.Id, String, A] 
type R[A] = WriterT[P, List[String], A] 

val MR = MonadReader[R, String] 
val MW = MonadListen[R, List[String]] 

def apply: R[String] = MR.ask >>= { greeting => 
    MW.tell(List(s"greeting $greeting")) >>= { _ => 
    MW.point(s"Hello $greeting") 
    } 
} 
+0

就像一個魅力。爲了教學目的,你能想到另一個沒有'scalaz'實現的變壓器棧/類型組合,所以我可以練習編寫我自己的實現嗎? – mgryszko

+0

@mgryszko你可以看到Haskell的mtl [here](https://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Reader-Class.html)-'StateT'的列表, 'ListT','ContT','EitherT','IdT'等都是不錯的選擇。 –