clojure monad

51
Clojure Monad 느슨한 타입 언어로 모나드 맛보기 김은민

Upload: eunmin-kim

Post on 21-Jan-2018

558 views

Category:

Software


3 download

TRANSCRIPT

Page 1: Clojure Monad

Clojure�Monad�느슨한�타입�언어로�모나드�맛보기

김은민

Page 2: Clojure Monad

당신이�만약�모나드를�이해하고�나면,��

그걸�다른�사람에게�설명할�수�있는�능력을�잃어버리게�된다.��

이것이�모나드의�저주다.

Douglas Crokford

Page 3: Clojure Monad

그래서�

저는�모나드를�설명하려고�하기�때문에��

잘�이해하고�있지�않습니다.��

양해�바랍니다.�ㅋㅋㅋ

Eunmin Kim

Page 4: Clojure Monad

여러가지�모나드�설명,�또�다른�모나드�설명

Page 5: Clojure Monad

시작하기�전에

• 이해하기�쉬운�모나드�

• 구현의�관점�/�타입을�지우자�

• 하스켈�용어를�사용�

• 클로저�맛보기

(def x 1) (defn inc [x] (+ x 1)) (fn [x] (inc x))

var x = 1; function inc(x) { return x + 1; } function (x) { return inc(x); }

Page 6: Clojure Monad

내�맘대로�모나드란?

Page 7: Clojure Monad

컨텍스트가�있는��

연속된�계산을��

읽기�쉽게�작성하기�위한��

순수한�함수형�패턴

Page 8: Clojure Monad

컨텍스트가�있는��

연속된�계산을��

읽기�쉽게�작성하기�위한��

순수한�함수형�패턴

var value1 = getValue("key1"); var value2 = getValue(value1);var value3 = getValue(value2); …

계산 중에 값이 null이면 전체 계산 결과가 null이면...

Page 9: Clojure Monad

컨텍스트가�있는��

연속된�계산을��

읽기�쉽게�작성하기�위한��

순수한�함수형�패턴

var value1 = getValue("key1"); if (value1 !== null) { var value2 = getValue(value1); if (value2 !== null) { ... } } else { return null; }

Page 10: Clojure Monad

해법

• 객체�지향과�같은�언어에서도�다양한�방식으로�문제를�풀고�있다.�

• 계산�속에�컨텍스트가�섞여�있다면�계산의�본질을�파악하기(읽기)�

어렵다.�

• 컨텍스트와�계산을�분리하면�컨텍스트를�재사용�할�수�있다.�

• 순수�함수�스타일로�연속된�계산은�알아보기�힘들다.

Page 11: Clojure Monad

...�읽기�쉽게�작성하기�위한�순수한�함수형�패턴

• 복잡한�코드를�이해하기�쉽게�하기�위해�추상화 (분리된�개념)�

• 작은�함수를�조합해�큰�함수로�추상화�

• 작은�데이터를�조합해�큰�데이터로�추상화

Page 12: Clojure Monad

추상화의�예

(defn str "With no args, returns the empty string. With one arg x, returns x.toString(). (str nil) returns the empty string. With more than one arg, returns the concatenation of the str values of the args." {:tag String :added "1.0" :static true} (^String [] "") (^String [^Object x] (if (nil? x) "" (. x (toString)))) (^String [x & ys] ((fn [^StringBuilder sb more] (if more (recur (. sb (append (str (first more)))) (next more)) (str sb))) (new StringBuilder (str x)) ys)))

Page 13: Clojure Monad

추상화의�예

(str "Hello" "World" "!")

str이란�기호는�문자열을�합쳐주는�개념

Page 14: Clojure Monad

...�읽기�쉽게�작성하기�위한�순수한�함수형�패턴

• 부수효과가�없다.�

• 상태가�없다.�

• 전역�값도�없다.

(fn [x] (println "x:" x) (+ x 1))

var state = 1; state = 2; //�상태를�변경!

;;�부수효과�

(def y 1) (fn [x] (+ x y)) ;;�y는�전역�값�

Page 15: Clojure Monad

