53
MP in Clojure ~ herding cats with clj ~

MP in Clojure

Embed Size (px)

Citation preview

Page 1: MP in Clojure

MPinClojure~herdingcatswithclj~

Page 2: MP in Clojure

Self-introduction/laʒenɔʁɛ ̃k/カマイルカlagénorhynque

(defprofile lagénorhynque :name "Kent OHASHI"

:languages [Clojure Haskell Python Scala English français Deutsch русский]

:interests [programming language-learning mathematics]

:contributing [github.com/japan-clojurians/clojure-site-ja])

Page 3: MP in Clojure

Clojure×MP

Page 4: MP in Clojure

Contents1. WhatisMP?

2. WhyMPinClojure?

3. HowMPinClojure?

4. Examples

Page 5: MP in Clojure

WhatisMP?

Page 6: MP in Clojure

programmingwithmonads

cf.FP=functionalprogramming

MP=monadicprogramming

Page 7: MP in Clojure

de nitionofMonadinHaskellGHC.Base#Monad

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

(>>) :: forall a b. m a -> m b -> m b m >> k = m >>= \_ -> k {-# INLINE (>>) #-}

return :: a -> m a return = pure

fail :: String -> m a fail s = errorWithoutStackTrace s

Page 8: MP in Clojure

monadsinHaskell

型クラスMonadのインスタンス>>=:m a -> (a -> m b) -> m breturn:a -> m aモナド則( )を満たすシンプルで合成可能な構造→様々な形で利⽤可能do記法というシンタックスシュガー

monadlaws

Page 9: MP in Clojure

e.g.

※#渋⾕javaなので⼀応Javaの例から^_^;

java.util.Optionaljshell> Optional<Integer> a = Optional.of(2)a ==> Optional[2]

jshell> Optional<Integer> b = Optional.of(3)b ==> Optional[3]

jshell> a.flatMap( x -> // with `flatMap` & `map` ...> b.map( y -> ...> x * y ...> ) ...> )$3 ==> Optional[6]

Page 10: MP in Clojure

e.g.scala.Optionscala> val a = Some(2)a: Some[Int] = Some(2)scala> val b = Some(3)b: Some[Int] = Some(3)scala> a.flatMap { x => // with `flatMap` & `map` | b.map { y => | x * y | } | }res0: Option[Int] = Some(6)scala> for { // with `for` expression | x <- a | y <- b | } yield x * yres1: Option[Int] = Some(6)

Page 11: MP in Clojure

e.g.Prelude.Maybe> a = Just 2> b = Just 3> :{ -- with `>>=` & `return`| a >>= \x ->| b >>= \y ->| return $ x * y| :}Just 6> :{ -- with `do` notation| do| x <- a| y <- b| return $ x * y| :}Just 6

Page 12: MP in Clojure

e.g.cats.monad.maybeuser=> (require '[cats.core :as m] #_=> '[cats.monad.maybe :as maybe])niluser=> (def a (maybe/just 2))#'user/auser=> (def b (maybe/just 3))#'user/buser=> (m/>>= a (fn [x] ; with `>>=` & `return` #_=> (m/>>= b (fn [y] #_=> (m/return (* x y))))))#<Just 6>user=> (m/mlet [x a ; with `mlet` macro #_=> y b] #_=> (m/return (* x y)))#<Just 6>

Page 13: MP in Clojure

WhyMPinClojure?

Page 14: MP in Clojure

MPinClojure

Clojureではモナドは必須の機能ではないが……静的型付け&純粋関数型の⾔語ではないが……シンプルで汎⽤的なDSL構築フレームワークHaskellなどで有⽤なアイディアが活かせる⇒ClojureでもMPしてみよう(*> ᴗ •*)ゞ

Page 15: MP in Clojure

HowMPinClojure?

Page 16: MP in Clojure

MPlibrariesinClojureclojure/algo.monads

Macrosfordefiningmonads,anddefinitionofthemostcommon

monads

funcool/cats

CategoryTheoryandAlgebraicabstractionsforClojureand

ClojureScript.

Page 17: MP in Clojure

howtosupportMPinClojurefeature algo.monads cats

Monadtypeclass plainMapdata protocol

donotation macro macro

contextinference (n/a) protocol

Page 18: MP in Clojure

anatomyofalgo.monadsuser=> (require '[clojure.algo.monads :as m])nil

user=> (m/domonad m/maybe-m #_=> [x 2 #_=> y 3] #_=> (* x y))6

Page 19: MP in Clojure

macroexpand-1

clojure.algo.monads/with-monad?

user=> (macroexpand-1 #_=> '(m/domonad m/maybe-m #_=> [x 2 #_=> y 3] #_=> (* x y)))(clojure.algo.monads/with-monad m/maybe-m (m-bind 2 (fn [x] (m-bind 3 (fn [y] (m-result (* x y)))))))

Page 20: MP in Clojure

macroexpand-1oncemore

clojure.algo.monads/maybe-m?

user=> (macroexpand-1 *1)(clojure.core/let [name__3075__auto__ m/maybe-m m-bind (:m-bind name__3075__auto__) m-result (:m-result name__3075__auto__) m-zero (:m-zero name__3075__auto__) m-plus (:m-plus name__3075__auto__)] (clojure.tools.macro/with-symbol-macros (m-bind 2 (fn [x] (m-bind 3 (fn [y] (m-result (* x y))))))))

Page 21: MP in Clojure

clojure.algo.monads/maybe-m

justakeyword-functionMap

user=> clojure.algo.monads/maybe-m{:m-zero nil, :m-plus #object[clojure.algo.monads$fn__3167$m_plus_maybe__3172 0x77a772cf :m-result #object[clojure.algo.monads$fn__3167$m_result_maybe__3168 0x25af85a2 :m-bind #object[clojure.algo.monads$fn__3167$m_bind_maybe__3170 0x142db7c

user=> (class clojure.algo.monads/maybe-m)clojure.lang.PersistentArrayMap

Page 22: MP in Clojure

strategyinalgo.monads

1. :m-bind,:m-resultをkey、対応する関数をvalueとしたMapを⽤意

2. マクロによってMapから取り出した関数m-bind,m-resultの組み合わせに変換

Page 23: MP in Clojure

anatomyofcatsuser=> (require '[cats.core :as m] #_=> '[cats.monad.maybe :as maybe])nil

user=> (m/mlet [x (maybe/just 2) #_=> y (maybe/just 3)] #_=> (m/return (* x y)))#<Just 6>

Page 24: MP in Clojure

macroexpand-1

cats.core/bind?withoutexplicitmonadcontext?

user=> (macroexpand-1 #_=> '(m/mlet [x (maybe/just 2) #_=> y (maybe/just 3)] #_=> (m/return (* x y))))(cats.core/bind (maybe/just 2) (clojure.core/fn [x] (cats.core/bind (maybe/just 3) (clojure.core/fn [y] (do (m/return (* x y)))))))

Page 25: MP in Clojure

cats.core/bind

cats.context/infer?caninfermonadcontext?

user=> (source cats.core/bind)(defn bind ;; (docstring here) [mv f] (let [ctx (ctx/infer mv)] (p/-mbind ctx mv (fn [v] (ctx/with-context ctx (f v))))))nil

Page 26: MP in Clojure

cats.context/infer

cats.protocols/Contextual?

user=> (source cats.context/infer)(defn infer ;; (docstring here) ;; (0-arity pattern here) ([v] (cond ; blank lines omitted (not (nil? *context*)) *context* (satisfies? p/Contextual v) (p/-get-context v) :else (throw-illegal-argument (str "No context is set and it can not be automatically " "resolved from provided value")))))nil

Page 27: MP in Clojure

cats.protocols/Contextual

-get-contextmethod

user=> (source cats.protocols/Contextual)(defprotocol Contextual "Abstraction that establishes a concrete type as a member of a context.

A great example is the Maybe monad type Just. It implements this abstraction to establish that Just is part of the Maybe monad." (-get-context [_] "Get the context associated with the type."))nil

Page 28: MP in Clojure

cats.core/bind(again)

cats.protocols/-mbind?

user=> (source cats.core/bind)(defn bind ;; (docstring here) [mv f] (let [ctx (ctx/infer mv)] (p/-mbind ctx mv (fn [v] (ctx/with-context ctx (f v))))))nil

Page 29: MP in Clojure

cats.protocols/Monad

-mreturn&-mbindmethods

user=> (source cats.protocols/Monad))(defprotocol Monad "The Monad abstraction." (-mreturn [m v]) (-mbind [m mv f]))nil

Page 30: MP in Clojure

cats.core/bind( nally)

cats.context/with-context?

user=> (source cats.core/bind)(defn bind ;; (docstring here) [mv f] (let [ctx (ctx/infer mv)] (p/-mbind ctx mv (fn [v] (ctx/with-context ctx (f v))))))nil

Page 31: MP in Clojure

cats.context/with-context

dynamicVar*context*

user=> (source cats.context/with-context) (defmacro with-context "Set current context to specific monad." [ctx & body] `(do (when-not (context? ~ctx) (throw-illegal-argument "The provided context does not implements Context.")) (binding [*context* ~ctx] ~@body)))nil

user=> (source cats.context/*context*)(def ^:dynamic *context* nil)nil

Page 32: MP in Clojure

strategyincats

1. Monadプロトコル(-mreturn,-mbind)を実装したコンテキストオブジェクトを⽤意

2. Monad値はContextualプロトコルによってコンテキストオブジェクトを取り出せる

3. マクロで関数bind,returnの組み合わせに変換4. bindは第1引数のMonad値からコンテキストオブジェクトを取り出す(コンテキストの推論)

5. with-contextで⼀度推論したコンテキストオブジェクトを動的スコープで再利⽤

Page 33: MP in Clojure

Examples

Page 34: MP in Clojure

examplecoderepositories

cf.

lagenorhynque/mp-in-clojure

lagenorhynque/mp-in-haskell

Page 35: MP in Clojure

usingmonads:

safeRPNcalculatorfromLearnYouaHaskellforGreatGood!

10.1ReversePolishNotationCalculator14.6MakingaSafeRPNCalculator

Page 36: MP in Clojure

RPN:reversePolishnotation

↓withparentheses

↓evaluate

861-*2+

((8(61-)*)2+)

42

Page 37: MP in Clojure

withoutmonads

naïveimplementation

(defn- folding-function [[x y & ys :as xs] s] (cond (and x y (= s "*")) (conj ys (* y x)) (and x y (= s "+")) (conj ys (+ y x)) (and x y (= s "-")) (conj ys (- y x)) :else (conj xs (Double/parseDouble s))))

(defn solve-rpn [s] (as-> s v (str/split v #"\s+") (reduce folding-function () v) (first v)))

Page 38: MP in Clojure

;; valid RPNuser=> (solve-rpn "8 6 1 - * 2 +")42.0;; unsupported operatoruser=> (solve-rpn "8 6 1 - * 2 /")

NumberFormatException For input string: "/" sun.misc.FloatingDecimal.readJavaFormatString (;; invalid numberuser=> (solve-rpn "8 6 1 - * a +")

NumberFormatException For input string: "a" sun.misc.FloatingDecimal.readJavaFormatString (;; invalid RPNuser=> (solve-rpn "8 6 1 - * 2")2.0

Page 39: MP in Clojure

withMaybemonad

valid⇒justx;invalid⇒nothinglift-mforliftingconjfunction

(defn- read-maybe [s] (try (maybe/just (Double/parseDouble s)) (catch NumberFormatException _ (maybe/nothing))))

(defn- folding-function' [[x y & ys :as xs] s] (cond (and x y (= s "*")) (maybe/just (conj ys (* y x))) (and x y (= s "+")) (maybe/just (conj ys (+ y x))) (and x y (= s "-")) (maybe/just (conj ys (- y x))) :else ((m/lift-m 1 #(conj xs %)) (read-maybe s))))

Page 40: MP in Clojure

reducewithmonadicfunctionusingfoldmlengthofresultsequence≠1⇒nothingMonadZerocf.MonadPlus,Alternative

(defn solve-rpn' [s] (m/mlet [result (m/foldm folding-function' () (str/split s #"\s+")) :when (= (count result) 1)] (m/return (first result))))

Page 41: MP in Clojure

;; valid RPNuser=> (solve-rpn' "8 6 1 - * 2 +")#<Just 42.0>;; unsupported operatoruser=> (solve-rpn' "8 6 1 - * 2 /")#<Nothing>;; invalid numberuser=> (solve-rpn' "8 6 1 - * a +")#<Nothing>;; invalid RPNuser=> (solve-rpn' "8 6 1 - * 2")#<Nothing>

Page 42: MP in Clojure

makingmonads:

probabilitydistributionfromLearnYouaHaskellforGreatGood!

14.8MakingMonads

Page 43: MP in Clojure

probabilitydistributione.g.6-sideddie

n p

1 1/6

2 1/6

3 1/6

4 1/6

5 1/6

6 1/6

Page 44: MP in Clojure

implementingProbmonad

definethedatatypeProb

(deftype Prob [v] p/Contextual (-get-context [_] context)

p/Extract (-extract [_] v)

p/Printable (-repr [_] (str "#<Prob " (pr-str v) ">"))

Object (equals [this obj] (= (.v this) (.v obj))))

Page 45: MP in Clojure

definethecontextobjectforProb

(def context (reify ;; (other protocol implementations here) p/Monad (-mreturn [m v] (p/-pure m v)) (-mbind [_ mv f] (assert (prob? mv) (str "Context mismatch: " (p/-repr mv) " is not allowed to use with prob context.")) (->Prob (for [[x p] (p/-extract mv) [y q] (p/-extract (f x))] [y (* p q)]))) ;; (below omitted)

Page 46: MP in Clojure

definefactory/conversionfunctions

(defn uniform [s] ; sequence (let [n (count s)] ; -> Prob value of uniform distribution (->> s (map (fn [x] [x (/ 1 n)])) ->Prob)))

(defn prob->dist [prob] ; Prob value -> Map of distribution (letfn [(add-prob [d [x p]] (update d x (fnil #(+ % p) 0)))] (reduce add-prob {} (p/-extract prob))))

Page 47: MP in Clojure

sumof2diceuser=> (def die (range 1 (inc 6)))#'user/die

user=> (def prob #_=> (m/mlet [d1 (uniform die) #_=> d2 (uniform die)] #_=> (m/return (+ d1 d2))))#'user/prob

user=> prob#<Prob ([2 1/36] [3 1/36] [4 1/36] [5 1/36] [6 1/36] [7 1/36] [3 1/36] [

user=> (prob->dist prob){7 1/6, 4 1/12, 6 5/36, 3 1/18, 12 1/36, 2 1/36, 11 1/18, 9 1/9, 5 1/9, 10 1/12, 8 5/36}

Page 48: MP in Clojure

MontyHallproblemuser=> (def doors #{:a :b :c})#'user/doors

user=> (prob->dist #_=> (m/mlet [prize (uniform doors) #_=> choice (uniform doors)] #_=> (m/return (if (= choice prize) #_=> :win #_=> :lose)))){:win 1/3, :lose 2/3}

Page 49: MP in Clojure

user=> (prob->dist #_=> (m/mlet [prize (uniform doors) #_=> choice (uniform doors) #_=> opened (uniform (disj doors prize choice)) #_=> choice' (uniform (disj doors opened choice))] #_=> (m/return (if (= choice' prize) #_=> :win #_=> :lose)))){:lose 1/3, :win 2/3}

Page 50: MP in Clojure

VivelesS-expressions!

LongliveS-expressions!

Page 52: MP in Clojure

/

第14章もうちょっとだけモナド/ForaFewMonadsMore

10.1逆ポーランド記法電卓/

14.6安全な逆ポーランド記法電卓を作ろう/MakingaSafeRPNCalculator

14.8モナドを作る/

『すごいHaskellたのしく学ぼう!』 LearnYouaHaskellforGreatGood!

ReversePolishNotationCalculator

MakingMonads

Page 53: MP in Clojure

cf.

HaskellのMonadとは⾔語内DSLのフレームワークであるFunctor,Applicative,Monadのシンプルな定式化継承によらないポリモーフィズム実現⼿法思ったほど怖くない!HaskellonJVM超⼊⾨MPinScala

MPinHaskell

FreeMonadsGettingStarted