PROGRAMMING IN HASKELL

Slides:



Advertisements
Similar presentations
アルゴリズムとプログラミン グ (Algorithms and Programming) 第6回:クラスとインスタンス クラスの宣言 アクセス修飾子 インスタンスの生成 (new キーワード) this キーワード フィールドとメソッドの実際の定義と使い 方 クラスの宣言 アクセス修飾子 インスタンスの生成.
Advertisements

プログラミング言語論 第10回(演習) 情報工学科 木村昌臣   篠埜 功.
情報・知能工学系 山本一公 プログラミング演習Ⅱ 第3回 配列(1) 情報・知能工学系 山本一公
(Rubyistのための) 超音速:ML入門
プログラミング演習II 2004年11月 30日(第6回) 理学部数学科・木村巌.
プログラミング言語論 第8回 LISP 担当:犬塚.
京都大学情報学研究科 通信情報システム専攻 湯淺研究室 M2 平石 拓
プログラミング言語としてのR 情報知能学科 白井 英俊.
ソフトウェア基礎科学 授業資料: 論理関係(logical relations)のお話
最適化ソルバーのための Python言語入門
はじめに 教科書 プログラミングHaskell Graham Hutton (著), 山本 和彦 (翻訳) オーム社、全 232 ページ
型宣言 (Type Declarations)
読んだもの1 P0145R1: Refining Expression Evaluation Order for Idiomatic C++
型宣言(Type Declarations)
プログラミング言語論 第4回 式の構文、式の評価
論理式の表現を数学的に取り扱いやすくするために代数学の助けを借りる.
条件式 (Conditional Expressions)
数理論理学 第1回 茨城大学工学部情報工学科 佐々木 稔.
Tokuda Lab. NISHIMURA Taichi
ML 演習 第 7 回 新井淳也、中村宇佑、前田俊行 2011/05/31.
プログラミング言語論 第12回 関数型プログラミング 情報工学科 篠埜 功.
PROGRAMMING IN HASKELL
4 ソフトウェア工学 Software Engineering 抽象データ型 ABSTRACT DATA TYPE.
PROGRAMMING IN HASKELL
プログラミング言語論 第3回 BNF記法について(演習付き)
PROGRAMMING IN HASKELL
PROGRAMMING IN HASKELL
アルゴリズムとプログラミング (Algorithms and Programming)
PROGRAMMING IN HASKELL
6. リスト処理関数の設計(発展版) プログラミング論 I.
お仕事にまったく役にたたない内容のコードレビューやりたいと思います。
6.リストの生成.
国立情報学研究所 ソフトウェア研究系 助教授/
書き換え型プログラムの生成・検証 研究課題:適切な実行戦略を効率よく探索する、 より自動化された手続きの実現 書き換え型プログラム
4.リスト,シンボル,文字列.
プログラミング言語論 第9回 情報工学科 木村昌臣 篠埜 功.
15.cons と種々のデータ構造.
プログラミング基礎a 第6回 C言語によるプログラミング入門 配列と文字列(その2)
15.1 文字列処理の基本 15.2 文字列処理用ライブラリ関数
アルゴリズムとデータ構造 第3章 ヒープ 5月31日分
統計ソフトウエアRの基礎.
プログラミング 3 2 次元配列.
オブジェクト指向言語論 第三回 知能情報学部 新田直也.
計算の理論 I -数学的概念と記法- 火曜 12:50~14:20 大月 美佳 2004年4月20日.
ポインタとポインタを用いた関数定義.
PROGRAMMING IN HASKELL
プログラミング言語論 第10回 情報工学科 篠埜 功.
アルゴリズムとデータ構造1 2009年6月15日
計算の理論 I -数学的概念と記法- 月曜3校時 大月 美佳.
情報処理Ⅱ 第7回 2004年11月16日(火).
~sumii/class/proenb2009/ml6/
情報処理Ⅱ 2005年10月28日(金).
5. 任意長の合成データ:リスト プログラミング論I.
PROGRAMMING IN HASKELL
関数型言語の基礎 型なしl計算 型理論 構成的プログラミング GUIにあらわれる関数概念 PBD VL
~sumii/class/proenb2010/ml5/
香川大学工学部 富永浩之 情報数学1 第5-2章 命題論理式の 同値変形とカルノー表 香川大学工学部 富永浩之
コンパイラ 2012年10月11日
プログラミング 4 文字列.
アルゴリズムとデータ構造 2010年6月17日
PROGRAMMING IN HASKELL
プログラミング演習I 2003年6月11日(第9回) 木村巌.
Haskell Programming Language
7. 設計の抽象化 プログラミング論 I.
情報処理Ⅱ 2005年11月25日(金).
プログラミング基礎a 第5回 C言語によるプログラミング入門 配列と文字列
情報処理Ⅱ 小テスト 2005年2月1日(火).
計算技術研究会 第5回 C言語勉強会 関数(function)を使う
オブジェクト指向言語論 第六回 知能情報学部 新田直也.
計算機プログラミングI 第2回 2002年10月17日(木) 履習登録 複習 ライブラリの利用 (2.6-7) 式・値・代入 (2.6-8)
Presentation transcript:

PROGRAMMING IN HASKELL Chapter 7 - Higher-Order Functions 高階関数 愛知県立大学 情報科学部 計算機言語論(山本晋一郎・大久保弘崇、2011年)講義資料 オリジナルは http://www.cs.nott.ac.uk/~gmh/book.html を参照のこと

twice は第 1 引数に関数を取るので高階関数 カリー化により 2 引数以上の関数の戻り値は関数となるため、多くの関数が厳密には高階関数である。より限定して引数として関数を取る関数という立場も。 Introduction 関数を引数とする、または返り値とする関数を高階関数という twice :: (a  a)  a  a twice f x = f (f x) twice は第 1 引数に関数を取るので高階関数

高階関数は有用か? 高階関数によって広く用いられるプログラミングのイディオムを自然に表現できる 高階関数を用いてドメイン固有言語(domain specific language, DSL)を定義できる 高階関数の代数的性質はプログラムに関する論証に用いられる

Map 関数 高階ライブラリ関数 map は、引数として与えられた関数をリストの要素全てに適用する For example: map :: (a  b)  [a]  [b] For example: > map (+1) [1,3,5,7] [2,4,6,8]

map 関数はリスト内包表記を用いてとても簡潔に定義できる: map f xs = [f x | x  xs] 別な方法として、主に証明に使うために、再帰を用いても定義できる: map f [] = [] map f (x:xs) = f x : map f xs

filter :: (a  Bool)  [a]  [a] For example: > filter even [1..10] [2,4,6,8,10]

filter p xs = [x | x  xs, p x] 別な方法として、再帰を用いても定義できる: filter p [] = [] filter p (x:xs) | p x = x : filter p xs | otherwise = filter p xs

右畳み込み(foldr) リスト関数の多くは、単純な再帰パターンによって定義される(演算子  と初期値 v がパラメータ): f [] = v f (x:xs) = x  f xs f は空リストを初期値 v に写像し、非空リストを、 (先頭要素)と(残りのリストに f を適用した結果) に演算子  を適用した結果に写像する

v = 0 v = 1 v = True For example: sum [] = 0 sum (x:xs) = x + sum xs  = + product [] = 1 product (x:xs) = x * product xs v = 1  = * and [] = True and (x:xs) = x && and xs v = True  = &&

高階ライブラリ関数 foldr (fold right, 右畳み込み)は、関数 () と初期値 v を引数として、この単純な再帰パターンを表現する For example: 教科書はポイントフリー(引数を明示しない)スタイルで書かれているので、両辺に引数を補って考える sum xs = foldr (+) 0 xs product xs = foldr (*) 1 xs or xs = foldr (||) False xs and xs  = foldr (&&) True xs

foldr 自体は再帰を用いて定義できる: foldr :: (a  b  b)  b  [a]  b foldr f v [] = v foldr f v (x:xs) = f x (foldr f v xs) または foldr f v (x:xs) = x `f` (foldr f v xs) 実際には、foldr を再帰的に理解するのではなく、 リストの cons (:) を演算子  に、 終端 [] を初期値 v に、同時に置き換えると理解すべき 第 1, 2 引数は変化しない(持ち回される)