...�읽기�쉽게�작성하기�위한�순수한�함수형�패턴

• 대부분의�언어는�순수하지�않은�부분을�허용한다.�

• 순수한�것이�좋은�지는�여기서�논하지�않는다.�

• 모나드는�순수의�감옥에�살고�있다.

Page 16: Clojure Monad

컨텍스트가�있는�연속된�계산을�...

• 절차적�형태�

• 순수한�함수�형태

var x = 1; var y = 2; x + y;

(((fn [x] (fn [y] (+ x y))) 1) 2)

Page 17: Clojure Monad

컨텍스트가�있는�연속된�계산을�...

• 들여쓰기를�해서�보기�좋게�만들기

(((fn [x] (fn [y] (+ x y))) 1) 2)

Page 18: Clojure Monad

컨텍스트가�있는�연속된�계산을�...

• 들여쓰기를�해서�보기�좋게�만들기�

• 절차를�추상화

(((fn [x] (fn [y] (+ x y))) 1) 2)

(((fn [x] (fn [y] 결과)) 계산) 계산)

Page 19: Clojure Monad

컨텍스트가�있는�연속된�계산을�...

• Helper�함수를�만들어서�더�보기�좋게

(defn bind [mv f] (f mv))

(bind 1 (fn [x] (bind 2 (fn [y] (+ x y)))))

Page 20: Clojure Monad

컨텍스트가�있는�연속된�계산을�...

• 들여쓰기를�해서�보기�좋게�만들기

(defn bind [mv f] (f mv))

(bind 1 (fn [x] (bind 2 (fn [y] (+ x y)))))

Page 21: Clojure Monad

컨텍스트가�있는�연속된�계산을�...

• 들여쓰기를�해서�보기�좋게�만들기�

• 연속된�계산�패턴을�추상화

(bind 1 (fn [x] (bind 2 (fn [y] (+ x y)))))

var x = 1; var y = 2; x + y;

(bind 계산 (fn [계산�결과를�묶을�기호] ;;�줄 (bind 계산 (fn [계산�결과를�묶을�기호] ;;�줄… ;;�줄… ;;�줄 결과)))) ;;�줄

Page 22: Clojure Monad

컨텍스트가�있는�연속된�계산을�...

• 모나드�

• bind�함수에�따라�모나드�동작(특성)이�달라진다.�

• identity�모나드

(defn bind [mv f] (f mv))

(bind 계산 (fn [계산�결과를�묶을�기호] ;;�줄�������������������������������������������������������������������������������������������������������������������������������������

(bind 계산 (fn [계산�결과를�묶을�기호] ;;�줄… ��;;�줄�

… �;;�줄� 결과)))) ;;�줄

Page 23: Clojure Monad

컨텍스트가�있는�연속된�계산을�...

• 컨텍스트는�연속된�계산�과정을�감싸고�있는�생각(로직,�데이터)�

• 예�

• Maybe�모나드�

• 연속된�계산�중�결과가�nil�이면�전체�결과가�nil��

• Writer�모나드�

• 연속된�계산�중�다른�환경에�값을�기록하고�싶은�경우�

• Reader�모나드�

• 연속된�계산�중�다른�환경에서�값을�읽어�오고�싶은�경우�

• State�모나드�

• 연속된�계산�중�읽고�쓰고�하는�환경이�필요한�경우

Page 24: Clojure Monad

Maybe���연속된�계산�중�결과가�nil�이면�전체�결과가�nil�

• nil을�리턴�할�수도�있는�value�함수

(defn value [x] (if (pos? x) x nil)) (bind (value 0) (fn [x] (bind (inc x) (fn [y] (+ x y)))))

1. Unhandled java.lang.NullPointerException (No message)

(defn value [x] (when (pos? x) (f mv)))

Page 25: Clojure Monad

Maybe���연속된�계산�중�결과가�nil�이면�전체�결과가�nil�

• nil을�리턴�할�수도�있는�value�함수

(defn value [x] (if (pos? x) x nil)) (bind (value 0) (fn [x] (bind (inc x) (fn [y] ;;�(inc�nil)�은�예외를�발생�����������������������������������������������������������������������������������������������������������������������������

(+ x y)))))

