The Trivial Monad 번역
The Trivial Monad라는 블로그를 읽었다. http://sigfpe.blogspot.com/2007/04/trivial-monad.html
모나드에 대한 아주 잘 정리된 글이라고 생각되어 주절주절 번역해 본다. 언어가 다소 정제되지않아 읽기에 다소 거북함을 미리 밝혀둔다.

Haskell 모나드는 타입 클래스를 형성한다. 그리고 Haskell의 타입 클래스는 많은 타입들이 공유하는 핵심적인 인터페이스 이다. 그렇다면 모나드 타입 클래스는 많은 다른 타입들과 애기하기위한 공통 API이다. 따라서 다음과 같은 의문이 생길것이다: 이 API가 뭐가 그렇게 특별하지?
API를 이해하는 한가지 방법은 정확한 예제를 보는 것이다. 일반적으로 모나드 API는 콘테이너 혹은 IO를 통하여 소개된다. 하지만 필자의 생각에는 모든 사람들이 이해할 수 있는 모나드의 프로토타입이 훨씬 간단하다 - 바로 하찮은(trivial) 모나드이다.

API는 종종 디자인 패턴을 나타낸다. 그리고 여기서 사용할 디자인 패턴은 단방향(one-way) 래퍼(wrapper)이다. 뭔가를 감쌀 수는 있지만, 감싼 것을 풀수는 없는 단방향이다. 물런 싸여진 데이타에 대해서 우리가 하고싶은 뭔가를 할 수 있다. 하지만 싸여진 내용물에 의존성이 있는 모든것은 역시 싸여져 있어야 한다.

쓸데없는 군 소리는 치우고, 래퍼 타임의 보자:


data W a = W a deriving Show


( Show 는 인터프리터에서 작업하기 쉽게 하기 위해 필요하다.)

한 일이라고는 단지 껍질을 쉬운것 뿐이다. 그리고 뭔가를 하기 위해 해야할 첫번째 일은 바로 뭐든지 껍질로 감싸는 것이다. 다음과 같은 함수로 쉽게 껍질을 쉬울 수 있다:


return :: a -> W a
return x = W x

(물런 return 함수 대신에 그냥 W를 사용할 수 도 있다. 하지만 필자는 다른 타입들에서도 사용할 공통 API를 지향하고 있으므로 당연히 W를 사용할 수 없다.)

이제 우리는 다른게 하나 더 필요하다 - 싸여진 데이타를 껍질을 벗기지 않고 처리하는 방법. 명백한 방법이 떠오를 것이다. 주어진 함수  a -> b 가 있을 경우 이 함수를 W a -> W b의 함수로 바꾸는 함수를 작성할 수 있다. 이 함수는 싸여진 데이타를 여전히 싸여진 채로 둔다. 따라서



fmap :: (a -> b) -> (W a -> W b)
fmap f (W x) = W (f x)
.

이제 할 일은 다 한 것으로 보인다. 예를 들어 우리는 숫자를 감싸고, 그리고 1을 증가시킬 수 있다:


a = W 1
b = fmap (+1) a


하지만 할 수 없는 일도 있다. 다음과 같은 함수 f를 정의하자:


f :: Int -> W Int
f x = W (x+1)



이 함수는 1을 증가시키고 껍질에 싸인 결과를 돌려준다. 이제 이 연산을 두번 적용한다고 가정해 보자. 즉, 우리는 수자를 2를 증가시키고 그 결과를 껍질에 감싸서 반환한다.불행히도 f를 두번 사용할 수 는 없다. 그 이유는
(1) 그럴 경우 두겹의 껍질에 쌓여질 것이고
(2) 껍질에 쌓인 것과 1을 더하려고 할 것인데 전혀 의미없는 행위이다.
fmap도 우리가 원하는 결과를 줄 수 없다. Haskell 인터프리터에서 한번 해보라. 아마도 우리가 필요로 하는 것은 f를 적용하고, 그 결과의 껍질을 벗기고 다시 f를 적용하는 것을 것이다.하지만 이미 말했듯이 우리는 사람들이 껍질을 벗기는 것을 원하지 않는다.우리는 우리를 위해 껍질을 벗기고 연산을 행해줄 고차 함수 (higher order function)을 제공할 필요가 있다.이 함수가 항상 우리에게 껍질에 쌓인 결과를 준다면 함수의 사용자들은 결코 껍질을 벗기고 안에든 내용물을 가지고 장난칠 수 없을 것이다.
그러한 함수에 대한 아이디어는 다음과 같다:



