首页 文章

斯卡拉兹州的monad例子

提问于
浏览
75

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

我已经玩了但我会欢迎其他的 . 此外,如果有人可以举例说明为什么 initmodifyputgets 用于那将是很好的 .

编辑:here是一个令人敬畏的2小时状态monad演示文稿 .

3 回答

  • 82

    我假设,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 应用于 tenDoubleThrows . traverse 相当于 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 ,只是试图了解它 .

    Additional info on @ziggystar comment

    我放弃尝试使用 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]()))
    
  • 15

    这是一个关于如何使用 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)))
    }
    

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

    让我们努力击中老板让他从他的_504855失去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)))
    

    因此,我们几乎没有击中老板,其中一个单位已经死亡,RIP .

    这里的重点是构图 . State (这只是一个函数 S => (A, S) )允许您定义生成结果的操作,并且在不了解状态来源的情况下操纵某些状态 . Monad 部分为您提供合成,以便您的行动可以组成:

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

    等等 .

    附:至于 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 ,或者您可以单独使用它们 .

  • 12

    我偶然发现了一个有趣的博客文章Grok Haskell Monad Transformers来自sigfp,它有一个通过monad变换器应用两个状态monad的例子 . 这是一个scalaz翻译 .

    first example 显示 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 comprehension中的状态 . 所以上面的例子可以解读为:

    • a <- init[Int] :以 Int 状态开始,将其设置为 State[Int, _] monad包装的值并将其绑定到 a

    • _ <- modify[Int](_ + 1) :递增 Int 状态

    • b <- init[Int] :采取 Int 状态并将其绑定到 b (与 a 相同但现在状态增加)

    • 使用 ab 产生 State[Int, (Int, Int)] 值 .

    for comprehension语法已经使得在 State[S, A]A 侧工作变得微不足道 . initmodifyputgets 提供一些工具在 State[S, A]S 方面工作 .

    博客文章中的 second example 转换为:

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

    test1 完全相同的解释 .

    third example 更棘手,我希望有一些更简单的东西我还没有发现 .

    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 进行flatMap的方式访问状态 .

    编辑:如果我们真正重用 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))
    }
    

相关问题