scala Scalaz 状态 monad 示例

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7734756/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 03:33:49  来源:igfitidea点击:

Scalaz state monad examples

scalascalazstate-monad

提问by huynhjl

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

我还没有看到很多 scalaz 状态 monad 的例子。有这个例子,但很难理解,似乎只有一个关于堆栈溢出的问题

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, putand getsare used for that would be great.

我将发布一些我玩过的例子,但我欢迎更多的例子。此外,如果有人可以提供上的例子,为什么initmodifyputgets用于将是巨大的。

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

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

回答by huynhjl

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

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

import scalaz._
import Scalaz._

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

状态类型定义为State[S, A]whereS是状态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")) 

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 Statefunction...

状态可以通过函数调用进行线程化。要做到这一点而不是Function[A, B],请定义Function[A, State[S, B]]]。使用该State功能...

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

Then the for/yieldsyntax can be used to compose functions:

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

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

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

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.

使用序列得到一个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))

Or we can use sequenceUwhich will infer the types:

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

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. freqSumcomputes the sum of the throws and counts frequencies.

另一个示例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)
}

Now use traverse to apply freqSumover tenDoubleThrows. traverseis equivalent to map(freqSum).sequence.

现在使用遍历申请freqSumtenDoubleThrowstraverse相当于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 traverseUto infer the types:

或者更简洁地使用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]]

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

请注意,由于State[S, A]是 的类型别名StateT[Id, S, A],因此tenDoubleThrows2 最终被键入为Id。我copoint过去常常把它变回一种List类型。

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 statein production code, just trying to get a feel for it.

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

Additional info on @ziggystar comment

关于@ziggystar 评论的附加信息

I gave up on trying using stateTmay be someone else can show if StateFreqor StateRandomcan 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:

我放弃了尝试使用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
}

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

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

回答by huynhjl

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

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

The first exampleshows a State[Int, _]monad:

一个示例显示了一个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 initand 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:

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

  • a <- init[Int]: start with an Intstate, set it as the value wrapped by the State[Int, _]monad and bind it to a
  • _ <- modify[Int](_ + 1): increment the Intstate
  • b <- init[Int]: take the Intstate and bind it to b(same as for abut now the state is incremented)
  • yield a State[Int, (Int, Int)]value using aand b.
  • a <- init[Int]: 从一个Int状态开始,将其设置为由State[Int, _]monad包装的值并将其绑定到a
  • _ <- modify[Int](_ + 1): 增加Int状态
  • b <- init[Int]: 获取Int状态并将其绑定到b(与 for 相同,a但现在状态增加)
  • State[Int, (Int, Int)]使用a和产生一个值b

The for comprehension syntax already makes it trivial to work on the Aside in State[S, A]. init, modify, putand getsprovide some tools to work on the Sside in State[S, A].

for 理解语法已经使得AState[S, A]. init, modify,putgets提供一些工具来SState[S, A].

The second examplein 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.

与 的解释非常相似test1

The third exampleis 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, stTranstakes care of the transformation of both states (increment and suffix with "1") as well as pulling out the Stringstate. stateTallows us to add state transformation on an arbitrary monad M. In this case the state is an Intthat is incremented. If we called stTrans ! 0we would end up with M[String]. In our example, Mis StateString, so we'll end up with StateString[String]which is State[String, String].

在该代码中,stTrans处理两个状态的转换(递增和后缀为"1")以及拉出String状态。stateT允许我们在任意 monad 上添加状态转换M。在这种情况下,状态Int是递增的。如果我们打电话,stTrans ! 0我们最终会得到M[String]. 在我们的例子中,Mis StateString,所以我们最终会得到StateString[String]which is State[String, String]

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

这里棘手的部分是我们想要IntstTrans. 这initT就是为了。它只是创建一个对象,该对象以我们可以使用 flatMap 的方式访问状态stTrans

Edit: Turns out all of that awkwardness can be avoided if we truly reused test1and test2which conveniently store the wanted states in the _2element of their returned tuples:

编辑:如果我们真正重用test1并且test2方便地将想要的状态存储在_2它们返回的元组的元素中,那么所有这些尴尬都可以避免:

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

回答by Alexey Raga

Here is a very small example on how Statecan be used:

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

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

让我们定义一个小“游戏”,其中一些游戏单位正在与 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)))
}

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:

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

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

让我们重击boss,让他损失10 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.

而且老板可以反击!当他这样做时,聚会中的每个人都会失去 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 composethese actions into play:

现在我们可以将这些动作组合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.

所以我们几乎没有击中boss,其中一个单位已经死亡,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 Monadpart gives you composition so your actions can be composed:

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

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

and so on.

等等。

P.S.As for differences between get, putand modify:

PS至于get,put和之间的区别modify

modifycan be seen as getand puttogether:

modify可以看作getput一起:

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 modifyyou conceptually use getand put, or you can just use them alone.

因此,当您modify在概念上使用getand 时put,您也可以单独使用它们。