ALGOBIT

2010/12/10

ぷログラミング

Filed under: 離散的な気まぐれ — タグ: , , — Kohyama @ 14:38

昨日 pgcafe の忘年会に参加して参りました.

pgcafe がコラボレーションのきっかけになっていますという常連皆さんのまとめ発表に混じり, ひとり自分勝手に最近興味のある関数プログラミングについて語ってきました. すいません.

私が最近になってはまっているというだけで, 関数プログラミング自体は新しい話題という訳ではないので, 既に知っている方か, 特に興味のない方が大半だと思うのですが, もしかしたら私の発表で興味を持ってくださった方もいないとも限りませんので一応資料載せておきます.

あわてて作った資料のため「ぷログラミング」と誤植していたのですが, 意外にかわいいのでそのまま修正せずにおきます.

関数ぷログラミング紹介

準備・持ち寄りしてくださった皆さんありがとうございました.
夜から参加で手伝えずいただくばかりでごちそうさまでした.

そして今年一年ありがとうございました.
来年は昼間は行けそうにありませんが, オンラインで, 二次会やイベントで, 参加できればと思っています.

追記: Haskell では構文をプログラムから書き換えるようなことはできるかというご質問ありました. Haskell ではできません. LISP ではできます. → LISPならば容易にできます. Haskell でも LISP ほど容易ではないけれどもできます.([2010.12.11] eagletmt さん情報により修正)
プログラムソースの構文が, プログラムが扱うデータと同じ構文になっていることを「同図像性」というのですが, LISP (といっても特定しきれませんが) はこの特徴を持っており, 構文を再定義することができます.
同図像性は関数プログラミングの特色というわけではないと思います.
逆に, 関数プログラミングの特色でないもので Haskell が持っている機能をリストしてしまってあるかもしれません. あしからず.

2010/12/02

第二双対定理(foldr と foldl の関係)

Filed under: 離散的な気まぐれ — タグ: , — Kohyama @ 06:00

第二双対定理

演算子 ⊕, ⊗ および a は全ての x, y, z について, つぎの関係を満たすものとする.

x ⊕ (yz) = (xy) ⊗ z
x a = ax

つまり, ⊕ と ⊗ はたがいに結合的であり, ⊕ の右側に a があることと ⊗ の左側に a があることが等しいということである.
これらの条件のもとで, 任意の有限リスト xs に対して,

foldr (⊕) a xs = foldl (⊗) a xs

が成り立つ.

出典: 「関数プログラミング」p.72

だそうだ.

xs の要素を x1, x2, x3, … xn
とすると

foldr (⊕) a xsx1 ⊕ (x2 ⊕ (x3 ⊕ … (xna) … ))
foldl (⊗) a xs ⇒…(((ax1) ⊗ x2) ⊗ x3) … ⊗ xn

であり, 上の条件化ではこの二つは等しいからだ.

GHCi で, Haskell における reverse の再実装をしてみよう.

Prelude> :{
Prelude| let revr = foldr postfix []
Prelude| where postfix a xs = xs ++ [a]
Prelude| :}
Prelude> revr [1, 2, 3, 4]
[4,3,2,1]
Prelude> :{
Prelude| let revl = foldl prefix []
Prelude| where prefix xs a = [a] ++ xs
Prelude| :}
Prelude> revl [1, 2, 3, 4]
[4,3,2,1]

a `postfix` xs はリスト xs の後に, a だけからなるリストを連結したリスト.
xs `prefix` a は a だけからなるリストの前に, リスト xs を連結したリスト.

x `postfix` (y `prefix` z) と (x `postfix` y) `prefix` z は等しい.
x `postfix` [] と [] `prefix` x は等しい.

ので, revr と revl は(数学的には)等しい.

ふむ… わかったようなわからんような.


R. バード,P. ワドラー
近代科学社
発売日:1991-04

