ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Scala State Monad
    scala/basic 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/

    댓글

Designed by Tistory.