2009年09月26日

Yet Another Haskell Tutorial (和訳): 4.4 関数型

4.4 関数型

Hakell では、関数はれっきとしたクラス値である。つまり、1'c' が型を持つ値であるのとまったく同じである。これは、square++ といった関数についても言える。関数についてあれこれ述べる前に、少し横道に逸れてごく理論的なコンピュータサイエンスの分野に分け入って (それほど苦痛なものにはならないと思うのでご心配なく)、ラムダ計算について述べておく必要がある。

4.4.1 ラムダ計算

“ラムダ計算”という名称は、もしかするととっつきにくい感じを受けるかもしれないが、関数を表現するきわめて単純なシステムを表している。ラムダ計算風に平方関数を書くと、λx.x*x のようになる。これは、値を1つ取り、それを x と呼び (それが“λx.”の意味である)、x 同士を掛け合わせる、という意味だ。λは“ラムダ抽象”と呼ばれる。λはふつう、パラメータを1つだけ取ることができる。2つの数をとる関数を書きたいなら、はじめの部分をもう1つ用意して2番目に加え、λxλy.2*x+y のように書く。ラムダ式に値を適用するときは、一番外側のλを取り除き、ラムダ変数をすべて値に置き換える。たとえば (λx.x * x)5 を計算する場合、λを取り除き、x をすべて 5 に置き換え、(5 * 5) つまり 25 となる。
実際、Hakell はラムダ計算の拡張を元にしているところが多く、この2つの式は Hakell で直接記述できる (λをバックスラッシュに置き換え、. を矢印に置き換える。また、λは繰り返し書く必要は無い。そしてもちろん、Haskell では、関数を定義すればそれに名前をつけることができる)。

square = \x -> x*x
f = \x y -> 2*x + y

ラムダ式を対話シェルで計算することもできる。

Prelude> (\x -> x*x) 5
25
Prelude> (\x y -> 2*x + y) 5 4
14


2つめの例では、ラムダ抽象に引数を 2 つ (1つは x、もう1つは y) 与える必要があることがわかる。

4.4.2 高階型

関数には“高階型”という名称が与えられている。関数に与えられる型は、関数のラムダ計算の表現によく似ている。たとえば、平方は、λx.x * x と定義される。この型は、まず x の型が何であるかを考えてみるとわかる。たとえば xInt であったとしよう。すると、関数 squareInt を引数に取り、値 x*x を生成することがわかる。2つの整数値を掛け合わせると Int になることがわかっているので、squre の結果の型も Int である。ゆえに、squre の型は、IntInt であると言う。
前述の関数 f についても、同じように考察できる。この関数の値 (関数は値であることを思い出してほしい) は値 x を引数に取り、その値から新しい値を生成する。その値は値 y を引数に取り、2*x+y を生成する。たとえば f の場合、数値を 1 つだけ適用したとすると、(λxλy.2x + y)5 となり、新しい値 λy.2(5) + y となる。ここでは、x すべてを適用した値 5 で置き換えている。
だから、f は Int を引数に取り、何らかの型 (それが何だかはっきりしないが) の値を生成することは知っている。しかし、この値の型は λy.2(5) + y の型であることはわかっている。上記の考察を当てはめると、この式は IntInt という型をもつことがわかる。ゆえに、fInt を引数に取り、IntInt という型を持つ何かを生成する。だから、f の型は Int → (IntInt) である。

■注■ 括弧は必須ではない。関数型では、α → β → γ であれば β → γ がグループ化されているとみなされる。もし、α → β がグループ化されているとしたい場合は、そこを括弧で囲む必要がある。

これは完全に正確なわけではない。以前述べたとおり、5 のような数値は、実際には Int 型ではなく、Num a ⇒ a 型である。
Prelude 関数の型は、前述のとおり、":t" を使えば容易にわかる。

Prelude> :t head
head :: [a] -> a
Prelude> :t tail
tail :: [a] -> [a]
Prelude> :t null
null :: [a] -> Bool
Prelude> :t fst
fst :: (a,b) -> a
Prelude> :t snd
snd :: (a,b) -> b

これは次のような意味である。"head" は型 "a" の値を含むリストを 1 つ引数に取り、型 "a" の値を返す関数。"tail" は "a" のリストを 1 つ引数に取り、"a" のリストを返す。"null" は "a" のリストを 1 つ引数に取り、真偽値を返す。"fst" は型 "(a,b)" のペアを 1 つ引数に取り、型 "a" の何かを返す。などである。

■注■ fst の型が (a, b) → a であっても、必ずしも単純に最初の要素を返すことを意味するわけではない。返す値が最初の要素と型が同じであることを意味するだけである。

+*++: のような演算子の型を得ることもできる。ただし、括弧でくくる必要がある。ふつう、二項演算関数として使う関数 (つまり、2 つの引数の前ではなく、間に置く関数) はすべて、型を調べるときには括弧でくくる必要がある。

Prelude> :t (+)
(+) :: Num a => a -> a -> a
Prelude> :t (*)
(*) :: Num a => a -> a -> a
Prelude> :t (++)
(++) :: [a] -> [a] -> [a]
Prelude> :t (:)
(:) :: a -> [a] -> [a]

+* の型は同じで、+Num のインスタンスのある型 a に対し、型 a の値を 1 つ引数に取り、型 a の値を 1 つ引数に取って型 a の値を生成する別の関数を生成する。簡潔に言うなら、+ は型 a の値を 2 つ引数に取り、型 a の値を生成するとも言えるが、やや正確さに欠ける。
++ の型の意味は、簡潔に言えば、与えられた a に対し、++a のリストを2つ引数に取り、新たな a のリストを生成する、ということだ。同様に、: は型 a の値1つと型 [a] の別の値 (つまり a のリスト) 1つを引数に取り、型 [a] の別の値を生成する。

4.4.3 煩わしい型 IO

putStrLn といった関数の型を調べようと思うかもしれない。

Prelude> :t putStrLn
putStrLn :: String -> IO ()
Prelude> :t readFile
readFile :: FilePath -> IO String

いったい、この IO というのは何だろうか? 簡単にいってしまえば、関数が本当の関数ではない時、Haskell はこういう表現をするのだ。これを、"IO アクション" (以後、IO) と言う。すぐにこんな疑問が生じるだろう。それはそうと、IO を出させないようにするにはどうすればよいのだろうか? 手短に言うと、直接取り除くことはできない。つまり、IO String → String 型の関数を書くことはできない。IO 型の物を使うには、たとえば do 記述を使って別の関数と結びつけるしかない。
たとえば readFile を使ってファイルを読み込んでいるとき、おそらく、返される文字列に対して何かをしたいと思うだろう (そうでなければ、そもそもなぜファイルを読み込んだのだろう?)。String を引数に取り Int を生成する関数 f があるとしよう。f の入力は String だが、readFile の出力は IOString で一致しないため、readFile の結果を直接 f に入れることはできない。しかし、以下のようにすると結びつけることができる。

main = do
s <- readFile "somefile"
let i = f s
putStrLn (show i)

ここで、矢印記号を使って "IOアクションから文字列を取り出し"、その文字列 (s としている) に f を適用している。そして、たとえば i を画面上に出力する。ここで使われている let には対応する in が無いことに注意してほしい。これは、do ブロックの中だからである。また、f はIOアクションではなく単なる通常の関数なので、i <- f s とは書かないことにも注意が必要である。

4.4.4 明示的な型定義

以下のような理由で、ある要素や関数の型を明示的に指定したい時がある。
  • 明瞭性
  • 速度
  • デバッグ
トップレベルの関数は必ず型を指定するのがソフトウェア・エンジニアリング上正しいと考える人もいる。少なくとも、プログラムのコンパイル時に型エラーが出て理由がよくわからない時は、いくつかの関数の型を明示的に定義するとエラーがどこにあるのかわかりやすくなるかもしれない。
型定義は関数定義とは別に記述する。たとえば、以下のようなコードで square 関数の型を指定できる (明示的に定義した型を 型シグネチャ と言う)。

square :: Num a => a -> a
square x = x*x

この2行は、続けて記述する必要すらない。ただし、指定する型は、関数定義から推測される型と一致しなければならない。この定義では、square は Num のインスタンス (Int、Double、など) の平方が適用できる。ただし、squareInt型の値にだけ適用されるという前提条件を知っていれば、その型を以下のように精錬することができる。

square :: Int -> Int
square x = x*x

これで、squre は Int 型の値にのみ適用できる。さらにこの定義では、squre には Int 型の値しか適用しないことがわかっているので、コンパイラは変更前の定義で指定していたような一般的なコードを生成する必要が無く、実行速度の速いコードを生成することができるだろう。
もし、拡張機能を有効にしてあるならば (Hugs では "-98"、GHC(i)では "-fglasgow-exts")、関数だけでなく、拡張機能にも型シグネチャを追加できる。たとえば、以下のように書くことができる。

square (x :: Int) = x*x

この定義は、xInt であることをコンパイラに教えるが、式の残りの部分の型はコンパイラの推論に任されている。この例の squre の型は何だろうか? まずは考えてみよう。答えはこのコードをファイルに入力してインタープリタにロードするか、式の型を尋ねてみれば分かる。

Prelude> :t (\(x :: Int) -> x*x)

このラムダ抽象は上記の関数定義と同等である。

4.4.5 関数引数

第3.3節で、関数を引数に取る関数の例を考察した。たとえば、map は、関数を1つ引数に取り、その関数をリストの各要素に適用する。filter は、リストのどの要素を残すかを判定する関数を1つ引数に取る。foldl は、リストの要素の結合方法を与える関数を1つ引数に取る。Haskell のほかの関数と同様、これにもきちんとした型がある。
まず、map 関数について考えてみよう。map の役割は要素のリストを引数に取り、別の要素のリストを生成することだ。この2つのリストは、必ずしも同じ型の要素を持つ必要はない。だから、mapは、型 [a] の値を引数に取り、型 [b] の値を生成する。map 関数は、それをどのように行っているのだろうか? ユーザーが提供する関数を用いて変換を行っている。1 つの a を 1 つの b に変換するためには、この関数の型は a → b でなければならない。ゆえに、map の型は(a → b) → [a] → [b] である。これはインタープリタの ":t" で確かめられる。
同様の考察は filter にも当てはまり、(a → Bool) → [a] → [a] という型を持つことがわかる。foldl 関数については、(a → a → a) → a → [a] → a という型だと考えたくなるかもしれない。実際 foldl は、より一般的な型 (a → b → a) → a → [b] → a を持っている。だから、1 つの a と 1 つの b を 1 つの a に変える関数 1 つと、型 a の 初期値 1 つと、型 b のリスト 1 つを引数に取る。これは型 a を生成する。
これを確かめるには、与えられた制約を満たすリストのメンバーがいくつあるかを数える関数 count を書けばよい。もちろん filterlength を使ってもよいが、foldr を使うこともできる。

module Count
where
import Char
count1 p l = length (filter p l)
count2 p l = foldr (\x c -> if p x then c+1 else c) 0 l

count1 の機能は単純だ。述語 p に従ってリスト l にフィルターをかけ、その結果得られたリストの長さを引数に取る。一方、count2 は初期値 (これは整数値である) を使って現在のカウント値を保持する。count2 は、リスト l の各要素に対して、上記のラムダ式を適用する。このラムダ式は 2 つの引数を取る。現在のカウント値 c と、リスト中の対象となる要素 x である。ラムダ式は、xp を満たすかどうかを調べる。もし満たすなら、述語を満たす要素の数を 1 つ増やして c+1 を返す。満たさないなら、そのまま c を返す。

演習

演習4.3 以下の式を自分で計算せよ。また、もしそれが型を持つなら、その型を確かめよ。式が型エラーを起こすかどうかにも注意すること。

1. \x -> [x]
2. \x y z -> (x,y:z:[])
3. \x -> x + 5
4. \x -> "hello, world"
5. \x -> x 'a'
6. \x -> x x
7. \x -> x + x


前ページ「4.3 型クラス
次ページ「4.5 データ型


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 19:11 | 東京 ☁ | Comment(2) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年08月08日

Yet Another Haskell Tutorial (和訳): 4.3 型クラス

4.3 型クラス

前節で、5という数値に対して奇妙な型定義をしなければならないことがわかった。型クラスについて深く掘り下げて調べる前に少し話を戻し、そもそも型クラスを導入することになった動機について少し考察しよう。

4.3.1 動機

多くの言語 (C++、Javaなど) で、オーバーロードという仕組みが存在する。つまり、さまざまなパラメータ型をとるように、関数を記述することができる仕組みだ。たとえば、同値関数がそのよい例だ。2つの整数を比較する場合は整数の比較関数を使用する必要がある。2つの浮動小数点数を比較する場合は浮動小数点数の比較関数を使用する必要がある。2つの文字を比較する場合は文字の比較関数を使用する必要がある。一般に、型αのもの同士を比較する場合は、型αの比較関数が必要になるのだ。このαは型を値とする変数なので、型変数と呼んでいる。

■注■ 型変数には、ギリシャ文字の最初のほうの文字、α、β、γ、δ... を使うのがふつうである。

ところがこれは、静的型チェックにとっては都合が悪い。というのも、ある処理 (たとえば同値テスト) がどの型のためのものか、型チェック機能にはわからないからだ。この問題の解決策は、静的型言語ごとにすべて異なる (これは少し大げさかもしれないが、そう外れてもいないだろう)。Haskell が選んだのは型クラスという仕組みである。これが正しくて最良の解決策かどうかは、もちろん利用分野によって異なる。しかし、Haskell がこの方法を採用している以上、これに親しむにはどうすればよいかを考えるべきだろう。

4.3.2 同値テスト

同値テストの問題に戻ろう。やりたいことは、2つの同じ型 (αとする) を持つパラメータをとり、booleanを返す演算子 == (同値演算子) を定義することである。しかし、この関数はあらゆる型に対して定義することはできず、定義できる型は限られている。そこで、この関数 == を型クラスに対応付けし、その型クラスを Eq と呼ぶことにする。もし、特定の型αが、ある型クラスに属する (つまり、そのクラスに関連づけられている関数は、すべてαに対して実装されている) ならば、αはそのクラスのインスタンスであるという。たとえば、等号は整数に対して定義されているので、IntEq のインスタンスである。

4.3.3 クラス Num

==のような演算子のオーバーロード以外に、Haskell には定数のオーバーロード (たとえば、1, 2, 3, など) がある。この仕組みにより、たとえば、5 のような数値を入力した場合、コンパイラは適切と思われるものに応じて、整数とも浮動小数点型の数とも解釈できるようになる。クラス Num は、このような数値すべてと、最低限の演算子 (たとば、加算など) を含むものとして定義される。基本的な数値型 (Int、Double) は、Num のインスタンスとして定義される。
ここでは、型クラスの威力 (と複雑さ) を、表面的にさっと触れるにとどめる。型クラスについては、8.4節で詳しく述べるが、その前にその背景についてもっと知っておく必要がある。その前に、関数についてもう少し述べる必要がある。

4.3.4 クラス Show

Haskell のもう1つの標準的なクラスは Show である。クラス Show のメンバーの型には、その型の値を文字列に変換する関数がある。この関数を show と言う。たとえば、整数 5 に show を適用すると文字列 "5" になり、文字 'a' に show を適用すると 3 文字の文字列 "'a'" になる (最初と最後の文字は、アポストロフィ)。文字列に show を適用すると、文字列が引用符でくくられるだけだ。これは、インタープリタで試すことができる。

Prelude> show 5
"5"
Prelude> show 'a'
"'a'"
Prelude> show "Hello World"
"\"Hello World\""


■注■最後の行にバックスラッシュが表示されているのは、内側の引用符がエスケープされているからである。つまり、内側の引用符は文字列の一部であり、インタープリタが値を出力する引用符ではないことを意味する。実際の文字列に、バックスラッシュは含まれない。

Show のインスタンスではない型もある (たとえば関数)。関数 (sqrt など) を show しようとすると、コンパイラまたはインタープリタは、インスタンスの定義が存在しないかクラス制約が不正である、という謎めいたエラーメッセージを出力するだろう。

前ページ「4.2 多相型
次ページ「4.4 関数型


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 22:31 | 東京 🌁 | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年05月10日

Yet Another Haskell Tutorial (和訳): 4.2 多相型

4.2 多相型

Haskellは多相型システムを採用している。これは本質的には、以前それとなく言及しておいた型変数というものを持つことができるということだ。たとえば、tailのような関数は、リストの要素が何であってもかまわないことに注意してほしい。

Prelude> tail [5,6,7,8,9]
[6,7,8,9]
Prelude> tail "hello"
"ello"
Prelude> tail ["the","man","is","happy"]
["man","is","happy"]

このようなことが可能なのは、tail が多相型 ([α] → [α]) を持つからである。つまり、tailあらゆるリストを引数に取ることができ、同じ型のリストを返す。
同じ分析により、fst の型も説明できる。

Prelude> :t fst
forall a b . (a,b) -> a

ここで、GHCi は型の値の完全修飾名を明らかにしている。つまり、すべての型 ab について、 fst(a, b) から a への関数であることを示している。

演習

演習 4.2 以下の式を自分で計算し、もしそれが型を持つならその型を確かめよ。また、式が型エラーであるかどうか注意せよ。
1. snd
2. head
3. null
4. head . tail
5. head . head


前ページ「4.1 単純型
次ページ「4.3 型クラス


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 01:14 | 東京 ☀ | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年05月05日

Yet Another Haskell Tutorial (和訳): 4.1 単純型

4.1 単純型

組み込まれている型には、Int (正負両方の整数)、Char (文字)、String (文字列) など多数ある。Char型に属する式についてはすでに見たので、String型に属する式を調べてみよう。

Prelude> :t "Hello"
"Hello" :: String

もっと複雑な式を入力することもできる。たとえば、同値であることを調べてみよう。

Prelude> :t 'a' == 'b'
'a' == 'b' :: Bool

この式が偽であっても、ある型、すなわちBool型を持つことに注意が必要である。

■注■ Bool は Boolean (通常は "ブーリアン" と読むが、1度か2度 "ブーリーン" と言うのを聞いたことがある) の短縮形で、取り得る値は True または False のどちらかである。

型に誤りのある式の型をシェルに出力させれば、型チェックや型推論がどのように処理されるか観察できる。たとえば、等号は2つの引数の型は同じ型に属する必要がある。文字と文字列を比較させようとすれば、CharStringが異なる型であることがわかる。

Prelude> :t 'a' == "a"
ERROR - Type error in application
*** Expression : 'a' == "a"
*** Term : 'a'
*** Type : Char
*** Does not match : [Char]

エラーの1行目 ("Expression" と書かれている行) は、型エラーが起きている式を教えている。2行目は、この式のどの部分の型が正しくないかを示している。3行目はこの項がどの型に推論されたかを示し、4行目はどの型に一致する必要があったかを示している。この場合は、Char型が[Char]型 (文字のリスト。Haskellの文字列は文字のリストとして表現される) に一致していないと言っている。
前述のように、:: 演算子を使えば式の型を明示的に指定できる。たとえば、上の例で "a" と書く代わりに ("a"::String) と書くこともできた。この場合、"a" には1とおりの解釈しか存在しないため何も変化しない。しかし、数値の場合を考えてみよう。以下を試してほしい。

Prelude> :t 5 :: Int
5 :: Int
Prelude> :t 5 :: Double
5 :: Double

このように、数値5はIntDoubleのどちらにインスタンス化させることもできることがわかる。型を指定しないとどうなるだろうか?

Prelude> :t 5
5 :: Num a => a

予想と違っていたのではないだろうか? 手短に説明すると、これが意味するのは、ある型 a がクラス Num のインスタンスなら、式 5 の型は型 a に属するということである。もし意味がわからなくても、今はかまわない。第4.3節で型クラス (ここで述べているもの) について広範囲に論じる。この読み方だが、「Num のインスタンスである a が a を実装している」となる。

演習

演習 4.1 以下の式を自分で計算し、もしそれが型を持つならその型を確かめよ。また、式が型エラーであるかどうか注意せよ。
1. 'h':'e':'l':'l':'o':[]
2. [5,'a']
3. (5,'a')
4. (5::Int) + 10
5. (5::Int) + (10::Double)


前ページ「4 型の基礎
次ページ「4.2 多相型


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 20:28 | 東京 ☔ | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年05月03日

Yet Another Haskell Tutorial (和訳): 4 型の基礎

第4章

型の基礎


Haskellは静的型チェックという仕組みを使用している。つまり、Haskell内ではあらゆる式にが割り当てられる。たとえば、'a' は "character" を意味するChar型を持つ。そして、ある型の引数をとる関数に誤った型を与えると、コンパイル時エラーになる (つまり、そのプログラムをコンパイルできない)。これにより、プログラムに紛れ込むバグの数を劇的に減らすことができる。
さらに、Haskellは型推論という仕組みを使用している。つまり、式に型を指定しなくてもよい。ちなみに、Cでは変数を定義するとき (たとえば、int、charなどの) 型を指定する必要がある。Haskellではその必要がない。型は状況に応じて推論される。

