Freeは、Functorを受け取ってMonadをつくることができるモナドです。
Freeのデータ型の定義は以下です。
data Free f a
= Pure a
| Free (f (Free f a))
よくわからない再帰的な定義がされていますね。初見で読めたらすごいと思います。
この記事では、上の定義のようなFreeがどのようにMonadをつくるかを見ていきます。
次のような順で説明していきます。
- PureScriptの予備知識と用語説明
- ListについてFunctor / Monadを実装する
-
Free f
について、同じようにFunctor / Monadを実装する
Free f
だけでなく、Freeそれ自体もモナドではあるんですが、1つ高いレイヤー(誤魔化した言い方)でのモナドなのでこの記事では触れません。
PureScriptの型システムでは表現できないですしね。
忙しい人のためのFree
この記事で説明したいことをひとことで言うと、
-
Pure
がpure
、Free
がjoin
の役割をするので、後はmap
があればMonadが実装できる
です。
PureScriptの基本と用語の説明
この記事を読む人に必要があるのかはわかりませんが、さらっとだけ1説明しておきます。
知っているという人は読み飛ばしても大丈夫です。
わからなくなったときに戻ってくる感じの読み方でもいいと思います。
PureScriptについて困ったときは公式のドキュメントとPursuitを見ましょう。
purescript/documentation: Documentation for the PureScript language, compiler, and tools.
Pursuitは本当によく使うので、"p"まで打つと出るようになってしまいました。
データ型の定義
データ型は、その型の値を生成する値コンストラクタとよばれる関数の集まりとして定義されます。
data <型名> <型引数1> <型引数2> ...
= <値コンストラクタ名1> <引数1の型> <引数2の型> ...
| <値コンストラクタ名2> <引数1の型> ...
| ...
-
|
は値コンストラクタの区切り - 型引数は0個以上
- 値コンストラクタの引数は0個以上 (引数なしの値コンストラクタ = 定数)
- 値コンストラクタは0個以上 (値コンストラクタがないデータ型 = 値を生成できないデータ型もつくれる)
data List a
= Nil
| Cons a (List a)
値コンストラクタ
Nil
やCons
を値コンストラクタとよびます。
その型の値を生成(construct)することに由来します。
また、値コンストラクタは関数です。
-- List a型の定数
Nil :: forall a. List a
-- a型の値とList a型の値を受け取り、List a型の値を生成する関数
Cons :: forall a. a -> List a -> List a
forall
については後ほど関数の定義で説明します。
余談1:値コンストラクタと関数の違い
イメージは以下のようになります。
・通常の関数
関数適用add 1 2
→ 3
定数3
→ これ以上評価できない
・値コンストラクタ
定数Nil
→ これ以上評価できない
関数適用Cons 1 Nil
→ これ以上評価できない
順に説明していきます。
add 1 2
も3
もPureScriptの式です。
また、これ以上評価できない式を値とよびます。2
よって、定数3
は値ですが、関数適用add 1 2
は値ではありません。
値コンストラクタでは少し異なります。
Nil
は定数であり、これ以上評価できません。
問題はCons
ですが、
Cons 1 Nil
は関数適用ですが、これ以上評価できません。
つまり、Cons 1 Nil
は値です。
文字通り、値コンストラクタは値を生成するためです。
また、パターンマッチは値に対して使える機能です。
case xs of
Nil -> ...
Cons x xs -> ...
case n of
0 -> ...
1 -> ...
_ -> ...
型コンストラクタ
List Int
などのList
を型コンストラクタとよびます。
型を生成(construct)することに由来します。
また、「型の型」を、**カインド(kind、種)**とよびます。
List
のように、型引数を取る型を高カインド型とよびます。
以下のような対応が考えられます。
引数なし | 引数あり | |
---|---|---|
値 | 定数 | 関数 |
型 | 型 | 高カインド型 |
PureScriptでは、型のカインドを表現する構文と値の型を表現する構文は同じなので、混同しないよう注意してください。
<値> :: <型>
<型> :: <カインド>
-- <型 or 高カインド型 (型コンストラクタ)> :: <カインド>
List :: Type -> Type
List Int :: Type
-- <値 or 関数> :: <型>
Nil :: forall a. List a
Cons :: forall a. a -> List a -> List a
※値コンストラクタと型コンストラクタの混同
値コンストラクタと型コンストラクタは、たまに混同してしまうことがあります。
例として、二分木のデータ型Tree
を考えます。
data Tree a
= Leaf a
| Tree (Tree a) (Tree a)
このとき、
Tree Int
は、型であり、このTree
は型コンストラクタです。
また、
Tree (Leaf 0) (Leaf 1)
は、値であり、このTree
は値コンストラクタです。
Tree a
などと書いてあると、これが型なのか、値なのか、文脈で判断するしかありません。
型変数a
を使った型とも、Tree a -> Tree a
という型をもつ関数とも見ることができるためです。
実際、
Tree :: Tree a -> Tree a -> Tree a
なので、
Tree a :: Tree a -> Tree a
となります。
::
より前のTree a
は関数で、後ろに現れるTree a
は型です。
ややこしい……。
型クラスの定義と実装
型クラスは、ある型が満たすべき性質を定義します。
定義
class <型クラス名> <型変数> where
<実装すべき関数名1> :: <関数1の型>
<実装すべき関数名2> :: <関数2の型>
...
- 実装すべき関数は0個以上 (0個の場合、
where
も省略する)
class Functor f where
map :: forall a b. (a -> b) -> f a -> f b
「(高カインド)型f
がFunctorになるためには、関数map
を実装する必要がある」と読めます。
型クラスは、型についても、高カインド型についても定義できます。
また、型クラスの定義には親子階層のような条件を付けられます。
class (<実装しているべき型クラス名1> <型変数>, <実装しているべき型クラス名2>, <型変数>, ...) <= <型クラス名> <型変数> where
<関数名1> :: <関数1の型>
<関数名2> :: <関数2の型>
...
- 実装しているべき型クラスは0個以上 (0個の場合、
(...) <=
は省略)
先ほど紹介した構文は、実際にはこの構文の満たすべき型クラスが0個の場合です。
class (Functor t, Foldable t) <= Traversable t where
traverse :: forall a b m. Applicative m => (a -> m b) -> t a -> m (t b)
sequence :: forall a m. Applicative m => t (m a) -> m (t a)
「(高カインド)型t
がTraversableになるためには、t
は型クラスFunctor
とFoldable
を実装している必要があり、また関数traverse
とsequence
を実装する必要がある」と読めます。
<=
は、逆向きで「ならば」と読めます。
(Functor t, Foldable t) <= Traversable t
は「t
がTraversable
ならば、t
はFunctor
であり、かつFoldable
である」と読めます。
「である」は「を実装している」の意味です。
関数の型定義の方の=>
は、後ほど関数の定義で説明します。
実装
instance <インスタンス名> :: <実装する型クラス名> <型名> where
<実装すべき関数名1> :: <関数1の型>
<関数1の実装>
...
- インスタンス名はJavaScriptに変換した際のデバッグに使われる (ので、あまり気にしなくてもいい)
- 実装すべき関数が0個の場合は
where
以下を省略 - 型名の例:
Int
、List
、List Int
、List a
instance functorList :: Functor List where
map :: forall a b. (a -> b) -> List a -> List b
map f Nil = Nil
map f (Cons x xs) = Cons (f x) (map f xs)
ある型に対する型クラスの実装を、その型クラスのインスタンスとよびます。
「Listに対するFunctorのインスタンスを実装する」と読めます。3
これを少し雑に「ListにFunctorを実装する」と言っています。
また、型クラスの定義と同様に、型クラスの実装にも条件を付けることができます。
instance <インスタンス名> :: <実装しているべき型クラス名1> <型変数n> => <実装しているべき型クラス名2> <型変数m> => ... => <型クラス名> <型名> where
<関数名1> :: <関数1の型>
...
- 型変数は型名の中に現れるもの
-
=>
部分は0個以上
instance eqList :: Eq a => Eq (List a) where
eq = eq1
直感的な説明になりますが、ListはListの中身もまた比較可能である場合にのみ、比較することができます。
eq1
は、Pursuitで見てもらうといいですが、今言ったような意味の型クラスEq1
で実装を課される関数です。
Data.Eq - purescript-prelude - Pursuit
関数の定義
<関数名> :: forall <型変数1> <型変数2> ... . <型>
<関数名> <引数名1> <引数名2> ... = <実装>
identity :: forall a. a -> a
identity x = x
「関数identity
は、任意の型a
について、a -> a
という型をもち、実装は以下となる」と読めます。
forall
は「任意の」と読めます。
また、forall
の型変数に、型クラスの制約を付けることができます。
<関数名> :: forall <型変数1> ... . <型クラス名1> <型変数n> => <型クラス名2> <かんた変数m> => <型>
<実装>
elem :: forall a f. Foldable f => Eq a => a -> f a -> Boolean
-- 実装は省略
「関数elem
は、任意の型a
とf
について、f
がFoldableを実装していて、かつa
がEqを実装しているとき、a -> f a -> Boolean
という型をもつ」と読めます。
=>
は「ならば」「のとき」と読めます。
論理学の話になりますが、上で「かつ」と読んでいるのは、「A ⇒ B ⇒ C」は「A ∧ B ⇒ C」と読み替えることができるためです。
また、引数の型が複数の値コンストラクタをもつ場合、以下のような書き方ができます。
length :: forall a. List a -> Int
length Nil = 0
length (Cons x xs) = 1 + (length xs)
Listは2つの値コンストラクタ、Nil
、Cons
をもちます。
また、値コンストラクタは値を生成するもの4なので、パターンマッチが使えます。
関数定義にもwhere
があります。5
append :: forall a. List a -> List a -> List a
append xs Nil = xs
append xs (Cons y ys) = append (snoc xs y) ys
where
snoc :: forall a. List a -> a -> List a
snoc Nil y = (Cons y Nil)
snoc (Cons x xs) y = Cons x (snoc y xs)
where
には1個以上の関数が書けます。
長い前振りが終わった
ここまでが予備知識です。
Listの例
Free f
がMonadになることを見る前に、一般的なFunctor / Monadの実装について見ていきます。
具体的な例として、ListについてFunctor / Monadを実装してみます。
Listの定義は以下とします。
data List a
= Nil
| Cons a (List a)
List as Functor
Functorの定義は以下です。
class Functor f where
map :: forall a b. (a -> b) -> f a -> f b
FunctorはType -> Type
というカインドをもつ型に対する型クラスです。
「(高カインド)型f
がFunctorになるためには、関数map
を実装する必要がある」と読めます。
map
は「関数a -> b
をFunctorの関数f a -> f b
に持ち上げる関数」と見ることができます。
Listでの実装は以下です。
instance functorList :: Functor List where
map :: forall a b. (a -> b) -> List a -> List b
map f Nil = Nil
map f (Cons x xs) = Cons (f x) (map f xs)
とくに難しいところはないと思います。
ここで意識してほしいのは、map
を実装するとき、使える関数は
-
Nil
、Cons
(Listの値コンストラクタ) -
map
(関数の再帰的な定義) - 以上の関数を使って実装された関数
だけだということです。
List as Applicative Functor
PureScriptでは、Applicative Functorはさらに細かくわけられます。
Applicative m => Apply m => Functor m
「Applicative FunctorであるならばApply」、「ApplyであるならばFunctor」と読めます。
Apply → Applicativeの順で実装していきます。
Applyの定義は以下です。
class (Functor m) <= Apply m where
apply :: forall a b. m (a -> b) -> m a -> m b
「(高カインド)型m
がApplyになるためには、m
はFunctorである必要があり、また関数apply
を実装する必要がある」と読めます。
apply
は「Applyで包んだ関数を、Applyで包んだ値に適用する関数」と見ることができます。
Listでの実装は以下です。
instance applyList :: Apply List where
apply :: forall a b. List (a -> b) -> List a -> List b
apply fs xs = concat (map (flip map xs) fs)
この実装は少し難しいです。
map
と違い簡単に思いつきそうにはありません。
というわけで、型パズル6の時間です。
型を見ることで、apply
を実装する過程を見ていきます。
apply
の実装に使えるのは、Functorの実装で触れた通り、
-
Nil
、Cons
apply
-
map
(ListはすでにFunctorを実装しているため) - 以上の関数を使って実装された関数
です。
上記の関数を使ってapply :: forall a b. List (a -> b) -> List a -> List b
を実装していきます。
それではレッツ型パズル!
-- Goal
apply :: List (a -> b) -> List a -> List b
apply fs xs = ...
-- 実装に使える関数
Nil :: List a
Cons :: a -> List a -> List a
map :: (a -> b) -> List a -> List b
apply :: List (a -> b) -> List a -> List b
-- applyの引数
fs :: List (a -> b)
xs :: List a
map :: (a -> b) -> List a -> List b
flip map :: List a -> (a -> b) -> List b
flip map xs :: (a -> b) -> List b
map (flip map xs) :: List (a -> b) -> List (List b)
map (flip map xs) fs :: List (List b)
※簡単のためforall
を省略しています。ただし、apply
の引数であるfs
とxs
の型変数を読み換える際には注意が必要です。
※flip
は関数の引数を入れ替える関数です。いきなり例外ですが、これはFunction型で実装された関数なので使えます。なくてもラムダ抽象を使えば同じ実装は可能です。
※map :: (a -> b) -> List a -> List b
が「関数a -> b
を関数List a -> List b
に持ち上げる関数」であったことを思い出すとわかりやすいです。
ここで、List (List b) -> List b
という型をもつ関数が欲しくなります。
ListのListをListにする関数なので、「中身のListをすべてつなげて1つのListにする関数」と考えられます。
この関数をconcat
とします。
concat :: forall b. List (List b) -> List b
concat Nil = Nil
concat (Cons xs xss) = append xs (concat xss)
append
はList a -> List a -> List a
という型をもつ、2つのListをつなげる関数です。
関数の定義の例として実装したので、わからない人は参照してください。
このconcat
を使って、apply
を組み立てます。
map (flip map xs) fs :: List (List b)
concat :: List (List b) -> List b
concat (map (flip map xs) fs) :: List b
-- Goal
apply :: List (a -> b) -> List a -> List b
apply fs xs = concat (map (flip map xs) fs)
これでapply
が実装できました。
型パズルすごい。
Applyが実装できたので、Applicativeを実装します。
class (Apply m) <= Applicative m where
pure :: forall a. a -> m a
instance applicativeList :: Applicative List where
pure :: forall a. a -> List a
pure x = Cons x Nil
pure
は値を1つ取って、Applicative Functorで包む関数です。
List as Monad
PureScriptでは、Monadはさらに細かくわけられます。
Monad m => (Applicative m, Bind m)
Bind m => Apply m
「MonadであるならばApplicativeかつBind」、「BindであるならばApply」と読めます。
Bindの定義は以下です。
class (Apply m) <= Bind m where
bind :: forall a b. m a -> (a -> m b) -> m b
「(高カインド)型mがBindになるためには、m
はApplicativeかつBindである必要があり、また関数bind
を実装する必要がある」と読めます。
Listでの実装は以下です。
instance bindList :: Bind List where
bind :: forall a b. List a -> (a -> List b) -> List b
bind xs f = concat (map f xs)
型を見ることで、bind
の実装を確かめてみます。
concat :: List (List b) -> List b
map :: (a -> b) -> List a -> List b
xs :: List a
f :: a -> List b
map :: (a -> b) -> List a -> List b
-- bをList bで読み替える
map :: (a -> List b) -> List a -> List (List b)
map f :: List a -> List (List b)
map f xs :: List (List b)
concat (map f xs) :: List b
bind :: List a -> (a -> List b) -> List b
bind xs f = concat (map f xs)
大丈夫そうです。
Bindが実装できたので、Monadを実装します。
class (Applicative m, Bind m) <= Monad m
instance monadList :: Monad List
Monadは、ApplicativeとBindが実装されていれば他に実装するべき関数はありません。
class
構文のwhere
以下は省略されます。
instance
構文でも同様です。
ここまででListにMonadを実装できました。
Freeモナド
前置きが長くなりましたが、本題のFreeです。
FreeはFunctorを受け取り、Free f
のような形でMonadになります。
Freeの定義は以下のような形をしています。
data Free f a
= Pure a
| Free (f (Free f a))
Freeの型には、f
への制約がありません。
代わりに、型クラスの実装時にf
がFunctorであることを課します。
よって、f
がFunctorのときのみFree f
はMonadになり、それ以外のときはMonadにはなりません。
それでは、Free f
についてFunctor / Monadを実装していきます。
Free as Functor
Functorの定義は以下です。
class Functor f where
map :: forall a b. (a -> b) -> f a -> f b
Free f
での実装は以下です。
instance functorFree :: Functor f => Functor (Free f) where
map f (Pure x) = Pure (f x)
map f (Free x) = Free (map (map f) x)
map
の実装は簡単には読めないと思います。
この実装がどこから出てきたのかというと、型パズルでひねり出しました。
まず、Freeには値コンストラクタが2つあったことを思い出します。
Pure
とFree
です。
それぞれ場合分けして考えます。
Pure x
のとき
-- Goal
map f (Pure x) :: (a -> b) -> (Free f) a -> (Free f) b
map f (Pure x) = ...
-- 使える関数
Pure :: a -> Free f a
Free :: f (Free f a) -> Free f a
map :: (a -> b) -> f a -> f b
-- 引数の型
f :: a -> b
Pure x :: Free f a
※わかりやすいように(Free f) a
としていますが、Free f a
でも大丈夫です。
ここで、Pure :: a -> Free f a
であることから、Pure x
のx
の型がa
であることがわかります。
-- 使える関数
Pure :: a -> Free f a
-- mapの引数
f :: a -> b
x :: a
f x :: b
Pure (f x) :: Free f b
-- Goal
map :: (a -> b) -> Free f a -> Free f b
map f (Pure x) = Pure (f x)
こちらはあまり難しくないと思います。
問題は次です。
Free x
のとき
-- Goal
map f (Free x) :: (a -> b) -> Free f a -> Free f b
map f (Free x) = ...
-- 使える関数
Pure :: a -> Free f a
Free :: f (Free f a) -> Free f a
map :: (a -> b) -> Free f a -> Free f b
map :: (a -> b) -> f a -> f b
-- 引数の型
f :: a -> b
Free x :: Free f a
※map
はFree f
に対するものと、f
に対するものがあります。Free f
のf
がFunctorでなければならない理由がここにあります。
また、Pure x
のときと同様に、Free :: f (Free f a) -> Free a
より、引数のFree x
のx
の型がf (Free f a)
であることがわかります。
-- 使える関数
Pure :: a -> Free a
Free :: f (Free f a) -> Free f a
map :: (a -> b) -> Free f a -> Free f b
map :: (a -> b) -> f a -> f b
-- 引数の型
f :: a -> b
x :: f (Free f a)
-- fに対するmapを使う
map :: (a -> b) -> f a -> f b
flip map :: f a -> (a -> b) -> f b
-- aをFree f aに読み替える
flip map :: f (Free f a) -> (Free f a -> b) -> f b
flip map x :: (Free f a -> b) -> f (Free f a)
-- Free f a -> bがほしい
-- Free fに対するmapを使う
map :: (a -> b) -> Free f a -> Free f b
map f :: Free f a -> Free f b
flip map x :: (Free f a -> b) -> f (Free f a)
-- bをFree f bで読み替える
flip map x :: (Free f a -> Free f b) -> f (Free f a)
flip map x (map f) :: f (Free f a)
-- flipを外す
map (map f) x :: f (Free f a)
Free (map (map f) x) :: Free f a
-- Goal
map :: (a -> b) -> Free f a -> Free f b
map f (Free x) = Free (map (map f) x)
これでmap
が実装できました。
この型パズルを解くのは結構楽しいので、以降のApplicative FunctorとMonadの型パズルは答えを見る前にやってみると面白いかもしれません。
ただ「できた!」と思ったらmap f (Free x) = map f (Free x)
になっていた、みたいなことが数回あったのでみなさんはそうはならないよう気を付けてください。
Free as Applicative Functor
PureScriptでは
Applicative m => Apply m => Functor m
となっているので、Applyから実装していきます。
Applyの定義は以下です。
class (Functor m) => Apply m where
apply :: forall a b. m (a -> b) -> m a -> m b
Free f
での実装は以下です。
instance applyFree :: Functor f => Apply (Free f) where
apply :: Free f (a -> b) -> Free f a -> Free f b
apply (Pure f) (Pure x) = Pure (f x)
apply (Free f) (Pure x) = Free (map (flip apply (Pure x)) f)
apply f (Free x) = Free (map (apply f) x)
map
のときとは異なり、apply
の引数では関数もFree f
で包まれています。
よって、値コンストラクタでの場合分けが4パターンになります。
ただし、上の実装の通り実際には3パターンに潰せます。
型パズルは省略します。
実装に疑いのある人は各自で確認してみてください。
Applyが実装できたので、Applicativeを実装します。
class (Apply m) => Applicative m where
pure :: forall a. a -> m a
instance applicativeFree :: Functor f => Applicative (Free f) where
pure = Pure
pure
の実装は値コンストラクタそのままです。
ただし、「pure = Pure
となっている」ということはとても重要な事実になります。
直感的な説明で後ほど説明します。
Free as Monad
PureScriptでは
Monad m => (Applicative m, Bind m)
Bind m => Apply m
となっているので、Bindから実装していきます。
Bindの定義は以下です。
class (Apply m) => Bind m where
bind :: forall a b. m a -> (a -> m b) -> m b
Free f
での実装は以下です。
instance bindFree :: Functor f => Bind (Free f) where
bind (Pure x) f = f x
bind (Free x) f = Free (map (flip bind f) x)
納得できない人は同様に型を確かめてみてください。
ここまででf
がFunctorのとき、Free f
にMonadを実装できました。
FreeがFunctorからMonadをつくるとは、こういうことになります。
直感的な説明
「Monadを実装できたので、Monadです」では納得できないかもしれないので、なぜMonadを実装できたかの直感的な説明をしておきます。
まずMonadの定義について話します。
Functor / Applicative Functor / Monadの定義から、
map :: (a -> b) -> m a -> m b
pure :: a -> m a
bind :: m a -> (a -> m b) -> m b
の3つを実装すれば、Monadになることがわかります。
apply
は必ずしも必要ではありません。
実際、pure
とbind
があればapply
は実装できます。
apply :: m (a -> b) -> m a -> m b
apply f x = bind f (\f' ->
bind x (\x' -> pure (f' x'))
)
do記法を使うと以下のように書けます。
do記法はbind
のシンタックスシュガーです。
apply f x = do
f' <- f
x' <- x
pure (f' x')
閑話休題。
実は、Monadには別の定義もあります。
map :: (a -> b) -> m a -> m b
pure :: a- > m a
join :: m (m a) -> m a
bind
の代わりにjoin
という関数を使います。
この定義がbind
を使ったものと同じかどうかは、bind
を実装してみればわかります。
bind :: m a -> (a -> m b) -> m b
bind x f = join (map f x)
List as Monadで見たように、concat :: List (List a) -> List a
はListでのjoin
になります。
ここで、Free
の定義をもう一度見てみます。
data Free f a
= Pure a
| Free (f (Free f a))
値コンストラクタの型は以下のようになります。
Pure :: a -> Free f a
Free :: f (Free f a) -> Free f a
先ほどのMonadの別定義をもう一度見てみます。
map :: (a -> b) -> m a -> m b
pure :: a -> m a
join :: m (m a) -> m b
すると、Pure
がpure
に、Free
がjoin
に対応していることがわかります。
FunctorやMonadを実装する際には、そのデータ型の値コンストラクタを使えるのでした。
つまり、Freeはその値コンストラクタの形により、Monadを実装することができるのです。
あとMonadを実装するのに足りないのはmap
だけです。
ここで、f
のmap
があると、Free f
のmap
も実装できたことを思い出します。
これがFree f
の**f
がFunctorでなければならない理由**となります。
Freeの定義のあの不思議な形には、実はこういった理由があったわけです。
余談2:Functor則 / Monad則
Functorの定義は
class Functor f where
map :: forall a b. (a -> b) -> f a -> f b
でした。
しかし、この型クラスを実装するだけでは、本当の意味でのFunctorにはなりません。
本当の意味、とはどういうことでしょうか。
そもそも、Functorは圏論において数学的に定義されたものです。
それをPureScriptの型システム上で表現したものがFunctor型クラスです。
ただし、FunctorにはFunctor則とよばれる、以下の条件があります。
- 恒等射(
identity
)の保存:map identity = identity
- (関数)合成の保存: map (f <<< g) = map f <<< map g
f <<< g
は関数合成\x -> f (g x)
です。
これらの条件を満たして初めて、本当の意味でFunctorになります。
PursuitにはFunctor則が書いてあります。Pursuit優秀すごい。
Data.Functor - purescript-prelude - Pursuit
PureScriptの型システムにはそこまでの表現力はないので、map identity = identity
のような条件は保証できません。
よって、FunctorではないFunctor、MonadではないMonadなどが実装できてしまいます。
つまり、コンパイラでは保証できないので、実装を行う人間が保証するしかないということです。7
同様に、MonadにもMonad則があります。
Control.Monad - purescript-prelude - Pursuit
FunctorやMonadを実装する際には注意する必要があります。
-
の、予定だったんですが詳しく書きすぎてPureScript入門みたいになってしまいました……。 ↩
-
なんらかの意味での正規形 (Normal form) を値とよんでいます。用語が合っているのか自信がありません。 ↩
-
これも用語が正確に合っているのか自信がありません。 ↩
-
余談1:値コンストラクタと関数の違いで説明しています。 ↩
-
where
は英語的な表現で、後出しで説明をするときに使います。「AはBである。ここで、BとはCである」みたいなとき、“A is B where B is C.”と書きます。 ↩ -
本当は型を合わせるだけでは十分でないのですが、今回は考えないことにします。余談2:Functor則 / Monad則で説明しています。 ↩
-
C言語で「お前は配列!」と言いながらポインタを扱うのに似ていますね (暴言)。 ↩