Skip to content

Instantly share code, notes, and snippets.

@kohyama
Last active January 28, 2024 11:51
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kohyama/5856037 to your computer and use it in GitHub Desktop.
Save kohyama/5856037 to your computer and use it in GitHub Desktop.
ファンクタ, アプリカティブ, モナド

ファンクタ, アプリカティブ, モナド

はじめに

「そもそも概念が分からない」という方に向けた説明です.
簡略化のため大幅に説明を省略しています. ご容赦ください.
誤りは御指摘いただければ幸いです.

「ファンクタ」, 「アプリカティブ」, 「モナド」 などは Haskell に限定された概念・用語ではありませんが, 例は Haskell で書きます.

例にある動作を GHC(I) で確認される場合は

import Control.Applicative
import Data.Char

が必要です.

ファンクタ

Haskell の Functor クラスの定義には

class Functor f where
fmap::(a -> b) -> f a -> f b

と書いてあります.

任意の型 a, と別の任意の型 b について,
「型 a の値を引数にとり, 型 b の値を返す関数」と
「型 a の値による『何か』」を引数にとり
「型 b の値による『何か』」を返す関数
fmap が定義されていれば, その『何か』はファンクタです.

リストや Maybe はファンクタ

fmap length              ["f", "ba", "baz"] -- [1, 2, 3]    (*1)
fmap (digitToInt . head) ["f", "ba", "baz"] -- [15, 11, 11] (*2)
fmap length              (Just "foo")       -- Just 3       (*3)
fmap length              Nothing            -- Nothing

(*1) length は文字列を引数に与えると文字列の長さを返します.

length "foo" -- 3

(*2) (digitToInt . head) は文字列を与えると最初の文字を数字として解釈して数値を返します.

(digitToInt . head) "foo" -- 15

16進の 'f' は 15 だから.

(*3) Maybe a という型は, Just (型 a の値) かまたは Nothing をとるような型です.
Just 3, Just 4, Nothing などは Maybe Int 型の値です.

アプリカティブ

class Applicative f where
(<*>):: f (a -> b) -> f a -> f b

任意の型 a, と別の任意の型 b について,
「[型 a の値を引数にとり, 型 b の値を返す関数]による『何か』」と
「型 a の値による『何か』」を引数にとり
「型 b の値による『何か』」を返す関数
(<*>) が定義されていれば, その『何か』はアプリカティブです.

リストや Maybe はアプリカティブ

(<*>) [count, digitToInt . head] ["f", "ba", "baz"] -- [1,2,3,15,11,11]
(<*>) (Just (\x -> x * x))       (Just 3)           -- Just 9   (*4)
(<*>) Nothing                    (Just 3)           -- Nothing
(<*>) (Just (\x -> x * x))       Nothing            -- Nothing

(*4) (\x -> x * x) は数値を与えるとその二乗を返します.

(\x -> x * x) 3 -- 9

モナド

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

任意の型 a, と別の任意の型 b について,
「型 a の値による『何か』」と
「型 a の値を引数にとり, 型 b の値による『何か』を返す関数」を引数にとり
「型 b の値による『何か』」を返す関数
(>>=) が定義されていれば, その『何か』はモナドです.

(注意: かなり説明を省略しています.)

リストや Maybe はモナド

(>>=) ["f", "ba", "baz"] id                   -- ['f' 'b' 'a' 'b' 'a' 'z'] (*5)
(>>=) (Just 3)           (\x -> Just (x * x)) -- Just 9                    (*6)

(*5) Haskell では文字列は文字のリストでもあるので, それそのものを返す id 関数は 文字列を引数に取って文字のリストを返す関数とみなせます.
説明のために結果を ['f' 'b' 'a' 'b' 'a' 'z'] のように文字のリスト で書きましたが, インタプリタで表示された場合は "fbabaz" のように表示 されるかもしれません.

(*6) (\x -> Just (x * x)) は, 数値を与えると, その二乗を Maybe 数値型に して返します.

(\x -> Just (x * x)) 3 -- Just 9

で, 何が嬉しいの?

ファンクタもアプリカティブもモナドも, その定義そのものは, 具体的に何かをするものではありません.
リストや Maybe しか例にあげませんでしたが, 沢山役に立つものを定義していくと, それらは, 上述それぞれの『何か』として機能するという意味において, 同等の機能を提供するものであるということが分かって来たので, その『何か』に名前を付けた訳です.

同等である『何か』に名前を付けて認識するのはとても重要です.
それが「抽象化」です.

例えば, 新しく『何か』を作ろうとしたときに, それがモナドであると気づけば, モナドとして捉えた場合に同じ機能である関数には同じ名前を使ったり, モナドであるためにはどんな法則を満たしていれば良い性質を保てるか といった過去に他のモナドが作られたときの経験が活かせたり, ある程度自動でそいうったチェックを行ったり, 自明なところは自動で導出したり, といったことが可能になります.

ただし, 主にこの利点を享受できるのは, 新しく『何か』を作ろうとするときです. 『何か』を使うときに役に立つのは, あくまでその『何か』そのものであり, その『何か』があるグループ(例えばモナド)の一員であるという事実ではありません.

従って, 少なくとも初学段階では『何か』がモナドに属するということや, モナドとは何であるかにあまり拘らず, その『何か』の使い方を学べば 良いと思います.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment