2011-10-12 116 views
72

我還沒有看到斯卡拉狀態單子的許多例子。有this example,但它很難理解,似乎只有一個other question堆棧溢出。斯卡拉斯狀態單子例子

我打算髮表一些我玩過的例子,但我會歡迎更多的例子。此外,如果有人可以提供例子,爲什麼init,modify, putgets用於這將是偉大的。

編輯:here是一個很棒的2個小時的國家單體演示。

回答

80

我認爲,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) 
} 

現在使用遍歷超過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類型。

總之,似乎使用狀態的關鍵是有函數返回一個函數修改狀態和實際的結果值所需... 免責聲明:我從來沒有在生產代碼中使用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]())) 
+0

是不是「國家」monad而不是「國家變壓器」在reallity?作爲第二個問題:是否有更好的方法將擲骰子和總結合併爲一個單一的國家monad?考慮到兩個monad,你會怎麼做? – ziggystar

+0

@ziggystar,技術上'StateFreq'和'StateRandom'是單子。我不認爲'State [S,x]'是一個monad變壓器,因爲'S'不需要是monad。爲了更好地結合,我也想知道。我沒有看到任何顯而易見的東西。可能是'stateT'可以幫助,但我還沒有弄清楚。 – huynhjl

+0

我沒有寫「monad變壓器」,而是「狀態變壓器」。 「國家[S,X]」的對象並不是一個國家,而是一個國家的轉變。只是我認爲這個名字可以被選擇的更少。這不關你的答案,而是斯卡拉斯。 – ziggystar

15

我無意中發現了一個有趣的博客文章Grok Haskell Monad Transformers從siGFP轉已經通過一個單子轉換應用兩種狀態單子的例子。這是一個scalaz翻譯。

第一個例子顯示State[Int, _]單子:

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]是一種方便的方式來改變理解中的狀態。所以上面的示例中可以讀作:

  • a <- init[Int]:與Int狀態開始,將其設置爲通過State[Int, _]單子包裹的值,並將其綁定到a
  • _ <- modify[Int](_ + 1):遞增Int狀態
  • b <- init[Int]:取Int狀態並將其綁定到b(與a相同,但現在狀態遞增)
  • 產生一個State[Int, (Int, Int)]值usi ng ab

for comprehension語法已經使側的State[S, A]工作變得微不足道。init,modify,putgets提供了一些工具在State[S, A]S側工作。

在博客文章中第二個例子翻譯爲:

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平面地圖的方式訪問該狀態。

編輯:原來所有的尷尬都可以可以避免的,如果我們真正重用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)) 
} 
10

這裏是一個非常小的例子在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))) 
} 

當劇本是我們要跟蹤的遊戲狀態,所以讓我們定義的狀態單子的方面我們的「動作」:

讓我們碰上老闆很辛苦,所以他從失去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] 

等。

P.S.至於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,或者你可以單獨使用它們。