SlideShare a Scribd company logo
Haskell
Study
14. useful monads
Writer Monad
이전에 다뤘던 Maybe, List, IO 등의 모나드 외의 많이 쓰이는 다른 유용한 모나드들에 대해
다뤄봅시다. 첫 번째는 Writer 모나드입니다. Writer 모나드는 작업 중간중간에 로그를 남기고 싶을
때 굉장히 유용하게 사용할 수 있습니다.
newtype Writer extra a = Writer { runWriter :: (a, extra) }
instance Monoid extra => Monad (Writer extra) where
	 return a = Writer (a, mempty)
	 Writer (a, e) >>= f =
		let (b, e') = runWriter (f a) in Writer (b, e `mappend` e')
extra타입은 해당 작업의 결과로 인해 발생한 여분의 값(로그 등)의 타입이고 a 타입은 실제
작업하고자 하는 값의 타입입니다.
Writer Monad
Writer 모나드의 return 함수는 계산하고자 하는 값과, extra 타입의 가장 작은 값(mempty)을
묶은 튜플을 반환합니다.
Prelude> runWriter (return 3 :: Writer String Int)
(3, "")
Prelude> runWriter (return 3 :: Writer (Sum Int) Int)
(3, Sum {getSum = 0})
Prelude> runWriter (return 3 :: Writer (Product Int) Int)
(3, Product {getProduct = 1})
runWriter 함수는 Writer 타입의 값을 일반적인 튜플값으로 바꿔주는 역할을 하죠.
Writer Monad
Writer 모나드를 이용해 간단한 로그를 남기는 예제를 작성해봅시다.
import Control.Monad.Writer
logNumber :: Int -> Writer [String] Int
-- Writer가 아니라 writer인 것에 주의. 관련 내용은 Monad Transformer에서 다룹니다.
logNumber x = writer (x, ["Got Number: " ++ show x])
multWithLog :: Writer [String] Int
multWithLog = do
	a <- logNumber 3
	b <- logNumber 5
	 return (a*b)
Prelude> runWriter multWithLog
(15, ["Got Number: 3", "Got Number: 5"])
Writer Monad
앞의 예제와 같이 Writer 모나드를 사용하면 각각의 연산에 대한 결과 로그를 쉽게 남길 수 있다는
것을 알 수 있습니다. 그리고 Writer 모나드에서 유용하게 사용할 수 있는 함수로 tell이라는 함수가
있습니다.
tell :: extra -> Writer extra ()
tell e = writer ((), e)
tell 함수는 위 선언에서 볼 수 있듯이, 실제 연산 값에는 아무런 영향을 미치지 않고 여분의 값에 대해
특정 값을 추가하고 싶을 때 사용하는 함수입니다. 계산 중간중간에 원하는 로그를 삽입하고 싶을 때
사용할 수 있겠죠.
Writer Monad
tell을 활용하여, 두 수의 gcd를 구하는 함수의 연산 과정을 기록해봅시다.
import Control.Monad.Writer
gcdWithLog :: Int -> Int -> Writer [String] Int
gcdWithLog a b
	 | b == 0 = do
		tell ["Finished with " ++ show a]
		return a
	 | otherwise = do
		 tell [show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b)]
		 gcdWithLog b (a `mod` b)
Writer Monad
gcd 결과 값을 얻고 싶다면 함수를 수행한 Writer 값에서 첫 번째 요소를 가져오면 되고, 계산 로그가
궁금하다면 두 번째 요소를 가져오면 되겠죠.
Prelude> fst $ runWriter (gcdWithLog 8 3)
1
Prelude> snd $ runWriter (gcdWithLog 8 3)
["8 mod 3 = 2", "3 mod 2 = 1", "2 mod 1 = 0", "Finished With 1"]
State Monad
이번엔 State 모나드입니다. State 모나드는 상태의 변화가 필요한 연산을 구현할 때 굉장히
유용하게 쓸 수 있습니다.
newtype State s a = State { runState :: s -> (a,s) }
State s a는 s 타입의 상태와 a 타입의 결과값을 가지는 연산으로 생각할 수 있습니다.
instance Monad (State s) where
	 return x = State $ s -> (x,s)
	 (State h) >>= f = State $ s -> let (a, newState) = h s
											(State g) = f a
									 in g newState
