Skip to content

Instantly share code, notes, and snippets.

@nattybear
Last active June 30, 2021 13:34
Show Gist options
  • Save nattybear/4bea2b783cf463d5d16e99ea36cca1e7 to your computer and use it in GitHub Desktop.
Save nattybear/4bea2b783cf463d5d16e99ea36cca1e7 to your computer and use it in GitHub Desktop.
하스켈 좌충우돌 State Monad 구현기

Functor

타입 클래스 Functor는 구조는 건드리지 않고 구조 안의 값을 바꾼다.

class Functor a where
  fmap :: (a -> b) -> f a -> f b

함수 fmap을 구현할 때는 그런 점을 염두에 두고 구조인 f는 건드리지 않고 구조 안의 값인 a만 바꾸는 방향으로 구현을 하면 된다. 예를 들어 타입 MaybeFunctor 인스턴스를 구현할 때는 구조인 Just는 그대로 두고 안에 값인 a만 바꾸면 된다.

instance Functor Maybe where
  fmap :: (a -> b) -> Maybe a -> Maybe b
  fmap _ Nothing  = Nothing
  fmap f (Just a) = Just $ f a
ghci> fmap (+1) (Just 2)
Just 3
ghci> fmap (+1) Nothing
Nothing

State

타입 State의 정의는 아래와 같다.

newtype State s a =
  State { runState :: s -> (a, s) }

상태 s를 넣으면 값 a와 새로운 상태가 나오는 함수이다.

StateFunctor 인스턴스는 아래와 같다.

instance Functor (State s) where
  fmap f (State sa) =
    State $ \s ->
      let (a, s') = sa s
      in  (f a, s')

Functor는 구조는 바꾸지 않는다고 했다. 여기서 구조는 State s인데 s의 타입이 바뀌진 않았지만 결과적으로 뭔가 새로운 상태인 s'가 결과로 나온다.

State Monad

내가 왜 이렇게 생각했는지 모르겠지만 처음 StateMonad 인스턴스를 구현할 때 아래와 같이 상태의 변화를 연계시켜주지 않고 무시해버렸다.

instance Monad (State s) where
  (>>=) :: State s a
        -> (a -> State s b)
        -> State s b
  State sa >>= f =
    State $ \s ->
      let (a, _)  = sa s
          State g = f a
      in  g s

그런데 이렇게 상태 s의 변화를 연계시켜주지 않고 무시했더니 문제에서 요구하는대로 동작하지 않았다.

디스코드 하스켈 학교에 문의를 했더니 임기정 님께서 이렇게 말씀해주셨다.

상태를 변화시켜주는 게 State의 존재 의의입니다 ㅎ

그렇구나, 듣고 보니 참 맞는 말이다 ㅎ

아래처럼 구현해야 맞겠다.

instance Monad (State s) where
  State sa >>= f =
    State $ \s ->
      let (a, s') = sa s
          State g = f a
      in  g s'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment