斯卡拉兹州的monad例子

时间:2011-10-12 02:56:26

标签: scala scalaz state-monad

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

我将发布一些我玩过的例子,但我会欢迎其他的例子。此外,如果有人可以举例说明为什么initmodifyputgets会很好。

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

3 个答案:

答案 0 :(得分: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应用于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的关键是让函数返回一个函数来修改状态和所需的实际结果值... 免责声明:我从未在生产代码中使用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]()))

答案 1 :(得分:15)

我在sigfp上发现了一篇有趣的博客文章Grok Haskell Monad Transformers,其中有一个通过monad变换器应用两个状态monad的例子。这是一个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 comprehension中的状态。所以上面的例子可以理解为:

  • a <- init[Int]:以Int状态开头,将其设置为State[Int, _] monad包含的值并将其绑定到a
  • _ <- modify[Int](_ + 1):增加Int
  • b <- init[Int]:采用Int状态并将其绑定到b(与a相同,但现在状态会增加)
  • 使用State[Int, (Int, Int)]a生成b值。

for comprehension语法已经使得在A中的State[S, A]方面工作变得微不足道。 initmodifyputgets提供了一些工具,可以在S的{​​{1}}方面工作。

博客文章中的第二个示例转换为:

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允许我们在任意monad stateT上添加状态转换。在这种情况下,状态是递增的M。如果我们致电Int,我们会以stTrans ! 0结束。在我们的示例中,M[String]M,因此我们最终会得到StateString StateString[String]

这里棘手的部分是我们要从State[String, String]中提取Int状态值。这是stTrans的用途。它只是创建一个对象,以一种我们可以使用initT进行flatMap的方式访问状态。

编辑:如果我们真正重用stTranstest1可以避免所有这些尴尬,它可以方便地将所需状态存储在其返回元组的test2元素中:

_2

答案 2 :(得分:13)

以下是关于如何使用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来定义我们的“行动”:

让我们努力击中老板,让他从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)
}

现在我们可以将这些操作组成<{1}}:

play

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

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

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

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

这里的要点是组合val res = play.exec(Game.init) println(res) >> Game(0,GameUnit(80),List(GameUnit(10))) (这只是一个函数State)允许您定义生成结果的操作,并且在不了解状态来源的情况下操纵某些状态。 S => (A, S)部分为您提供合成,以便您的行动可以合成:

Monad

等等。

P.S。至于 A => State[S, B] B => State[S, C] ------------------ A => State[S, C] getput之间的差异:

modify可以被视为modifyget

put

或只是

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))) 时,您在概念上使用modifyget,或者您可以单独使用它们。