■注■ 確かに、式に対して明示的に型を指定することも可能ではある。そうしておくとデバッグの際に役立つことが多い。実際、最外関数の型は明示的に指定するのがよいとされることもある。

HugsとGHCiのどちらにも、型推論を適用して式の型を見つけ出す機能が用意されている。:tコマンドを使用すればそれが可能だ。たとえば、お気に入りのシェルを起動して以下のようにしてみよう。

Prelude> :t 'c'
'c' :: Char

これは、式 'c' が Char という型を持つことを示している (Haskell では、二重コロン :: を使って型を指定する)。

前ページ「3.8 対話処理
次ページ「4.1 単純型


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 02:47 | 東京 ☀ | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年04月25日

Yet Another Haskell Tutorial (和訳): 3.8 対話処理

3.8 対話処理

他の (命令的) 言語に関する本をよく読む人なら、他の言語のチュートリアルでよく書かれているプログラム (ユーザーに名前を尋ね、その名前を使って挨拶を返すようなもの) がなぜ出てこないのか、不思議に思うかもしれない。その理由は単純だ。純粋な関数型言語であるため、ユーザー入力などの処理をどのように扱えばよいかがあまりはっきりしていないからだ。
何はともあれ、キーボードからの入力文字列を読み込む関数があったとしよう。この関数を2度呼び出し、1度目に何かを入力し、2度目にまた別の何かを入力したとき、返される2つの値が異なるなら、それはもはや関数ではない。この問題に対する解決策は、構成的数学の1分野である圏論の底のほうから見いだすことができる。それがモナド (monad) である。まだ正式にモナドについて論じる準備はできていないが、ここでは単純に、モナドは入出力のような操作を表現する便利な手段であると考えることにしよう。ここで触れるモナドの詳細は第5章で論じている。また、第9章をモナドについての章に充てている。
さて、対話的な関数を書こうとしているとしよう。そのためには、キーワード do を使用する。doを使えば処理の順序を指定できる (Haskellはlazyな言語なので、通常は処理が評価される順序を指定できないことを思い出してほしい)。つまり、ユーザーに名前と住所を直接尋ねる単純なプログラムを書くには、以下のようなコードを "Name.hs" に入力する。

module Main
where

import IO

main = do
hSetBuffering stdin LineBuffering
putStrLn "Please enter your name: "
name <- getLine
putStrLn ("Hello, " ++ name ++ ", how are you?")


■注■ 2つめのputStrLnは括弧が必要だが、1つめでは不要である。それは、++より関数のほうが結びつきが強いからだ。つまり、2つめは括弧を付けないと(putStrLn "Hello, ") ++ name ++ ...と解釈されてしまう。

このコードをインタープリタにロードし、"main" と入力すれば実行できる。コンパイルしてコマンドラインから実行することもできる。以下に示したのは、対話モードでの実行結果だ。

Main> main
Please enter your name:
Hal
Hello, Hal, how are you?
Main>

ここでは対話処理を行っている。さて、コードに戻って少し見てみよう。コンパイルできるように、モジュール名を "Main" としている。プログラムの起動時に実行する関数がどれかコンパイラがわかるように、中心となる関数は "main" という名前にする。4行目でIOライブラリをインポートし、IO関連の関数を利用できるようにしている。7行目はdoから始まっている。このようにすると、Haskellはコマンドを順次実行する。
最初のコマンドは hSetBuffering stdin LineBuffering だが、おそらくこれは、今は無視した方がよいだろう (これはたまたまGHCでのみ必要であり、Hugsではこの記述が無くても動作する)。GHCでこれが必要なのは、GHCは入力を読み込むときかなり大きなブロック単位で読もうとするからだ。このブロックがいっぱいになるほど長い名前の人はどこにもいない。そのため、標準入力から読み込もうとすると、GHCはブロック全部にデータが入るまで待ち状態になってしまう。ここではこの機能を無効にするために、ブロックバッファの代わりに LineBuffering を使用するよう指定している。
次のコマンドは putStrLn で、画面上に文字列を出力する。9行目では "name <- getLine" と記述している。"name = getLine" と書くのがふつうだが、等号の代わりに矢印を使うと getLine が本当の関数ではなく、異なる値を返すこととがあることを表している。このコマンドは、「アクション getLine を実行し、その結果を name に格納せよ」という意味になる。
最後の行は、直前の行で読み出した内容から文字列を生成し、画面上に表示している。
本当の関数ではない例をもう1つ挙げると、乱数値を返すものがある。ここでは、その関数を randomRIO と呼ぶことにする。これを使えば「番号当て」プログラムを書くことができる。以下のコードを "Guess.hs" に入力する。

module Main
where

import IO
import Random

main = do
hSetBuffering stdin LineBuffering
num <- randomRIO (1::Int, 100)
putStrLn "I’m thinking of a number between 1 and 100"
doGuessing num

doGuessing num = do
putStrLn "Enter your guess:"
guess <- getLine
let guessNum = read guess
if guessNum < num
then do putStrLn "Too low!"
doGuessing num
else if read guess > num
then do putStrLn "Too high!"
doGuessing num
else do putStrLn "You Win!"

このコードを試してみよう。5行目で "import Random" と記述して、コンパイラに乱数関連の関数を使おうとしていることを伝えている (Preludeに乱数関数は組み込まれていない)。mainの最初の行で、(1, 100) の範囲の乱数を尋ねている。浮動小数点や他の数値ではなく、ここでは整数値を使用しようとしていることをコンパイラに伝えるために、::Int と書く必要がある。これについての詳細は、第4章で述べる。次の行で、ユーザーに何が起きているかを伝え、main の最後の行でコマンド doGuessing を実行するようコンパイラに伝えている。
関数 doGuessing は、ユーザーが推測した数を引数に取る。まず、ユーザーに推測するよう尋ね、キーボードから推測した値 (これは String である) を受け取る。まず、if文で推測した値が小さ過ぎないか確認する。ただし、guess が文字列で num が整数なので、まず guessread して整数値に変換する必要がある。read guessは単なる純粋な関数なので (そして、IO操作も行わないので)、<- と記述する必要はない (実際のところ、そのように記述することもできない)。ここでは単純に、値をguessNumに代入する。do記述の中では、letに対するinは必要ない。
入力した値が小さ過ぎた場合はメッセージを表示し、ふたたび doGuessing を開始する。そうでなければ、入力した値が大き過ぎないか調べる。大き過ぎる場合はメッセージを表示し、ふたたび doGuessing を開始する。そうでなければ、小さ過ぎもせず、大き過ぎもしない数値を入力したことになり、値が正しかったはずである。そこで、正解したことを表示して終了する。実際には、それ以降コマンドが存在しないことにより暗黙のうちに終了している。明示的に return () 文を記述する必要はない。
このコマンドはコンパイルすることもできるし、インタープリタにロードすることもできる。以下のようになる。

Main> main
I’m thinking of a number between 1 and 100
Enter your guess:
50
Too low!
Enter your guess:
75
Too low!
Enter your guess:
85
Too high!
Enter your guess:
80
Too high!
Enter your guess:
78
Too low!
Enter your guess:
79
You Win!

ここで取り上げた再帰的なアクションは、実のところ、値を返してどこかで使うようなことはしていない。返り値を使う場合、コマンドを「陽に」記述するのは正しくない。ここでは、まず正しくない例を取り上げ、それが正しくない理由を説明し、そして正しい例を取り上げる。
たとえば、ユーザーに対して繰り返しいくつかの単語を入力するように求める単純なプログラムを書いているとしよう。ユーザーが空の単語を入力した場合 (つまり、何も入力せずにENTERキーを押した場合)、プログラムはそれまでにユーザーが入力したものをすべて出力して終了する。このプログラムの中心となる関数 (実際にはアクション) は、ユーザーに単語の入力を求め、それが空かどうかを確かめ、続行または終了するものである。正しくない実装例は、たとえば以下のようなものだ。

askForWords = do
putStrLn "Please enter a word:"
word <- getLine
if word == ""
then return []
else return (word : askForWords)

この先に読み進める前に、このコードのどこが間違っているのかわかるかどうか試してみること。
誤りは最後の行、word : askForWords のところにある。(:) を使うと、要素 (この場合は word) と別のリスト (この場合は askForWords) からリストを作ることを思い出してほしい。ところが、askForWords はリストではなく、実行するとリストを生成するアクションである。つまり、先頭に何かを追加する前に、アクションを実行して結果を得ておく必要がある。この例では、以下のようにしたいのだ。

askForWords = do
putStrLn "Please enter a word:"
word <- getLine
if word == ""
then return []
else do
rest <- askForWords
return (word : rest)

ここでは、まず askForWords を実行しでその結果を変数 rest に格納している。その後でwordrest から生成されるリストを返している。
これで、単純な関数を書いてコンパイルし、関数とプログラムを対話環境で試し、リストを操作する方法をよく理解できたことと思う。

演習

演習 3.10 ユーザーが0を入力するまで繰り返し数字を尋ね、0が入力されたら数字すべての合計と積、および各数の階数値を出力するプログラムを書け。たとえば、一連の流れは以下のようになる。

Give me a number (or 0 to stop):
5
Give me a number (or 0 to stop):
8
Give me a number (or 0 to stop):
2
Give me a number (or 0 to stop):
0
The sum is 15
The product is 80
5 factorial is 120
8 factorial is 40320
2 factorial is 2

ヒント:番号を読み出し、もしそれが0なら空のリストを返すIOアクションを書く。0なら自分自身を再帰的に呼び出して読み出した数値と再帰呼び出しの結果からリストを作成する。


前ページ「3.7 再帰呼び出し
次ページ「4 型の基礎


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 23:41 | 東京 ☔ | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年03月16日

Yet Another Haskell Tutorial (和訳): 3.7 再帰呼び出し

3.7 再帰呼び出し

CやJavaのような命令的言語でもっとも基本的な制御構造は (for などの) ループである。しかし、forループは破壊的更新を必要とするためHaskellではあまり意味を持たない。その代わりに、Haskellでは再帰呼び出しを使用する。
関数がそれ自身を呼び出すとき、その関数は再帰的であるという (詳細は付録Bを参照)。再帰的関数はCやJavaにも存在するが、関数型言語に比べれば使われることが少ない。典型的な再帰的関数は階数関数だ。命令的言語では、再帰的関数を以下のように書くかもしれない。

int factorial(int n) {
int fact = 1;
for (int i=2; i <= n; i++)
fact = fact * i;
return fact;
}

このコードは正の整数については階数の値を正常に計算するが、階数の基本的な定義をいくつか無視している。階数は通常、以下のように定義される。

n! = 1 when n = 1, n * (n − 1)! Otherwise