State Monad
return x = State $ s -> (x,s)
코드가 어려울 땐 타입을 기준으로 하나씩 살펴보는 것이 좋습니다.
return 함수는 원래 (Monad m) => a -> m a라는 타입을 갖고 있는데, 이 때 m이 State s이므로
여기서 return 함수는 a -> State s a 라는 타입을 가지게 됩니다. 그래서 return x는 상태 s를
인자로 받아 결과값 x와 상태 s의 튜플을 돌려주는 연산으로 정의됩니다.
State Monad
(State h) >>= f = State $ s -> let (a, newState) = h s
								 (State g) = f a
								 in g newState
State 모나드에서 >>= 함수는 두 개의 stateful한 연산을 이어주는 역할을 한다고 생각하면 됩니다.
역시 타입부터 하나씩 살펴봅시다.
원래 >>= 함수는 (Monad m) => m a -> (a -> m b) -> m b 라는 타입을 갖고 있습니다.
따라서 여기서는 타입이 State s a -> (a -> State s b) -> State s b가 되겠죠.
그리고 State s a의 내부에 저장된 값은 s -> (a,s)라는 타입을 가집니다. 이 타입을 머릿 속에
잘 새겨둔 상태에서 다음 슬라이드들을 따라가봅시다.
State Monad
(State h) >>= f = State $ s -> let (a, newState) = h s
								 (State g) = f a
								 in g newState
우선 결과값은 State 값 생성자로 시작하고, 이 생성자에 람다를 인자로 주고 있습니다. 따라서 이
람다의 타입은 s -> (b,s)가 되어야겠죠.
State Monad
(State h) >>= f = State $ s -> let (a, newState) = h s
								 (State g) = f a
								 in g newState
따라서 람다의 인자 s는 타입 s를 가지게 됩니다(타입 / 값 헷갈리면 안돼요!). 여기서 이제 let ~ in
구문이 나오죠. let 구문 안 쪽의 내용부터 봅시다.
State Monad
(State h) >>= f = State $ s -> let (a, newState) = h s
								 (State g) = f a
								 in g newState
h s의 결과값을 (a, newState)로 나타내고 있습니다. h 함수는 처음에 말했듯이 s -> (a,s)
타입 서명을 갖고 있죠. 따라서 a값은 a 타입, newState값은 s 타입을 갖게 됩니다. 의미 상으로는
주어진 상태 s에 첫번째 stateful한 연산 h를 적용한 결과, 결과값 a와 바뀐 상태 newState를
얻었다고 할 수 있을 겁니다.
State Monad
(State h) >>= f = State $ s -> let (a, newState) = h s
								 (State g) = f a
								 in g newState
이제 그 상태에서 f a의 결과값을 State g로 나타내고 있죠. f 함수는 a -> State s b 타입을
갖고 있으니, g 함수는 s -> (b,s) 타입을 갖게 될 겁니다. 의미적으로는, h 함수를 적용한
결과 얻은 a 값을 이용하는 두 번째 stateful한 연산 g를 f 함수를 이용해 구하는 것으로 생각할 수
있습니다.
State Monad
(State h) >>= f = State $ s -> let (a, newState) = h s
								 (State g) = f a
								 in g newState