bind :: (a -> W b) -> (W a -> W b)
bind f (W x) = f x


fmap함수와 매우 유사하지만 훨씬 간단하다는 사실에 주목하자. 이제 우리는 +1을 두번, 세번 이라도 할 수 있다


c = bind f (f 1)
d = bind f (bind f (f 1))


왜 bind가 fmap보다 더 일반적인지에 주목하라. 사실 fmap f = bind (return . f)이다.

이게 다 이다. return과 bind를 사용하여 뭔가를 껍질에 싸서는 껍질을 벗기지 않고 자유롭게 처리하는 성과를 이루었다.게다가 껍질을 여러겹 싸거나 벗기지 않고도 연속적으로 함수를 적용할 수 도 있다. 그리고 이것이 바로 Haskell 모나드가 어떤 것인가를 요약한 것이다.

자 그럼 몇가지 과제를 해 보자:


(1) g x (W y) = W (x+y)를 만족하는 함수 g :: Int -> W Int -> W Int를 정의하라. 명백히 정의되지 않을 것이다. 식의 좌측에 W y 패턴이 있으므로 껍질을 벗기는 것이다.
함수를 재정의하여 bind의 의해서만 껍질을 벗기게 되도록 하라.

(2) h (W x) (W y) = W (x+y)를 만족하는 함수 h :: W Int -> W Int -> W Int 를 정의하라. 다시, 껍질을 벗기고.

위 예제를 풀고나서 여러분이 비록 데이타가 껍질에 쌓여있더라도 어떻게 자유롭게 처리할 수 있는지 알 수 있기를 바란다.

Haskell에서 bind를 사용하기 보다는 >>= 연산자를 주로 사용한다. bind f x = x >>= f 이다.

이제 마지막 질문: 왜 데이타를 껍질에 싸야만 하는가? 실제로는 하찮은 모나드를 별로 사용하지는 않을 것이다. 그럼에도 불구하고 오염된 데이타를 나타내는데 어떻게 사용될 수 있는지 알 수 있을 것이다. 껍질에 싸여진 데이타는 오염된 데이타이다. 우리의 API는 데이타가 오염되었다는 사실을 잊지않게 해주고 동시에 우리가 원하는 바를 행할 수 있도록 해준다.오염된 데이타를 가지고 뭔가를 하더라도 그 결과는 여전히 오염된 데이타이며, 그게 바로 우리가 원하는 바이다.

필자의 생각에 재미있는 점은 모든 모나드들 (IO, 리스트,  probability, 를 포함하여)은 아주 자연스럽게 오염의 변형된 형태로 생각할 수 있다는 점이다. 빠른 시간내에 이 점에 대해 더 많은 애기를 할 것이다.

어쨌던, 위의 코드들은 컴파일 되지는 않을 것이다. 이름이 충돌하기 때문이다. 완전한 정의는 다음과 같다.


> data W x = W x deriving Show

> instance Functor W where
> fmap f (W x) = W (f x)

> instance Monad W where
> return x = W x
> W x >>= f = f x



예제 3: W의 three monad laws 를 증명하라. 거의 자명한 일이다.

예제 4: 모나드 API를 사용해서는 껍질을 완전히 벗길 수 없다. 그러나 두겹으로 싸여진 경우 한겹을 벗길 수 있다.
퀴즈: 함수 join :: W (W a) -> W a 를 모나드 API를 사용해서 정의하라. 완전히 껍질을 벗기지는 말아야 한다.

결론적으로, 대부분의 모나드 튜토리얼은 모나드가 무엇이며, 어떻게 사용하는지 보여준다. 필자는 모나드 인터페이스가 왜 정확히 두 개의 함수로 구성되어 있는가에 대한 통찰력을 주었기를 바란다.필자가 오염에 대해 좀 더 애기할 때 그 통찰은 보다 명백해 질 것이다.


"wrap"이라는 단어에 대응하는 적절한 표현을 찾기가 힘들어 다소 저급하게 들리는 "껍질"이라는 단어를 사용했다.


by undoc | 2007/05/01 22:31 | 트랙백 | 덧글(0)
트랙백 주소 : http://undoc.egloos.com/tb/3148247
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]

:         :

:

비공개 덧글

< 이전페이지 다음페이지 >