この定義自体が再帰的な定義となっている。つまり、n! の値が (n – 1)! の値に依存している。! を関数としてとらえるなら、! は自分自身を呼び出していることになる。この定義はほぼ一字一句Haskellコードに変換できる。

factorial 1 = 1
factorial n = n * factorial (n-1)

おそらくこれより簡単な再帰的関数を見たことがないだろうが、これは文法的に正しい。

■注■ もちろん、再帰呼び出しを命令的に記述することもできる。

int factorial(int n) {
if (n == 1)
return 1;
else
return n * factorial(n-1);
}

しかし、おそらく C でループを使うよりもずっと遅いだろう。

再帰呼び出しは習得しにくい場合がある。再帰呼び出しは数学的帰納法の概念と酷似している (付録Bで、この問題について詳細に扱っている)。しかし通常は、1つまたは複数の初期値と、1つまたは複数の再帰的定義を持つものとして問題を考えることができる。階乗の場合、初期値が1つ (n = 1 の場合) と、再帰的定義が1つ (n > 1 の場合) ある。独自の再帰的アルゴリズムを設計する場合、この2つを変えてみると役立つことが多い。
ここで、累乗の処理に話を移そう。2つの正の整数abがあったとき、abを計算したい。この問題には、初期値が1つある。つまり、bが1のときだ。そして、b > 1 の場合が再帰的定義となる。一般形は以下のように書ける。

a^b = a when b = 1, a * a^(b−1) otherwise

これもHaskellコードに直接変換される。

exponent a 1 = a
exponent a b = a * exponent a (b-1)

整数に対する再帰的関数を定義するのとまったく同様に、リストに対する再帰的関数を定義できる。この場合、初期値は通常空のリスト [] で、再帰的定義はリストのコンス (つまり、別のリストにコンスした値) である。
リストの長さを計算する場合を考えてみよう。これもまた2つに分けることができる。リストが空の場合と空でない場合だ。明らかに空のリストの長さは0だ。また、コンスリストの場合は、このリストの長さは第1要素を除いたリストの長さに1を加えた値に等しい。したがって、長さを求める関数は以下のように定義できる。

my_length [] = 0
my_length (x:xs) = 1 + my_length xs


■注■ 以下、Haskellの標準関数に別の定義を与える場合は、必ず関数名の先頭にmy_をつけてコンパイラが混乱しないようにする。

filter関数も同様に考えることができる。これもやはり、初期値は空のリストで、再帰的定義はコンスリストだ。ただし今回は、特定の述語が成立するかどうかに応じてその要素を破棄するかどうか選択する。フィルタ関数は以下のように定義できる。

my_filter p [] = []
my_filter p (x:xs) =
if p x
then x : my_filter p xs
else my_filter p xs

このコードでは空のリストを与えると単純に空のリストを返している。というのも、フィルタは要素を取り除くだけで、加えることはありえないからだ。
(x:xs)という形のリストに対しては、値xを削除するかどうか判断する必要がある。そのためにif文と述語pを使う。p xが真なら、xで始まり、第1要素を除いたリストをフィルタリングしたものが続くリストを返す。p xが偽なら、xを取り除き、第1要素を除いたリストをフィルタリングしたものを返す。
mapや2つの折りたたみ関数も明示的な再帰呼び出しを用いて定義できる。mapの定義は演習を、折りたたみ関数の定義は第7章を参照のこと。

演習

演習 3.7 フィボナッチ数列は以下のように定義される。

F(n) = 1 when n = 1 or n = 2, F(n−2) + F(n−1) when otherwise

正の整数nをパラメータにとり、Fnを計算する再帰的関数fibを書け。

演習 3.8 2つの正の整数abをとり、a*bを返す再帰的関数multを定義せよ。ただし、加算のみを使用すること (つまり、乗算を使用することを禁ずる)。まずは、前問およびこの節の残りの部分のように数学的定義を書くこと。

演習 3.9 標準関数mapとは独立に動作する再帰的関数my_mapを定義せよ。


前ページ「3.6 コメント
次ページ「3.8 対話処理


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 15:18 | 東京 ☀ | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年03月13日

Yet Another Haskell Tutorial (和訳): 3.6 コメント

3.6 コメント

Haskellには2種類のコメントがある。行コメントとブロックコメントだ。行コメントはトークン--で始まり、その行の末尾までがコメントとなる。ブロックコメントは{-で始まり、対応する-}までがコメントとなる。ブロックコメントはネストさせることができる。

■注■ Haskellの--はC++やJavaの//に対応し、{-と-}は/*と*/に対応する。

コメントはプログラムを言葉で説明するのに使われ、コンパイラとインタープリタからは完全に無視される。たとえば、以下のとおりである。

module Test2
where

main =
putStrLn "Hello World" -- write a string
-- to the screen

{- f is a function which takes an integer and
produces integer. {- this is an embedded
comment -} the original comment extends to the
matching end-comment token: -}
f x =
case x of
0 -> 1 -- 0 maps to 1
1 -> 5 -- 1 maps to 5
2 -> 2 -- 2 maps to 2
_ -> -1 -- everything else maps to -1

このプログラム例では、行コメントと (埋め込まれた) ブロックコメントの両方の使用例を示している。

前ページ「3.5 関数
次ページ「3.7 再帰呼び出し


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 08:13 | 東京 ☀ | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年03月10日

Yet Another Haskell Tutorial (和訳): 3.5 関数

3.5 関数

コードをファイルに書く方法を学んだので、関数を書き始めることができる。関数型言語であることから予想がつくとおり、関数がHaskellの中心である。つまり、プログラムの評価は関数の評価に過ぎない。
数値の2乗を計算する簡単な関数を書き、ファイルTest.hsに入力することができる。この関数は以下のように定義できる。

square x = x * x

この関数定義では、引数 (パラメータともいう) を1つとるsquareという関数を定義しており、引数名をxとしている。そして、square xの値がx * xに等しいとしている。
Haskellは標準的な条件式もサポートしている。たとえば、引数が0未満の場合は-1を、引数が0の場合は0を、引数が0より大きい場合は1を返す関数を定義できる (これを符号関数 (signum function) という)。

signum x =
if x < 0
then -1
else if x > 0
then 1
else 0

この関数を試すと、以下のようになる。

Test> signum 5
1
Test> signum 0
0
Test> signum (5-10)
-1
Test> signum (-1)
-1

最後の例で、"-1" には括弧をつける必要がある。括弧をつけないとsignumという値から1を引こうとしているとシステムに解釈されてしまい、型が正しくない。
Haskellのif/then/else構造は、他のほとんどの言語ととてもよく似ている。しかし、then節とelse節の両方が必須である。条件式は条件を評価する (この場合は、x < 0。もしこれがTrueと評価されれば条件thenを評価する。Falseと評価されれば条件elseを評価する)。
ファイルを編集してインタープリタにロードし直せば、このプログラムをテストできる。作業中のモジュールがすでにTestになっている場合は、再度:l Test.hsと入力する代わりに、:reloadまたは:rと入力するだけでリロードされる。通常は、このほうがずっと早い。
Haskellも、他の多くの言語と同じようにcase構文をサポートしている。case構文は、確かめたい値が複数存在する場合に使われる (case式は、実際にはそれよりももう少し強力な機能を持つ。詳細は7.4節を参照)。
引数が0なら1、1なら5、2なら2、それ以外なら-1となる関数を定義したいとしよう。この関数をif文を使って書くと長くてかなり読みにくいものとなるだろう。そこで、以下のようにcase文を使って書く (この関数をfとする)。

f x =
case x of
0 -> 1
1 -> 5
2 -> 2
_ -> -1

このプログラムでは、引数xをとり、xの値を調べる関数fを定義している。もしxの値が0に等しければ、fの値は1になる。xの値が1に等しければ、fの値は5になる。xの値が2に等しければ、fの値は2になる。そして、その時点でどれにも等しくなければ、fの値は-1になる (アンダースコアは、すべてにマッチする「ワイルドカード」とみなされる)。
ここでは、字下げが重要である。Haskellでは、コードを構造化するために「レイアウト」という仕組みが使われている (プログラミング言語Pythonが同様の仕組みを使用している)。CやJavaではセミコロンや中括弧が必須だが、レイアウトシステムを使えばセミコロンや括弧を明示的に使うことなくコードを書くことができる。

■警告■ Haskellは空白文字が重要なので、タブを使っているのかスペースを使っているのか注意する必要がある。エディタでタブを使わないように設定できるなら、おそらくそのように設定しておいたほうがよい。もしなければ、タブを8桁にしておけば問題が起きにくくなる。

レイアウトは一般的に次のように解釈される。キーワードwhereletdoofの後に左中括弧が挿入され、次のコマンド行のインデント位置が記憶される。次の行以降、インデント位置が同じであれば直前の行末にセミコロンが挿入される。次の行のインデント幅が狭ければ、右中括弧が挿入される。複雑に感じるかもしれないが、これらのキーワードの後のインデントに関する原則に従えば覚える必要はなくなる (レイアウトに関する詳細な説明は7.11節を参照)。
レイアウトを使うことを好まず、中括弧とセミコロンを明示的に使う人もいる。そういった書き方もまったく問題なくできる。その書式の場合、上の関数は以下のようになる。

f x = case x of
{ 0 -> 1 ; 1 -> 5 ; 2 -> 2 ; _ -> 1 }

もちろん、中括弧とセミコロンを明示的に使えば、コードを自由な構造で書くことができる。以下のコードも同じく有効である。

f x =
case x of { 0 -> 1 ;
1 -> 5 ; 2 -> 2
; _ -> 1 }

しかし、コードの構造をこのようにすると、コードが読めなくなるだけだ (この例のように)。
関数は部分的に定義していくこともできる。つまり、あるパラメータに対する関数の内容を書き、そして別のパラメータに対する別の内容を書くのだ。たとえば、上の関数fは、以下のように書くこともできる。

f 0 = 1
f 1 = 5
f 2 = 2
f _ = -1

このとき、順序が重要となる。最後の行を最初に書くとすべての引数にマッチすることになり、fは引数によらず-1を返すようになる (ただし、ほとんどのコンパイラは、パターンが重なり合っているなどといった警告を表示する)。この最後の行を指定しないと、0、1、2以外が指定されるとfはエラーとなる (ほとんどのコンパイラは、この場合もパターンが完全ではないなどといった警告を表示する)。このように部分的に定義していく方法はよく使われ、このチュートリアルでもかなり頻繁に使用する。fのこの2種類の定義は実際に同等で、部分的に定義していく方法はcase式に変換される。
単純な関数から、関数合成 (function composition) を使ってより複雑な関数を構築できる。関数合成は単純に、ある関数の適用結果を別の関数の引数として使う方法である。この方法については、すでに計算に関する節 (3.1節) で、5*4+3について書いたところで述べている。その例では、5 * 4を評価し、その結果に+ 3を適用している。関数squrefでも同じことができる。

Test> square (f 1)
25
Test> square (f 2)
4
Test> f (square 1)
5
Test> f (square 2)
-1

上記の関数の適用結果はじつに明確だ。内側の関数を囲む括弧は必須である。括弧をつけないと、たとえば最初の行ではインタープリタは "square f" の値を得ようとしていると解釈し、意味をなさなくなる。このような関数の適用方法は、ほとんどのプログラミング言語でごく一般的である。もう1つ、より数学的指向性の高い関数合成の表現方法がある。それは、関数 (.) (ピリオド1つ) を使う方法だ。この関数 (.) は、数学の演算子 (o) に見立てたものだろう。

■注■数学ではf o gと書くと「gの適用後にfを適用」を意味し、Haskellでもf . gと書くと「gの適用後にfを適用」を意味する。
f o gは単純に、(f o g)(x) = f(g(x))を意味する。つまり、値xに関数f o gを適用することは、gを適用した後にfを適用することに等しい。

関数 (.) (関数合成関数という) は2つの関数を引数にとり、それを1つの関数にする。たとえば (square . f) と書くと、1つの引数をとり、その引数にfを適用した後、その結果にsquareを適用する新しい関数が生成されることを意味する。逆に (f . square) は、1つの引数をとり、その引数にsquareを適用した後、その結果にfを適用する新しい関数が生成されることを意味する。これまでと同様、テストすればこのことがわかる。

Test> (square . f) 1
25
Test> (square . f) 2
4
Test> (f . square) 1
5
Test> (f . square) 2
-1

ここで、関数合成は括弧で囲まなければならない。そうしないとHaskellコンパイラは、最初の例では f 1 の値にsquareを合成しようとしていると解釈するが、f 1 は関数でさえないので意味をなさない。
おそらく、ここでいったん話を中断し、Preludeで定義されている関数をいくつか見てみるのが賢明だろう。まず間違いなく、既存の関数を書き換えてしまうミスをいずれ起こすものだ (私は数え切れないほどそういう誤りを犯した)。しかし、それを最低限に抑えられればかなり時間を節約できる。以下、簡単な関数をいくつか挙げる。そのうちのいくつかは、すでに触れたものだ。
sqrt平方根関数
id恒等関数。id x = x
fstペアから最初の要素を取り出す
sndペアから2番目の要素を取り出す
nullリストが空かどうかを返す
head空でないリストの最初の要素を返す
tail空でないリストの最初の要素以外を返す
++2つのリストを結合させる
==2つの要素が等しいかどうか調べる
/=1つの要素が等しくないかどうか調べる

以下、各関数の使用例を示す。

Prelude> sqrt 2
1.41421
Prelude> id "hello"
"hello"
Prelude> id 5
5
Prelude> fst (5,2)
5
Prelude> snd (5,2)
2
Prelude> null []
True
Prelude> null [1,2,3,4]
False
Prelude> head [1,2,3,4]
1
Prelude> tail [1,2,3,4]
[2,3,4]
Prelude> [1,2,3] ++ [4,5,6]
[1,2,3,4,5,6]
Prelude> [1,2,3] == [1,2,3]
True
Prelude> 'a' /= 'b'
True
Prelude> head []
Program error: {head []}

空のリストにheadを適用するとエラーになることがわかる (正確なエラーメッセージはGHCiを使っているかHugsを使っているかにより異なる。上の例はHugsのエラーメッセージである)。

3.5.1 束縛let

関数内でローカル定義を利用したくなることがよくある。たとえば、学校で習った数学を思い出してみよう。以下の等式は ax2 + bx + c = 0 の形の多項式の根 (零点) を求めるのに使われる。x = (−b ± √b2 − 4ac)/2a この2種類の x の値を計算する、以下のような関数が記述できるだろう。

roots a b c =
((-b + sqrt(b*b - 4*a*c)) / (2*a),
(-b - sqrt(b*b - 4*a*c)) / (2*a))

この問題を改善するために、Haskellではローカル束縛が可能となっている。ローカル束縛とは、その関数だけが参照できる値を関数内に作れるというものだ。たとえば、sqrt(b*b-4*a*c) のローカル束縛を作成し、それにdet などの名前を付け、sqrt(b*b-4*a*c) が現れる箇所を両方ともそれに置き換えてみよう。これは、let/in 定義を使って実現できる。

roots a b c =
let det = sqrt (b*b - 4*a*c)
in ((-b + det) / (2*a),
(-b - det) / (2*a))

じつは、let内には複数の定義を書くこともできる。なお、レイアウトの問題が生じないように必ずインデント幅を同じにしておくこと。

roots a b c =
let det = sqrt (b*b - 4*a*c)
twice_a = 2*a
in ((-b + det) / twice_a,
(-b - det) / twice_a)


3.5.2 2項演算関数

2項演算関数は、文字というより記号から成る関数である。たとえば、(+)(*)(++)はすべて2項演算関数である。2項演算関数は、括弧で囲めば前置形式で使える。したがって、以下の2つの式は同じである。

Prelude> 5 + 10
15
Prelude> (+) 5 10
15

同様に、(mapのような) 前置関数はバッククォート (英語キーボードでは、チルダキーをクリックすると表示される) で囲めば2項演算関数にすることができる。

Prelude> map Char.toUpper "Hello World"
"HELLO WORLD"
Prelude> Char.toUpper `map` "Hello World"
"HELLO WORLD"


■警告■ Hugsユーザーの場合: HugsではChar.toUpperのような修飾付きの名前は好まれない。Hugsでは、単にtoUpperを使う。


前ページ「3.4 ソースコードファイル
次ページ「3.6 コメント


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 21:02 | 東京 ☁ | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年03月02日

Yet Another Haskell Tutorial (和訳): 3.4 ソースコードファイル

3.4 ソースコードファイル

プログラマとしては、単にこのような小さな式を評価したいのではない。椅子に座り、好みのエディタでコードを書き、保存して使いたいのだ。
すでに2.2節と2.3節で、Hello Worldプログラムの書き方とそのコンパイルのしかたを学んだ。ここでは、ソースコードファイル内で定義されている関数を対話環境で利用する方法を紹介する。そのために、Test.hsというファイルを作成し、以下のコードを入力する。

module Test
where

x = 5
y = (6, "Hello")
z = x * fst y

これはHaskellで書かれたとても単純な「プログラム」だ。"Test" というモジュールを定義している (ふつう、モジュール名はファイル名と同じものにすべきである。これに関する詳細は、第6章を参照のこと)。そして、そのモジュールで x、y、z の3つを定義している。このファイルを書いて保存したら、保存先のディレクトリでお気に入りのインタープリタで以下のいずれかを実行し、ロードする。

% hugs Test.hs
% ghci Test.hs

すると、それぞれHugsまたはGHCiが起動し、ファイルがロードされる。もう1つの方法は、もしHugsかGHCiが起動してあれば、以下のように ":load" コマンド(または、単に ":l") を使ってモジュールをロードできる。

Prelude> :l Test.hs
...
Test>

1行目と最後の行との間には、インタープリタが実行中の内容を説明するさまざまなデータを出力する。エラーが表示されたら、おそらくファイルに何かタイプミスがある。再確認し、もう1度実行してみる。
これまで "Prelude" と表示されていたところが、今は "Test" となっていることに気づくだろう。これは、現在のモジュールがTestであることを意味している。おそらくこう考えるだろう。"Prelude" もモジュールに違いない、と。まさにそのとおりである。モジュールPrelude (ふつう、単に "Prelude" と呼ぶ) はつねにロードされていて、標準的な定義が含まれている (たとえば、リストの (:) 演算子、(+) や (*)、fstsnd、など)。
Testをロードしたので、Testで定義したものが使える。たとえば、以下のとおりである。

Test> x
5
Test> y
(6,"Hello")
Test> z
30

完璧だ! 思ったとおりの結果になった。
最後に1つ残っている課題は、プログラムを単独の実行可能ファイルにコンパイルする方法である。プログラムを実行可能ファイルにコンパイルするには、"Main" というモジュールがあり、mainという関数を含んでいなければならないしたがって、Test.hs に手を加えて "Main" に変更する (module Testとなっている行をmodule Mainに変更する) のであれば、単純に関数mainを定義する必要がある。以下を試してみよう。

main = putStrLn "Hello World"

さて、ファイルを保存し、コンパイルする (使用しているコンパイラでのコンパイル方法については、前に戻って第2章を参照のこと)。たとえば、GHCでは以下のようにする。

% ghc --make Test.hs -o test


■注■ Windowsでは、"-o test.exe" とする。

これで "test" というファイル (Windowsでは "test.exe") が作られ、実行できるようになる。

% ./test
Hello World


■注■ Windowsでは以下のとおり。

C:\> test.exe
Hello World


前ページ「3.3 リスト
次ページ「3.5 関数


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 11:42 | 東京 ☀ | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年03月01日

Yet Another Haskell Tutorial (和訳): 3.3 リスト

3.3 リスト

タプルのおもな制約は、ペアは2つ、3つ組は3つなど、決められた数の要素しか保持できない点である。任意の数の要素を保持できるデータ構造がリストである。リストは、括弧の代わりにカギ括弧を使う点以外はタプルとよく似た形をしている。リストは以下のように定義できる。

Prelude> [1,2]
[1,2]
Prelude> [1,2,3]
[1,2,3]

リストには要素がなくてもかまわない。空のリストは、単に[]である。
タプルとは異なり、コロン演算子を用いればごく簡単にリストの先頭に要素を追加できる。このコロンを「コンス」(cons) 演算子といい、要素を追加する処理を「コンスする」という。この語の語源は、追加する要素と古いリストから新しいリストをconstruct (構築) することから来ている。コンス演算子の動作は以下の例でわかる。

Prelude> 0:[1,2]
[0,1,2]
Prelude> 5:[1,2,3,4]
[5,1,2,3,4]

実際、コンス演算子 (コロン) と空リストを使えば任意のリストを構築できる。

Prelude> 5:1:2:3:4:[]
[5,1,2,3,4]

実は、[5,1,2,3,4]という構文は、コンス演算子と空リストを明示的に使った式の「構文糖」である。[5,1,2,3,4]という表記法を使って書くと、コンパイラは:と[]を使った表現に単純に置き換える。

■注■ 一般に、「構文糖」という言語機能は厳密には必要がない。これは構文をよりよくするために追加されたものである。