그리고 in 이후 구문에서 이렇게 얻은 새로운 stateful 연산 g에 newState를 넘기고 있죠. 그 결과는
g 함수의 타입이 s -> (b,s)이므로 (b,s) 타입이 될겁니다. 즉, 람다는s 타입의 값을 받아 (b,s)
타입을 리턴하고, 그러니 (State h) >>= f 의 최종 결과 타입은 State s b가 되겠죠.
따라서 (State h) >>= f 라는 식은 의미적으로는 stateful한 연산인 h 함수를 수행한 후, 그
결과값을 이용하는 stateful 연산 g에 h를 수행한 후의 현재 상태(newState)를 넘긴 후의 결과
(b,s), 즉 g 함수의 결과로 얻은 b타입의 값과 새로운 상태 s를 얻는 것으로 생각할 수 있죠. 결국 두
개의 stateful한 연산을 자연스럽게 하나로 엮어주게 되는 것입니다.
stack
State 모나드를 활용하는 예제로 stack를 생각해봅시다. stack은 push와 pop이라는 두 가지
연산을 지원합니다. push / pop은 현재의 stateful한 연산이므로 State 모나드를 이용해 구현할 수
있겠죠.
import Control.Monad.State
type Stack a = [a]
--역시 State가 아니라 state인 것에 주의.
push :: a -> State (Stack a) ()
push val = state $ s -> ((), val:s)
pop :: State (Stack a) a
pop = state $ (top:s) -> (top,s)
stack
이제 이렇게 구현한 stack을 한 번 테스트해봅시다.
stackTest :: State (Stack a) (a,a)
stackTest = do
a <- pop
push a
b <- pop
return (a,b)
ghci> runState stackTest [1,2,3]
((1,1),[2,3])
ghci> runState stackTest [5,4,3,2,1]
((5,5),(4,3,2,1))
Random
다른 유용한 State 모나드 활용 예제로는 random이 있습니다. System.Random 모듈에 있는 난수
생성 함수 random은 아래와 같은 타입을 갖고 있죠.
random :: (RandomGen g, Random a) => g -> (a, g)
이 함수는 난수의 시드값 역할을 할 수 있는 RandomGen 타입 클래스에 속하는 타입 g와, Random
타입 클래스에 속하는 타입 a에 대해 g 값을 받아 난수값 a와 그 이후 시드값 g의 튜플 (a,g)를
반환하는 함수입니다. 저 타입으로부터 random이 stateful 한 함수이며 따라서 State 모나드를
활용할 수 있다는 걸 알 수 있죠.
randomSt :: (RandomGen g, Random a) => State g a
randomSt = state random
Random
이제 randomSt 함수를 이용하면 여러 개의 random 값을 손쉽게 얻어낼 수 있습니다.
import System.Random
import Control.Monad.State
threeRandom :: State StdGen (Bool, Bool, Bool)
threeRandom = do
	a <- randomSt
	b <- randomSt
	c <- randomSt
	 return (a,b,c)
ghci> runState threeRandom (mkStdGen 10)
((True, False, False),356856746 2103410263)
Useful functions
모나드를 쓸 때 유용한 함수 몇 가지를 살펴봅시다. 우선 liftM 함수와 ap 함수입니다.
liftM :: (Monad m) => (a -> b) -> m a -> m b
이 함수는 Monad에 대해 동작한다는 점만 다를 뿐 Functor의 fmap과 동일합니다. Monad가
Functor보다 더 나아간 개념이므로 Monad에 대해서도 fmap과 동일한 연산을 수행할 수 있는 것은
당연하겠죠.
ap :: (Monad m) => m (a -> b) -> m a -> m b
ap역시 Monad에 대해 동작한다는 점만 다를 뿐 Applicative Functor의 <*>과 동일합니다.
Useful functions
ghci> liftM (*2) (Just 5)
Just 10
ghci> liftM (*3) Nothing
Nothing
ghci> liftM (*5) [1,2,3]
[5,10,15]
ghci> Just (*2) `ap` Just 4
Just 8
ghci> Nothing `ap` Just 5
Nothing
ghci> [(*2),(+3)] `ap` [1,2,3]
[2,4,6,4,5,6]
Useful functions
다음은 filterM입니다. 고차함수를 다룰 때 나왔던 filter 함수가 list에 대해서만 동작하는 것이었다면,
filterM 함수는 그걸 일반적인 Monad 차원으로 확장시킨 함수입니다. 이 함수의 타입은 아래와
같습니다.
filter :: (a -> Bool) -> [a] -> [a]
filterM :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
이 filterM 함수를 사용하면 해당 monad의 컨텍스트와 관련된 filter 함수를 수행할 수 있습니다.
Useful functions
ghci> filterM (_ -> Just True) [1,2,3]
Just [1,2,3]
ghci> filterM (x -> if x == 2 then Just True else Just False) [1,2,3]
Just [2]
ghci> filterM (_ -> Nothing) [1,2,3]
Nothing
위와 같이 Maybe 모나드에 대해 filterM 함수를 쓰면 원래 Maybe 모나드가 가진 컨텍스트인
'실패할 수 있는 연산'이 그대로 적용된다는 걸 알 수 있습니다. Nothing이 하나라도 포함되면
Nothing, 그렇지 않다면 술어함수를 통과한 값만 남기죠. 그렇다면 list 타입에 대한 filterM은 어떻게
동작할까요? 역시 마찬가지로 list 타입이 지닌 컨텍스트인 '비결정성'을 그대로 가지고 동작합니다.
Useful functions
이 비결정성이라는 특징을 이용해 어떤 집합의 멱집합(powerset - 해당 집합의 부분집합으로
이루어진 집합)을 쉽게 구할 수 있습니다.
powerset :: [a] -> [[a]]
powerset = filterM (x -> [True, False])
ghci> powerset [1,2,3]
[[1,2,3],[1,2],[1,3],[1],[2,3],[2],[3],[]]