学部時代, 本著の翻訳者である武市先生の授業の教科書でした.
当時(1997年だったかな)C言語で計測値に対する解析計算をするプログラムを書くのに精一杯だった私にとっては, 実用性の無い数学論に見えましたが, Haskell や他の関数型言語を齧った現在読むと, とても系統的に関数型言語についてまとめられていることが分かります.
翻訳も, 翻訳であることを意識させない滑らかさです. 元の英語を括弧書きする頻度も抜群です.

動く様を見ないでも, 理論からその世界を想像できる方は, いきなりこれを読んでもよいでしょう.
実際に動かしてみないと分からない私のような人は, なんらかの関数型言語のチュートリアルを終えた後に読むと理解が系統立ってくるので良いと思います.

カリー化

Filed under: 離散的な気まぐれ — タグ: , , — Kohyama @ 02:21

「カリー化」は元々は, 複数引数を取る関数を, 「1つだけ引数を取り関数を返す関数」の連鎖で統一的に表現する記述方法の事を言う.
三つの引数を取りその和を返す関数 f を (ある型 a に対して)「(a, a, a) → a」という型を持つ関数「f (x, y, z) = x + y + z」と表現するか, 「a → (a → (a → a))」(あるいは ‘→’ は右結合であると約束して「a → a → a → a」) という型を持つ関数「f x y z = x + y + z」として表現するかの二つの表現方法について後者の事を「カリー化」された記述と呼ぶ.
実際の処理内容の違いを直接示す訳ではないし, なんらかの変換手続きの事でもない.

ところが, プログラミング言語を特定すると「書き方」が決められてくる. なんとなれば, プログラミング言語とは, ある処理をどう書くかを規定するものだからだ.

C言語で

int f(int x, int y, int z) { return x + y + z;}

という関数 f の定義があったとして「f(3) と書くと, 『二つ引数を取り, 3 とそれらの総和を取る関数』を意味する」というようなことはない. コンパイラが構文エラーを吐くか, コンパイラのエラーを無視して, スタックに 3 を積んで, 関数 f のアドレスへジャンプしても, スタックの不整合が起こるのみである.

Haskell や ML では, 内部構文がカリー化されている. Haskell で

f x y z = x + y + z

のように, 「三つの引数を取り, それらの総和を返す関数」f を定義をした場合, f 3 は「二つの引数を取り, 3 とそれらの総和を返す関数」であるし, f 3 4 は「一つの引数を取り, 3, 4 およびその引数の総和を返す関数」であり, f 3 4 5 は「3, 4, 5 の和を返す関数」である.
より正確に言うと, f は【引数を一つ取り x をその値に束縛し『引数を一つ取り y をその値に束縛し「引数を一つとり, x と y とその数の和を返す関数」を返す関数』を返す関数】であり,
Haskell での型宣言 f :: a -> a -> a-> a は, そのことを示している.
そもそも, 複数引数を取る関数が定義できているかのように記述できることの方が構文糖衣 (内部的には, もっと基本的な別の構文に変換されるような, コードを見やすくするために提供される書き方のバリエーション) なのである.

たとえば, 「二引数を取り値を返す関数」を要求する高階関数への引数として f 3 を渡すことができる. Haskell インタプリタ GHCi で確認してみよう.

Prelude> let f x y z = x + y + z   -- 3引数の和を返す関数 f を定義
Prelude> :t f                      -- f の型を確認
f :: (Num a) => a -> a -> a -> a   -- Num に属する任意の型 (仮に a とする) について
                                   --   a 型の引数を一つ取り,
                                   --     a 型の引数を一つ取り,
                                   --       a 型の引数を一つ取り a 型の値を返す関数
                                   --     を返す関数
                                   --   を返す関数
                                   -- 言い換えると,
                                   --   三つの引数を取り, 一つの値を返す関数.
                                   -- になっている.

Prelude> :t (f 3)                  -- f 3 の型を確認
(f 3) :: (Num t) => t -> t -> t    -- Num に属する任意の型 (仮に t とする) について
                                   --   t 型の引数を一つ取り,
                                   --     t 型の引数を一つ取り t 型の値を返す関数
                                   --   を返す関数
                                   -- 言い換えると,
                                   --   二つの引数を取り, 一つの値を返す関数.
                                   -- になっている.