リストとタプルのもう1つの相違点は、タプルが異なる型の要素から成るのに対し、リストは型が同一でなければならないことだ (homogeneous)。つまり、整数と文字列の両方を含むリストは作れない。もし作ろうとすると、型エラーとなる。
もちろん、リストが含むことができるのは整数や文字列だけではない。リストはタプルを含むこともできるし、他のリストを含むことすらできる。タプルも同様に、リストや他のタプルを含むことができる。以下に挙げるものをいくつか試してみよう。

Prelude> [(1,1),(2,4),(3,9),(4,16)]
[(1,1),(2,4),(3,9),(4,16)]
Prelude> ([1,2,3,4],[5,6,7])
([1,2,3,4],[5,6,7])

基本的なリスト関数には、headtailの2つがある。関数headは (空でない) リストの最初の要素を返し、関数tailは (空でない) リストの最初の要素以外のすべてを返す。
リストの長さを得るには、関数lengthを使う。

Prelude> length [1,2,3,4,10]
5
Prelude> head [1,2,3,4,10]
1
Prelude> length (tail [1,2,3,4,10])
4


3.3.1 文字列

Haskellでは、Stringは単なるCharのリストである。したがって、文字列 "Hello" は以下のように生成できる。

Prelude> ’H’:’e’:’l’:’l’:’o’:[]
"Hello"

リスト (および、もちろん文字列) は、++演算子を使って連結できる。

Prelude> "Hello " ++ "World"
"Hello World"

さらに、関数showを使うと文字列でない値を文字列に変換でき、関数readを使うと文字列を文字列以外の値に変換できる。もちろん、不正な値を読もうとするとエラーとなる (なお、これはコンパイル時エラーではなく、ランタイムエラーである)。

Prelude> "Five squared is " ++ show (5*5)
"Five squared is 25"
Prelude> read "5" + 3
8
Prelude> read "Hello" + 3
Program error: Prelude.read: no parse

上の例で、正確なエラーメッセージは実装に依存する。しかし、インタープリタが暗に述べているのは、何かに3を加えようとしたということだ。つまり、read "Hello" を実行したら数値が返されることが期待されている。しかし、"Hello" が数値と解釈できないのでエラーとなるのだ。

3.3.2 簡単なリスト関数

Haskellプログラムの計算の多くはリストを処理して行われる。リストの処理関数にはおもに3つある。mapfilterfoldr (およびfoldl) である。
関数mapは、値のリストと、各値に適用する関数を引数にとる。たとえば、Char.toUpperという組み込み関数がある。これはCharを入力値にとり、もとの引数を大文字にする。したがって、文字列 (単なる文字のリスト) 全体を大文字に変換するには、リスト全体に関数toUpperをマップすればよい。

Prelude> map Char.toUpper "Hello World"
"HELLO WORLD"


■警告■ Hugsユーザーの場合: HugsではChar.toUpperのような修飾付きの名前は好まれない。Hugsでは、単にtoUpperを使う。

リスト全体にマップしてもリストの長さは決して変化せず、リスト内の個々の値のみが変化する。
リストから要素を削除するには、関数filterを使う。この関数を使うと、その値に応じてリストから要素を取り除くことができる。ただし、コンテキストに応じた要素の削除はできない。たとえば、関数Char.isLowerは、与えられた文字が小文字かどうかを返す。これを使うと、小文字以外の文字を取り除くことができる。

Prelude> filter Char.isLower "Hello World"
"elloorld"

関数foldrにはちょっとした慣れが必要だ。foldrは、関数、初期値、リストの3つの引数をとる。foldrとは、リストのコンス演算子 (:) をパラメータに指定した関数で置き換え、空リスト生成子 ([]) を初期値で置き換えたものであると考えるのが一番わかりやすい。したがって、リスト
   3 : 8 : 12 : 5 : []
があったとして、これに foldr (+) 0 を適用すると、
   3 + 8 + 12 + 5 + 0
が得られ、合計値が計算される。
これは、以下のようにして確かめられる。

Prelude> foldr (+) 0 [3,8,12,5]
28

同様の処理で、リスト内の全要素の積を計算できる。

Prelude> foldr (*) 1 [4,8,5]
160

