はじめに
ちょいと Haskell やってみる。むずい。
私は分析は得意ではないが、把握は得意な方です。
そう、私はジェネラリストです。
モナドとは?
Haskell 対話環境が教えてくれる。
$ ghci
GHCi, version 8.0.2: http://www.haskell.org/ghc/ :? for help
Prelude> :info Monad
class Applicative m => Monad (m :: * -> *) where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
{-# MINIMAL (>>=) #-}
-- Defined in ‘GHC.Base’
instance Monad (Either e) -- Defined in ‘Data.Either’
instance Monad [] -- Defined in ‘GHC.Base’
instance Monad Maybe -- Defined in ‘GHC.Base’
instance Monad IO -- Defined in ‘GHC.Base’
instance Monad ((->) r) -- Defined in ‘GHC.Base’
instance Monoid a => Monad ((,) a) -- Defined in ‘GHC.Base’
わからん。が、
class Applicative m => Monad (m :: * -> *)
となっていて、
意味は「m
が Monad
であるならば、m
は Applicative
でなければならない」と。
アプリカティブとは?
Prelude> :info Applicative
class Functor f => Applicative (f :: * -> *) where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
{-# MINIMAL pure, (<*>) #-}
-- Defined in ‘GHC.Base’
instance Applicative (Either e) -- Defined in ‘Data.Either’
instance Applicative [] -- Defined in ‘GHC.Base’
instance Applicative Maybe -- Defined in ‘GHC.Base’
instance Applicative IO -- Defined in ‘GHC.Base’
instance Applicative ((->) a) -- Defined in ‘GHC.Base’
instance Monoid a => Applicative ((,) a) -- Defined in ‘GHC.Base’
やはりだが「Applicative
であるためには Functor
でなければならない」と。
ファンクターとは?
Prelude> :info Functor
class Functor (f :: * -> *) where
fmap :: (a -> b) -> f a -> f b
(<$) :: a -> f b -> f a
{-# MINIMAL fmap #-}
-- Defined in ‘GHC.Base’
instance Functor (Either a) -- Defined in ‘Data.Either’
instance Functor [] -- Defined in ‘GHC.Base’
instance Functor Maybe -- Defined in ‘GHC.Base’
instance Functor IO -- Defined in ‘GHC.Base’
instance Functor ((->) r) -- Defined in ‘GHC.Base’
instance Functor ((,) a) -- Defined in ‘GHC.Base’
制約の連鎖が止まった。
再びモナドとは?
ファンクターの拡張版をアプリカティブという。
アプリカティブの拡張版をモナドという。
再びファンクターとは?
-- '引数 -> 返り値'(ラムダ抽象)
class Functor (f :: * -> *) where -- '*' は具体型を表す
fmap :: (a -> b) -> f a -> f b -- 'a' 'b' は任意型を表す
f
がファンクターであるためには fmap
という関数を持っていなければならない。
「オレが世界征服を果たすためには fmap
貫光殺砲を体得しなければならない」(?
map
と聞けば反射的にリストを思い浮かべる。
> :type map
map :: (a -> b) -> [a] -> [b]
-- :: (a -> b) -> [] a -> [] b と等価
-- '[]' はリスト生成前置関数
fmap
と map
を見比べると f = []
だなと。
リストはファンクターなのかもしれないと。
Prelude> :info Functor
class Functor (f :: * -> *) where
fmap :: (a -> b) -> f a -> f b
(<$) :: a -> f b -> f a
{-# MINIMAL fmap #-}
-- Defined in ‘GHC.Base’
instance Functor (Either a) -- Defined in ‘Data.Either’
instance Functor [] -- Defined in ‘GHC.Base’ <== ここだよー くさかりまさおー
instance Functor Maybe -- Defined in ‘GHC.Base’
instance Functor IO -- Defined in ‘GHC.Base’
instance Functor ((->) r) -- Defined in ‘GHC.Base’
instance Functor ((,) a) -- Defined in ‘GHC.Base’
GHC.Base を覗くと、
instance Functor [] where
{-# INLINE fmap #-}
fmap = map
まんまだ。
リスト型 []
は、fmap
を満たす map
を持っているのでファンクターである。
ピッコロ「孫、手をぬきやがって、オレはまじめに修行して新開発したってのによ」
-- ピッコロ型と型生成関数の宣言
-- 'deriving (Show)' で「うまいこと文字列で表現してくれ」と頼んでいる
data Piccolo a = Piccolo a deriving (Show)
-- ピッコロ型はファンクターである
instance Functor Piccolo where
-- fmap 実装('g' は任意関数を表す)
fmap g (Piccolo x) = Piccolo (g x)
-- 必殺技のアルゴリズム(値を適当倍する)
cannon :: Float -> Float
cannon = (* 4.131)
ピッコロ「fmap
貫光殺砲うけてみろ!」
> fmap cannon (Piccolo 322)
Piccolo 1330.182
ラディッツ「くそ!こいつら戦闘力を自在に操りやがる!!」
関数型言語を知らない人も昨今は map
の有り難みを知っている。
「配列とかリストとかの要素に、一気に処理をかけるやつだよね」
そう、ファンクターとは、それを更に抽象化したものだ。
配列とかリストやらの並びに制限されない map
、
すなわち fmap
に適用できるものだ。
この「○○に制限されない」の○○を「文脈(コンテキスト)」と言う。
上記に示した :info Functor
の instance
は文脈をも表している。
Either
は「どちらか」を、
[]
は「並び」を、
Maybe
は「無いか有るか」を、
IO
は「入出力」を、
(->)
は「関数」を、
(,)
は「組」を。
圏論の Functor
ではなく、
Mappable
と命名した方が分かりやすいのでは?という意見があるらしい。
再びアプリカティブとは?
ナッパ「あいつナメック星人だぜ」
ピッコロ「オレはナメック星人だったのか・・・」
data Namekian a = Namekian a deriving (Show)
instance Functor Namekian where
fmap g (Namekian x) = Namekian (g x)
cannon :: Float -> Float
cannon = (* 4.131)
ネイル「わ・・・わたしと同化しろ・・・」
ピッコロ「なに!?きさまと!?」
class Functor f => Applicative (f :: * -> *) where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b -- '<*>' は2引数の中置関数を表す
instance Applicative Namekian where
pure = Namekian
Namekian g <*> namekian = fmap g namekian
-- 同化のアルゴリズム(てきとー)
assimilate :: Float -> Float -> Float
assimilate m n = m * n / 1000
ネイル「お・・・おまえのその力が数倍にもなる・・・」
> piccolo = Namekian 41310
> nail = Namekian 42000
> newPiccolo = pure assimilate <*> piccolo <*> nail
> newPiccolo
Namekian 1735020.0
> fmap cannon newPiccolo
Namekian 7167367.5
ピッコロ「こっ これが同化というやつなのか・・・!!」
ファンクターでは無理だった2引数関数を扱えるようになった。
それだけではない。一気に神様とだって融合できるのだ。
assimilate3 :: Float -> Float -> Float -> Float
assimilate3 x y z = x * y * z / 1000
> kami = Namekian 220
> pure assimilate3 <*> piccolo <*> nail <*> kami
Namekian 3.8170442e8 -- もうよくわかんね
アプリカティブとは、関数のアリティをも制限しないファンクターなのである。
そういうことから Applicative
ではなく、
Zippable
の方が分かりやすいのでは?という意見があるらしい。
だが、これで終わっては次段階のモナドを見誤る。
Haskell の関数に sequenceA
というものがある。
実際の定義よりもシンプルに下記に示す。
sequenceA :: Applicative f => [f a] -> f [a]
-- 空リストならば、そのまま文脈に包む
sequenceA [] = pure []
-- リストの要素を、文脈に包まれたリストに再帰的に詰め込む
-- ':' は2引数の中置関数(コンシング/詰め込みを表す)
sequenceA (x:xs) = pure (:) <*> x <*> sequenceA xs -- 再帰定義
ピッコロはよく自らを増殖(分裂)して戦ったものだ。
> piccolo = Namekian 322
> replicate 3 piccolo
[Namekian 322,Namekian 322,Namekian 322]
> sequenceA (replicate 3 piccolo)
Namekian [322,322,322]
replicate
は単に値を複製しているが、
sequenceA
はそこから文脈を取り出し外側に置く。
言い換えれば、文脈(コンテキスト)を伴った反復処理を実現している。
つまりモナドとは?!
「プログラミングの基礎」として出てくる言葉「逐次、分岐、反復」。
乱暴に言えば、この3つの機能を備えたものはチューリング完全となる。
アプリカティブまでによって逐次・反復は実現できた。
(逐次:Applicative
、反復:Functor
)
その拡張版がモナドなのである。
何を求め拡張するのか?
残された「分岐」である。
そう、モナドはチューリング完全を実現できるのである。
この点に関して IO
がどうとか、do
がどうとかの話は必要がない。
プログラマの立場からしたら、まずはこの話だけで十分ではないか?
○○モナドはこんなに便利なんだと先に聞かされても気持ちが悪い。
真っ先に知りたいのは「モナドが何者であるか」だ。
Monad
の別名も提案があがっている。
Chainable
, Joinable
, FlatMappable
モナドの定義など見なくとも納得がいく。
連鎖できるのだ。
繋げられるのだ。
平坦に写像できるのだ。
そう、モナドならね。
なにを?
文脈を伴った計算を。
なぜ?
チューリング完全だから。
モナドはチューリング完全な Haskell の内部 DSL だ。
しかも(やはり)それはファースト・クラスなのである。
おお、すごい。
という理解でよいのでしょうか?