그림으로 설명하는 펑터, 아플리커티브, 모나드

[ 원글 : http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html ]

여기 간단한 값(value)의 예가 있다:

이 값에 함수를 적용해 보자:

간단하지요. 어떤 문맥 안에 값을 놓고 반복해보자. 당분간 문맥을 값을 집어 넣을 수 있는 박스로 생각하자.:

함수를 이 값에 적용하면 그 문맥에 따라 다른 결과를 얻을 것이다. 펑터, 어플리커티브, 모나드, 애로우 등 모든 개념이 이 아이디어를 기초로 하고 있다.  Maybe 데이터 타입은 두 가지 연관된 문맥을 정의한다:

  data Maybe a = Nothing | Just a 

Just a와 Nothing일 때 함수를 적용하는 방법이 다르다는 것을 바로 알 수 있을 것다. 먼저 펑터에 대해 얘기하자!

펑터(Functors)

문맥 안에 값을 감싸두면 보통 함수를 그 값에 적용할 수 없다:

이런 상황에서 fmap이 필요하다. fmap은 문맥을 알고 있다. fmap을 통해 문맥 속의 값에 함수를 적용할 수 있다. 함수 (+3)을 Just 2에 적용한다고 가정해보자. fmap을 사용하자:

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

두둥!  fmap은 어떻게 하는지 가르쳐준다. 그런데 fmap은 이 함수를 적용하는 법을 어떻게 알 까?

펑터가 뭐지, 진짜?

펑터는 타입클래스다. 여기 그 정의가 있다:

Functor는 fmap을 적용하는 방법을 정의하는 데이터타입이다. 다음은 fmap의 동작 방식이다:

따라서 이렇게 할 수 있다:

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

Maybe는 펑터이기 때문에 마법같이 fmap으로 이 함수를 적용한다. 이 펑터로 fmap을 Just들과 Nothing들에 어떻게 함수를 적용하는지 정한다:

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

fmap (+3) (Just 2)라고 코드의 동작을 다음과 같이 설명할 수 있다:

fmap으로 (+3)을 Nothing에 적용해볼까?

> fmap (+3) Nothing
Nothing
Bill O’Reilly being totally ignorant about the Maybe functor

빌 오라일리, Maybe 펑터에 대해 전혀 알고 있지 않은

영화 매트릭스의 모피어스처럼 fmap은 무엇을 해야할지 알고 있다. Nothing을 가지고 시작하면 Nothing으로 끝날 뿐이다! fmap은 젠(zen)이다. 이제 Maybe 데이터타입이 존재하는 이유가 있다. 예를 들어, Maybe가 없는 언어로 데이터베이스 레코드를 다룬다고 하자:

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

하스켈로 옮겨보면:

  fmap (getPostTitle) (findPost 1)

