Functors, Applicatives, And Monads In Pictures

여기, 단순한 값(value)이 있습니다.

value.png

Prelude> 2
2

그리고, 우리는 이 값에 함수(function)를 적용하는(apply) 법을 알고 있습니다.

value_apply.png

Prelude> (+3) 2
5

아주 간단합니다. 그럼 이제, 이 값이 어떠한 상태(context)안에 들어있다고 가정해봅시다. 이제 여러분은 값을 담을 수 있는 어떠한 상태를, 상자라고 생각할 수 있을 것입니다.

value_and_context.png

Prelude> Just 2
Just 2

이제, 이 값에 함수를 적용하게 되면, 상태에 따라 다른 결과를 얻게 될 것입니다.

Functors, 이를 기반으로한 개념(idea) 중에는 Applicatives, Monads, Arrows 등이 있습니다. Maybe 데이터 타입은 두가지 상태로 정의할 수 있습니다.

context.png

data Maybe a = Nothing | Just a
Prelude> :type Just
Just :: a -> Maybe a

Prelude> :type Nothing
Nothing :: Maybe a

다음으로, Just aNothing의 상태에 따라, 함수가 어떻게 적용되는지 보도록 하겠습니다. 우선, Functors에 대해 이야기해 보도록 하겠습니다.

Functors

상자안에 있는 값에는, 단순한 함수를 적용할 수 없습니다.

no_fmap_ouch.png

Prelude> (+3) (Just 2)
 ERR - No instance for (Num (Maybe a0))

길거리 출신인 fmap은 상자에 대해서는 빠삭합니다. fmap은 상자안에 있는 값에 함수를 어떻게 적용해야 할지를 알고 있습니다. 예를 들어, Just 2(+3)를 적용시켜 봅시다. fmap을 사용해보면,

> fmap (+3) (Just 2)
Just 5

fmap_apply.png

쨘! fmap이 멋지게 해냈습니다. 어떻게 fmap이 함수를 적용시키는 법을 알고 있는 걸까요?

Functor가 도대체 뭡니까?

Functortypeclass입니다. 여기 정의가 나와있습니다.

functor_def.png

Functor는 fmap을 적용시키는 법을 정의한 데이터 타입입니다. 여기, 어떻게 fmap이 동작하는지 나와 있습니다.

fmap_def.png

따라서, 우리는 다음과 같이 할 수 있습니다.

> fmap (+3) (Just 2)
Just 5

Maybe역시 Functor이기에, fmap은 마법과도 같이 함수를 적용하였습니다. 다음으로, JustNothing에 대해 fmap을 적용시키는 방법에 대해 나와있습니다.

instance Functor Maybe where
    fmap func (Just val) = Just (func val)
    fmap func Nothing = Nothing

여기, fmap (+3) (Just 2)이라고 쳤을때, 어떠한 일이 뒤에서 일어나는지 나와있습니다.

fmap_just.png

이번에는, Nothing(+3)을 적용시켜 보도록 하겠습니다.

fmap_nothing.png

> fmap (+3) Nothing
Nothing

bill.png

Maybe functor도 모르는 Bill O’Reilly

메트릭스의 모피어스처럼, fmap은 무엇을 해야할지 알고 있습니다. Nothing으로 시작하면, Nothing으로 끝난다! fmap은 zen(시작이자 끝)입니다. 이제, Maybe 데이터 타입이 왜 존재하는지 알아봅시다. 예를들어, 다음과 같이 Maybe가 없는 언어에서 데이터를 가지고 작업을 한다고 한다면,

post = Post.find_by_id(1)
if post
  return post.title
else
  return nil
end

Haskell은 다음과 같이 할 수 있습니다.

fmap (getPostTitle) (findPost 1)

만약, findPost가 게시글(post)를 반환한다면, getPostTitle로 제목(title)을 얻을 것입니다. Nothing을 반환하면, Nothing을 얻을 것입니다. 매우 간단하지 않습니까? fmap의 중위표기(infix) 버전은 <$>이며, 다음과 같이 쓰기도 합니다.

