Scalaz State Monad示例


77

我还没有看到斯卡拉兹州单子的许多例子。有这个例子,但是很难理解,而且似乎在堆栈溢出上只有另一个问题

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

编辑:是一个关于状态monad的2小时真棒演示。

Answers:


83

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

import scalaz._
import Scalaz._

状态类型定义为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")) 

要对初始值运行状态计算:

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

或者我们可以使用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))

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

现在使用遍历申请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]]

或更简洁地通过使用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]()))

Statemonad难道不是现实中的“状态转换器”吗?第二个问题是:是否有更好的方法将掷骰子和求和合并为一个状态单子?考虑到这两个单子,你会怎么做?
ziggystar 2011年

@ziggystar,在技术上StateFreqStateRandom有单子。我不认为这State[S, x]是单子变压器,因为S不需要是单子。对于更好的组合方式,我也想知道。我看不到任何明显可用的信息。可能stateT会有所帮助,但我还没有弄清楚。
huynhjl,2011年

我写的不是“单变压器”,而是“状态变压器”。该State[S, x]'对象不举行国,但后者的转型。只是我认为该名称可以选择得较少混淆。这与您的答案无关,而与Scalaz有关。
ziggystar,2011年

@ziggystar,我想出了如何stateT将滚动和求和结合为一个StateT单子的方法!请参阅stackoverflow.com/q/7782589/257449。陷入困境,然后我traverse终于明白了。
huynhjl 2011年

1
@DavidB。,类似于运算符的语法似乎已经消失,并被名称代替。!现在eval; ~>现在exec
huynhjl 2013年

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)

因此,这里有一个使用init和的示例modify。玩了一点之后,init[S]事实证明生成一个State[S,S]值确实很方便,但是它允许的另一件事是访问内的状态以进行理解。modify[S]是一种转换内部状态以进行理解的便捷方法。因此,上面的示例可以理解为:

  • 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在侧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]。在我们的示例中,Mis StateString,因此我们将得出StateString[String]which是State[String, String]

这里最棘手的部分是我们Int要从中提取状态值stTrans。这initT是为了什么。它只是创建一个对象,该对象以我们可以使用flatMap的方式提供对状态的访问stTrans

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

14

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

进行比赛时,我们要跟踪游戏状态,因此让我们根据状态单子来定义“动作”:

让我们重击老板,让他从他的职业中输掉10点health

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]

等等。

PS至于之间的差异getput以及modify

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,也可以仅单独使用它们。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.