cons (:) を  に、終端 [] を v に置換 foldr () v [x0, x1, ..., xn] = foldr () v (x0 : x1 : ... : xn : []) = x0  (x1  (... ( xn  v)))

For example: Replace each (:) by (+) and [] by 0. = = = = sum [1,2,3] foldr (+) 0 [1,2,3] = foldr (+) 0 (1:(2:(3:[]))) = 1+(2+(3+0)) = 6 =

For example: Replace each (:) by (*) and [] by 1. = = = = product [1,2,3] foldr (*) 1 [1,2,3] = foldr (*) 1 (1:(2:(3:[]))) = 1*(2*(3*1)) = 6 =

foldr を用いた他の例 foldr は単純な再帰をパターン化しただけだが、 想像以上に多様な関数を定義できる length 関数について考えてみる: length :: [a]  Int length [] = 0 length (_:xs) = 1 + length xs

Replace each (:) 例えば: by _ n  1+n and [] by 0. = = = 従って: length [1,2,3] length (1:(2:(3:[]))) = 1+(1+(1+0)) = 3 = 従って: length xs = foldr (_ n  1+n) 0 xs

_ n  1+n はどうやって求める? length [1, 2, 3] = foldr f v (1 : (2 : (3 : []))) = 1 `f` (2 `f` (3 `f` v)) where v = 0 f x y = 1 + y f 3 v より、f x y は要素 3 を x に、初期値 v を y に取って、 [3] の長さ 1 を返す f 2 (f 3 v) より、f x y は要素 2 を x に、残りのリストの長さ 1 を y に取って、 [2, 3] の長さ 2 を返す f x y は、要素を x に、残りのリストの長さを y に取って、 y に 1 を加えて返せば良い(v は 0) f x y = 1 + y (x は利用しないので _ で十分) 注意: 3 `f` v == f 3 v

Replace each (:) by x xs  xs ++ [x] reverse について考えてみる: reverse [] = [] reverse (x:xs) = reverse xs ++ [x] 例えば: Replace each (:) by x xs  xs ++ [x] and [] by []. reverse [1,2,3] reverse (1:(2:(3:[]))) = (([] ++ [3]) ++ [2]) ++ [1] = [3,2,1] =

Replace each (:) by (:) and [] by ys. 従って: reverse xs = foldr (x xs  xs ++ [x]) [] xs 最後に、リストの連結 (++) を foldr によって極めて簡潔に定義する: (++) xs ys = foldr (:) ys xs Replace each (:) by (:) and [] by ys.

x xs  xs ++ [x] はどうやって求める? reverse [1,2,3] = foldr f v (1 : (2 : (3 : []))) = 1 `f` (2 `f` (3 `f` v)) where v = [] f x y = y ++ [x] f 3 v より、f x y は要素 3 を x に、初期値 v を y に取って、 [3] の反転 [3] を返す f 2 (f 3 v) より、f x y は要素 2 を x に、残りのリストの反転 [3] を y に取って、 [2,3] の反転 [3,2] を返す f x y は、要素を x に、残りのリストの反転を y に取って、 y の後ろに [x] を結合したリストを返せば良い(v は[]) f x y = y ++ [x] 注意: 3 `f` v == f 3 v

reverse の補足 要素をリストの末尾に追加する関数 snoc を導入 snoc x xs = xs ++ [x] = 1 `snoc` (2 `snoc` (3 `snoc` [])) = (([] ++ [3]) ++ [2]) ++ [1] = [] ++ [3] ++ [2] ++ [1] = [3,2,1]

Replace each (:) by (:) and [] by ys. 従って: reverse xs = foldr (x xs  xs ++ [x]) [] xs 最後に、リストの連結 (++) を foldr によって極めて簡潔に定義する: (++) xs ys = foldr (:) ys xs Replace each (:) by (:) and [] by ys.

append を foldr によって表現

foldr は有用か? sum のようなリスト関数をより簡潔に定義できる foldr を用いて定義された関数の性質は、foldr の代数的性質(fusion や banana split 規則)を用いて証明できる 明示的な再帰の代わりに foldr を用いると、高度なプログラムの最適化が容易になる

