Scalaz 状态 monad 示例

发布于 2024-12-09 12:04:46 字数 541 浏览 0 评论 0原文

我还没有看到很多 scalaz 状态单子的例子。有这个示例,但很难理解,并且只有一个其他问题看起来堆栈溢出。

我将发布一些我玩过的例子,但我欢迎更多的例子。另外,如果有人可以提供为什么使用 initmodifyputgets 的示例,那就太好了。

编辑:这里是关于状态 monad 的精彩 2 小时演示。

I haven't seen many examples of the scalaz state monad. There is this example but it is hard to understand and there is only one other question on stack overflow it seems.

I'm going to post a few examples I've played with but I would welcome additional ones. Also if somebody can provide example on why init, modify, put and gets are used for that would be great.

Edit: here is an awesome 2 hours presentation on the state monad.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

蓝眸 2024-12-16 12:04:46

我假设,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, State[S, B]]],而不是 Function[A, 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)
}

现在使用 traverse 在 tenDoubleThrows 上应用 freqSumtraverse 相当于 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类型。

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

@ziggystar 评论的其他信息

我放弃了尝试使用 stateT 可能其他人可以显示如果StateFreq 或可以增强 StateRandom 来执行组合计算。相反,我发现两个状态变换器的组合可以这样组合:

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]()))

I assume, scalaz 7.0.x and the following imports (look at answer history for scalaz 6.x):

import scalaz._
import Scalaz._

The state type is defined as State[S, A] where S is type of the state and A is the type of the value being decorated. The basic syntax to create a state value makes use of the State[S, A] function:

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

To run the state computation on a initial value:

// 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")

The state can be threaded through function calls. To do this instead of Function[A, B], define Function[A, State[S, B]]]. Use the State function...

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

Then the for/yield syntax can be used to compose functions:

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)

Here is another example. Fill a list with TwoDice() state computations.

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

Use sequence to get a State[Random, List[(Int,Int)]]. We can provide a type alias.

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))

Or we can use sequenceU which will infer the types:

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))

Another example with State[Map[Int, Int], Int] to compute frequency of sums on the list above. freqSum computes the sum of the throws and counts frequencies.

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)
}

Now use traverse to apply freqSum over tenDoubleThrows. traverse is equivalent to 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]]

Or more succinctly by using traverseU to infer the types:

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]]

Note that because State[S, A] is a type alias for StateT[Id, S, A], tenDoubleThrows2 ends up being typed as Id. I use copoint to turn it back into a List type.

In short, it seems the key to use state is to have functions returning a function modifying the state and the actual result value desired... Disclaimer: I have never used state in production code, just trying to get a feel for it.

Additional info on @ziggystar comment

I gave up on trying using stateT may be someone else can show if StateFreq or StateRandom can be augmented to perform the combined computation. What I found instead is that the composition of the two state transformers can be combined like this:

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
}

It's predicated on g being a one parameter function taking the result of the first state transformer and returning a state transformer. Then the following would work:

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]()))
清音悠歌 2024-12-16 12:04:46

我偶然发现了一篇有趣的博客文章 Grok Haskell Monad Transformers 来自 sigfp,其中有一个通过 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] 值确实很方便,但它允许的另一件事是访问内部的状态以进行理解。 modify[S] 是一种转换 for 推导式内部状态的便捷方法。所以上面的例子可以理解为:

  • a <- init[Int]:以Int状态开始,将其设置为State包裹的值[Int, _] monad 并将其绑定到 a
  • _ <- 修改[Int](_ + 1):增加 Int< /代码>状态
  • <代码>b <- init[Int]:获取Int状态并将其绑定到b(与a相同,但现在state 递增)
  • 使用 ab 生成一个 State[Int, (Int, Int)] 值。

for 理解语法已经使得在 State[S, A] 中的 A 端工作变得微不足道。 initmodifyputgets 提供了一些在 S 上工作的工具位于状态[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 进行 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))
}

I stumbled on an interesting blog post Grok Haskell Monad Transformers from sigfp that has an example of applying two state monads through a monad transformer. Here is a scalaz translation.

The first example shows a 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)

So I have here an example of using init and modify. After playing with it a bit, init[S] turns out to be really convenient to generate a State[S,S] value, but the other thing it allows is to access the state inside the for comprehension. modify[S] is a convenient way to transform the state inside the for comprehension. So the example above can be read as:

  • a <- init[Int]: start with an Int state, set it as the value wrapped by the State[Int, _] monad and bind it to a
  • _ <- modify[Int](_ + 1): increment the Int state
  • b <- init[Int]: take the Int state and bind it to b (same as for a but now the state is incremented)
  • yield a State[Int, (Int, Int)] value using a and b.

The for comprehension syntax already makes it trivial to work on the A side in State[S, A]. init, modify, put and gets provide some tools to work on the S side in State[S, A].

The second example in the blog post translates to:

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

val go2 = test2 ! "0"
// (String, String) = ("0","01")

Very much the same explanation as test1.

The third example is more tricky and I hope there is something simpler that I have yet to discover.

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")

In that code, stTrans takes care of the transformation of both states (increment and suffix with "1") as well as pulling out the String state. stateT allows us to add state transformation on an arbitrary monad M. In this case the state is an Int that is incremented. If we called stTrans ! 0 we would end up with M[String]. In our example, M is StateString, so we'll end up with StateString[String] which is State[String, String].

The tricky part here is that we want to pull out the Int state value out from stTrans. This is what initT is for. It just creates an object that gives access to the state in a way we can flatMap with stTrans.

Edit: Turns out all of that awkwardness can be avoided if we truly reused test1 and test2 which conveniently store the wanted states in the _2 element of their returned tuples:

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

下面是一个关于如何使用 State 的小示例:

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

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,让他失去 10 点生命值

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

并且老板可以反击!当他这样做时,队伍中的每个人都会失去 5 点生命值。

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)
}

现在我们可以这些动作组合成游戏

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 => (A, S))允许您定义产生结果的操作,并在不知道太多情况下操作某些状态状态是从哪里来的。
Monad 部分为您提供组合,以便可以组合您的操作:

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

等等。

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

Here is a very small example on how State can be used:

Let's define a small "game" where some game units are fighting the boss (who is also a game unit).

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)))
}

When the play is on we want to keep track of the game state, so let's define our "actions" in terms of a state monad:

Let's hit the boss hard so he loses 10 from his health:

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

And the boss can strike back! When he does everyone in a party loses 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)
}

Now we can compose these actions into play:

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

Of course in the real life the play will be more dynamic, but it is food enough for my small example :)

We can run it now to see the final state of the game:

val res = play.exec(Game.init)
println(res)

>> Game(0,GameUnit(80),List(GameUnit(10)))

So we barely hit the boss and one of the units have died, RIP.

The point here is the composition.
State (which is just a function S => (A, S)) allows you to define actions that produce results and as well manipulate some state without knowing too much where the state is coming from.
The Monad part gives you composition so your actions can be composed:

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

and so on.

P.S. As for differences between get, put and modify:

modify can be seen as get and put together:

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

or simply

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

So when you use modify you conceptually use get and put, or you can just use them alone.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文