[[분류:프로그래밍 언어 문법]] [include(틀:상위 문서, top1=Haskell)] [include(틀:프로그래밍 언어 문법)] [목차] == 개요 == 프로그래밍 언어 [[하스켈]]의 주요 개념인 '''모나드'''(Monad)에 관한 정확한 이해를 돕기 위한 문서이다. == 총론: 모나드의 정의 == 모나드란 타입 생성자(type constructor)[* {{{Maybe}}}, {{{[]}}} 등 타입을 인자로 넣으면 타입이 나오는 것들을 의미한다.] 중 후술할 성질들을 만족하는 것을 의미한다. 모나드의 정확한 의미를 파악하기 위해서는 우선 '''함자'''(Functor)와 '''적용자'''(Applicative)[* 통일된 번역이 아직 없어 임의로 번역하였다.]에 관한 이해가 우선 필요하다. 모나드의 상위개념으로 적용자가, 적용자의 상위개념으로 함자가 있기 때문이다. === 함자 === 타입 생성자 {{{f}}}에 대해 적절한 함수 {{{fmap :: (a -> b) -> f a -> f b}}}이 존재하여 다음 성질들을 만족할 때, {{{f}}}를 '''함자'''(Functor)라고 한다. * 항등함수 {{{id :: a -> a}}}에 대해 {{{fmap id :: f a -> f a}}}도 항등함수다. * 모든 함수 {{{f :: b -> c}}}와 {{{g :: a -> b}}}에 대해 {{{fmap (f . g) ≡ fmap f . fmap g}}}[* {{{≡}}}는 함수로서 같다는 걸 의미한다. 컴퓨터가 일반적인 경우에서 이를 증명할 수 없으므로 {{{==}}}가 아니라 {{{≡}}}로 표기한다.]이다. === 적용자 === 함자 {{{f}}}에 대해 적절한 함수 {{{pure :: a -> f a}}}와 {{{(<*>) :: f (a -> b) -> f a -> f b}}}[* apply라고 읽으며, 결합방향(fixity)은 왼쪽이다. 즉 {{{u <*> v <*> w}}}는 {{{(u <*> v) <*> w}}}이다.]가 존재하여 다음 성질들을 만족할 때, {{{f}}}를 '''적용자'''(Applicative)라고 하며, 임의의 타입 {{{a}}}에 대해 {{{f a}}}를 '''액션'''(action)이라 한다[* 다만, {{{f}}}가 모나드라야 액션이라고 부르는 경우가 더 많다.]. * 모든 함수 {{{f :: a -> b}}}와 액션 {{{v :: f a}}}에 대해 {{{pure f <*> v ≡ fmap f v}}}이다.[* 이 성질을 이용해 만든 {{{fmap}}}은 {{{liftA}}}라고 하여, {{{Control.Applicative}}} 모듈에서 제공한다.] * 모든 액션 {{{u :: f (a -> b)}}}와 값 {{{y :: a}}}에 대해 {{{u <*> pure y ≡ fmap (\f -> f y) u}}}이다. * 모든 액션 {{{u :: f (b -> c)}}}, {{{v :: f (a -> b)}}}와 {{{w :: f a}}}에 대해 {{{fmap (.) u <*> v <*> w ≡ u <*> (v <*> w)}}}이다. === 모나드 === 적용자 {{{m}}}에 대해 적절한 함수 {{{(>>=) :: m a -> (a -> m b) -> m b}}}[* bind라고 읽으며, 결합방향은 왼쪽이다.]가 존재하여 다음 성질들을 만족할 때, {{{m}}}을 '''모나드'''(Monad)라고 한다. * 모든 값 {{{x :: a}}}와 함수 {{{k :: a -> m b}}}에 대해 {{{pure x >>= k ≡ k x}}}이다. * 모든 액션 {{{u :: m a}}}에 대해 {{{u >>= pure ≡ u}}}이다. * 모든 액션 {{{u :: m a}}}, 함수 {{{k :: a -> m b}}}와 {{{h :: b -> m c}}}에 대해 {{{u >>= (\x -> k x >>= h) ≡ u >>= k >>= h}}}이다. * 모든 액션 {{{u :: m a}}}와 함수 {{{f :: a -> b}}}에 대해 {{{u >>= (pure . f) ≡ fmap f u}}}이다. [* 이 성질을 이용해 만든 {{{fmap}}}은 {{{liftM}}}이라고 하여, {{{Control.Monad}}} 모듈에서 제공한다.] * 모든 액션 {{{u :: m (a -> b)}}}와 {{{v :: m a}}}에 대해 {{{u >>= (\f -> v >>= (\x -> pure (f x))) ≡ u <*> v}}}이다. [* 이 성질을 이용해 만든 {{{(<*>)}}}는 {{{ap}}}이라고 하여, 역시 {{{Control.Monad}}} 모듈에서 제공한다.] 하스켈에서는 모나드를 편리하게 다루게 해 주는 {{{do}}} 문법을 제공한다. {{{do}}}를 쓰고 다음 줄부터 들여쓰기를 하거나 중괄호와 세미콜론을 써서 사용할 수 있는데, 예를 들자면: >{{{do {f <- u; x <- v; pure (f x)}}}} 이것은 {{{u >>= (\f -> v >>= (\x -> pure (f x)))}}}와 같은 뜻이다. 즉 각 줄(마지막 줄은 빼고)마다 {{{<-}}} 앞에 있는 것을 함수의 인자(parameter)로 취급하여 그 다음 줄로 보내며, 각 줄은 {{{>>=}}}로 연결된다. 한편, {{{<-}}}가 없다면 거기에 해당하는 함수는 그 인자를 무시한다[* 즉 {{{\_}}}로 시작하는 함수.]는 것을 의미한다. == 각론: 각 모나드의 기능 == 여기까지 읽어봤더라도 모나드가 대체 무엇인지 아리송할 것이다. 모나드 자체로는 너무나도 추상적이고 함축적인 의미를 가지고 있기 때문이기도 하고, 아직까지는 모나드의 정의만 나왔을 뿐, 모나드를 구성하는 함수인 {{{pure}}}과 {{{>>=}}}에 관한 정의는 안 나왔기 때문이기도 하다. 따라서 각론에서는 각 모나드에 대해 {{{pure}}}과 {{{>>=}}}의 정의를 살펴보겠다. 이 정의들로부터 위 정의가 충족됨을 확인하는 것도 모나드를 이해하기 위한 좋은 연습이다. 간결한 설명을 위해, 각 모나드의 구조만을 추리고, 실제로 구현된 코드를 복붙하지 않는다. 특히 {{{pure}}}과 {{{>>=}}}는 원래 Applicative, Monad 클래스의 메서드지만, 여기선 그냥 함수처럼 서술한다. 여기선 base 패키지에서 제공하는 모나드만 서술돼 있지만, 다른 패키지에서는 다른 모나드도 제공하며, 필요하다면 모나드를 직접 만들어 쓸 수도 있다. === Identity === > {{{newtype Identity a = Identity a}}} > {{{pure = Identity}}} > {{{Identity x >>= k = k x}}} 값을 생성자 {{{Identity}}}를 통해 그대로 담는, 아무런 기능이 없는 모나드이다. === Maybe === > {{{data Maybe a = Nothing | Just a}}} > {{{pure = Just}}} > {{{Nothing >>= _ = Nothing}}} > {{{Just x >>= k = k x}}} 값을 {{{Just}}}를 통해 담을 수도 있지만, {{{Nothing}}}을 통해 값이 '''없을''' 수도 있다는 것을 의미한다. 값이 없다면 {{{>>=}}}를 통하더라도 결과값은 여전히 "값 없음"이며, {{{fmap}}}과 {{{<*>}}}도 마찬가지다. 흔히 '''오류'''를 나타내기 위해 사용한다. === Either === > {{{data Either e a = Left e | Right a}}} > {{{pure = Right}}} > {{{Left err >>= _ = Left err}}} > {{{Right x >>= k = k x}}} {{{Maybe}}}가 단순히 오류만을 나타낸다면, {{{Either e}}}는 '''무슨''' 오류인지도 타입 {{{e}}}로서 나타낼 수 있다. {{{Left}}}가 오류임을, {{{Right}}}가 정상임을 의미하는데, 영단어 right에 "오른쪽"이란 뜻도 있지만, "옳다", "바르다"라는 뜻도 있다는 것으로 외울 수 있다. === \[\] === > {{{data [a] = [] | a : [a]}}} > {{{pure x = [x]}}} > {{{[] >>= _ = []}}} > {{{x : xs >>= k = k x ++ (xs >>= k)}}} {{{[]}}}는 '''리스트''', 즉 값의 늘어놓음을 의미한다. 생성자 {{{[]}}}를 통해 값을 안 (즉 0개) 담을 수도 있으며, {{{:}}}를 통해 값을 앞에 추가할 수도 있다. 여기서 {{{pure}}}는 주어진 값을 '''1개''' 담으며, {{{>>=}}}는 각 값에 함수를 매핑하여 리스트들을 만든 다음 '''이어붙인다'''. (즉 {{{Data.List}}} 모듈의 {{{concatMap}}}과 같다.) 값이 없으면 오류를 나타낸다는 점에서는 {{{Maybe}}}의 역할도 모두 수행할 수 있으나, 값을 여러 개 담음으로써 '''중의성'''을 나타낼 수도 있다. === NonEmpty === > {{{data NonEmpty a = a :| [a]}}} > ({{{pure}}}과 {{{>>=}}}는 생략) {{{Data.List.NonEmpty}}} 모듈에서 제공하며, 값이 적어도 하나 들어 있음이 보장된 리스트라고 할 수 있다. 즉 오류는 나타낼 수 없지만, 중의성은 나타낼 수 있다. === Proxy === > {{{data Proxy a = Proxy}}} > {{{pure _ = Proxy}}} > {{{_ >>= _ = Proxy}}} {{{Data.Proxy}}} 모듈에서 제공한다. 인자로 주어진 타입을 '''무시하는''' 황당한 모나드. 모종의 이유로 값 없이 타입만 제공해야 할 때 사용한다. === (,) === > {{{data (a, b) = (a, b)}}} > {{{pure x = (mempty, x)}}} > {{{(s, x) >>= k = let (t, y) = k x in (s <> t, y)}}} '''출력자'''(Writer) 모나드라고 불린다. 각 액션에서 콤마의 왼쪽에 있는 값들을 [[모노이드]](Monoid)에 출력한다. {{{pure}}}을 사용할 때 모노이드의 항등원이 공백의 역할을 수행한다. === (->) === > ({{{a -> b}}}는 {{{a}}}가 정의역이고 {{{b}}}가 공역인 함수들의 모임을 나타낸다.) > {{{pure x = \_ -> x}}} > {{{f >>= k = \r -> k (f r) r}}} '''입력자'''(Reader) 모나드라고 불린다. 각 액션은 인자가 입력되길 '''대기'''하는 행위로 취급되어, {{{pure}}}는 인자를 무시하고, {{{>>=}}}는 인자를 각 액션에 대해 입력한다. === State === > {{{newtype State s a = State (s -> (a, s))}}} > {{{pure x = State (\s -> (x, s))}}} > {{{State m >>= k = State (\s -> let {(x, t) = m s; State n = k x} in n t)}}} 이 모나드는 base 패키지에서 제공하진 않으나, 후술할 모나드들을 이해하는 데 핵심이 된다. {{{State s}}}는 타입 {{{s}}}로써 '''상태'''를 가지는 기능을 수행한다. 이 "상태"는 마음대로 읽히거나 덧쓰일 수 있다. '''즉 이 모나드는 함수형 언어가 명령형 언어의 기능도 수행할 수 있음을 증명한다!''' 물론 상태의 초기값은 필요하다. === ST === {{{Control.Monad.ST}}} 또는 {{{Control.Monad.ST.Lazy}}} 모듈에서 제공한다. 전자는 각 액션이 strict하게, 후자는 각 액션이 lazy하게 실행된다[* 액션으로 구분되는 각 단계가 그러할 뿐, 그 입출력값은 여전히 기본적으로 lazy하므로 주의해야 한다.]. {{{State s}}} 모나드는 그 상태의 타입이 {{{s}}}여야 한다는 제약이 있는데, 그 제약을 완화한 것이 {{{ST}}}이다. {{{ST}}}에 들어 있는 상태를 다루기 위해서는 {{{Data.STRef}}} 또는 {{{Data.STRef.Lazy}}} 모듈에 들어 있는 다음 함수들이 필요하다[* {{{State}}}의 조건을 완화했다면서 여전히 {{{s}}}를 인자로 받는 모습을 볼 수 있는데, 어차피 이 함수들 모두 {{{s}}}를 제한하지 않으므로, 프로그래머 입장에서는 무시해도 된다.]: * {{{newSTRef :: a -> ST s (STRef s a)}}} : 타입이 {{{a}}}인 변수를 생성한 다음, 입력값으로 초기화한다. * {{{readSTRef :: STRef s a -> ST s a}}} : 변수에서 값을 읽어들인다. * {{{writeSTRef :: STRef s a -> a -> ST s ()}}} : 변수에 값을 덧씌운다. * {{{modifySTRef :: STRef s a -> (a -> a) -> ST s ()}}} : 변수의 값을 함수를 통해 변경한다. 보다시피 모두 액션을 내놓는 함수인데, 이렇게 얻은 {{{ST s a}}}에서 타입 {{{a}}}를 가진 결과값을 얻으려면 {{{runST}}} 함수를 사용한다. === IO === {{{ST}}} 모나드의 상태의 타입에는 제약이 없다고 하였다. 그렇다면 '''컴퓨터 그 자체'''[* 아예 '''현실 그 자체'''라고 해석해도 된다.]를 상태의 타입으로 삼을 수 있지 않을까? 그리하여 대망의 {{{IO}}} 모나드를 얻게 된다. 그 이름에서 알 수 있듯이, 이 모나드는 현실에서의 '''입출력'''을 담당한다. 이게 가능한 이유는 이 모나드가 현실의 입력장치 및 출력장치인 마우스, 키보드, 모니터, 스피커 등등 그 자체를 변수로 취급하기 때문이다[* 반대로 말하면 이게 가능한 모나드는 {{{IO}}}밖에 없으므로, 현실의 모든 입출력은 {{{IO}}}를 통할 수밖에 없다.]. 당장 CUI 환경에서의 기초적인 입출력을 담당하는 {{{putStrLn :: String -> IO ()}}}와 {{{getLine :: IO String}}}부터가 액션 혹은 액션을 내놓는 함수이다. 물론 {{{IO}}}에서도 변수를 만들 수 있는데, {{{Data.IORef}}} 모듈에 들어 있는 다음 함수들을 쓰면 된다: * {{{newIORef :: a -> IO (IORef a)}}} * {{{readIORef :: IORef a -> IO a}}} * {{{writeIORef :: IORef a -> a -> IO ()}}} * {{{modifyIORef :: IORef a -> (a -> a) -> IO ()}}}