Reader 모나드

박준규·2023년 3월 14일

이 글은 책 Learn You a Haskell for Great Good!에서 섹션 Reader? Ugh, not this joke again.을 읽고 내용을 정리한 것이다.

제목에 대하여

우선 이 책에는 농담이나 패러디가 많다. 작가가 개그 욕심이 많은 듯... 하스켈에는 이름이 Writer라는 타입이 있다. 이 섹션 앞에서 타입 Writer를 소개하는 섹션의 제목이 Writer? I hardly know her!인데 아마도 의역을 하자면 '작가? 잘 모르겠는데? 듣보잡'인데 말장난이다. 이 농담에 이어서 하스켈에는 이름이 Reader인 타입이 있는데 이번 섹션의 제목은 Reader? Ugh, not this joke again.으로 의역하자면 '독자? 어휴 이 농담은 그만 하자. 뇌절' 정도가 될 것이다.

Reader? Ugh, not this joke again.

applicative 챕터에서 봤듯이 타입 (->) rFunctor의 인스턴스이다. fmap f g와 같이 하면 새로운 함수를 만드는데 이 함수는 원래 g가 받는 타입의 인자를 받고 그 인자에 g를 적용한 후에 f를 적용하는 함수이다. 그래서 사실 g랑 같은 함수인데 결과를 리턴하기 전에 그 결과에 f를 적용하는 것으로 볼 수 있다.

ghci> let f = (*5)
ghci> let g = (+3)
ghci> (fmap f g) 8
55

함수는 애플리케이티브 펑터(applicative functors)이기도 하다. 이 점을 이용하면 함수의 결과를 미리 가져다 쓰는 것처럼 할 수 있다.

ghci> let f = (+) <$> (*2) <*> (+10)
ghci> f 3
19

표현식 (+) <$> (*2) <*> (+10)은 수 하나를 입력 받아서 그 수에 먼저 (*2)를 적용한 값과 (+10)을 적용한 값을 서로 더한 값이 나오는 함수를 만든다. 예를 들어 이 함수에 3을 적용해서 (*2)(+10)을 각각 3에 적용하면 613이 나오고 (+)613을 더하면 19가 나온다.

함수 타입 (->) r은 모나드이기도 하다. 지금까지 봤던 다른 모나딕 값처럼 함수도 어떤 문맥이 있는 값으로 볼 수 있다. (모나딕 값은 타입이 Monad m => m a인 값이다.) 함수의 문맥은 값이 아직 없다는 것이고 결과 값을 얻으려면 결국 이 함수를 어딘가에 적용해야 한다는 것이다.

함수의 Monad 인스턴스는 다음과 같이 정의되어 있다.

instance Monad ((->) r) where
  return x = \_ -> x
  h >>= f = \w -> f (h w) w

returnpure와 같은데 값을 받아서 문맥을 부여하고 그 결과는 항상 원래 값과 같다. 항상 특정 값을 결과로 주는 함수를 만드려면 인자를 무시하는 함수를 만들면 된다.

>>=의 구현을 보면 약간 암호 같지만 자세히 보면 별 거 아니다. >>=를 이용해서 함수에 모나딕 값을 적용하면 그 결과도 항상 모나딕 값이다. 여기에서는 함수에 함수를 적용하면 그 결과도 함수가 된다. 그래서 이 구현을 보면 람다로 시작을 한다. 함수의 결과를 얻으려면 결국 어딘가에 적용을 해야 한다. (h w)를 해서 함수의 결과를 얻고 그 결과에 f를 적용한다. f는 모나딕 값을 리턴하고 그게 결국 함수이니까 여기에 다시 w를 적용하면 된다.

아래와 같이 do 표현식으로 쓴 예제를 보자.

addStuff :: Int -> Int
addStuff = do
  a <- (*2)
  b <- (+10)
  return (a+b)

앞에서 했던 애플리케이티브 표현식과 같은데 여기서는 함수가 모나드라는 점만 다르다. do 표현식은 결과가 항상 모나딕 값이어야 한다. 여기서 모나딕 값의 결과는 함수이다. 이 함수는 수 하나를 입력 받아서 그 수에 (*2)를 적용하고 그 값을 a라고 한다. (+10)을 처음 입력 받은 수에 적용하고 그 결과를 b라고 한다. return은 다른 이펙트(effect)의 추가 없이 어떤 결과를 모나딕 값으로 만들어 주기만 하고 여기서는 a+b를 이 함수의 결과로 만들어 준다.

ghci> addStuff 3
19

모든 함수가 같은 값을 읽어서(read) 공통된 인자로 사용하기 때문에 함수 모나드를 Reader 모나드라고 부른다. 아래처럼 쓸 수도 있다.

addStuff :: Int -> Int
addStuff x = let
  a = (*2) x
  b = (+10) x
  in a+b

Reader 모나드는 함수를 문맥을 가진 값으로 볼 수 있게 해준다. 여러 함수를 이어 붙여서 하나의 함수로 만들고 이 하나의 함수의 인자를 이어 붙인 모든 함수들에 각각 적용해서 각 함수에 인자를 넣기도 전에 결과 값을 이미 아는 것처럼 쓰는 것이 가능하다. 그래서 여러 함수가 있는데 이 함수들에 모두 같은 인자를 넣는 상황이라면 Reader 모나드를 이용하면 편리하다.

참고

profile
코딩하는 물총새

0개의 댓글