scala/basic
Scala State Monad
wefree
2021. 8. 3. 12:12
문제
State Monad 를 만들고, 이를 사용해 다음을 계산하라.
골프 공을 쳐서 처음에는 20m, 두번째는 10m, 세번째는 15m 를 보냈다.
그런데 시작라인에서 3m 를 더 나온 위치에 공을 두고 쳤다면 최종 골프공은 시작라인에서 어느정도 떨어져 있을까?
3 + 20 + 10 + 15 = 48
코드
case class State[S, A](run: S => (S, A)) {
def flatMap[B](f: A => State[S, B]): State[S, B] = State { s0 =>
val (s, a) = run(s0) // 현재 상태의 run 을 적용하고
f(a).run(s) // 이후에 f 를 적용해 S => (S, A) 함수를 갱신함
} // 기존 run 과는 다른 새롭게 정의된 S => (S, A) 함수를 이용하는 State 가 만들어짐
def map[B](f: A => B): State[S, B] = flatMap(a => State.lift(f(a)))
}
object State {
// 초기 결과값으로 value 로 부터 State 를 만듬
// State 를 만든다는 것은 S => (S, A) 를 정의하는 것인데, A 값으로 value 사용
def lift[S, A](value: A): State[S, A] = State(s => (s, value))
}
object GolfDistance {
// 이동 거리를 계속 더해야 하기 때문에 state, result 를 모두 s + n 으로
def swing(n: Int): State[Int, Int] = State(s => (s + n, s + n))
def main(args: Array[String]): Unit = {
val distance: State[Int, Int] = for {
_ <- swing(20)
_ <- swing(10)
total <- swing(15) // 최종 state 와 result 에만 관심이 있다!
} yield total
val (s, a) = distance.run(3) // 3m 앞에서 시작해서 초기값을 3으로 해서 실행함
println(a)
}
}
설명
State Monad 를 직접 구현해 사용했지만, ZIO prelude 의 State Monad 에서 처럼 zio-prelude 라이브러리를 이용할 수도 있다. zio-prelude ZPure 를 사용하더라도 코드가 비슷함을 확인 할 수 있다.
그런데 java.util.concurrent._ 를 이용해 아래처럼 OOP 스타일로도 구현할 수 있지 않을까?
import java.util.concurrent.atomic.AtomicInteger
object GolfDistance {
case class State(s: AtomicInteger) {
def swing(n: Int): Int = s.addAndGet(n)
def get: Int = s.get()
}
def main(args: Array[String]): Unit = {
val distance = State(new AtomicInteger(3))
distance.swing(20)
distance.swing(10)
distance.swing(15)
val total = distance.get
println(total)
}
}
위의 구현이 더 이해하기 쉬워 좋아보이는데, State Monad 로 구현했을 때 장점은 무엇인가?
그것은 아래 코드 때문인 것 같다.
def swing(n: Int): Int = s.addAndGet(n)
현재 요구 사항에 맞는 addAndGet 이 있어서 위와 같이 구현했지만, 더 복잡한 로직이 들어간다면 atomic 하게 처리할 수 있을까?
참고: https://alvinalexander.com/scala/functional-programming-simplified-book/