Scalaz 상태 모나드 예제
나는 스칼라 즈 상태 모나드의 많은 예를 보지 못했습니다. 이 예제는 하지만 이해하기 어렵 단 하나가 다른 질문 보인다 오버 플로우 스택은.
나는 내가 가지고 놀았 던 몇 가지 예를 게시 할 것이지만 추가 사례를 환영합니다. 누군가에 대한 예를 제공 할 수 있습니다 또한 경우에 왜 init
, modify
, put
및 gets
그 사용되는 것은 좋은 것입니다.
편집 : 여기 상태 모나드에 멋진 2 시간 프리젠 테이션입니다.
나는 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
하여 tenDoubleThrows
. traverse
와 동일합니다 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
다른 사람 이 결합 된 계산을 수행 할 수 있는지 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]()))
나는 모나드 변환기 를 통해 두 개의 상태 모나드를 적용하는 예가있는 sigfp 의 흥미로운 블로그 게시물 Grok Haskell Monad Transformers 를 우연히 발견했습니다 . 다음은 스칼라 즈 번역입니다.
첫 번째 예 는 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)
그래서 여기에 init
및 사용의 예가 modify
있습니다. 그것을 조금 가지고 놀다 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)]
사용하여 값을 산출합니다 .a
b
이해 구문은 이미 작업 할 사소한 수 A
의 측면 State[S, A]
. init
, modify
, put
와 gets
상의 작업에 몇 가지 도구를 제공 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")
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))
}
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.
참고URL : https://stackoverflow.com/questions/7734756/scalaz-state-monad-examples
'program tip' 카테고리의 다른 글
파일 설명자에서 FILE 포인터를 얻는 방법은 무엇입니까? (0) | 2020.10.17 |
---|---|
결과에 대한 시작 조각처럼 작동하는 방법이 있습니까? (0) | 2020.10.17 |
변수가 Moment.js 객체인지 테스트하는 방법은 무엇입니까? (0) | 2020.10.17 |
새 프로젝트는 log4j 대신 logback을 사용해야합니까? (0) | 2020.10.17 |
"text-align : justify;"를 사용하지 않아야합니까? (0) | 2020.10.17 |