More Related Content

PDF
Haskell study 13
PDF
Haskell study 15
PDF
Haskell study 12
PDF
Haskell study 8
PDF
Haskell study 5
PDF
Haskell study 4
PDF
Haskell study 3
PDF
Monad as functor with pair of natural transformations
Haskell study 13
Haskell study 15
Haskell study 12
Haskell study 8
Haskell study 5
Haskell study 4
Haskell study 3
Monad as functor with pair of natural transformations

What's hot (20)

PPTX
OOPs in Java
PPT
Oops in Java
PPT
PPTX
Access modifier and inheritance
PDF
Closures in Javascript
PPTX
Object Oriented Programing JAVA presentaion
PPTX
Polymorphism presentation in java
PDF
Oops concepts || Object Oriented Programming Concepts in Java
PPTX
the Concept of Object-Oriented Programming
PPTX
Lecture - 5 Control Statement
PDF
Hola Mundo - Hello World - cheat sheet
PPTX
Spring Web MVC
PDF
Haskell study 2
PDF
Kleisli Composition
PDF
PPTX
Spring 3.x - Spring MVC - Advanced topics
PPTX
Member Function in C++
PPT
Java interfaces & abstract classes
PPT
Object Oriented Programming Concepts
PPTX
Java class,object,method introduction
OOPs in Java
Oops in Java
Access modifier and inheritance
Closures in Javascript
Object Oriented Programing JAVA presentaion
Polymorphism presentation in java
Oops concepts || Object Oriented Programming Concepts in Java
the Concept of Object-Oriented Programming
Lecture - 5 Control Statement
Hola Mundo - Hello World - cheat sheet
Spring Web MVC
Haskell study 2
Kleisli Composition
Spring 3.x - Spring MVC - Advanced topics
Member Function in C++
Java interfaces & abstract classes
Object Oriented Programming Concepts
Java class,object,method introduction
Ad

Viewers also liked (18)

PDF
Telling my retail startup's story w/ pics & graphics
PPTX
Report Presentation 01.10.2015
PDF
Modulo de lectura . unidad 3
PDF
Reseña literaria de "Itinerario breve" de Blanca Isaza de Jaramillo
PPTX
Interacción web
PDF
Portfolio_Dylan Russ
PDF
Capital Owners Vs. Workers?
PPTX
하스켈 성능 튜닝
PPTX
vectores de fuerza
PPTX
Unit 11 web authoring
PPTX
üMbrikupalk.nõmme
PDF
はじめてのWordPress勉強会 vol.03 Word Pressの基本操作
PDF
はじめてのWordPress勉強会 vol.02 Word Pressの導入
PDF
Age Of Empires II : Age Of Kings Postmotem
PDF
はじめてのWordPress勉強会 vol.01 Word Pressの概要
PPTX
Histerectomia total abdominal
PDF
Effective c++ chapter 1,2 요약
PDF
Impacto del Diagnostico y Tratamiento oportuno sobre la progresión de la ERC
Telling my retail startup's story w/ pics & graphics
Report Presentation 01.10.2015
Modulo de lectura . unidad 3
Reseña literaria de "Itinerario breve" de Blanca Isaza de Jaramillo
Interacción web
Portfolio_Dylan Russ
Capital Owners Vs. Workers?
하스켈 성능 튜닝
vectores de fuerza
Unit 11 web authoring
üMbrikupalk.nõmme
はじめてのWordPress勉強会 vol.03 Word Pressの基本操作
はじめてのWordPress勉強会 vol.02 Word Pressの導入
Age Of Empires II : Age Of Kings Postmotem
はじめてのWordPress勉強会 vol.01 Word Pressの概要
Histerectomia total abdominal
Effective c++ chapter 1,2 요약
Impacto del Diagnostico y Tratamiento oportuno sobre la progresión de la ERC
Ad

