Mercurial > hg > Papers > 2014 > toma-master
annotate paper/chapter1.tex @ 60:79d168016df4
add memorize
author | Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Tue, 11 Feb 2014 22:58:43 +0900 |
parents | 0a8d66c9ccd1 |
children | d11f4c6c7657 |
rev | line source |
---|---|
6
37efb7dc0bda
describe introduciton
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
5
diff
changeset
|
1 \chapter{Haskellとは} \label{ch:haskell} |
47 | 2 Haskell とは純粋関数型プログラミング言語である. |
2 | 3 |
15 | 4 \section{純粋関数型プログラミング} |
47 | 5 関数とは, 一つの引数を取り一つの結果を返す変換器のことである. |
6 関数型プログラミング言語では, 関数を引数に適用させていくことで計算を行う. | |
15 | 7 |
60 | 8 \subsubsection{関数の定義} |
47 | 9 既存の手続き型言語と異なり, 手順を記述するのではなく, この関数が何であるかということを記述する. |
10 例えば, Haskell でフィボナッチ数列を定義するにはソースコード\ref{src:fib}のように記述する. | |
11 Haskell は, 関数を引数を適用するという考えに基づいて, 抽象的なプログラミングを可能とする. | |
7 | 12 |
15 | 13 \begin{lstlisting}[label=src:fib, caption=フィボナッチ数列] |
34 | 14 fib :: Int -> Int |
15 | 15 fib 0 = 0 |
16 fib 1 = 1 | |
17 fib n = fib (n-2) + fib (n-1) | |
18 \end{lstlisting} | |
19 | |
47 | 20 fib :: Int -$>$ Int は, 関数の型宣言である. |
21 この関数は, Int を受け取って Int を返す関数ということを示す. | |
22 フィボナッチ数列の関数が三行に渡って書かれているが, これは Haskell のパターンマッチを活用している. | |
60 | 23 引数の値が 0 ならば 2 行目が呼び出され, 関数の結果は 0 となる. |
24 引数の値が 1 ならば 3 行目が呼び出され, 関数の結果は 1 となる. | |
25 上から順に引数と一致する行がないか調べていき, 引数が 0 でも 1 でもなければ引数は n に束縛され, fib (n-2) + fib (n-1) が計算される. | |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
26 |
47 | 27 フィボナッチ数列の関数は, 自分自身を使って再帰的に定義している. |
28 再帰は, 関数型プログラミング言語において必要不可欠な要素である. | |
60 | 29 手続き型言語では, 配列とループを主に用いてプログラミングを行うが Haskell ではリスト構造と再帰を用いる. |
15 | 30 |
60 | 31 \subsubsection{変数の代入} |
47 | 32 純粋関数型プログラミングでは, 変数の代入は一度のみで後から書き換えることはできない. |
33 フィボナッチ数列の関数でも, 一度引数を束縛した n を書き換えることはできない. | |
34 関数にできることは, 何かを計算してその結果を返すことだけであり, 引数が同じならば関数は必ず同じ値を返すことが保証されている. | |
35 この性質は関数の理解を容易にし, プログラムの証明を可能にする. | |
36 正しいと分かる単純な関数を組み合わせて, より複雑な正しい関数を組み立てていくのが関数型言語のプログラミングスタイルである. | |
37 | |
60 | 38 \subsubsection{高階関数} |
47 | 39 関数型プログラミング言語は, 関数を変数の値にすることができる. |
60 | 40 これは, 関数を第一級オブジェクトとして扱うことができるということである. |
47 | 41 Haskell は, 引数として関数を取ったり返り値として関数を返すことができる高階関数を定義できる. |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
42 |
47 | 43 高階関数の例として Haskell のカリー化が挙げられる. |
44 Haskell では, 全ての関数は一度に一つの引数だけを取る. | |
45 2 つのInt を受け取り, 大きい方を返す max の型はソースコード\ref{src:max}のように定義できる. | |
46 | |
47 \begin{lstlisting}[label=src:max, caption=max関数] | |
48 max :: Int -> Int -> Int | |
60 | 49 max x y = if x <= y |
50 then y | |
51 else x | |
47 | 52 \end{lstlisting} |
53 | |
54 この関数定義に現れる-$>$は左結合である. | |
55 複数の引数を取るようにみえる関数は, 実際には1つの引数を取り, その次の引数を受け取る関数を返す(ソースコード\ref{src:max_curry}). | |
7 | 56 |
47 | 57 \begin{lstlisting}[label=src:max_curry, caption=max関数のカリー化] |
58 max :: Int -> (Int -> Int) | |
59 \end{lstlisting} | |
7 | 60 |
47 | 61 このように関数を返すことで全ての関数を一引数関数として表すことをカリー化という. |
62 カリー化によって, 関数を本来より少ない引数で呼び出した際に部分適用された関数を得ることができる(ソースコード\ref{src:partial}). | |
63 | |
64 \begin{lstlisting}[label=src:partial, caption=関数の部分適用] | |
60 | 65 x = max 3 -- x は Int -> Int の関数 |
66 x 4 -- (max 3) 4 の結果として 4 が返される | |
47 | 67 \end{lstlisting} |
7 | 68 |
60 | 69 \clearpage |
7 | 70 \section{型} |
47 | 71 Haskell では, すべての式, すべての関数に型がある. |
72 値の型は, その値が同じ型の別の値と何らかの性質を共有していることを示す. | |
73 例えば, 数値は加算できる, 文字列は表示できるといった性質である. | |
15 | 74 |
47 | 75 型はプログラムに抽象をもたらす. |
76 型を導入することで, 低水準の詳細を気にせずプログラミングが可能になる. | |
60 | 77 例えば, 値の型が文字列ならば, どのように実装されているかということを気にせず, |
47 | 78 その文字列が他の文字列と同じように振る舞うとみなすことができる. |
15 | 79 |
60 | 80 \subsubsection{型の定義と型検査} |
47 | 81 Haskell は静的型検査によりエラーを検出することができる. |
82 Haskell では, 評価の際に型に起因するエラーが起きないことを保証している. | |
60 | 83 例えば, 引数として整数を受け取る関数に文字列を渡そうとしても Haskell のコンパイラはこれを受け付けない. |
47 | 84 Haskell は, すべての式, すべての関数に型があるためコンパイル時に多くのエラーを捕まえることができる. |
15 | 85 |
60 | 86 エラーの検出の例として Haskell で最もよく使われるデータ構造であるリスト型で確認を行う. |
87 また, リスト型の定義とあわせてデータ型の定義についても説明する. | |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
88 |
47 | 89 リストとは, 角括弧で包まれた同じ型の要素の並びである. [1,2,3] などと表現する. |
90 リストはソースコード\ref{src:list}のように定義されている. | |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
91 |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
92 \begin{lstlisting}[label=src:list, caption=Haskellのリスト定義] |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
93 data [] a = [] | a : [a] |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
94 \end{lstlisting} |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
95 |
47 | 96 data というのは新しい型を定義する際に利用するキーワードである. |
60 | 97 data キーワードのすぐ後にある [] a というのが型名である. |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
98 |
60 | 99 a というのは, 型変数と呼ばれるものである. |
100 型変数は任意の型を取ることができる. | |
101 リスト型は, Intのリスト, Floatのリストといった様々な型のリストを作ることができ, 型変数を用いてそれを実現する. | |
102 型変数が何の型になるのかという情報は実行時には決まっており, 関数に渡される際に型の不整合が起きることはない. | |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
103 |
60 | 104 = の右側には, 新しい型の定義として型の値となるものを列挙する. |
47 | 105 $|$ は, もしくはという意味である. |
106 つまりリストは, [] もしくは a : [a] という値になることが分かる. | |
60 | 107 |
47 | 108 [] は空リストを表す. 型名と同じであるが, 型とは名前領域が異なるため問題ない. |
109 型名はプログラム中では注釈としてしか使われないためである. | |
110 | |
111 a : [a] は再帰的なデータ構造である. | |
60 | 112 値 a を : で繋げて, 再度リストの定義を呼んでいる. |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
113 |
60 | 114 この定義 [] $|$ a : [a] は, a : a : a : .. : a : [] となり, a が任意個続いた後に, [] が来ることとなる. |
115 つまり, リストは無限に繋げることができ, リストの終端は空のリスト, つまり [] で終わる. | |
116 | |
117 Haskell では, [1,2,3] という様にリストを表すが, これは単なるシンタックスシュガーであり, | |
47 | 118 内部では 1 : 2 : 3 : [] のように表現される. |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
119 |
60 | 120 このように定義した型であっても, Haskell は型検査を行う. |
121 リストは : を使うことで新しい要素を加えることができるが, 全ての要素は同じ型の要素である必要がある. | |
47 | 122 違った型の要素を付け加えようとすると Haskell はコンパイル時にエラーを出す. |
123 例えば, Int のリスト [3,4] に, 文字である 'b' を付け加えようとした場合エラーが発生する(ソースコード\ref{src:error}). | |
60 | 124 型の定義に型変数を用いても Haskell は, [3,4] が何の型になるのかといった情報を推論する. |
125 そのため文字である 'b' をIntのリスト [3,4] に付け加えることはできない. | |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
126 |
47 | 127 \begin{lstlisting}[label=src:error, caption=Haskellのコンパイル時エラー] |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
128 <interactive>:3:7: |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
129 Couldn't match type `Int' with `Char' |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
130 Expected type: [Char] |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
131 Actual type: [Int] |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
132 \end{lstlisting} |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
133 |
47 | 134 型検査でも捕まえられないエラーは存在する. |
135 例えば, 式 "1 `div` 0" は, 型エラーではないが, 0 での除算は定義されていないので評価時にエラーとなる. | |
15 | 136 |
137 \subsubsection{型推論} | |
47 | 138 Haskell は型推論を持つ. |
139 型推論とは, 型の宣言をせずともそれを導くのに使われた関数の型シグネチャなどから自動的に型を決定する機構のことである. | |
140 型推論のない静的型付け言語は, プログラマが型の宣言を行うことが強制されるが Haskell では型の宣言は必須ではない. | |
15 | 141 |
47 | 142 例として, 開発したデータベースで実装した getChildren という関数に対して型推論を行ってみる(ソースコード\ref{src:getchildren}). |
143 | |
144 \begin{lstlisting}[label=src:getchildren, caption=getChildren関数] | |
145 getChildren node path = elems (children (getNode node path)) | |
8 | 146 \end{lstlisting} |
147 | |
47 | 148 型の注釈なしに関数を定義し, Haskell の対話環境である GHCi で型情報を取得してみる. |
149 型情報を取得するには, :type の後ろに関数名を入力する(ソースコード\ref{src:type}). | |
8 | 150 |
47 | 151 \begin{lstlisting}[label=src:type, caption=型情報の取得] |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
152 *Jungle> :type getChildren |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
153 getChildren :: Node -> [Int] -> [Node] |
8 | 154 \end{lstlisting} |
155 | |
47 | 156 そうすると, 推論された型情報 Node -$>$ [Int] -$>$ [Node]が得られる. |
157 この型情報は期待する型の定義と一致する. | |
158 | |
159 この情報はgetChildrenに含まれるいくつかの関数の型を確認することで導き出すことができる(ソースコード\ref{src:type2}). | |
15 | 160 |
47 | 161 \begin{lstlisting}[label=src:type2, caption=getChildrenに含まれる関数の型] |
162 getNode :: Node -> Path -> Node | |
163 elems :: Map k a -> [a] | |
164 children :: Node -> Map Int Node | |
165 \end{lstlisting} | |
166 | |
167 例えば, getChildrenの引数である, node と path は getNode に渡されているため, getNode の型である Node 型と Path 型であることが分かる. | |
168 返り値となる型は, elemsの[a]となる. このaは, elemsが受け取るMapの2つ目の型と一致するため, childrenの返り値であるMap Int Node より, [Node]ということが分かる. | |
169 | |
170 Haskell では, プログラマが型の宣言を行わずとも, 型を推論し型安全を保つ. | |
49 | 171 しかし, 明示的な型宣言は可読性の向上や問題の発見に役に立つため, トップレベルの関数には型を明記することが一般的である. |
34 | 172 |
60 | 173 \clearpage |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
174 \section{モナド} |
47 | 175 Haskell では, さまざまな目的にモナドを使う. |
176 I/O 処理を行うためには IO モナドを使う必要がある. | |
177 プログラミングを行うにあたり, I/O 処理は欠かせないため, モナドの説明を行う. | |
34 | 178 |
47 | 179 モナドとは, 型クラスの 1 つである. |
180 型クラスは型の振る舞いを定義するものである. | |
181 ある型クラスのインスタンスである型は, その型クラスに属する関数の集まりを実装する. | |
182 これは, それらの関数がその型ではどのような意味になるのか定義するということである. | |
34 | 183 |
60 | 184 モナドとなる型は, 型変数として具体型をただ 1 つ取る. |
47 | 185 これにより何かしらのコンテナに包まれた値を実現する. |
186 モナドの振る舞いは型クラスとして実装し, 関数として return および $>>$= (bind) を定義する(ソースコード\ref{monad}). | |
16
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
187 |
47 | 188 \begin{lstlisting}[label=monad, caption=モナドに属する関数の型] |
16
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
189 return :: Monad m => a -> m a |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
190 (>>=) :: Monad m => m a -> (a -> m b) -> m b |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
191 \end{lstlisting} |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
192 |
47 | 193 return は値を持ち上げてコンテナに包む機能を実装する(図\ref{fig:monad_return}). |
16
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
194 |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
195 \begin{figure}[!htbp] |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
196 \begin{center} |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
197 \includegraphics[scale=0.6]{./images/monad_return.pdf} |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
198 \end{center} |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
199 \caption{モナドに属する return 関数} |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
200 \label{fig:monad_return} |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
201 \end{figure} |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
202 |
47 | 203 bind は, 「コンテナに包まれた値」と, 「普通の値を取りコンテナに包まれた値を返す関数」を引数にとり, コンテナに包まれた値をその関数に適用する(図\ref{fig:monad_bind}). |
204 適用する際, 前のコンテナの結果に依存して, 後のコンテナの振る舞いを変えられる. | |
16
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
205 |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
206 \begin{figure}[!htbp] |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
207 \begin{center} |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
208 \includegraphics[scale=0.6]{./images/monad_bind.pdf} |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
209 \end{center} |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
210 \caption{モナドに属する $>$$>$= (bind) 関数} |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
211 \label{fig:monad_bind} |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
212 \end{figure} |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
213 |
47 | 214 この2つの関数を利用することにより, 文脈を保ったまま関数を繋いでいくことができる. |
215 Haskell の遅延評価は記述した順序で実行することを保証しないが, モナドの bind は実行順序の指定も可能で, IO モナドを bind で繋いだものは記述順に実行することができる. | |
8 | 216 |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
217 \subsubsection{Maybe モナド} |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
218 |
47 | 219 文脈を保ったまま関数を繋いでいくとはどういうことなのか, 具体例を用いて説明する. |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
220 |
47 | 221 Maybe 型は失敗する可能性を扱うデータ型である(ソースコード\ref{src:maybe}). |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
222 |
47 | 223 \begin{lstlisting}[label=src:maybe,caption=Maybe型の定義] |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
224 data Maybe a = Nothing | Just a |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
225 \end{lstlisting} |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
226 |
47 | 227 失敗したことを表す Nothing, もしくは成功したことを表す Just a のいずれかの値を取る. |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
228 |
47 | 229 Maybe 型が使われている例として, Data.Map の lookup 関数がある. |
230 Data.Map は Key と Value を保持する辞書型のデータ構造である. | |
231 何らかの Key を渡して, Data.Map から値を取得しようとした時, 返される値は Maybe Value 型である. | |
232 何かしらの値が取得できた場合は, Just a として Value に Just がついて返される. | |
233 取得できなければ, Nothing が返る. | |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
234 |
47 | 235 Maybe モナドを使いたい場面は, 失敗するかもしれないという計算を繋いでいく時である. |
236 Maybe モナドの定義をみていく(ソースコード\ref{src:maybe_monad}). | |
237 | |
238 \begin{lstlisting}[label=src:maybe_monad, caption=Maybeモナドの定義] | |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
239 instance Monad Maybe where |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
240 return x = Just x |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
241 Nothing >>= f = Nothing |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
242 Just x >>= f = f x |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
243 \end{lstlisting} |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
244 |
47 | 245 instance Monad Maybe whereというのは, Maybe型をモナド型クラスのインスタンスとするのに使う構文である. |
60 | 246 モナド型クラスに属させるために必要な関数である return と $>>$= (bind) を, Maybeではどう振る舞うかを考え定義していく. |
47 | 247 |
248 Maybe モナドの return は, 値をJustで包む. | |
249 これがコンテナに包む機能という意味である. | |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
250 |
47 | 251 $>>$= (bind) は, 「コンテナに包まれた値」と, 「普通の値を取りコンテナに包まれた値を返す関数」を引数にとり, コンテナに包まれた値をその関数に適用すると説明した. |
252 Maybe モナドの場合, コンテナが Nothing なら, そのまま Nothing を返す. | |
253 コンテナがJustならば, Just に包まれた値を取り出し, 「普通の値を取りコンテナに包まれた値を返す関数」に適用する. | |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
254 |
47 | 255 失敗するかもしれない計算を繋いでいくとはどういうことなのか. |
256 単純な関数を定義して説明する(ソースコード\ref{src:updown}). | |
257 | |
258 \begin{lstlisting}[label=src:updown, caption=up関数とdown関数] | |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
259 up 4 = Nothing |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
260 up n = Just (n + 1) |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
261 |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
262 down 0 = Nothing |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
263 down n = Just (n - 1) |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
264 \end{lstlisting} |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
265 |
47 | 266 関数 up と down を定義した. |
60 | 267 up の場合, 引数として4が渡された場合は上がれないため失敗, |
268 down の場合, 引数として0が渡された場合は下がれないため失敗と考える. | |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
269 |
47 | 270 3 という値にdown, down, up, up 繰り返していく時, モナドを使わない場合ソースコード\ref{src:up_without_monad}のように定義することになる. |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
271 |
47 | 272 \begin{lstlisting}[label=src:up_without_monad, caption=Maybeモナドを使わずにupとdownを行う] |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
273 updown :: Maybe Int |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
274 updown = case down 3 of |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
275 Nothing -> Nothing |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
276 Just place1 -> case down place1 of |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
277 Nothing -> Nothing |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
278 Just place2 -> case up place2 of |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
279 Nothing -> Nothing |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
280 Just place3 -> up place3 |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
281 |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
282 \end{lstlisting} |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
283 |
60 | 284 case 式は, caseとofの間の式を評価し, その値によって評価を分岐させるためのものである. |
285 case を受け取る $->$ の左の部分には式の値を書き, その式の値によって評価を分岐させる. | |
286 例えば, 3 行目は down 3 の結果が Nothing なら, Nothing を返すという意味である. | |
287 Justの場合, 値をplace1という変数に束縛しその後の処理を続ける. | |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
288 |
60 | 289 ソースコード\ref{src:up_without_monad}では, 毎回失敗したか成功したか確認するために非常に煩雑なコードとなってしまった. |
290 これを文脈を保ったまま, 関数を繋げられるモナドを使えばソースコード\ref{src:upmonad}のように記述できる. | |
47 | 291 |
292 \begin{lstlisting}[label=src:upmonad, caption=Maybeモナドを用いてupとdownを行う] | |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
293 return 3 >>= down >>= down >>= up >>= up |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
294 \end{lstlisting} |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
295 |
60 | 296 Maybe モナドを使うことで, この計算は失敗しているかもしれないという文脈を保ったまま処理を繋げていくことができる. |
37
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
297 |
909c9097ebb8
describe the maybe monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
34
diff
changeset
|
298 \subsubsection{IO モナド} |
47 | 299 Haskellで副作用を持つ処理を実行するには, IO モナドを利用する. |
60 | 300 IO モナドは, 処理系が現実世界に対する副作用に変換できるモナドである. |
301 | |
302 IO モナド自体は, 「文字を1文字取ってくる」といった命令書である. | |
47 | 303 bind を使って, 小さな命令書を合成して大きな命令書を作成できる. |
304 最終的に, mainという名前をつけることで初めてランタイムにより実行される(ソースコード\ref{src:iomonad}). | |
16
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
305 |
47 | 306 \begin{lstlisting}[label=src:iomonad, caption=Hello Worldと出力] |
307 main :: IO () | |
308 main = putStrLn "Hello, World!" | |
309 \end{lstlisting} | |
8 | 310 |
47 | 311 Haskell の関数には副作用がないと述べたが, IO モナドを返す関数にも副作用は存在しない. |
312 | |
313 例えば, Jungle には getRootNode というIOを返す関数がある. | |
314 呼び出した状況によって, 返ってくるノードが違うため副作用があるようにみえる. | |
315 しかし, 実際にこの関数が返すのは, 「ノードを取得する」という命令書である. | |
316 どんな状況においても同じ命令書を返すため, 副作用はない. | |
16
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
317 |
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
318 \clearpage |
60 | 319 \section{遅延評価} |
320 Haskell では, 必要となるまで式の評価が遅延される. | |
321 Haskell の対話環境である GHCi でどのように評価されるか確認することができる. | |
322 | |
323 GHCiで評価を強制せずに値を表示する :sprint コマンドを使う. | |
324 例えば, x を定義した後に :sprint コマンドを使うとソースコード\ref{src:sprint} のように表示される\footnote{GHCiでは, 関数や変数を定義する際にはletが必要となる.}. | |
325 | |
326 \begin{lstlisting}[label=src:sprint, caption=sprintコマンドの使用例] | |
327 ghci> let x = 1 + 2 | |
328 ghci> :sprint x | |
329 x = _ | |
330 \end{lstlisting} | |
331 | |
332 \_ は式が未評価であることを示している. | |
333 Haskell ではこの未評価の部分を thunk と呼ぶ. | |
334 | |
335 \begin{lstlisting}[label=src:list_sprint, caption=リストの評価の様子] | |
336 ghci> let y = map (+1) [1,2,3] :: [Int] | |
337 ghci> :sprint y | |
338 y = _ | |
339 ghci> length y | |
340 3 | |
341 ghci> :sprint y | |
342 y = [_,_,_] | |
343 ghci> head y | |
344 2 | |
345 ghci> :sprint y | |
346 y = [2,_,_] | |
347 \end{lstlisting} | |
348 | |
349 リストを使ってどのように評価されるのか確認する. | |
350 ソースコード\ref{src:list_sprint} では, まずmap関数を利用してリストの要素を全て (+1) している. | |
351 しかし, この計算は必要となるまで計算されない. | |
352 直後にsprintを行うと, ただ\_が表示される. | |
353 | |
354 リストの長さを表示する関数であるlengthを実行後に sprint を行った場合は, | |
355 リストの要素数を確認しているため、要素数分のthunkを持つリストとなる. | |
356 | |
357 実際に値が必要となる関数を適用する. | |
358 head はリストの先頭要素を取得する関数である. | |
359 head を適用後に, sprint を行うと先頭要素だけが評価されたリストとなる. | |
360 | |
361 リストの例より, Haskell では本当に値が必要となるまで決して計算しないことが分かる. | |
362 | |
363 \subsubsection{引数のメモ化} | |
364 | |
365 Haskell の遅延評価では引数のメモ化を行う. | |
366 ある関数の仮引数が, その関数の本体に複数回出現したとしても評価回数が1回のみである. | |
367 例えば, ソースコード\ref{memo} は, 図 \ref{fig:memo} のようにメモ化される. | |
368 y は未評価のthunkとして同じ x を参照する. | |
369 そのため, x が2回評価されることはない. | |
370 | |
371 \newpage | |
372 \begin{lstlisting}[label=memo, caption=メモ化] | |
373 Prelude> let x = 1 + 2 :: Int | |
374 Prelude> let y = (x,x) | |
375 Prelude> :sprint y | |
376 y = (_,_) | |
377 \end{lstlisting} | |
378 | |
379 \begin{figure}[!htbp] | |
380 \begin{center} | |
381 \includegraphics[scale=0.6]{./images/memo.pdf} | |
382 \end{center} | |
383 \caption{メモ化の様子} | |
384 \label{fig:memo} | |
385 \end{figure} | |
386 | |
387 \clearpage | |
5
658281be77ec
describe the abstract
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
2
diff
changeset
|
388 \section{並列実行} |
47 | 389 Haskellはデフォルトではシングルスレッドで走る. |
390 並列に実行したい場合は, -threaded 付きでコンパイルし, RTS の -N オプションを付けて実行する. | |
60 | 391 -N オプションで指定された数だけ, OSのスレッドが立ち上がり実行される(ソースコード\ref{concurrent}). |
8 | 392 |
393 \begin{lstlisting}[language=bash, label=concurrent, caption=並列実行の様子] | |
394 $ ghc -O2 par.hs -threaded | |
395 $ ./par +RTS -N2 | |
396 \end{lstlisting} | |
397 | |
47 | 398 当然これだけでは並列に動かず, 並列に実行できるようにプログラムを書く必要がある. |
8 | 399 |
47 | 400 Control.Parallel.Strategies モジュールにある, |
401 Eval モナドを用いた並列化について説明する. | |
402 Eval モナドは並列処理を行うためのモナドである. Eval モナドで並列処理を行う使用例を示す(ソースコード\ref{src:evalmonad}). | |
39
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
403 |
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
404 %% 完全に動くプログラム |
47 | 405 \begin{lstlisting}[label=src:evalmonad, caption=Evalモナドの使用例] |
39
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
406 import Control.Parallel.Strategies |
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
407 |
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
408 main = print (runEval test) |
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
409 |
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
410 num :: Integer |
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
411 num = 1000000 |
8 | 412 |
47 | 413 |
39
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
414 test :: Eval (Integer, Integer) |
47 | 415 test = rpar (sum' 0 num) >>= (\a -> |
416 rpar (sum' num (num*2)) >>= (\b -> | |
417 return (a,b))) | |
8 | 418 |
39
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
419 sum' :: Integer -> Integer -> Integer |
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
420 sum' begin end = if begin < end |
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
421 then begin + (sum' (begin + 1) end) |
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
422 else begin |
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
423 \end{lstlisting} |
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
424 |
47 | 425 まず, Eval モナドが定義された, Control.Parallel.Strategies をロードし, Eval モナドを利用できるようにしている. |
39
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
426 |
47 | 427 Haskell のプログラムはmainという名前と実行したい関数を関連付けることで実行される. |
428 今回は, print (runEval test)が実行される. | |
8 | 429 |
60 | 430 並列処理を行いたい箇所には, rpar を使う. |
431 rpar の引数とした関数は並列に実行可能であることを示すことができる. | |
432 Eval モナドの関数の型をみると, rpar は, a を モナドに包み, 逆にrunEval はモナドから a を取り出している(ソースコード\ref{eval}). | |
433 rpar で並列化可能計算を示したあと, runEvalで実行するという流れになる. | |
434 | |
435 \begin{lstlisting}[label=eval, caption=Eval モナドの型] | |
8 | 436 data Eval a |
437 instance Monad Eval | |
438 | |
439 runEval :: Eval a -> a | |
39
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
440 rpar :: a -> Eval a |
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
441 \end{lstlisting} |
8 | 442 |
47 | 443 rpar を利用している test 関数は rpar モナドをbindで接続しているが, 入れ子構造となり書き方は煩雑となっている. |
444 Haskell にはモナドのために do 構文というシンタックスシュガーが用意されており, それを用いることでソースコード\ref{src:do}のように書くことができる. | |
445 do 構文を使うことでbindの入れ子構造を簡潔に書ける. | |
39
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
446 |
47 | 447 \begin{lstlisting}[label=src:do, caption=do構文] |
39
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
448 test :: Eval (Integer, Integer) |
47 | 449 test = do |
450 a <- rpar (sum' 0 num) | |
451 b <- rpar (sum' num (num*2)) | |
452 return (a,b) | |
8 | 453 \end{lstlisting} |
454 | |
47 | 455 sum' は2つの引数をとって, 開始点から終了点までの値をすべて足し合わせる関数である. |
456 並列処理に負荷を与えるために使う. | |
457 ifで, 開始点が終了点を超えてないか調べ, 超えてなければ再帰的に呼び出して足し合わせを行う. | |
458 | |
459 test で返ってくる型は, Eval (Integer, Integer)で, その後 runEval 関数を適用することで, (Integer, Integer)となる. | |
460 そして最後にprint で出力される. | |
8 | 461 |
47 | 462 Haskell は遅延評価を行うため, 必要となるまで式の評価が遅延される. |
463 今回の場合, mainのprintがなければそもそも計算が行われない(ソースコード\ref{src:donteval}). | |
464 | |
465 \begin{lstlisting}[label=src:donteval, caption=計算が行われない例] | |
466 main = return (runEval test) | |
467 \end{lstlisting} | |
39
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
468 |
47 | 469 出力することで, 値が必要となるため計算される. |
470 また, testで返される2つの計算を1つだけ出力するようにした場合, 1つのみ計算され並列実行は行われない(ソースコード\ref{src:donteval2}). | |
471 fst は, (a,b)とあったとき aを取得する関数である. | |
472 | |
473 \begin{lstlisting}[label=src:donteval2, caption=並列実行されない例] | |
474 main = print (fst (runEval test)) | |
475 \end{lstlisting} | |
8 | 476 |
47 | 477 Haskell で並列実行を行いたい場合は, 遅延評価に気をつける必要がある. |
48 | 478 rpar は単なるマーキングに過ぎず, rpar に渡したからといって直ちに並列実行が行われるわけではない. |
60 | 479 rpar で作成した Eval モナドを runEval に渡したあと, 値が必要となるprintなどを行えば, 並列に実行可能な部分が並列に実行される. |
39
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
480 |
47 | 481 また, rpar を使用する際, 別の計算の値に依存する計算がある場合, その2つの計算は並列実行できない. |
49 | 482 例えば, ソースコード\ref{src:rpar}の場合は並列実行ができない. |
16
eb6a70fc9c9f
describe the haskell
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
15
diff
changeset
|
483 |
47 | 484 \begin{lstlisting}[label=src:rpar, caption=前の計算に依存した計算] |
39
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
485 test2 :: Eval (Integer, Integer) |
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
486 test2 = do |
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
487 a <- rpar (sum' 0 num) |
a7981a22f12e
describe the eval monad
Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp>
parents:
38
diff
changeset
|
488 b <- rpar (sum' num (if a < num then a else (num*2))) |
9 | 489 return (a,b) |
8 | 490 \end{lstlisting} |
491 |