Control.Arrow
パイプ感覚のプログラミング
2014/8 haruharu
1
パイプとは
• 前の命令の結果を次の命令に渡す機能。
sh-3.1$ ls
MSYS.lnk c_advanced.jar eclipse workspace
c_advanced cmd.bat pleiades-e4.3-cpp-32bit-jre_20130626
sh-3.1$ ls | sort | cat -n
1 MSYS.lnk
2 c_advanced
3 c_advanced.jar
4 cmd.bat
5 eclipse
6 pleiades-e4.3-cpp-32bit-jre_20130626
7 workspace
2
パイプのイメージ
f g
x f(x) g(f(x))
• 関数の合成に似ている
• よく次のような絵で説明される
3
パイプのイメージ
• 変換前後の「型」に着目しよう。
• Unix/Linuxの命令の多くは、「文字列型」を受け取っ
て「文字列型」を返す関数と捉えられる。
ls sort
void型 String型 String型
cat -n
String型
4
数学の関数との違い
• 型は数学の関数では集合と捉えると都合良い。
• 数学の関数は、行き先の集合を複数にしたり、複数
の集合をまとめて1つの関数で扱ったりできる。
A
B’ C’
D
B Cf
f’
g
h
A → B×B’ → C×C’ → D
x → (f(x),f’(x))→ (g(f(x)),g’(f’(x))) → h(g(f(x)),g’(f’(x)))
∋
∋
∋
∋
g’
参考:数式だとこんな感じ
5
Arrow
• 数学の関数のイメージでプログラミング出来るように
用意された文法としてHaskellのArrowがある。
• 関数を繋げるための演算子が用意されている。
f >>> g ・・・ (f >>> g) x = g(f(x))
f &&& g ・・・ (f &&& g) x = (f(x),g(x))
first f ・・・ (first f) (x,y) = (f(x), y)
second g ・・・ (second g) (x,y) = (x, g(y))
f *** g ・・・ (f *** g) (x,y) = (f(x), g(y))
f ||| g ・・・ (f ||| g) (Left x) = f(x)
(f ||| g) (Right x) = g(x)
など
6
例
• 受け取った整数(Int型)を、4で割り切れたらBool型の
Trueを、割りきれなかったら受け取った整数そのも
のを返す関数 isModBy4
• 複雑ではあるが所詮関数の合成なので、
分解して読んでいけばいい。
• 逆に、書くときもどういう関数を組み合わせるかを
考えればよい。
isModBy4 = (id &&& flip mod 4) >>> first (repeat . Right)
>>> first ((:) $ Left True) >>> uncurry (!!)
7
例
isModBy4 = (id &&& flip mod 4) >>> first (repeat . Right)
>>> first ((:) $ Left True) >>> uncurry (!!)
8
•Intから(Int,Int)への関数。
•idは恒等関数といい、何もしない。id(x) = x
•flipは引数を逆転する関数。flip f x y = f y x
•modは割った余りを求める関数。mod x y = xをyで割った余り
•従って(flip mod 4) xはxを4で割った余りを求める関数。
※Haskellではカッコが多くなるのを防ぐために、
f(x)を f x と書いたり、
g(f(x))を g $ f x や g . f $ x と書いたりします。
(id &&& flip mod 4)1999 (1999, 3)
例
isModBy4 = (id &&& flip mod 4) >>> first (repeat . Right)
>>> first ((:) $ Left True) >>> uncurry (!!)
9
•(Int, Int)から(Int, Either ??? Int)への関数。
•Either A Bは「型Aまたは型Bになる」という型。C言語のunionみたいな感じ。
•???の部分は先を読まないと分からない。
•Right xは、型Bの変数xを(Either ??? B)型に変換する関数。
•repeat xはxを要素として持つ無限リスト[x, x, x, ・・・]を作る関数。
first (repeat . Right)(1999, 3)
([Right 1999,
Right 1999,
Right 1999, …] , 3)
例
isModBy4 = (id &&& flip mod 4) >>> first (repeat . Right)
>>> first ((:) $ Left True) >>> uncurry (!!)
10
•(Int, Either Bool Int)から(Int, Either Bool Int)への関数。
•Left Trueの部分を見て、先ほど???だった部分がBoolだと分かる。
•Left xは、型Aの変数xを(Either A ???)型に変換する関数。
•:はリストに要素を結合する演算子。
x : xsで、リストxsの先頭にxがくっつけられる。
•演算子を()で囲むと関数になる。(:) x xsはx : xsと同じ意味。
Arrowでは関数を繋げていかないといけないので、関数化した。
first ((:) $ Left True)
([Left True,
Right 1999,
Right 1999, …] , 3)
([Right 1999,
Right 1999,
Right 1999, …] , 3)
例
isModBy4 = (id &&& flip mod 4) >>> first (repeat . Right)
>>> first ((:) $ Left True) >>> uncurry (!!)
11
uncurry (!!) Right 1999
([Left True,
Right 1999,
Right 1999, …] , 3)
•(Int, Either Bool Int)からEither Bool Intへの関数。
•uncurryはf x yという関数をf (x,y)に変換する関数。
例えば(+)をuncurryすると、uncurry (+) (1, 3) = 4のような計算ができる。
•!!はリスト操作の演算子で、xs !! nとすると、リストxsのn番目の要素を
取り出す。
•受け渡されてくる値がペア型なので、uncurryした。
考えてみよう
• isModBy4関数に整数を渡すと、場合によってBool型
やらInt型が求められるので、Either Bool Int型とした。
• しかし、結果を出力すると
Left TrueやらRight 1999
などと表示されてLeftとRightが鬱陶しい。
• そこで、結果を”True”、”1999”などとString型で統一
したい。isModBy4関数をどのように変更すれば良い
か?
12
isModBy4 = (id &&& flip mod 4) >>> first (repeat . Right)
>>> first ((:) $ Left True) >>> uncurry (!!)
Arrowの表示的意味論
• 関数型言語(特にラムダ計算)と集合論(を一般化した圏論)に
は、密接な関係がある。(スコット理論)
• 関数型言語を圏論など数学の世界に対応付けて捉えようと
いう学問を表示的意味論と呼ぶ。
• ざっくり言うと、 「型」 を 「集合」 に対応させると、いろい
ろ上手く対応づく、という理論。
参考
13
Arrowの表示的意味論
• ***演算子等で使われるペア型は一見すると直積集合と対
応しそうである。
• しかし(f *** g)は、f×gと等価ではない。
参考
数学の世界 プログラム
C’
C
D’
Df×g
C’
C
D’
D
f
g
fとgはまとめて同時に作用する fとgはどちらかが先に作用する
14
Arrowの表示的意味論
• 実際、プログラムにおける(f *** g)はf×gでは表現できない。
• なぜなら、数学の世界において「fとgを直積集合C×C’の要素に
作用させる関数」は唯一つであり、それをf×gと表記しているの
に対し、(f *** g)はfとgの作用順が不明瞭なので、数学的に1つ
の関数で表現できない。
参考
C’
C
D’
D
f
g
fとgはどちらかが先に作用する
作用順によって2通りのパス
C×C’
C×D’
D×C’
D×D’
f
f
g g
15
Arrowの表示的意味論
• そこで直積集合より弱い、 「fとgを直積集合C×C’の要素に作用
させる関数」が2通りあるような集合を考えようとした。
• しかし、普通の集合論では作用が同時である(順序がない)以上、
どうあがいてもそのような集合を表現できないので、集合論を一
般化した圏論の世界で表現した。
• この圏(集合を一般化したもの)をPremonoidal Categoryと呼ぶ。
参考
C×C’
C×D’
D×C’
D×D’
f
f
g g
16

Control.Arrow