问题 在scalaz中堆叠StateT


我试图通过移植Dan Piponi本教程中的一些例子来理解Scala中的Monad Transformers: http://blog.sigfpe.com/2006/05/grok-haskell-monad-transformers.html

我做了几件简单的事情:

import Control.Monad.State
import Control.Monad.Identity

test1 = do
    a <- get
    modify (+1)
    b <- get
    return (a,b)

test2 = do
    a <- get
    modify (++"1")
    b <- get
    return (a,b)

go1 = evalState test1 0
go2 = evalState test2 "0" 

变为:

import scalaz._, Scalaz._

val test1 = for {
  a <- get[Int]
  _ <- modify[Int](1+)
  b <- get
} yield (a,b)

val test2 = for {
  a <- get[String]
  _ <- modify[String](_ + "1")
  b <- get
} yield (a,b)

val go1 = test1.eval(0)
val go2 = test2.eval("0")

但是我怎么能将下一个例子移植到Scala?

test3 = do
    modify (+ 1)
    lift $ modify (++ "1")
    a <- get
    b <- lift get
    return (a,b)

go3 = runIdentity $ evalStateT (evalStateT test3 0) "0"

我已经使用scalaz 7.1.0-M6了解到这一点:

type SST[F[_],A] = StateT[F,String,A]
type IST[F[_],A] = StateT[F,Int,A]