Prelude> :t zipWith                -- zipWith の型を確認
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
                                   -- 任意三つの型 (仮に, a, b, c とする) について
                                   --   (a 型の引数を取り
                                   --      b 型の引数を取り c 型の値を返す関数
                                   --    を返す関数)
                                   --   を引数に取り
                                   --     a 型のリストを引数を取り
                                   --       b 型のリストを引数に取り c 型のリストを返す関数
                                   --     を返す関数
                                   --   を返す関数
                                   -- 言い換えると,
                                   --   (二つの引数を取り, 値を返す関数) と, 二つのリストを引数に取り,
                                   --   リストを返す関数.
                                   -- となっている.

Prelude> zipWith (f 3) [5, 7, 11, 13] [17, 19, 23, 29]
[25,29,37,45]                      -- 引数を二つ取る関数 (f 3) に
                                   -- 二つのリストの要素を順に与え,
                                   -- 返り値を順に要素とするリストを得た.

このように最初からいくつ目かまでの引数を適用し, 残りの引数を取るような関数をオブジェクトとして扱うことを「関数の部分適用」という.
カリー化表現を全面的に採用している言語では, 部分適用した関数を作るために特別な処理は必要ないが, そうでなくて且つ部分適用をサポートする言語では, 関数と最初の引数を与えると, 残りの引数を取る関数に変換することができる.
この関数の変換を行う関数が curry という名前であることが多いが, curry が行うのは関数部分適用であって, 「カリー化」ではない.

紛らわしいことには, Haskell にも curry という関数があり, これは, 一つのペアを引数に取る関数を二引数関数(実際には一引数を取る関数を返す一引数の関数)に変換する.

元の意味はともかく, 引数を部分適用した関数を作成する関数が curry という名前であることが多く, この curry 関数がしてくれる変換を「カリー化 (currying)」と呼ぶ人も多いので, 別にそれでもいいんではないかとは思う.

いずれにせよ, プログラマにとっては引数を部分適用した関数が作成できてオブジェクトとして扱えるかどうか, その処理はどう書くか, が関係するのみだ.
高階関数(関数を引数に取ったり, 関数を返したりする関数)があるような言語, とりわけ関数が第一級のオブジェクト(値も関数も同じように変数に束縛できる)であるような言語では, curry という名前の関数がこの変換を行ってくれる.

追記:
scala だと, _ を使った表記で, 引数を部分的に適用した関数を表現したり,

scala> def func1(x:Int, y:Int, z:Int):Int = x + y + z
func1: (Int,Int,Int)Int
scala> func1(3, _:Int, _:Int)
res1: (Int, Int) => Int = <function>

そもそもカリー化された状態で関数を記述し, 最初から任意個までの引数を適用した関数を表現したり (最後にアンダーバー ‘_’ が要るけど) できるようだ.

scala> def func2(x:Int)(y:Int)(z:Int):Int = x + y + z
func2: (Int)(Int)(Int)Int
scala> func2(3)_
res2: (Int) => (Int) => Int = <function>

また, Function.curried(func1 _) で func2 と同じ型の関数を, Function.uncurried(func2 _) で func1 と同じ型の関数を作れるらしい. この命名は適切に思える.

Haskell の例と同じことをやろうとしたが, zipWith が無いので, zip して map したが, それだと (Int, Int) のペア一つを取る関数を要求してしまう. func1 も func2 も直接渡せない. なんか良い例題ないかな…

scala> List(5, 7, 11, 13) zip List(17, 19, 23, 29) map func2(3)_
<console>:6: error: type mismatch;
 found   : (Int) => (Int) => Int
 required: ((Int, Int)) => ?
       List(5, 7, 11, 13) zip List(17, 19, 23, 29) map func2(3)_
                                                       ^

↓これでは部分適用の例題にならない…

scala> List(5, 7, 11, 13) zip List(17, 19, 23, 29) map {p => func2(3)(p._1)(p._2) }
res3: List[Int] = List(25, 29, 37, 45)

Copyright © 2010 Yoshinori Kohyama All Rights Reserved.