【问题标题】:Scalaz state monad examplesScalaz 状态单子示例
【发布时间】:2011-12-05 19:01:03
【问题描述】:

我还没有看到很多 scalaz state monad 的例子。有this example 但很难理解,而且似乎只有一个other question 堆栈溢出。

我将发布一些我玩过的示例,但我欢迎其他示例。另外,如果有人可以举例说明为什么使用initmodifyputgets,那就太好了。

编辑:here 是关于 state monad 的 2 小时精彩演示。

【问题讨论】:

    标签: scala scalaz state-monad


    【解决方案1】:

    我假设,scalaz 7.0.x 和以下导入(查看 scalaz 6.x 的答案历史记录):

    import scalaz._
    import Scalaz._
    

    状态类型定义为State[S, A],其中S 是状态类型,A 是被修饰值的类型。创建状态值的基本语法使用State[S, A] 函数:

    // Create a state computation incrementing the state and returning the "str" value
    val s = State[Int, String](i => (i + 1, "str")) 
    

    在初始值上运行状态计算:

    // start with state of 1, pass it to s
    s.eval(1)
    // returns result value "str"
    
    // same but only retrieve the state
    s.exec(1)
    // 2
    
    // get both state and value
    s(1) // or s.run(1)
    // (2, "str")
    

    状态可以通过函数调用线程化。要执行此操作而不是 Function[A, B],请定义 Function[A, State[S, B]]]。使用State函数...

    import java.util.Random
    def dice() = State[Random, Int](r => (r, r.nextInt(6) + 1))
    

    那么for/yield语法可以用来组合函数:

    def TwoDice() = for {
      r1 <- dice()
      r2 <- dice()
    } yield (r1, r2)
    
    // start with a known seed 
    TwoDice().eval(new Random(1L))
    // resulting value is (Int, Int) = (4,5)
    

    这是另一个例子。用TwoDice() 状态计算填充列表。

    val list = List.fill(10)(TwoDice())
    // List[scalaz.IndexedStateT[scalaz.Id.Id,Random,Random,(Int, Int)]]
    

    使用序列获取State[Random, List[(Int,Int)]]。我们可以提供一个类型别名。

    type StateRandom[x] = State[Random,x]
    val list2 = list.sequence[StateRandom, (Int,Int)]
    // list2: StateRandom[List[(Int, Int)]] = ...
    // run this computation starting with state new Random(1L)
    val tenDoubleThrows2 = list2.eval(new Random(1L))
    // tenDoubleThrows2  : scalaz.Id.Id[List[(Int, Int)]] =
    //   List((4,5), (2,4), (3,5), (3,5), (5,5), (2,2), (2,4), (1,5), (3,1), (1,6))
    

    或者我们可以使用sequenceU 来推断类型:

    val list3 = list.sequenceU
    val tenDoubleThrows3 = list3.eval(new Random(1L))
    // tenDoubleThrows3  : scalaz.Id.Id[List[(Int, Int)]] = 
    //   List((4,5), (2,4), (3,5), (3,5), (5,5), (2,2), (2,4), (1,5), (3,1), (1,6))
    

    另一个使用State[Map[Int, Int], Int] 的示例来计算上面列表中总和的频率。 freqSum 计算投掷和计数频率的总和。

    def freqSum(dice: (Int, Int)) = State[Map[Int,Int], Int]{ freq =>
      val s = dice._1 + dice._2
      val tuple = s -> (freq.getOrElse(s, 0) + 1)
      (freq + tuple, s)
    }
    

    现在使用遍历将freqSum 应用于tenDoubleThrowstraverse 等价于map(freqSum).sequence

    type StateFreq[x] = State[Map[Int,Int],x]
    // only get the state
    tenDoubleThrows2.copoint.traverse[StateFreq, Int](freqSum).exec(Map[Int,Int]())
    // Map(10 -> 1, 6 -> 3, 9 -> 1, 7 -> 1, 8 -> 2, 4 -> 2) : scalaz.Id.Id[Map[Int,Int]]
    

    或者更简洁地使用traverseU 来推断类型:

    tenDoubleThrows2.copoint.traverseU(freqSum).exec(Map[Int,Int]())
    // Map(10 -> 1, 6 -> 3, 9 -> 1, 7 -> 1, 8 -> 2, 4 -> 2) : scalaz.Id.Id[Map[Int,Int]]
    

    请注意,因为State[S, A]StateT[Id, S, A] 的类型别名,所以tenDoubleThrows2 最终被键入为Id。我使用copoint 将其转回List 类型。

    简而言之,使用状态的关键似乎是让函数返回一个修改状态的函数和所需的实际结果值...... 免责声明:我从未在生产代码中使用过state,只是在尝试感受一下。

    @ziggystar 评论的其他信息

    我放弃了尝试使用stateT 可能是其他人可以证明StateFreqStateRandom 是否可以增强以执行组合计算。相反,我发现两个状态转换器的组成可以这样组合:

    def stateBicompose[S, T, A, B](
          f: State[S, A],
          g: (A) => State[T, B]) = State[(S,T), B]{ case (s, t) =>
      val (newS, a) = f(s)
      val (newT, b) = g(a) apply t
      (newS, newT) -> b
    }
    

    它基于g 是一个单参数函数,它获取第一个状态转换器的结果并返回一个状态转换器。然后以下将起作用:

    def diceAndFreqSum = stateBicompose(TwoDice, freqSum)
    type St2[x] = State[(Random, Map[Int,Int]), x]
    List.fill(10)(diceAndFreqSum).sequence[St2, Int].exec((new Random(1L), Map[Int,Int]()))
    

    【讨论】:

    • State monad 不是现实中的“状态转换器”吗?作为第二个问题:有没有更好的方法将掷骰子和求和组合成一个单一的状态单子?考虑到这两个 monad,你会怎么做?
    • @ziggystar,技术上StateFreqStateRandom 是单子。我不认为State[S, x] 是单子转换器,因为S 不需要是单子。为了更好的结合方式,我也想知道。我没有看到任何明显现成的东西。可能是stateT 可能会有所帮助,但我还没有弄清楚。
    • 我写的不是“monad transformer”而是“state transformer”。 State[S, x]' 对象不持有状态,而是后者的转换。只是我认为这个名字可以选择不那么混乱。这与您的答案无关,而是关于 Scalaz。
    • @ziggystar,我想出了如何利用stateT 将滚动和求和组合成一个StateT monad!见stackoverflow.com/q/7782589/257449。卡在最后,然后我最终发现了traverse
    • @DavidB.,类似运算符的语法似乎已经消失并被名称所取代。 ! 现在是 eval~&gt; 现在是 exec
    【解决方案2】:

    我偶然发现了来自 sigfp 的一篇有趣的博文 Grok Haskell Monad Transformers,其中有一个通过单子转换器应用两个状态单子的示例。这是一个scalaz翻译。

    第一个示例显示了一个 State[Int, _] monad:

    val test1 = for {
      a <- init[Int] 
      _ <- modify[Int](_ + 1)
      b <- init[Int]
    } yield (a, b)
    
    val go1 = test1 ! 0
    // (Int, Int) = (0,1)
    

    所以我这里有一个使用initmodify 的例子。玩了一会儿之后,init[S] 被证明非常方便地生成 State[S,S] 值,但它允许的另一件事是访问 for comprehension 中的状态。 modify[S] 是在 for 理解中转换状态的便捷方式。所以上面的例子可以理解为:

    • a &lt;- init[Int]:以Int 状态开始,将其设置为State[Int, _] monad 包裹的值并将其绑定到a
    • _ &lt;- modify[Int](_ + 1):增加Int 状态
    • b &lt;- init[Int]:获取Int 状态并将其绑定到b(与a 相同,但现在状态递增)
    • 使用ab 生成State[Int, (Int, Int)] 值。

    for 理解语法已经使得在State[S, A] 中的A 一侧工作变得微不足道。 initmodifyputgets 提供了一些在 S 一侧工作的工具 State[S, A]

    博文中的第二个示例翻译为:

    val test2 = for {
      a <- init[String]
      _ <- modify[String](_ + "1")
      b <- init[String]
    } yield (a, b)
    
    val go2 = test2 ! "0"
    // (String, String) = ("0","01")
    

    test1的解释非常相似。

    第三个例子比较复杂,我希望有一些更简单的东西我还没有发现。

    type StateString[x] = State[String, x]
    
    val test3 = {
      val stTrans = stateT[StateString, Int, String]{ i => 
        for {
          _ <- init[String]
          _ <- modify[String](_ + "1")
          s <- init[String]
        } yield (i+1, s)
      }
      val initT = stateT[StateString, Int, Int]{ s => (s,s).pure[StateString] }
      for {
        b <- stTrans
        a <- initT
      } yield (a, b)
    }
    
    val go3 = test3 ! 0 ! "0"
    // (Int, String) = (1,"01")
    

    在该代码中,stTrans 负责两种状态的转换(增量和带有"1" 的后缀)以及拉出String 状态。 stateT 允许我们在任意 monad M 上添加状态转换。在这种情况下,状态是递增的Int。如果我们调用stTrans ! 0,我们最终会得到M[String]。在我们的示例中,MStateString,所以我们最终会得到 StateString[String],即 State[String, String]

    这里棘手的部分是我们想从stTrans 中提取Int 状态值。这就是initT 的用途。它只是创建了一个对象,以一种我们可以使用stTrans 进行平面映射的方式访问状态。

    编辑:事实证明,如果我们真正重用 test1test2,它们可以方便地将所需状态存储在它们返回的元组的 _2 元素中:

    // same as test3:
    val test31 = stateT[StateString, Int, (Int, String)]{ i => 
      val (_, a) = test1 ! i
      for (t <- test2) yield (a, (a, t._2))
    }
    

    【讨论】:

      【解决方案3】:

      这是一个关于如何使用State 的小例子:

      让我们定义一个小型“游戏”,其中一些游戏单位正在与老板(也是游戏单位)战斗。

      case class GameUnit(health: Int)
      case class Game(score: Int, boss: GameUnit, party: List[GameUnit])
      
      
      object Game {
        val init = Game(0, GameUnit(100), List(GameUnit(20), GameUnit(10)))
      }
      

      当游戏开始时,我们想要跟踪游戏状态,所以让我们用状态单子来定义我们的“动作”:

      让我们狠狠地打boss,让他从health中损失10:

      def strike : State[Game, Unit] = modify[Game] { s =>
        s.copy(
          boss = s.boss.copy(health = s.boss.health - 10)
        )
      }
      

      老板可以反击!当他这样做时,聚会中的每个人都会输 5 health

      def fireBreath : State[Game, Unit] = modify[Game] { s =>
        val us = s.party
          .map(u => u.copy(health = u.health - 5))
          .filter(_.health > 0)
      
        s.copy(party = us)
      }
      

      现在我们可以将这些操作组合play

      def play = for {
        _ <- strike
        _ <- fireBreath
        _ <- fireBreath
        _ <- strike
      } yield ()
      

      当然在现实生活中该剧会更有活力,但对于我的小例子来说已经足够了:)

      我们现在可以运行它来查看游戏的最终状态:

      val res = play.exec(Game.init)
      println(res)
      
      >> Game(0,GameUnit(80),List(GameUnit(10)))
      

      所以我们几乎没有击中 Boss,其中一个单位已经死亡,RIP。

      这里的重点是构图State(这只是一个函数S =&gt; (A, S))允许您定义产生结果的操作并在不知道状态来自何处的情况下操纵某些状态。 Monad 部分为您提供组合,以便您的动作可以组合:

       A => State[S, B] 
       B => State[S, C]
      ------------------
       A => State[S, C]
      

      等等。

      P.S.至于getputmodify之间的区别:

      modify 可以看作getput 在一起:

      def modify[S](f: S => S) : State[S, Unit] = for {
        s <- get
        _ <- put(f(s))
      } yield ()
      

      或者干脆

      def modify[S](f: S => S) : State[S, Unit] = get[S].flatMap(s => put(f(s)))
      

      因此,当您使用modify 时,您在概念上使用getput,或者您可以单独使用它们。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-07
        • 2011-08-20
        • 2014-02-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多