foldr (右畳み込み)のまとめ f [] = v f (x:xs) = x  f xs と定義される関数 f xs は、 foldr () v xs と書ける foldr () v [x0, x1, ..., xn] = x0  (x1  (... (xn  v))) sum xs = foldr (+) 0 xs product xs = foldr (*) 1 xs and xs = foldr (&&) True xs

ここから foldl の説明が始まるが、foldr を 初めて学んだ人は後回しにしても良い

左畳み込み(foldl) リスト関数の多くは、単純な再帰パターンによって定義される(演算子  と蓄積変数の初期値 v がパラメータ): f xs = f’ v xs f’ ac [] = ac f’ ac (x:xs) = f’ (ac  x) xs f’: f の補助関数 ac: それまで処理した中間結果を保持する蓄積変数 f’ は、空リストを蓄積変数の値に、非空リストを、 (蓄積変数と先頭要素に  を適用した結果)と (残りのリスト)に f’ を適用した結果に写像する

v = 0 v = 1 v = []  = ys y -> y:ys For example: sum xs = sum’ 0 xs sum’ ac [] = ac sum’ ac (x:xs) = sum’ (ac+x) xs v = 0  = + product xs = prod’ 1 xs prod’ ac [] = ac prod’ ac (x:xs) = prod’ (ac*x) xs v = 1  = * rev xs = rev' [] xs rev' ac [] = ac rev' ac (x:xs) = rev' (x:ac) xs v = []  = ys y -> y:ys

高階ライブラリ関数 foldl (fold left, 左畳み込み)は、関数 () と蓄積変数の初期値 v を引数として、この単純な再帰パターンを表現する For example: sum xs = foldl (+) 0 xs product xs = foldl (*) 1 xs reverse xs = foldl (ys y -> y:ys) [] xs

foldl 自体は再帰を用いて定義できる: foldl :: (a  b  a)  a  [b]  a foldl f ac [] = ac foldl f ac (x:xs) = foldl f (f ac x) xs または foldl f ac (x:xs) = foldl f (ac `f` x) xs 実際には、foldl を再帰的に理解するのではなく、 蓄積変数の初期値を v とし、 左結合の演算子  を用いてリストから式を構成すると理解すべき 蓄積変数 ac に結果を蓄えていき、最後にその値を返す。 第 1 引数は変化しない(持ち回される)。

蓄積変数の初期値を v とし、 左結合の  を用いてリストから式を構成 foldl () v [x0, x1, ..., xn] = foldl () v (x0 : x1 ... : xn : []) = (((v  x0)  x1) ...)  xn

リストの前に蓄積変数の初期値 0 を置いて、(+) で左から演算する For example: リストの前に蓄積変数の初期値 0 を置いて、(+) で左から演算する sum [1,2,3] foldl (+) 0 [1,2,3] = foldl (+) 0 (1:(2:(3:[]))) = ((0+1)+2)+3 = 6 =

リストの前に蓄積変数の初期値 1 を置いて、(*) で左から演算する For example: リストの前に蓄積変数の初期値 1 を置いて、(*) で左から演算する product [1,2,3] foldl (*) 1 [1,2,3] = foldl (*) 1 (1:(2:(3:[]))) = ((1*1)*2)*3 = 6 =

reverse を foldl を用いて実現 rev [1,2,3] = foldl (ys y -> y:ys) [] [1,2,3] = foldl (…) ((…) [] 1) [2,3] = foldl (…) (1:[]) [2,3] = foldl (…) ((…) [1] 2) [3] = foldl (…) (2:[1]) [3] = foldl (…) ((…) [2,1] 3) [] = foldl (…) (3:[2,1]) [] = [3,2,1] 蓄積変数 ys には、既に処理した前方のリストを反転した結果が渡される

