Upload
kent-ohashi
View
2.989
Download
0
Embed Size (px)
Citation preview
MPinClojure~herdingcatswithclj~
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])
Clojure×MP
Contents1. WhatisMP?
2. WhyMPinClojure?
3. HowMPinClojure?
4. Examples
WhatisMP?
programmingwithmonads
cf.FP=functionalprogramming
MP=monadicprogramming
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
monadsinHaskell
型クラスMonadのインスタンス>>=:m a -> (a -> m b) -> m breturn:a -> m aモナド則( )を満たすシンプルで合成可能な構造→様々な形で利⽤可能do記法というシンタックスシュガー
monadlaws
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]
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)
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
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>
WhyMPinClojure?
MPinClojure
Clojureではモナドは必須の機能ではないが……静的型付け&純粋関数型の⾔語ではないが……シンプルで汎⽤的なDSL構築フレームワークHaskellなどで有⽤なアイディアが活かせる⇒ClojureでもMPしてみよう(*> ᴗ •*)ゞ
HowMPinClojure?
MPlibrariesinClojureclojure/algo.monads
Macrosfordefiningmonads,anddefinitionofthemostcommon
monads
funcool/cats
CategoryTheoryandAlgebraicabstractionsforClojureand
ClojureScript.
howtosupportMPinClojurefeature algo.monads cats
Monadtypeclass plainMapdata protocol
donotation macro macro
contextinference (n/a) protocol
anatomyofalgo.monadsuser=> (require '[clojure.algo.monads :as m])nil
user=> (m/domonad m/maybe-m #_=> [x 2 #_=> y 3] #_=> (* x y))6
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)))))))
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))))))))
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
strategyinalgo.monads
1. :m-bind,:m-resultをkey、対応する関数をvalueとしたMapを⽤意
2. マクロによってMapから取り出した関数m-bind,m-resultの組み合わせに変換
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>
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)))))))
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
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
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
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
cats.protocols/Monad
-mreturn&-mbindmethods
user=> (source cats.protocols/Monad))(defprotocol Monad "The Monad abstraction." (-mreturn [m v]) (-mbind [m mv f]))nil
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
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
strategyincats
1. Monadプロトコル(-mreturn,-mbind)を実装したコンテキストオブジェクトを⽤意
2. Monad値はContextualプロトコルによってコンテキストオブジェクトを取り出せる
3. マクロで関数bind,returnの組み合わせに変換4. bindは第1引数のMonad値からコンテキストオブジェクトを取り出す(コンテキストの推論)
5. with-contextで⼀度推論したコンテキストオブジェクトを動的スコープで再利⽤
Examples
examplecoderepositories
cf.
lagenorhynque/mp-in-clojure
lagenorhynque/mp-in-haskell
usingmonads:
safeRPNcalculatorfromLearnYouaHaskellforGreatGood!
10.1ReversePolishNotationCalculator14.6MakingaSafeRPNCalculator
RPN:reversePolishnotation
↓withparentheses
↓evaluate
861-*2+
((8(61-)*)2+)
42
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)))
;; 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
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))))
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))))
;; 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>
makingmonads:
probabilitydistributionfromLearnYouaHaskellforGreatGood!
14.8MakingMonads
probabilitydistributione.g.6-sideddie
n p
1 1/6
2 1/6
3 1/6
4 1/6
5 1/6
6 1/6
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))))
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)
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))))
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}
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}
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}
VivelesS-expressions!
LongliveS-expressions!
FurtherReading
/
/
clojure/algo.monads
khinsen/monads-in-clojure
funcool/cats
独習Scalaz learningScalaz
猫番 herdingcats
モナド(プログラミング)-Wikipedia
/
第14章もうちょっとだけモナド/ForaFewMonadsMore
10.1逆ポーランド記法電卓/
14.6安全な逆ポーランド記法電卓を作ろう/MakingaSafeRPNCalculator
14.8モナドを作る/
『すごいHaskellたのしく学ぼう!』 LearnYouaHaskellforGreatGood!
ReversePolishNotationCalculator
MakingMonads
cf.
HaskellのMonadとは⾔語内DSLのフレームワークであるFunctor,Applicative,Monadのシンプルな定式化継承によらないポリモーフィズム実現⼿法思ったほど怖くない!HaskellonJVM超⼊⾨MPinScala
MPinHaskell
FreeMonadsGettingStarted