1. Unhandled java.lang.NullPointerException (No message)

Page 26: Clojure Monad

Maybe���연속된�계산�중�결과가�nil�이면�전체�결과가�nil�

• bind�함수를�고쳐보자.�

• 하스켈�Maybe�모나드�예

(defn bind [mv f] (if mv (f mv) nil))

(bind (value 0) (fn [x] (bind (inc x) (fn [y] (+ x y))))) ;; => nil

Just 3 >>= (\x -> Nothing >>= (\y -> Just (x + y))) -- Nothing

Page 27: Clojure Monad

Writer���연속된�계산�중�다른�환경에�값을�기록하고�싶은�경우

• 계산�과정�중�로그를�기록�하는�예�

• tell�함수로�계산�중에�값을�기록�한다.�

• 새로운�함수�tell,�logs,�value�구현

(def result (bind 1 (fn [x] (bind (tell (str "x:" x)) (fn [_] (bind 2 (fn [y] (+ x y))))))))

(logs result) ;; ["x: 1"] (value result) ;; 3

Page 28: Clojure Monad

Writer���연속된�계산�중�다른�환경에�값을�기록하고�싶은�경우

• logs와�value�함수�구현�

• result�데이터�타입�

• logs와�value�함수

(logs result) ;; ["x: 1"] (value result) ;; 3

[3 ["x: 1"]]

(defn logs [mv] (second mv)) (defn value [mv] (first mv))

Page 29: Clojure Monad

Writer���연속된�계산�중�다른�환경에�값을�기록하고�싶은�경우

• bind�함수�구현�

• bind�함수

(def result (bind 1 (fn [x] (bind (tell (str "x:" x)) (fn [_] (bind 2 (fn [y] (+ x y))))))))

(defn bind [mv f] (let [[v w] mv [vv ww] (f v)] [vv (concat w ww)]))

계산�값을�담고�있는�mv�에서�값을�꺼내�f를�적용한다. 그런�의미에서�스칼라는�bind라는�이름�대신�flatMap이라는�이름을�사용한다.

Page 30: Clojure Monad

Writer���연속된�계산�중�다른�환경에�값을�기록하고�싶은�경우

• bind�함수�구현�

• bind�함수

(def result (bind 1 (fn [x] ;;�bind�함수는�1을�받지�못함!�����������������������������������������������������������������������������������������������������������������

(bind (tell (str "x:" x)) (fn [_] (bind 2 (fn [y] ;;�bind�함수는�2을�받지�못함!����������������������������������������������������������������������������������������������������������������

(+ x y)))))))) ;;�최종�결과는�3이면�안됨!

(defn bind [mv f] (let [[v w] mv [vv ww] (f v)] [vv (concat w ww)]))

Page 31: Clojure Monad

Writer���연속된�계산�중�다른�환경에�값을�기록하고�싶은�경우

• bind에�넘겨�줄�Helper�함수�만들기�

• 계산�고쳐�쓰기

(defn return [v] [v []])

(def result (bind (return 1) (fn [x] (bind (tell (str "x:" x)) (fn [_] (bind (return 2) (fn [y] (return (+ x y)))))))))

Page 32: Clojure Monad

Writer���연속된�계산�중�다른�환경에�값을�기록하고�싶은�경우

• tell�함수�만들기�

• 완성

(def result (bind (return 1) (fn [x] (bind (tell (str "x:" x)) (fn [_] (bind (return 2) (fn [y] (return (+ x y)))))))))

(defn tell [v] [nil [v]])

(logs result) ;; ["x: 1"] (value result) ;; 3

Page 33: Clojure Monad

Writer���연속된�계산�중�다른�환경에�값을�기록하고�싶은�경우

• 일반�값을�bind�함수에�넘기기�위한�return�함수�

• 모노이드�:�합칠�수�있는�값의�일반화�

• concat�->�append�

• []�->�emtpy

(defn bind [mv f] (let [[v w] mv [vv ww] (f v)] [vv (concat w ww)])) (defn return [v] [v []])