Similar to Haskell study 14 (20)

PDF
Haskell study 9
PDF
자바8 스트림 API 소개
PPTX
Lua 문법 -함수
PPTX
하스켈 프로그래밍 입문 2
PDF
[Swift] Functions
PDF
함수적 사고 2장
PDF
Policy gradient
PDF
Java jungsuk3 ch14_lambda_stream
PDF
키트웍스_발표자료_김경수functional_programming240920.pdf
PDF
나에 첫번째 자바8 람다식 지앤선
PPTX
파이썬+Operator+이해하기 20160409
PPTX
Matplotlib 기초 이해하기_20160730
PPTX
13장 연산자 오버로딩
PDF
SpringCamp 2013 : About Jdk8
PPTX
Startup JavaScript 6 - 함수, 스코프, 클로저
PDF
06장 함수
PDF
6 swift 고급함수
PDF
6 function
PPTX
Surface flingerservice(서피스 상태 변경 및 출력 요청)
PPTX
함수형 사고 - Functional thinking
Haskell study 9
자바8 스트림 API 소개
Lua 문법 -함수
하스켈 프로그래밍 입문 2
[Swift] Functions
함수적 사고 2장
Policy gradient
Java jungsuk3 ch14_lambda_stream
키트웍스_발표자료_김경수functional_programming240920.pdf
나에 첫번째 자바8 람다식 지앤선
파이썬+Operator+이해하기 20160409
Matplotlib 기초 이해하기_20160730
13장 연산자 오버로딩
SpringCamp 2013 : About Jdk8
Startup JavaScript 6 - 함수, 스코프, 클로저
06장 함수
6 swift 고급함수
6 function
Surface flingerservice(서피스 상태 변경 및 출력 요청)
함수형 사고 - Functional thinking

More from Nam Hyeonuk (17)

PPTX
Next 게임 실전 프로젝트 슬라이드
PDF
Haskell study 11
PDF
Haskell study 10
PDF
Haskell study 7
PDF
Haskell study 6
PDF
Haskell study 1
PDF
Haskell study 0
PDF
Multi thread
PDF
Memory & object pooling
PDF
Database
PDF
Exception&log
PDF
Iocp advanced
PDF
Iocp 기본 구조 이해
PDF
Tcp ip & io model
PDF
구문과 의미론(정적 의미론까지)
PDF
Gpg 1.1
PDF
Stl vector, list, map
Next 게임 실전 프로젝트 슬라이드
Haskell study 11
Haskell study 10
Haskell study 7
Haskell study 6
Haskell study 1
Haskell study 0
Multi thread
Memory & object pooling
Database
Exception&log
Iocp advanced
Iocp 기본 구조 이해
Tcp ip & io model
구문과 의미론(정적 의미론까지)
Gpg 1.1
Stl vector, list, map