ys y -> y:ys はどうやって求める? reverse [1,2,3] = foldl f v (1 : (2 : (3 : []))) = ((v `f` 1) `f` 2) `f` 3 where v = [] f x y = y:x f v 1 より、f x y は初期値 v を x に、要素 1 を y に取って、[1] の反転 [1] を返す f (f v 1) 2 より、f x y は前方のリストの反転 [1] を x に、要素 2 を y に取って、 [1,2] の反転 [2,1] を返す f x y は、前方のリストの反転を x に、要素を y に取って、 x の前に y を cons したリストを返せば良い(v は[]) f x y = y:x 注意: v `f` 1 == f v 1

foldl (左畳み込み)のまとめ f xs = f’ v xs f’ ac [] = ac f’ ac (x:xs) = f’ (ac  x) xs と定義される関数 f xs は、 foldl () v xs と書ける 空リストを蓄積変数の値に、非空リストを、(蓄積変数と先頭要素に  を適用した結果)と(残りのリスト)を f’ に適用した結果に写像 foldl () v [x0, x1, ..., xn] = (((v  x0)  x1) ...)  xn reverse xs = foldl (\ys y -> y:ys) [] xs

foldl の説明終わり

その他の高階ライブラリ関数 高階ライブラリ関数 (.) は、 2 つの関数を合成した関数を返す For example: (.) :: (b  c)  (a  b)  (a  c) f . g = x  f (g x) odd = not . even odd = x  not (even x) odd n = (not . even) n odd n = not (even n) odd n = not $ even n For example: odd :: Int  Bool odd = not . even

高階ライブラリ関数 all は、リストの全ての要素が与えられた述語を満たすか判定する all :: (a  Bool)  [a]  Bool all p xs = and [p x | x  xs] For example: > all even [2,4,6,8,10] True

all と双対な高階ライブラリ関数 any は、リストの要素の少なくとも 1 つが与えられた述語を満たすか判定する any :: (a  Bool)  [a]  Bool any p xs = or [p x | x  xs] For example: > any isSpace "abc def" True

高階ライブラリ関数 takeWhile は、リストの先頭から述語を満たす区間を取り出したリストを返す takeWhile :: (a  Bool)  [a]  [a] takeWhile p [] = [] takeWhile p (x:xs) | p x = x : takeWhile p xs | otherwise = [] For example: > takeWhile isAlpha "abc def" "abc"

takeWhile と双対な高階ライブラリ関数 dropWhile は、リストの先頭から述語を満たす区間を除いたリストを返す dropWhile :: (a  Bool)  [a]  [a] dropWhile p [] = [] dropWhile p (x:xs) | p x = dropWhile p xs | otherwise = x:xs For example: > dropWhile isSpace " abc" "abc"

まとめ(7章) 高階関数: 関数を引数、または返り値とする関数 map: 与えられた関数をリストの要素全てに適用 filter: リストから述語を満たす要素を選び出す foldr: 右畳み込み foldr () v [x0, x1, ..., xn] = x0  (x1  (... (xn  v))) sum xs = foldr (+) 0 xs product xs = foldr (*) 1 xs and xs = foldr (&&) True xs (.): 関数合成 f . g = x  f (g x) all: リストの全ての要素が述語を満たすか判定 takeWhile: リストの先頭から述語を満たす区間を取り出す

まとめ(foldr と foldl) 先頭要素は最外、v は最内右、foldr は結果の中へ 先頭要素は最内、v は最内左、結果は foldl の蓄積変数へ         foldr (+) 0 (1:(2:(3:[]))) = 1 + (foldr (+) 0 (2:(3:[]))) = 1 + (2 + (foldr (+) 0 (3:[]))) = 1 + (2 + (3 + (foldr (+) 0 []))) = 1 + (2 + (3 + 0)) = 6 foldl (+) 0 (1:(2:(3:[]))) = foldl (+) (0 + 1) (2:(3:[])) = foldl (+) ((0 + 1) + 2) (3:[]) = foldl (+) (((0 + 1) + 2) + 3) [] =  ((0 + 1) + 2) + 3 = 6

foldr ( ) v : 1 : 1 2 : 2 3 : 3 4 : 4 5 [] 5 v

foldl ( ) v : 1 : 5 2 : 4 3 : 3 4 : 2 5 [] v 1

foldr (:) ys : 1 2 3 4 5 [] : 1 : 2 : 3 : 4 : 5 ys