Page 34: Clojure Monad

Reader���연속된�계산�중�다른�환경에서�값을�읽어�오고�싶은�경우

• 전역�설정의�예

(def config {:debug true}) (defn calc1 [x] (+ x 1)) (defn calc2 [y] (if (:debug config) (+ y 2) 0)) (let [x (calc 1) y (calc x)] (+ x y))

Page 35: Clojure Monad

Reader���연속된�계산�중�다른�환경에서�값을�읽어�오고�싶은�경우

• 전역�값을�사용하지�않는�예

(defn calc1 [x] (+ x 1)) (defn calc2 [y config] (if (:debug config) (+ y 2) 0)) (let [config {:debug true} x (calc1 1) y (calc2 x config)] (+ x y))

Page 36: Clojure Monad

Reader���연속된�계산�중�다른�환경에서�값을�읽어�오고�싶은�경우

• 모나드�형태로�써�보면�

• ask�함수로�계산�과정�중에�컨텍스트�값을�쓸�수�있다.

(def result (bind (return (calc1 1)) (fn [x] (bind (ask) (fn [config] (bind (return (calc2 2 config)) (fn [y] (return (+ x y))))))))) (result {:debug true}) ;; 6

Page 37: Clojure Monad

Reader���연속된�계산�중�다른�환경에서�값을�읽어�오고�싶은�경우

• bind와�return�함수�

• ask�함수

(defn bind [mv f] (fn [r] ((f (mv r)) r))) (defn return [v] (fn [r] v))

(defn ask [] (fn [r] r))

Page 38: Clojure Monad

State���연속된�계산�중�읽고�쓰고�하는�환경이�필요한�경우

• 상태

var state = undefined; var x = 1; state = 1; // put state y = state; // get state var result = x + y;

Page 39: Clojure Monad

State���연속된�계산�중�읽고�쓰고�하는�환경이�필요한�경우

• 모나드�형태로�써보기�

• put-state�함수로�상태�값을�바꾼다.�

• get-state�함수로�상태�값을�가져온다.

(def result (bind (return 1) (fn [x] (bind (put-state x) (fn [_] (bind (get-state) (fn [y] (return (+ x y)))))))))

(value (result nil)) ;; 2

(state (result nil)) ;; 1

var state = undefined; var x = 1; state = 1; // put state y = state; // get state var result = x + y;

result; // 2

state; // 1

Page 40: Clojure Monad

State���연속된�계산�중�읽고�쓰고�하는�환경이�필요한�경우

• bind와�return�함수�

• get-state,�put-state�함수

(defn bind [mv f] (fn [s] (let [[v ss] (mv s)] ((f v) ss)))) (defn return [v] (fn [s] [v s])) ;;�v�값,�s�상태�값

(defn put-state [v] (fn [_] [nil v])) (defn get-state [] (fn [s] [s s]))

Page 41: Clojure Monad

State���연속된�계산�중�읽고�쓰고�하는�환경이�필요한�경우

• 결과에서�value,�state를�가져오는�함수와�사용법

(defn value [s] (first s)) (defn state [s] (second s)) (value (result nil)) ;;�nil은�초기�상태�값��������������������������������������������������������������������������������������������������������������������������������

(state (result nil))

Page 42: Clojure Monad

Haskell에서�State�모나드

(def result (bind (return 1) (fn [x] (bind (put-state x) (fn [_] (bind (get-state) (fn [y] (return (+ x y)))))))))

stateMonadWithBind :: State Int Int stateMonadWithBind = return 1 >>= \x -> put x >>= \_ -> get >>= \y -> return (x + y)

stateMonad :: State Int Int stateMonad = do x <- return 1 put x y <- get return (x + y)

runState stateMonad 0 — (2,1)

Page 43: Clojure Monad

bind의�다형성

• bind�함수는�다형성을�가져야�모나드�패턴을�다시�쓸�수�있다.�

• Writer�/�State�bind