getPostTitle <$> (findPost 1)

여기, 또 다른 예가 나와있습니다. 리스트에 함수를 적용시키면 무슨일이 생길까요?

fmap_list.png

리스트 역시 Functor였던 것이였습니다! 여기 정의가 나와있습니다.

instance Functor [] where
    fmap = map

자, 자, 마지막으로 하나 더. 함수에 또 다른 함수를 적용시키면 어떤일이 벌어질까요?

fmap (+3) (+1)
Prelude> :type fmap (+3) (+1)
fmap (+3) (+1) :: (Functor ((->) b), Num b) => b -> b

여기 하나의 함수가 있습니다.

function_with_value.png

함수에 또 다른 함수를 적용해 보도록 하겠습니다.

fmap_function.png

결과 역시 함수 입니다.

> import Control.Applicative
> let foo = fmap (+3) (+2)
> foo 10
15

따라서, 함수 역시 Functor입니다.

instance Functor ((->) r) where
    fmap f g = f . g

함수에 fmap을 사용하면, function composition을 한것과 같습니다.

Applicatives

다음 단계로 넘어가보도록 하겠습니다. Functors에서 나왔던 것처럼, 상자에 값을 넣습니다.

value_and_context.png

함수 역시 상자에 들어갈 수 있습니다.

function_and_context.png

그럼 자세히 살펴보도록 하겠습니다. Applicatives는 바보가 아닙니다. Control.Applicative는 상자 속 값에, 상자 속 함수를 적용하는 법을 알도록 <*>를 정의했습니다.

applicative_just.png

즉, 다음과 같이 할 수 있습니다.

Just (+3) <*> Just 2 == Just 5

<*>를 사용하면 조금 흥미로운 결과를 얻을 수 있습니다. 예를들면,

> [(*2), (+3)] <*> [1, 2, 3]
[2, 4, 6, 4, 5, 6]

applicative_list.png

여기, Functor로는 할 수 없지만, Applicative로 할 수 있는게 나와있습니다. 두개의 인자를 취하는 함수를, 어떻게 속에 값이 들어있는 두개의 상자에 적용시킬까요?

> (+) <$> (Just 5)
Just (+5)
> Just (+5) <$> (Just 4)
ERROR ??? WHAT DOES THIS EVEN MEAN WHY IS THE FUNCTION WRAPPED IN A JUST

Applicatives:

> (+) <$> (Just 5)
Just (+5)
> Just (+5) <*> (Just 3)
Just 8

Functor을 옆으로 밀쳐내면서 Applicative이 말합니다.

"어른은 여러 인자들을 다룰 수 있는 함수를 쓰지."

"값이 들어있는 상자와 함수가 들어있는 상자를 <$><*>를 이용해서, 값이 들어있는 상자를 얻을 수 있다고! ㅎㅎㅎㅎ!"

> (*) <$> Just 5 <*> Just 3
Just 15

TaTdV.gif

functor가 함수를 적용시키는 것을 지켜보고 있던 applicative

잠깐! 여기 동일한 일을 하는 liftA2도 있습니다.

> liftA2 (*) (Just 5) (Just 3)
Just 15

Monads

모나드를 배우는 방법 :

  1. 컴퓨터 공학 박사학위를 딴다.
  2. 필요없으니 집어 치운다!

Monads는 새로운 해법을 제시하였습니다.

Functors는 상자에 있는 값에 함수를 적용할 수 있습니다.

fmap.png

Applicatives는 상자에 있는 값에, 상자에 있는 함수를 적용 할 수 있습니다.

applicative.png

Monads는 상자에 있는 값에 함수를 적용시켜 값이 들어있는 상자를 반환 할 수 있습니다.

Monads는 이러한 일을 처리하는 ("bind"라 불리는) >>=라는 함수를 가지고 있습니다.

예제를 살표봅시다. 이제까지 봐왔던 Maybe는 모나드입니다.