val p1: StateT[Id,Int,Unit] = modify[Int](1+)
val p2: StateT[Id,String,Unit] = modify[String](_ + "1")
val p3: StateT[({type l[a]=StateT[Id,String,a]})#l,Int,Unit] = p2.liftM[IST]

但这还没有结束,甚至可能是我所能说的所有人的倒退。

我当然可以这样做:

import scalaz.Lens._
val test3 = for {
  _ <- firstLens[Int,String] lifts (modify (1+))
  _ <- secondLens[Int,String] lifts (modify (_ + "1"))
  a <- firstLens[Int,String] lifts get
  b <- secondLens[Int,String] lifts get
} yield (a,b)

val go3 = test3.eval(0,"0")

但是我根本就没有使用堆叠的StateT,所以它没有回答这个问题。

提前致谢!


5866
2018-05-06 05:11


起源



答案:


问题是, modify 你得到的通常进口来自 State,并不会帮助你 StateT

从Haskell类型签名开始是个好主意:

test3
  :: (MonadState [Char] m, MonadState s (t m), MonadTrans t,
      Num s) =>
     t m (s, [Char])

你应该能够翻译成这样的东西:

import scalaz._, Scalaz._

def test3[M[_]: Monad](implicit
  inner: MonadState[({ type T[s, a] = StateT[M, s, a] })#T, String],
  outer: MonadState[
    ({
      type T[s, a] = StateT[({ type L[y] = StateT[M, String, y] })#L, s, a ]
    })#T,
    Int
  ],
  mt: MonadTrans[({ type L[f[_], a] = StateT[f, Int, a] })#L]
) = for {
  _ <- outer.modify(_ + 1)
  _ <- mt.liftMU(inner.modify(_ + "1"))
  a <- outer.get
  b <- mt.liftMU(inner.get)
} yield (a, b)

它很可怕,但它是Haskell相当直接的重写。由于某种原因,编译器似乎没有找到 outer 例如,所以你必须帮助它一点:

def test3[M[_]: Monad](implicit
  inner: MonadState[({ type T[s, a] = StateT[M, s, a] })#T, String],
  mt: MonadTrans[({ type L[f[_], a] = StateT[f, Int, a] })#L]
) = {
  val outer =
    StateT.stateTMonadState[Int, ({ type L[y] = StateT[M, String, y] })#L]

  for {
    _ <- outer.modify(_ + 1)
    _ <- mt.liftMU(inner.modify(_ + "1"))
    a <- outer.get
    b <- mt.liftMU(inner.get)
  } yield (a, b)
}

现在您可以编写以下内容,例如:

scala> test3[Id].eval(0).eval("0")
res0: (Int, String) = (1,01)

正如在Haskell示例中一样。

脚注

如果您对承诺感到满意,可以稍微清理一下 Id 作为内部状态变换器的monad(正如你的评论所暗示):

def test3 = {
  val mt = MonadTrans[({ type L[f[_], a] = StateT[f, Int, a] })#L]
  val outer = StateT.stateTMonadState[Int, ({ type L[y] = State[String, y] })#L]
  for {
    _ <- outer.modify(_ + 1)
    _ <- mt.liftMU(modify[String](_ + "1"))
    a <- outer.get
    b <- mt.liftMU(get[String])
  } yield (a, b)
}

这有点不太通用,但它可能适合你。


10
2018-05-06 10:43



我喜欢从Haskell签名开始,然后向后工作的想法,但我从GHCi 7.4.2得到了这个: test3 :: StateT Integer (StateT [Char] Identity) (Integer, [Char])。我隐约明白,这相当于你发布的签名,但我怎么能自己到达那里?或者从这个签名到scalaz等价物? - arya
您可以通过定义来查看更通用的类型 test3 例如,它本身就是一个REPL。如果你只使用它 go3 它将专门针对您所看到的版本。 - Travis Brown
哦好的。鉴于State,String和Int无论如何都被硬编码到定义中,scalaz是否还有更简单/专门的实现?或者让编译器发现其中一些实例实际上简化了代码? - arya


答案:


问题是, modify 你得到的通常进口来自 State,并不会帮助你 StateT

从Haskell类型签名开始是个好主意:

test3
  :: (MonadState [Char] m, MonadState s (t m), MonadTrans t,
      Num s) =>
     t m (s, [Char])

你应该能够翻译成这样的东西:

import scalaz._, Scalaz._

def test3[M[_]: Monad](implicit
  inner: MonadState[({ type T[s, a] = StateT[M, s, a] })#T, String],
  outer: MonadState[
    ({
      type T[s, a] = StateT[({ type L[y] = StateT[M, String, y] })#L, s, a ]
    })#T,
    Int
  ],
  mt: MonadTrans[({ type L[f[_], a] = StateT[f, Int, a] })#L]
) = for {
  _ <- outer.modify(_ + 1)
  _ <- mt.liftMU(inner.modify(_ + "1"))
  a <- outer.get
  b <- mt.liftMU(inner.get)
} yield (a, b)

它很可怕,但它是Haskell相当直接的重写。由于某种原因,编译器似乎没有找到 outer 例如,所以你必须帮助它一点:

def test3[M[_]: Monad](implicit
  inner: MonadState[({ type T[s, a] = StateT[M, s, a] })#T, String],
  mt: MonadTrans[({ type L[f[_], a] = StateT[f, Int, a] })#L]
) = {
  val outer =
    StateT.stateTMonadState[Int, ({ type L[y] = StateT[M, String, y] })#L]

  for {
    _ <- outer.modify(_ + 1)
    _ <- mt.liftMU(inner.modify(_ + "1"))
    a <- outer.get
    b <- mt.liftMU(inner.get)
  } yield (a, b)
}

现在您可以编写以下内容,例如:

scala> test3[Id].eval(0).eval("0")
res0: (Int, String) = (1,01)

正如在Haskell示例中一样。

脚注

如果您对承诺感到满意,可以稍微清理一下 Id 作为内部状态变换器的monad(正如你的评论所暗示):

def test3 = {
  val mt = MonadTrans[({ type L[f[_], a] = StateT[f, Int, a] })#L]
  val outer = StateT.stateTMonadState[Int, ({ type L[y] = State[String, y] })#L]
  for {
    _ <- outer.modify(_ + 1)
    _ <- mt.liftMU(modify[String](_ + "1"))
    a <- outer.get
    b <- mt.liftMU(get[String])
  } yield (a, b)
}

这有点不太通用,但它可能适合你。


10
2018-05-06 10:43



我喜欢从Haskell签名开始,然后向后工作的想法,但我从GHCi 7.4.2得到了这个: test3 :: StateT Integer (StateT [Char] Identity) (Integer, [Char])。我隐约明白,这相当于你发布的签名,但我怎么能自己到达那里?或者从这个签名到scalaz等价物? - arya
您可以通过定义来查看更通用的类型 test3 例如,它本身就是一个REPL。如果你只使用它 go3 它将专门针对您所看到的版本。 - Travis Brown
哦好的。鉴于State,String和Int无论如何都被硬编码到定义中,scalaz是否还有更简单/专门的实现?或者让编译器发现其中一些实例实际上简化了代码? - arya