(bind 계산 (fn [계산�결과를�묶을�기호] ;;�줄�������������������������������������������������������������������������������������������������������������������������������������

(bind 계산 (fn [계산�결과를�묶을�기호] �;;�줄… ;;�줄… ;;�줄����������������������������������������������������������������������������������������������

결과)))) �;;�줄

(defn bind [mv f] (fn [s] (let [[v ss] (mv s)] ((f v) ss))))

(defn bind [mv f] (let [[v w] mv [vv ww] (f v)] [vv (concat w ww)]))

Page 44: Clojure Monad

bind의�다형성

• clojure.algo.monad�에서�사용하는�재사용�방법�

• domonad는�bind�함수를�감춰주는�syntactic�sugar�

• domonad�인자로�넘기는�monad�종류에�따라서�모나드의

특성이�달라진다.�하지만�타입�있는�언어들이�가지는�값에�따른�

bind의�다형성�효과를�누리지�못한다.

(def state-monad (domonad state-m [x (m-result 1) _ (set-state x) y (fetch-state)] (+ x y))) (first (state-monad 0))

Page 45: Clojure Monad

타입�따른�다형성과�모나드

• 클로저는�protocol과�multimethod로�다형성을�지원한다.

;; protocol / defrecord (defprotocol Monad (bind [mv f])) (defrecord Writer [v mv] Monad (bind [mv f] (let [ww (f v)] (->Writer (:v ww) (concat mv (:mv ww)))))) ;; multimethod (defmulti bind (fn [mv f] (class mv))) (defmethod bind Writer [{:keys [v mv]} f] (let [ww (f v)] (->Writer (:v ww) (concat mv (:mv ww)))))

Page 46: Clojure Monad

타입�따른�다형성과�모나드

• 클로저에서�deftype이나�defrecord로�만든�타입은�함수�값을�

표현하기�어렵다.�(Reader�/�State�모나드의�값은�함수)�

• 아래�두�함수를�다른�타입으로�인식하기�어렵다.

;; Long -> Long (def a (fn [x] (inc x))) ;; String -> String (def b (fn [x] (str x "!"))) (class a) ;; clojure.lang.IFn (class b) ;; clojure.lang.IFn

Page 47: Clojure Monad

타입과�클로저

• 타입을�표현하기�위한�(제약은�선택�사항)�라이브러리�

• core.typed�

• spec

(ann bind [[Number -> State] -> [Number -> [Number -> State]]]) (fn [x :- Number] …)

(s/fdef bind :args (s/cat :mv (s/fspec :args (s/cat :v number?) :ret state?) :f (s/fspec :args (s/cat :v number?) :ret state?)))

Page 48: Clojure Monad

정리

• 컨텍스트가�있는�연속된�계산을�읽기�쉽게�만들기�위한� 순수한�함수형�패턴�

• bind와�return�함수의�구성에�따라�모나드의�특성이�달라진다.�

• 모나드�값은�함수일�수도�있다.�(Reader�/�State의�예)�

• 클로저는�algo.monad�라이브러에�모나드�패턴을�사용하기�위

한�domonad�라는�Syntactic�Sugar와�자주�사용하는�모나드

를�만들어�놨다.�

• 타입�없는�언어는�타입에�따른�다형성의�제한으로�모나드�계산�

과정에서�다른�모나드를�함께�사용하기�힘들다.

Page 49: Clojure Monad

다루지�않은�부분

• 모나드의�다른�인터페이스�

• 펑터�

• 어플리커티브�펑터�

• 모노이드�

• 모나드�트랜스포머�

• 모나드�3법칙�

• List,�I/O,�Continuation,�Free�…�수�많은�모나드들�

• …

Page 50: Clojure Monad

가치

• 순수�함수형�안에서�모나드는�가치가�있다.�

• 컨텍스트와�계산�과정을�분리�

• DSL�interpreter�

• 순수한�사람이라면�더�좋은�코드를�만들기�위해�모나드를�사용

하는�것이�좋다.�

• 순수하지�않은�사람이라면�학습용으로�가치가�있다.�

• 순수한�것은�좋은�것인가?

Page 51: Clojure Monad

감사합니다.

Eunmin Kim