折りたたみ関数は、(:) を特定の関数に置き換え、([]) を初期値に置き換えるようなものだと述べた。では、指定した関数が結合的でない場合は何が起きるだろうか (a · (b · c) = (a · b) · c なら、関数 (·) は結合的である)。4 · 8 · 5 · 1と書いたとき、括弧を置く場所を指定する必要がある。つまり、((4 · 8) · 5) · 1 と 4 · (8 · ((5 · 1)) のどちらを意味するだろうか?foldrは、関数が右結合的であるとみなす (つまり、後者の括弧のつけかたが正しい)。ゆえに、(減算のような) 非結合的関数でこれを使うと以下のような結果になる。

Prelude> foldr (-) 1 [4,8,5]
0

正確には、以下のように導かれる。
    foldr (-) 1 [4,8,5]
==> 4 - (foldr (-) 1 [8,5])
==> 4 - (8 - foldr (-) 1 [5])
==> 4 - (8 - (5 - foldr (-) 1 []))
==> 4 - (8 - (5 - 1))
==> 4 - (8 - 4)
==> 4 - 4
==> 0

関数foldlは反対の動作をし、逆から括弧をつける。適用のしかたはfoldlも同じなので、foldlでもまったく同じように合計を出すことができる。

Prelude> foldl (+) 0 [3,8,12,5]
28

しかし、非結合的関数である減算を使用すると結果が異なる。

Prelude> foldl (-) 1 [4,8,5]
-16

それは、foldlは括弧のつけかたが逆だからである。本質的には、リストをくだっていって実行する。つまり、最後の要素を取り出し、与えられた関数で初期値と結びつける。得られた値に、リストの最後から2番目の要素を取り出して結びつける。リストに何も残らなくなるまでこれが行われる。
このようにして、逆のやり方で導かれていく。
    foldl (-) 1 [4,8,5]
==> foldl (-) (1 - 4) [8,5]
==> foldl (-) ((1 - 4) - 8) [5]
==> foldl (-) (((1 - 4) - 8) - 5) []
==> ((1 - 4) - 8) - 5
==> ((-3) - 8) - 5
==> (-11) - 5
==> -16

ここで注意しておきたいのは、foldlを取り去った状態がfoldrとまったく逆の括弧付けになっていることだ。

■注■ 7.8節で述べる理由から、foldrよりもfoldlのほうが効果的であることが多い。しかし、foldrは無限リストでも動作するが、foldlは動作しない。なぜなら、foldlは何をするにも必ずリストの最後で行わねばならないからだ。一方で、foldrは出力の生成をすぐに開始する。たとえば、foldr (:) [] [1,2,3,4,5] は、単純に同じリストを返す。無限リストであっても出力は生成される。同様の関数にfoldlを使用すると出力の生成は失敗する。

折りたたみ関数に関するこういった議論がまだよくわからなくても問題はない。これについては、7.8節で詳しく述べる。

演習

演習3.3 mapを使用して、文字列をもとのリストの各要素が小文字かどうかを表すbooleanのリストに変換せよ。つまり、文字列 "aBCde" を受けたら [True,False,False,True,True] を返すようにする。

演習3.4 この節で述べた関数を使い (2つ必要だろう)、文字列内の小文字の数を計算せよ。たとえば、"aBCde" なら 3 を返すようにする。

演習3.5 折りたたみ関数を使って合計や積を計算する方法を学んだ。関数maxが2つの数の最大値を返すとした場合、折りたたみを使ってリストの最大値 (リストが空なら0) を返す関数を書け。つまり、[5,10,2,8,1] が与えられたら、10を返す。なお、リストの値はつねに0以上とする。また、それが動作する理由を自分自身に説明せよ。

演習3.6 長さ2以上のペアのリストに対し、リストの2つめの要素の最初の成分を返す関数を書け。つまり、[(5,’b’),(1,’c’),(6,’a’)] が与えられたら1を返す。


前ページ「3.2 タプル
次ページ「3.4 ソースコードファイル


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 21:38 | 東京 ☀ | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年02月26日

Yet Another Haskell Tutorial (和訳): 3.2 タプル

3.2 タプル

単一の値に加えて、複数の値についても取り扱う必要がある。たとえば、x/y座標で場所を参照したいと思うかもしれない。これは整数のペアとなるだろう。整数のペアを作るのは簡単だ。整数のペアを括弧でくくり、カンマで区切ればよい。以下を試してみよう。

Prelude> (5,3)
(5,3)

これで、整数5と3のペアができる。Haskellでは、ペアの最初の要素が2つめの要素と同じ型である必要はない。つまり、ペアは異成分で成り立っていてもよい (heterogeneous)。たとえば、整数を文字列とペアにすることもできる。この点でリストと異なる。リストでは、同じ型の要素で成り立っていなければならない (リストについては、3.3節で詳しく述べる)。
ペアの最初の要素と2番目の要素を取り出す2つの関数があらかじめ定義されている。それは、それぞれfstsndだ。これは、以下のように動作する。

Prelude> fst (5, "hello")
5
Prelude> snd (5, "hello")
"hello"

ペア以外に、3つ組、4つ組なども定義できる。3つ組や4つ組を定義するには、それぞれ以下のように書く。

Prelude> (1,2,3)
(1,2,3)
Prelude> (1,2,3,4)
(1,2,3,4)

などである。一般に、ペア、3つ組などをタプル (tuple) といい、異なる型のデータを決められた個数格納できる。

■注■ 関数fstsndは、3要素以上のタプルには使えない。もし使おうとすると型にエラーがあるというメッセージが表示される。このエラーメッセージの意味は第4章で説明する。


演習

演習3.2 fstsndを組み合わせて使い、タプル ((1,’a’),"foo")から文字を取り出しなさい。


前ページ「3.1 計算
次ページ「3.3 リスト


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 15:16 | 東京 🌁 | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年02月25日

Yet Another Haskell Tutorial (和訳): 3.1 計算

3.1 計算

Haskellへの襲撃は計算から開始するとしよう。お好みの対話的シェル (HugsまたはGHCi。インストール手順については第2章を参照) を起動する。シェルは、そのシェルに関する説明と、何をしようとしているかを画面上に数行出力し、最後にカーソルを待ち受け状態にするはずである

Prelude>

これで、の評価を始めることができる。式とは、基本的には値を持つもののことである。たとえば、数値5は式である (その値は5)。値は別の値を組み合わせて作られることもある。たとえば、5 + 6は式である (その値は11)。実際、加算 (+)、減算 (-)、乗算 (*)、除算 (/)、累乗 (^)、平方根 (sqrt) など、簡単な算術演算子のほとんどがHaskellでサポートされている。対話的シェルに式を評価してもらいその値を受け取ることで、これらの演算子を試すことができる。こうすればHaskellのシェルは強力な計算機として使用できる。以下に挙げるものをいくつか試してみよう。

Prelude> 5*4+3
23
Prelude> 5ˆ5-2
3123
Prelude> sqrt 2
1.4142135623730951
Prelude> 5*(4+3)
35

Haskellは、標準的な算術演算子のほかに括弧によるグループ化もできることがわかる。そのため、5*4+3と5*(4+3)の値が異なっている。こうなる理由は、最初の式には演算子の優先順位により暗黙のグループ化(5*4)+3が起きているからだ。
関数の引数に括弧が必要ないことにも注意する必要がある。たとえば、他のほとんどの言語ではsqrt(2)と書かなければならないが、単にsqrt 2と書いている。括弧をつけて記述することもできるが、Haskellでは関数の利用がかなり一般的なので括弧は必須ではない。

■警告■ 括弧は必ずしも必要ではないが、つけておいたほうが良い場合もある。おそらく、あなたの書いたコードをほかの人が読まなければならないこともあるだろう。括弧をつけるとコードの意味がはっきりする場合は括弧をつけるべきである。

さて、2^5000を入力してみよう。動作するだろうか?

■注■ 他の言語でのプログラミングに慣れていると、sqrt 2は与えた引数が整数なのに小数点付きの値 (つまり浮動小数点型の数値) を返すことに違和感を覚えるかもしれない。この数値型の互換性はHaskellの型クラスという仕組みによるもので、第4.3節で詳しく述べる。


演習

演習3.1 乗算は加算よりも結びつきが強いことを学んだ。では、関数の適用が乗算より結びつきが強いかどうか調べるにはどうすればよいだろうか?


前ページ「3 Haskell言語の基礎
次ページ「3.2 タプル


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 21:50 | 東京 🌁 | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年02月24日

Yet Another Haskell Tutorial (和訳): 3 Haskell言語の基礎

第3章

Haskell言語の基礎


この章では、Haskellの基本的な概念を示す。対話的環境に慣れてもらい、簡単なプログラムをコンパイルする方法を示すだけでなく、Haskellの基本的な文法を紹介する。CやJavaのような言語に慣れているとしたら、おそらくHaskellはまったく異質な言語だろう。
しかし、この言語の詳細を述べる前に、Haskellの一般的な特性のいくつかをはっきりさせておく必要がある。もっとも重要なのは、Haskellがlazyな言語だということだ。つまり、Haskellは計算結果が使われる時点で計算を実行せざるをえなくなるまで計算を実行しない。
これは、たとえば、データ構造全体を使用しなければ、無限に大きなデータ構造を定義できることを意味する。たとえば、擬似的な命令的コードを用いても、以下のようなことをすれば各位置に数値1を含む無限のリストを生成することができる。

List makeList()
{
List current = new List();
current.value = 1;
current.next = makeList();
return current;
}

このコードを見れば、何をしようとしているかわかるだろう。新しいリストを作り、値を1に設定し、再帰的に自分自身を呼び出してリストの残りの部分を作る。もちろん、実際にこのコードを作って呼び出せば、プログラムはいつまでも終了しない。なぜならmakeListは無限に自分自身を呼び出し続けるからだ。
それは、この命令的な言語を (lazyと反対の) strictな言語と仮定しているからだ。strictな言語は "call-by-value"、lazyな言語は "call-by-name" と呼ばれることが多い。上記の擬似コードでは、5行目でmakeListを「実行」するとそのを得ようとする。これは無限ループとなる。
Haskellの同等のコードは以下のようになる。

makeList = 1 : makeList

このプログラムは以下のように読める。makeListという何かを定義しようとしている。これが等号の左側の処理だ。等号の右側でmakeListを定義している。Haskellでは、コロン演算子はリストの生成に使われる (これについては、すぐ後で述べる)。等号の右側は、makeListの値はmakeListの値の先頭に要素1が加えられたものであると言っている。
しかし、Haskellはlazyなので (つまり、call-by-nameなので)、この時点ではmakeListが実際に何なのかを評価しようとしない。将来makeListの2番目の要素が必要になったときにmakeListを見る必要があることを、単に記憶しておくのだ。
さて、makeListをファイルに書き込もうとしたり、画面上に表示しようとしたり、あるいは要素の合計を計算しようとしたりすると処理は終了しない。というのも、無限長のリストを評価しなければならないからだ。しかし、リストの有限な一部分 (たとえば、最初の10要素など) を使うだけならリストが無限長であることは問題とならない。最初の10要素だけを使うなら、最初の10要素だけが計算される。これがlazyということである。
次に重要なのは、Haskellが大文字と小文字を区別する (case-sensitive) ということだ。そのような言語は多い。だが、実はHaskellでは大文字と小文字で意味が異なる。Haskellは (たとえば、1, 2, 3,...といった数値)、文字列 ("abc", "hello",...)、文字 ('a', 'b', ' ',...)、 (値が属するカテゴリ) を区別する。
そのこと自体は特殊ではない。ほとんどの言語は何らかの型というシステムを持っている。Haskellが特殊なのは、関数と値は小文字で始まらなければならないし、型は大文字で始まらなければならない教訓は、ほかの点では正しいプログラムなのにコンパイルに失敗する場合は、関数にFooといった大文字から始まる名前をつけていないか確かめることだ。
Haskellは関数型言語なので、副作用が起きることを避けている。副作用は本質的に関数の実行の過程で起こるものであり、その関数から生成される出力には関係しないものである。
たとえば、CやJavaのような言語では、"グローバル" 変数を関数内から変更できる。このグローバル変数に変更を加えても関数が生成する出力には関係しないので、これは副作用である。さらに、実世界の状態を変更することも副作用とみなされる。画面に何かを表示したり、ファイルを読み込んだり、などはすべて副作用の処理である。
副作用を持たない関数を純関数 (pure function) という。ある関数が純関数かどうかを調べる簡単な方法は、自分自身に次のような簡単な質問をすることだ。「この関数は、同じ引数を与えたらつねに同じ結果を返すだろうか?」
これはつまり、コードを (CやJavaのような) 命令的言語で書くことに慣れているなら、別の考え方をしなければならないだろうということにほかならない。もっとも重要なのは、値xがあったら、xをレジスターや記憶場所といった性格のものとして考えてはならないことだ。xは、私の名前が「Hal」なのとまったく同じように、単なる名前なのだ。私の名前のところに別の人物を格納しようなどと勝手に決めることはできないのと同じで、xのところに別の値を格納しようなどと勝手に決めることはできない。つまり、以下のCコードのようなコードはHaskellでは正しくない (同等のコードも存在しない)。

int x = 5;
x = x + 1;

x = x + 1のような呼び出しは、実行前にxに何が入っていたとしてもそれを破壊して新しい値に置き換えるため、破壊的更新と呼ぶ。Haskellに破壊的更新は存在しない。
破壊的更新 (やそのような副作用を引き起こす処理) を許可しないため、Haskellのコードはとても理解しやすい。つまり、関数fを定義し、プログラムの先頭である引数aでその関数を呼び出し、プログラムの最後で同じ引数afを再度呼び出したら、同じ結果が得られる。なぜなら、aは変更されないし、faのみに依存するからだ (たとえば、グローバルカウンタがカウントアップされたりしていない)。この性質を参照透明性 (referential transparency) といい、基本的には、2つの関数fgが同じ引数に対して同じ値を生成するなら、fgに置き換えることができる (逆もできる) ことを主張している。

■注■ 参照透明性の正確な定義は人によって異なる。私は上記の定義が最もよいと思っている。誰でも解釈は同じである。表現のしかたが異なるだけだ。


前ページ「2.4 エディタ
次ページ「3.1 計算


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 18:30 | 東京 🌁 | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年02月23日

Yet Another Haskell Tutorial (和訳): 2.4 エディタ

2.4 エディタ

よいテキストエディタがあれば、プログラミングも楽しめる。もちろん、カット&ペーストだけしかないごくシンプルなエディタをうまく利用することも可能だが、優れたエディタは雑多な作業のほとんどを行ってくれるので、ユーザーは書いている対象に集中できる。Haskellでプログラムを書くことに関して言えば、以下の機能が可能である必要がある。

  • 文法に従ったソースファイルの強調表示
  • ソースファイルの字下げ処理
  • Haskellインタープリタ (HugsおよびGHCi) との対話
  • コンピュータによるコーディング援助
  • コード補完

執筆時点では選択肢がいくつかある。Emacs/XEmacsはhaskell-mode (http://www.haskell.org/haskell-mode/ から入手可能) でHaskellをサポートし、Elispコードが添えられている。そして...
ほかに何かあるだろうか?
(X)Emacsは上に列記した機能をすべて持ち、申し分ない仕事をしてくれるように思える。字下げはHaskellの2次元レイアウトルール (第7.11節参照) を考慮しており、とても洗練されていて、動作させてみる価値があると思う。"Definitions" メニューを使って選択した関数の定義にすぐにジャンプできるし、現在編集中の関数名がつねにモードラインに表示される。

前ページ「2.3 NHC
次ページ「3 Haskell言語の基礎


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 05:30 | 東京 ☀ | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年02月22日

Yet Another Haskell Tutorial (和訳): 2.3 NHC

2.3 NHC

NHCについて...

2.3.1 入手先

2.3.2 インストール手順

2.3.3 実行方法

2.3.4 プログラムオプション

2.3.5 ヘルプの入手法


前ページ「2.2 Glasgow Haskell Compiler
次ページ「2.4 エディタ


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 23:02 | 東京 ☀ | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

Yet Another Haskell Tutorial (和訳): 2.2 Glasgow Haskell Compiler

2.2 Glasgow Haskell Compiler

Glasgow Haskell Compiler (GHC) は、頑健でHaskell 98のすべての機能を持つ最適化コンパイラおよび対話的環境である。GHCはHaskellをコンパイルしてネイティブコードまたはCを生成する。GHCはHaskell 98への実験的な言語拡張を多数実装している。たとえば、並行性 (concurrency)、他言語とのインターフェイス、複数パラメータ型クラス (multi-parameter type class)、スコープ付きの型変数 (scoped type variables)、存在量化 (existential quantification)、全称量化 (universal quantification)、非ボックス化型 (unboxed type)、例外処理 (exception)、ウィークポインタ (weak pointer)、などである。GHCには世代別ガベージコレクタ、スペースプロファイラ、タイムプロファイラが装備されている。

2.2.1 入手先

GHCの公式Webページ http://haskell.org/ghc にアクセスし、最新のリリースをダウンロードする。このチュートリアルの執筆時点での最新バージョンは5.04.2で、GHCのダウンロードページ ("Download" リンクからたどる) からダウンロードできる。そのページから、使用しているコンピュータに適したバージョンのGHCをダウンロードできる。

2.2.2 インストール手順

GHCのダウンロード後のインストール手順はプラットフォームに依存するが、使用しているプラットフォームへのプログラムのインストールと大きな違いはない。

Windowsの場合 "msi" ファイルをクリックしてダウンロードした場合は、単に "Run This Program" を選択すれば自動的にインストールが開始する。その後は、画面に表示される手順に従うだけである。

RPMの場合 もっとも使い慣れているRPMインストールプログラムを使用してインストールする。

ソースの場合 まずgunzipした後、untarする。サポートされている他のシステムとは異なるシステムを使用しているのなら、おそらくそのシステムについてはよくご存じで、手作業でconfigureスクリプトを実行し、makeすることができるだろうと思う。

インストール手順の詳細な説明については、"Installing GHC" にあるGHCのユーザーマニュアルを参照のこと。

2.2.3 コンパイラの実行方法

コンパイラの実行はきわめて簡単である。Main.hsというファイルにmain関数を書いたとすると、以下のように入力するだけでコンパイルできる。

% ghc --make Main.hs -o main

"-make" オプションは、これがライブラリではなくプログラムであり、依存するモジュールとともにビルドしたいということをGHCに知らせる。"Main.hs" はコンパイルするファイルの名前を指定している。"-o main" は "main" というファイルに出力したいということを意味する。

■注■ Windowsでは、Windowsが実行可能ファイルであることをわかるように "-o main.exe" とする必要がある。

コンパイルしたら、プロンプトに "main" と入力するだけでプログラムを実行できる。

2.2.4 インタープリタの実行方法

GHCiは、コマンド "ghci" または "ghc -interactive" で起動される。コマンドラインから、1つまたは複数のファイル名を指定することもできる。これにより指定したモジュールやファイル名がロードされ、GHCiプロンプトで :load modulesと入力したのとまったく同じ指示をGHCiに行える。

2.2.5 プログラムオプション

すべてのオプションを列挙すると膨大なものとなる。現時点で最も重要なオプションは "-fglasgow-exts" である。"-fglasgow-exts" をつけずにGHCiを起動すると、Haskell 98モードとなり、拡張機能はすべてオフとなる。"-fglasgowexts" をつけて起動すると、拡張機能はすべてオンとなる。だれかのコードをダウンロードしてそのロードに失敗する場合には、まずこのフラグが適切に設定されているか確認する。
GHCとGHCiのオプションに関する詳しい情報は、GHCのWebページのマニュアルに書かれている。

2.2.6 ヘルプの入手法

GHC(i)用のヘルプは、GHCのWebページに行くと得られる。Haskellに関する一般的なヘルプは、HaskellのWebページに行くと得られる。

前ページ「2.1 Hugs
次ページ「2.3 NHC


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 22:19 | 東京 ☀ | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

Yet Another Haskell Tutorial (和訳): 2.1 Hugs

2.1 Hugs

Hugsは、Haskell 98規格の (いくつかのライブラリを除く) ほとんどすべてをサポートするほか、先進的、実験的な拡張機能を多数サポートしている。たとえば、複数パラメータ型クラス (Multi-Parameter Type Class)、拡張可能なレコード (Extensible Record)、ランク2多相性 (rank-2 polymorphism)、Existential、スコープ付きの型変数 (scoped type variables)、制限付きの同義名 (Restricted Type Synonym) などが含まれる。

2.1.1 入手先

Hugsの公式Webページは、
http://haskell.org/hugs
である。

このページにアクセスすると "Downloading" というリンクがあり、クリックするとダウンロードページに遷移する。ダウンロードページで、Hugsの適切なバージョンをコンピュータにダウンロードできる。

2.1.2 インストール手順

Hugsのダウンロード後のインストール手順はプラットフォームに依存するが、使用しているプラットフォームへのプログラムのインストールと大きな違いはない。

Windowsの場合 "msi" ファイルをクリックしてダウンロードした場合は、単に "Run This Program" を選択すれば自動的にインストールが開始する。その後は、画面に表示される手順に従うだけである。

RPMの場合 もっとも使い慣れているRPMインストールプログラムを使用してインストールする。

ソースの場合 まずgunzipした後、untarする。サポートされている他のシステムとは異なるシステムを使用しているのなら、おそらくそのシステムについてはよくご存じで、手作業でconfigureスクリプトを実行し、makeすることができるだろうと思う。


2.1.3 実行方法

Unixマシンでは通常、hugs [option - file] ... という形式のコマンドラインでHugsインタープリタを起動する。
Windowsでは、スタートメニューから選択するか、拡張子.hsまたは.lhsのファイルをダブルクリックしてHugsを起動する。このマニュアルでは、すでにHugsがシステムに正常にインストールされていることを前提としている。
Hugsではオプションを使ってシステムのパラメータを設定する。このオプションは+または-ではじまるもので区別され、インタープリタの振る舞いのカスタマイズに使われる。Hugsが起動すると、インタープリタは以下の役割を果たす。

  • 環境内のオプションが処理される。このオプションは変数HUGSFLAGSに保持されている。Windows 95/NTでは、レジストリも参照してHugsオプションを設定する。
  • コマンドラインオプションが処理される。
  • 内部データ構造が初期化される。特に、ヒープが初期化され、ヒープサイズはこの時点で確定する。デフォルトのヒープサイズ以外でインタープリタを実行したい場合は、コマンドラインオプションを使うか、環境変数またはレジストリで指定する。
  • Preludeファイルがロードされる。インタープリタは-Pオプションで指定されたパスからPreludeファイルを探す。ファイルPrelude.hsに存在するpreludeが、パスディレクトリのどれか、またはカレントディレクトリに見つからなければ、Hugsは終了する。Preludeファイルが無いとHugsは実行しない。
  • コマンドラインで指定したプログラムファイルがロードされる。コマンドhugs f1 ... fnは、hugsコマンドを起動して :load f1 ... fnと入力したのと同じ効果をもたらす。なお、指定しファイルのどれかをロードしようとして問題が発生しても、インタープリタは終了せずロードコマンドを中止する。

Hugsが使用する環境変数とコマンドラインオプションは、次の節以降で示す。

2.1.4 プログラムオプション

すべてのオプションを列挙すると膨大なものとなる。現時点で最も重要なオプションは、"+98" および "-98" である。"+98" をつけてhugsを起動するとhugsはHaskell 98モードとなり、拡張機能はすべてオフとなる。"-98" で起動するとHugsモードとなり、すべての拡張機能がオンとなる。だれかのコードをダウンロードしてそのロードに失敗する場合には、まず "98" フラグが適切に設定されているか確認する。
Hugsオプションに関する詳しい情報は、マニュアルの
http://cvs.haskell.org/Hugs/pages/hugsman/started.html
に書かれている。


2.1.5 ヘルプの入手法

Hugsに関する詳しいヘルプは、HugsのWebページに行くと得られる。Haskellに関する一般的なヘルプは、HaskellのWebページに行くと得られる。

前ページ「2 スタートガイド
次ページ「2.2 Glasgow Haskell Compiler


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 01:17 | 東京 ☀ | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年02月21日

Yet Another Haskell Tutorial (和訳): 2 スタートガイド

第2章

スタートガイド


Haskellには、よく知られたシステムが3つある。それは、Hugs、GHC、NHCだ。Hugsはインタープリタのみである。つまり、Hugsを使って単独のプログラムファイルにコンパイルすることはできないが、対話的な環境でプログラムを試し、デバッグすることができる。GHCは (Hugsのような) インタープリタと、単独のプログラムを生成するコンパイラの両方の機能を持つ。NHCはコンパイラのみである。どれを使用するかは、完全にユーザー次第である。以下のとおり相違点を列挙してみたが、もちろんこれは網羅的な物ではない。

Hugs - ファイルのロードはかなり速い。ファイルの実行は遅い。Haskell 98 (規格) のほとんどすべてと、拡張機能の大部分を実装。モジュールをブラウズする組み込み機能をサポート。単独のプログラムファイルを生成できない。Cで書かれている。ほとんどすべてのプラットフォームで動作する。グラフィックライブラリが組み込まれている。

GHC - 対話的環境はHugsよりもロードが遅いが、関数を定義できる (Hugsでは、関数定義はファイル内に記述しなければならない)。Haskel 98と拡張機能のすべてを実装。他言語とのインターフェイスの実装が優れている。ある意味、「デファクトスタンダード」。

NHC - 比較的利用者は少なく、対話的な環境も用意されていないが、生成される実行ファイルはGHCより小さく、たいていは高速である。Haskel 98といくつかの拡張機能をサポート。

私は個人的には、このすべてをインストールし、それぞれ別の目的で利用している。私はGHCを用いてコンパイルし (もっとも使い慣れているのが主な理由)、Hugsの対話的環境を使うことが多い (Hugsのほうが速いので)。これが私の推奨である。しかし、ダウンロードしてインストールするものがかなり多くなる。もし1つのシステムだけとるならGHCを選ぶだろう。というのも、GHCはコンパイラと対話的環境の両方を持つからだ。
以下それぞれについて、このチュートリアルの執筆時点でのダウンロードとインストールの方法を説明する。これは変更されているかもしれないので、最新の情報はhttp://haskell.org (HaskellのWebサイト)を参照のこと。

前ページ「1 はじめに
次ページ「2.1 Hugs


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 17:54 | 東京 ☀ | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年02月20日

Yet Another Haskell Tutorial (和訳): 1 はじめに

第1章

はじめに


このチュートリアルには多数のコード例が収められており、そのすべてがこれまでに配布された物に含められているはずである。もしなければ、HaskellのWebサイト (haskell.org) からのリンクを参照して入手していただきたい。この本では、コード例が他の文章より目立つような体裁にしている。

コードはこのように表記する。

読者とOSとの対話や、対話的シェル (第2章で詳述) を引用することもある。

対話はこのように表記する。

このチュートリアルのそこかしこで、記述内容への追記を加えている。追記の内容は、他のプログラム言語との比較や、役立つ情報といったものであることが多い。

■注■ 注はこのように表記する。

もし、難易度の高い話題や紛らわしい話題を取り上げていて、注意が必要な点がある場合には、警告を添えている。

■警告■ 警告はこのように表記する。

最後に、組み込み関数 (いわゆるPrelude関数) を参照することもある。
それはこのように表記する。

map :: (a− > b)− > [a]− > [b]

本文内では、Haskellキーワードは where のように、識別子は map のように、型は String のように、クラスは Eq のように表記する。

前ページ「目次
次ページ「2 スタートガイド


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 17:22 | 東京 ☁ | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする
×

この広告は180日以上新しい記事の投稿がないブログに表示されております。