context.png

놀고있던 Just a 모나드

짝수에 대해서만 동작하는 함수, half가 있다고 가정해봅시다.

half x = if even x
           then Just (x `div` 2)
           else Nothing

half.png

값이 들어있는 상자를 넣으면 어떻게 될까요?

half_ouch.png

함수에 값이 들어있는 상자를 밀어 넣을려면, >>=가 필요합니다. 여기, >>=의 사진이 있습니다.

plunger.jpg

어떻게 동작하는지 확인해 봅시다.

> Just 3 >>= half
Nothing
> Just 4 >>= half
Just 2
> Nothing >>= half
Nothing

내부에서 어떤일이 벌어진 걸까요? Monad는 또 다른 typeclass입니다. 여기, 정의 중 일부가 나와있습니다.

class Monad m where
    (>>=) :: m a -> (a -> m b) -> m b

>>=는,

bind_def.png

따라서, Maybe는 Monad입니다:

instance Monad Maybe where
    Nothing >>= func = Nothing
    Just val >>= func  = func val

여기, Just 3에 대 해 어떻게 동작하는지 나와있습니다!

monad_just.png

Nothing을 넣으면 보다 단순해 집니다.

monad_nothing.png

이렇게 연달아 호출할 수 도 있습니다.

> Just 20 >>= half >>= half >>= half
Nothing

monad_chain.png

whoa.png

끝내줍니다. 이제 우리는 Maybe가 Functor이자, Applicative이며, Monad라는 것을 알게 되었습니다.

이제 슬슬, IO 모나드 예제로 가 보겠습니다.

io.png

숫자 10아님... 영어 IO임

세개의 특별한 함수가 있습니다. getLine은 인자를 받지 않고, 사용자의 입력을 받습니다.

getLine.png

getLine :: IO String

readFile는 문자열(파일명)을 받아, 파일에 있는 내용을 반환합니다.

readFile.png

readFile :: FilePath -> IO String

putStrLn은 문자열을 받아 출력합니다.

putStrLn.png

putStrLn :: String -> IO ()

여기 나온 세개의 함수는 평범한 값(혹은 없거나)을 받아서, 값이 들어있는 상자를 반환합니다. 따라서, 우리는 이를 >>=로 묶을 수 있습니다!

monad_io.png

getLine >>= readFile >>= putStrLn

오 예! 모나드 쇼가 펼쳐졌습니다!

덧붙이자면, Haskell은 이 모나드에 대해, do라는 syntax suger를 제공해 주었습니다.

foo = do
    filename <- getLine
    contents <- readFile filename
    putStrLn contents

Conclusion

  1. functor는 Functor 타입 클래스를 구현한 데이터 타입이다.
  2. applicative는 Applicative 타입 클래스를 구현한 데이터 타입이다.
  3. monad는 Monad 타입 클래스를 구현한 데이터 타입이다.
  4. Maybe는 이 세개를 모두 구현했으므로, functor이자 applicative이며 monad이다.

이 셋의 차이점은 무엇일까?

recap.png

  • functors: fmap이나 <$>를 이용하여 상자 속 값에 함수를 적용할 수 있다.
  • applicatives: liftA<*>를 이용하여 상자 속 함수를 상자 속 값에 적용할 수 있다.
  • monads: liftM>>=를 이용하여 값이 들어있는 상자를 받아 함수를 적용하고 값이 들어있는 상자를 반환할 수 있다.

우리 모두 모나드라는 것이 쉽고 멋진 개념(트레이드 마크)이라는 것에 대해, 동의한다고 생각합니다. 이 가이드로 만족하기엔 아직 이릅니다. LYAH’s section on Monads를 확인하시기 바랍니다. Miran님 께서 이미 모나드에 관한 깊고, 훌륭한 일을 해냈기에, 저는 많은 것을 얼버무리고 넘어갔습니다.

러시아 버전도 있습니다. 더 많은 모나드와 그림을 원하신다면, three useful monads를 확인하시기 바랍니다.