Haskell study 14

  • 2. Writer Monad 이전에 다뤘던 Maybe, List, IO 등의 모나드 외의 많이 쓰이는 다른 유용한 모나드들에 대해 다뤄봅시다. 첫 번째는 Writer 모나드입니다. Writer 모나드는 작업 중간중간에 로그를 남기고 싶을 때 굉장히 유용하게 사용할 수 있습니다. newtype Writer extra a = Writer { runWriter :: (a, extra) } instance Monoid extra => Monad (Writer extra) where return a = Writer (a, mempty) Writer (a, e) >>= f = let (b, e') = runWriter (f a) in Writer (b, e `mappend` e') extra타입은 해당 작업의 결과로 인해 발생한 여분의 값(로그 등)의 타입이고 a 타입은 실제 작업하고자 하는 값의 타입입니다.
  • 3. Writer Monad Writer 모나드의 return 함수는 계산하고자 하는 값과, extra 타입의 가장 작은 값(mempty)을 묶은 튜플을 반환합니다. Prelude> runWriter (return 3 :: Writer String Int) (3, "") Prelude> runWriter (return 3 :: Writer (Sum Int) Int) (3, Sum {getSum = 0}) Prelude> runWriter (return 3 :: Writer (Product Int) Int) (3, Product {getProduct = 1}) runWriter 함수는 Writer 타입의 값을 일반적인 튜플값으로 바꿔주는 역할을 하죠.
  • 4. Writer Monad Writer 모나드를 이용해 간단한 로그를 남기는 예제를 작성해봅시다. import Control.Monad.Writer logNumber :: Int -> Writer [String] Int -- Writer가 아니라 writer인 것에 주의. 관련 내용은 Monad Transformer에서 다룹니다. logNumber x = writer (x, ["Got Number: " ++ show x]) multWithLog :: Writer [String] Int multWithLog = do a <- logNumber 3 b <- logNumber 5 return (a*b) Prelude> runWriter multWithLog (15, ["Got Number: 3", "Got Number: 5"])
  • 5. Writer Monad 앞의 예제와 같이 Writer 모나드를 사용하면 각각의 연산에 대한 결과 로그를 쉽게 남길 수 있다는 것을 알 수 있습니다. 그리고 Writer 모나드에서 유용하게 사용할 수 있는 함수로 tell이라는 함수가 있습니다. tell :: extra -> Writer extra () tell e = writer ((), e) tell 함수는 위 선언에서 볼 수 있듯이, 실제 연산 값에는 아무런 영향을 미치지 않고 여분의 값에 대해 특정 값을 추가하고 싶을 때 사용하는 함수입니다. 계산 중간중간에 원하는 로그를 삽입하고 싶을 때 사용할 수 있겠죠.
  • 6. Writer Monad tell을 활용하여, 두 수의 gcd를 구하는 함수의 연산 과정을 기록해봅시다. import Control.Monad.Writer gcdWithLog :: Int -> Int -> Writer [String] Int gcdWithLog a b | b == 0 = do tell ["Finished with " ++ show a] return a | otherwise = do tell [show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b)] gcdWithLog b (a `mod` b)
  • 7. Writer Monad gcd 결과 값을 얻고 싶다면 함수를 수행한 Writer 값에서 첫 번째 요소를 가져오면 되고, 계산 로그가 궁금하다면 두 번째 요소를 가져오면 되겠죠. Prelude> fst $ runWriter (gcdWithLog 8 3) 1 Prelude> snd $ runWriter (gcdWithLog 8 3) ["8 mod 3 = 2", "3 mod 2 = 1", "2 mod 1 = 0", "Finished With 1"]
  • 8. State Monad 이번엔 State 모나드입니다. State 모나드는 상태의 변화가 필요한 연산을 구현할 때 굉장히 유용하게 쓸 수 있습니다. newtype State s a = State { runState :: s -> (a,s) } State s a는 s 타입의 상태와 a 타입의 결과값을 가지는 연산으로 생각할 수 있습니다. instance Monad (State s) where return x = State $ s -> (x,s) (State h) >>= f = State $ s -> let (a, newState) = h s (State g) = f a in g newState
  • 9. State Monad return x = State $ s -> (x,s) 코드가 어려울 땐 타입을 기준으로 하나씩 살펴보는 것이 좋습니다. return 함수는 원래 (Monad m) => a -> m a라는 타입을 갖고 있는데, 이 때 m이 State s이므로 여기서 return 함수는 a -> State s a 라는 타입을 가지게 됩니다. 그래서 return x는 상태 s를 인자로 받아 결과값 x와 상태 s의 튜플을 돌려주는 연산으로 정의됩니다.
  • 10. State Monad (State h) >>= f = State $ s -> let (a, newState) = h s (State g) = f a in g newState State 모나드에서 >>= 함수는 두 개의 stateful한 연산을 이어주는 역할을 한다고 생각하면 됩니다. 역시 타입부터 하나씩 살펴봅시다. 원래 >>= 함수는 (Monad m) => m a -> (a -> m b) -> m b 라는 타입을 갖고 있습니다. 따라서 여기서는 타입이 State s a -> (a -> State s b) -> State s b가 되겠죠. 그리고 State s a의 내부에 저장된 값은 s -> (a,s)라는 타입을 가집니다. 이 타입을 머릿 속에 잘 새겨둔 상태에서 다음 슬라이드들을 따라가봅시다.
  • 11. State Monad (State h) >>= f = State $ s -> let (a, newState) = h s (State g) = f a in g newState 우선 결과값은 State 값 생성자로 시작하고, 이 생성자에 람다를 인자로 주고 있습니다. 따라서 이 람다의 타입은 s -> (b,s)가 되어야겠죠.
  • 12. State Monad (State h) >>= f = State $ s -> let (a, newState) = h s (State g) = f a in g newState 따라서 람다의 인자 s는 타입 s를 가지게 됩니다(타입 / 값 헷갈리면 안돼요!). 여기서 이제 let ~ in 구문이 나오죠. let 구문 안 쪽의 내용부터 봅시다.
  • 13. State Monad (State h) >>= f = State $ s -> let (a, newState) = h s (State g) = f a in g newState h s의 결과값을 (a, newState)로 나타내고 있습니다. h 함수는 처음에 말했듯이 s -> (a,s) 타입 서명을 갖고 있죠. 따라서 a값은 a 타입, newState값은 s 타입을 갖게 됩니다. 의미 상으로는 주어진 상태 s에 첫번째 stateful한 연산 h를 적용한 결과, 결과값 a와 바뀐 상태 newState를 얻었다고 할 수 있을 겁니다.
  • 14. State Monad (State h) >>= f = State $ s -> let (a, newState) = h s (State g) = f a in g newState 이제 그 상태에서 f a의 결과값을 State g로 나타내고 있죠. f 함수는 a -> State s b 타입을 갖고 있으니, g 함수는 s -> (b,s) 타입을 갖게 될 겁니다. 의미적으로는, h 함수를 적용한 결과 얻은 a 값을 이용하는 두 번째 stateful한 연산 g를 f 함수를 이용해 구하는 것으로 생각할 수 있습니다.
  • 15. State Monad (State h) >>= f = State $ s -> let (a, newState) = h s (State g) = f a in g newState 그리고 in 이후 구문에서 이렇게 얻은 새로운 stateful 연산 g에 newState를 넘기고 있죠. 그 결과는 g 함수의 타입이 s -> (b,s)이므로 (b,s) 타입이 될겁니다. 즉, 람다는s 타입의 값을 받아 (b,s) 타입을 리턴하고, 그러니 (State h) >>= f 의 최종 결과 타입은 State s b가 되겠죠. 따라서 (State h) >>= f 라는 식은 의미적으로는 stateful한 연산인 h 함수를 수행한 후, 그 결과값을 이용하는 stateful 연산 g에 h를 수행한 후의 현재 상태(newState)를 넘긴 후의 결과 (b,s), 즉 g 함수의 결과로 얻은 b타입의 값과 새로운 상태 s를 얻는 것으로 생각할 수 있죠. 결국 두 개의 stateful한 연산을 자연스럽게 하나로 엮어주게 되는 것입니다.
  • 16. stack State 모나드를 활용하는 예제로 stack를 생각해봅시다. stack은 push와 pop이라는 두 가지 연산을 지원합니다. push / pop은 현재의 stateful한 연산이므로 State 모나드를 이용해 구현할 수 있겠죠. import Control.Monad.State type Stack a = [a] --역시 State가 아니라 state인 것에 주의. push :: a -> State (Stack a) () push val = state $ s -> ((), val:s) pop :: State (Stack a) a pop = state $ (top:s) -> (top,s)
  • 17. stack 이제 이렇게 구현한 stack을 한 번 테스트해봅시다. stackTest :: State (Stack a) (a,a) stackTest = do a <- pop push a b <- pop return (a,b) ghci> runState stackTest [1,2,3] ((1,1),[2,3]) ghci> runState stackTest [5,4,3,2,1] ((5,5),(4,3,2,1))
  • 18. Random 다른 유용한 State 모나드 활용 예제로는 random이 있습니다. System.Random 모듈에 있는 난수 생성 함수 random은 아래와 같은 타입을 갖고 있죠. random :: (RandomGen g, Random a) => g -> (a, g) 이 함수는 난수의 시드값 역할을 할 수 있는 RandomGen 타입 클래스에 속하는 타입 g와, Random 타입 클래스에 속하는 타입 a에 대해 g 값을 받아 난수값 a와 그 이후 시드값 g의 튜플 (a,g)를 반환하는 함수입니다. 저 타입으로부터 random이 stateful 한 함수이며 따라서 State 모나드를 활용할 수 있다는 걸 알 수 있죠. randomSt :: (RandomGen g, Random a) => State g a randomSt = state random
  • 19. Random 이제 randomSt 함수를 이용하면 여러 개의 random 값을 손쉽게 얻어낼 수 있습니다. import System.Random import Control.Monad.State threeRandom :: State StdGen (Bool, Bool, Bool) threeRandom = do a <- randomSt b <- randomSt c <- randomSt return (a,b,c) ghci> runState threeRandom (mkStdGen 10) ((True, False, False),356856746 2103410263)
  • 20. Useful functions 모나드를 쓸 때 유용한 함수 몇 가지를 살펴봅시다. 우선 liftM 함수와 ap 함수입니다. liftM :: (Monad m) => (a -> b) -> m a -> m b 이 함수는 Monad에 대해 동작한다는 점만 다를 뿐 Functor의 fmap과 동일합니다. Monad가 Functor보다 더 나아간 개념이므로 Monad에 대해서도 fmap과 동일한 연산을 수행할 수 있는 것은 당연하겠죠. ap :: (Monad m) => m (a -> b) -> m a -> m b ap역시 Monad에 대해 동작한다는 점만 다를 뿐 Applicative Functor의 <*>과 동일합니다.
  • 21. Useful functions ghci> liftM (*2) (Just 5) Just 10 ghci> liftM (*3) Nothing Nothing ghci> liftM (*5) [1,2,3] [5,10,15] ghci> Just (*2) `ap` Just 4 Just 8 ghci> Nothing `ap` Just 5 Nothing ghci> [(*2),(+3)] `ap` [1,2,3] [2,4,6,4,5,6]
  • 22. Useful functions 다음은 filterM입니다. 고차함수를 다룰 때 나왔던 filter 함수가 list에 대해서만 동작하는 것이었다면, filterM 함수는 그걸 일반적인 Monad 차원으로 확장시킨 함수입니다. 이 함수의 타입은 아래와 같습니다. filter :: (a -> Bool) -> [a] -> [a] filterM :: (Monad m) => (a -> m Bool) -> [a] -> m [a] 이 filterM 함수를 사용하면 해당 monad의 컨텍스트와 관련된 filter 함수를 수행할 수 있습니다.
  • 23. Useful functions ghci> filterM (_ -> Just True) [1,2,3] Just [1,2,3] ghci> filterM (x -> if x == 2 then Just True else Just False) [1,2,3] Just [2] ghci> filterM (_ -> Nothing) [1,2,3] Nothing 위와 같이 Maybe 모나드에 대해 filterM 함수를 쓰면 원래 Maybe 모나드가 가진 컨텍스트인 '실패할 수 있는 연산'이 그대로 적용된다는 걸 알 수 있습니다. Nothing이 하나라도 포함되면 Nothing, 그렇지 않다면 술어함수를 통과한 값만 남기죠. 그렇다면 list 타입에 대한 filterM은 어떻게 동작할까요? 역시 마찬가지로 list 타입이 지닌 컨텍스트인 '비결정성'을 그대로 가지고 동작합니다.
  • 24. Useful functions 이 비결정성이라는 특징을 이용해 어떤 집합의 멱집합(powerset - 해당 집합의 부분집합으로 이루어진 집합)을 쉽게 구할 수 있습니다. powerset :: [a] -> [[a]] powerset = filterM (x -> [True, False]) ghci> powerset [1,2,3] [[1,2,3],[1,2],[1,3],[1],[2,3],[2],[3],[]]