만일  findPost에서 포스트를 반환하면 getPostTitle로 그 제목을 얻을 수 있다. 만일 findPost에서 아무것도 반환하지 않으면 (Nothing을 반환하면Nothing을 반환할 것이다. 꽤 깔끔하죠? <$>은 fmap의 인픽스(infix) 버전으로, 위 코드 대신 다음과 같이 작성할 수 있다:

  getPostTitle <$> (findPost 1)

다른 예를 보자: 함수를 리스트에 적용하면 어떻게 되나?

리스트도 펑터다! 여기 그 정의가 있다:

  instance Functor [] where
    fmap = map

오케이, 마지막 예: 함수에 다른 함수를 적용하면 어떨까? 

  fmap (+3) (+1)

여기 함수가 있다:

여기 다른 함수에 적용할 함수가 있다:

그 결과는 단지 또다른 함수다!

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

따라서 함수도 펑터다!

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

fmap을 함수에 사용하면 그 결과는 함수 조합이 된다!

어플리커티브(Applicatives)

어플리커티브는 그 다음 레벨로 데려다준다. 어플리커티브를 사용하면, 펑터처럼, 값을 어떤 문맥 안에 감쌀 수 있다:

그러나 함수도 문맥 안에 감쌀 수 있다!

자, 이해해보자. Control.Applicative에서 <*>를 두어 어떤 문맥에 감싸져 있는 함수를 관련된 문맥 속에 있는 값에 적용하는 방법을 지정한다:

예컨데:

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

<*>를 사용하면 재미있는 상황이 벌어질 수 있다. 예를 들어:

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

펑터로는 할 수 없지만 어플리커티브로 할 수 있는 것이 있다. 두 개의 인자를 받는 함수를 문맥에 감싸져 있는 두 개의 값에 어떻게 적용할 수 있을까?

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

어플리커티브:

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

Applicative는 Functor보다 유연하다. "다 큰 남자는 인자를 몇 개 받든 상관없이 어떠한 함수라도 사용할 수 있어야 한다."라고 마치 말하는 것 같다. "<$>와 <*>를 가지고 임의 개수의 값을 받는 어떤 함수에 포장된 값들을 전달하고 그 결과로 포장된 값을 반환 받을 수 있다!"

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

이러한 똑같은 일을 하는 liftA2라 부르는 함수가 있다:

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

모나드(Monads)

모나드를 배우는 방법:

  1. 컴퓨터 과학 분야의 박사학위를 얻은 다음,
  2. 그것을 던져 버린다. 왜냐하면 여기 설명에서는 그 학위가 필요하지 않기 때문에!

모나드는 새로운 방법을 추가한다.

펑터는 함수를 포장된 값에 적용한다:

어플리커티브는 포장된 함수를 포장된 값에 적용한다:

모나드는 포장된 값을 반환하는 함수에 포장된 값을 적용한다.모나드는 바인드라고 읽는 함수로 이것을 한다.

예를 살펴보자. Maybe는 모나드다: 

Just a monad hanging out

half는 짝수에 대해 동작하는 함수라 가정하자:

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

포장된 값을 전달하면 무슨 일이 벌어질까?

함수 >>=를 사용해서 포장된 값을 함수로 밀어낼 수 있다. 함수 >>=의 사진이 여기 있다:

동작 방식을 설명한다:

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

내부에서 어떤 일이 벌어지고 있나? Monad는 타입클래스다. 부분적으로 아래와 같이 정의한다: 

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

바인드 함수 >>=는:

따라서 Maybe는 Monad다:

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

아래에서 Just 3에 대한 동작을 설명한다!

Nothing을 주는 경우는 더 간단하다:

이런 함수 호출들을 차례로 묶을 수 있다:

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

쿨! 이제 Maybe는 펑터, 어플리커티브, 모나드라는 것을 알았다. 

이제 다른 예를 반복해서 살펴보자: IO 모나드:

특히 3가지 함수들. getLine은 특별한 인자 없이 사용자 입력을 받는다:

  getLine :: IO String

readFile은 파일 이름 문자열을 인자로 받고 그 파일 내용을 반환한다:

  readFile :: FilePath -> IO String

putStrLn은 문자열을 받아 출력한다:

  putStrLn :: String -> IO ()

세가지 모든 함수들은 일반 값을 받아(또는 받지 않고) 포장된 값을 반환한다. 이 함수들을 바인드 함수  >>=를 사용하여 차례대로 묶을 수 있다!

  getLine >>= readFile >>= putStrLn

하스켈 언어는 모나드를 사용하는데 편리한 구문, do 표기법을 제공한다:

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

결론

  1. 펑터는 Functor 타입클래스를 구현하는 데이터 타입이다.
  2. 어플리커티브는 Applicative 타입클래스를 구현하는 데이터 타입이다.
  3. 모나드는 Monad 타입클래스를 구현하는 데이터 타입이다.
  4. Maybe는 이 세가지를 모두 구현한다. 따라서 펑터이고, 어플리커티브이며, 모나드이다.

이 세가지의 차이점은 무엇인가?

  • 펑터:  fmap이나 <$>를 사용하여 함수를 포장된 값에 적용한다.
  • 어플리커티브: <*>나 liftA를 사용하여 포장된 함수를 포장된 값에 적용한다.
  • 모나드: >>=나 liftM을 사용하여 포장된 값을 리턴하는 함수를 포장된 값에 적용한다.

자, 여기까지 설명을 들었다면 모나드가 쉽고 SMART IDEA(tm)라는 점에 우리 모두 동의할 것이라 생각한다.  이 가이드로 여러분의 궁금증을 일부 해소했다면 이제 더 심화된 내용을 살펴볼 차례다. LYAH의 모나드 섹션을 살펴보라. 이 가이드에서 넘어갔던 많은 것이 있다. Miran은 자세한 설명을 해줄 것이다.



저작자 표시 비영리 변경 금지
신고

첫번째 하스켈 프로그래밍 스터디 계획


펜실베이나 대학(Univ. of Pennsylvania)의 스테파니 웨어릭(Stephanie Weirich) 교수의 CIS 552 고급 프로그래밍 강의 자료를 가지고 하스켈 프로그래밍 스터디를 진행한다.


목차


  1. 소개
  2. 리스트와 퍼스트 클래스 함수
  3. 고차원 프로그래밍 패턴
  4. 타입 클래스
  5. 모나드 소개
  6. 상태 모나드
  7. 임의의 모나드와 입출력
  8. 모나드를 사용한 파싱 [1,2]
  9. 퀵체크(QuickCheck) [1,2]
  10. 모나드 변환
  11. 동시성을 제공하는 모나드 [1,2]
  12. 순수 함수 자료 구조
  13. 일반화된 엘지브레익 데이터타입(Generalized Algebraic Datatypes)
  14. 타입을 유지하는 추상 구문
  15. 타입 리플렉션(Type Reflection) [1,2]

참고 강의 사이트
  • http://www.seas.upenn.edu/~cis552/11fa/schedule.html



저작자 표시 비영리 변경 금지
신고

하스켈 프로그래밍(Haskell) 커뮤니티를 시작하며.


1994년 대학원에 들어가서 처음 함수형 프로그래밍 언어 하스켈(Haskell, www.haskell.org)을 접하고 여러 잡다한 프로그래밍을 통해 그 이상한 매력에 끌리게 되었다. 마치 결벽증 환자처럼 "순수함"을 유지하려고 애쓰는 모습도 희한했고, 그러면서도 남들이 하는 일을, 똑같이, 조금 과장해서 말하자면, 더 잘 할 수 있는 것도 멋있었다. 그 매력에 끌려 한때는 모든 다른 프로그래밍 언어는 삼류고, 다 필요없고, 오직 하스켈만 절대 지존이고 유일한 프로그래밍 언어라고 생각했던 적도 있었다. 물론 착각이고. 


20년이 지난 오늘 주변에서 하스켈에 대해 관심있는 사람들이 몇몇 있다. 아직은 학교에 있는 학생들이 대부분인 것 같고 L모 회사에 근무하는 사람도 관심을 가지고 있더라. 그리고 몇 주 전에 참가한 어느 세미나에서 "모나드(Monad)"에 대한 설명을 듣는데 개인적으로 너무 마음에 들지 않았다. 하스켈의 악명 높은 주제이긴 하지만 다른 식으로 얘기할 수 있지 않았을까 생각했다. 하스켈이 유일 무이한 프로그래밍 언어는 아니라는 것을 잘 알고 있지만, "순수함"을 잃지 않으려는 이 대범한 노력을 누가 또 할 수 있을까 생각할 때마다 하스켈 프로그래밍을 하던 학생 시절에 그랬던 것 처럼 가슴이 뛰기도 한다. 그리고 "순수함"을 유지하는 하스켈에 "순수함"을 잃어버린 다른 프로그래밍 언어들이 지향해야하는 모습이 담겨 있다는 생각까지 하게 되면, 하스켈 프로그래밍을 배우는 것은 단지 하스켈 언어만을 배우는 것이 아니라는 생각이 강하게 든다.


이러한 생각을 하다보니 하스켈 프로그래밍 커뮤니티가 하나쯤은 있어도 되겠다는 생각이 문득 들었다. 먼저, 하스켈에서 배운대로, 특별한 개발 목적이 있어서가 아니라 그냥 재미삼아 하스켈 언어를 배우는 스터디 모임을 가지고 커뮤니티를 시작해보려 한다. 이 커뮤니티가 활성화되면 하스켈에 적용된 최신 프로그래밍언어 기술에 대해서 논의하고, 나아가서 상용 목적으로 하스켈을 응용하는 사례에 대해 살펴보는 장으로 발전하기를 기대해본다.


2015/8/31


K. Choi


저작자 표시 비영리 변경 금지
신고

+ Recent posts

티스토리 툴바