Haskellはやぐい by けいご 2006.9.21 -------- 関数の作り方: 関数名 引数1 引数2 ... = 関数本体 例: func x = x + 1 fact x = if x == 0 then 1 else x * fact (x-1) 新しい中値の演算子を作ることもできる。 x ** y = if y == 0 then 1 else x * (x**(y-1)) 一般に、中値の演算子は (**) のようにカッコで囲めば 普通の(前置の)関数として扱える。 (**) x y = if y == 0 then 1 else x * (x**(y-1)) f g x y = g x y h x = f (**) x x -------- 分岐とパターンマッチ: - case文 (後でもう一度やる) fact x = case x of 0 -> 1 a -> a * fact (a-1) 関数引数でマッチングもできる。 fact 0 = 1 fact x = x * fact (x-1) 上から順番にマッチされる。 -------- リスト: consは ':' (コロン),nilは [] (大カッコ開く閉じる)。 1:2:3:[] のように書く (右結合)。 また,[1,2,3] のようにも書ける。 内包表記については適当に調べてください。 '_' (アンダースコア) はワイルドカードで何にでもマッチするが値は取り出せない。 head (x:_) = x tail (_:xs) = xs リストの連接は (++)を使う。 [a, b, c] ++ [d, e, f] ===> [a, b, c, d, e, f] -------- let/where文: 関数内にローカルの関数を作れる。 cube x = let square y = y * y in square x * x where文を使えば後置で書ける。 cube x = square x * x where square y = y * y -------- 無名関数: \仮引数1 仮引数2 ... -> 本体 例: func = \x -> x + 1 plus = \x y -> x + y -------- Haskellのプログラム: コンパイルされたHaskellのプログラムは,C言語と同様にmain関数から実行される。 main = putStrLn "Hello, World!"  ただし、main関数の型(後述)は IO () という型を  もっている。このIOは「副作用を扱う」というタグだと  思っておいてよい。  また、とりあえず、HaskellではIO というタグが付いてない関数では  副作用を扱う関数を書けないと覚えていてほしい。 -------- Haskellのインタプリタ: Haskellのインタプリタでは、main以外の関数を実行することもできる。 例えば、 ghci-or-hugs> 1+1 2 などのようにできる。 他にも、一般の式の評価は大抵何でもできる(結果の型がShowに属する限り)。 しかし、インタプリタでは関数定義ができない。 例えば ghci-or-hugs> func x = x+1 などとするとエラー。 自分で書いた関数を実行したいときは、ファイルに書いておく。 例えば, func.hs: func x = x + 1 となっている場合,これをロード: ghci-or-hugs> :l func.hs Compiling Main ( func.hs, interpreted ) Ok, modules loaded: Main. ghci-or-hugs> func 1 2 のようにする。ファイル編集後のリロードは :r。 -------- 型の世界と値の世界: 値がもつ型を 値 :: 型 のように書く。 fact :: Int -> Int foldl :: (a -> b -> a) -> a -> [b] -> a a -> b は関数の型。 型宣言はほとんどの場合省略できる(型推論)。 逆に、型宣言だけで、関数を書かないとエラー。 また、部分式の型を明示することもできる。 (式 :: 型) のように書く。例: ("Hello, " ++ "World" :: String) (\x -> x + 1 :: Int -> Int) -------- 命名規則と名前空間: 1. 大文字と小文字  Haskellの世界では、  - 大文字で始まるのは「構築子」  - 小文字で始まるのは「変数」  のような感じで決まっている。  例えば、整数の型は Int/Integer (intではなく)。  ブール値は True/False (true/falseではなく)。  関数は小文字で始まらなければならない。  型変数は小文字で始まらなければならない(ふつうは小文字1文字にする)。 2. 名前空間  4つの名前空間がある。  (1)値 (2)型 (3)型クラス (4)モジュール  これらは互いに素である。  たとえば Haskellには Numという型クラスが既にあるが、  data Num = Num Integer などと、  新しい型Numと新しいデータ構築子Numを導入してもエラーにならない。  また、この例でも分かるように、データ構築子と型構築子の  名前を同じにしてもエラーにならない。 -------- 型の作り方: data 型 = データ構築子1 | データ構築子2 | ... 例: data Bool = True | False -------- より複雑なデータ型: data 型 = データ構築子1 引数型1 引数型2 ... | データ構築子2 引数型1 引数型2 ... | ... 例: data Point2D = Point2D Int Int data Entity = Company String | Person Int String -------- データ構築子とパターンマッチ: パターンマッチは case文で行う。 getName entity = case entity of { Company name -> name; Person age name -> name; } case文,let/where文,do文は中カッコ{}で囲み、各節をセミコロン;で区切る。 -------- レイアウト記法: 中カッコやセミコロンを省略できる規則がある (Haskellのコア文法とは関係ない)。 具体的には、 1. キーワードof/let/where/doの直後に来た非空白文字の直前(下の例だと'C')から  レイアウトが始まる。 (以後この桁をnとする)  直前に中カッコが挿入される。  (もちろん中カッコがきたら始まらない) 1234567890123456789 case entity of Company name -> ... Person age name -> ... これの場合、Companyの 'C' でレイアウトが始まるので n=3 2. 改行があった場合、次の行が何桁目から始まるかで動作が異なる。  A) n桁目より右側 ... 何も起こらない B) ちょうどn桁目 ... 直前にセミコロンが挿入される。 C) n桁目より前 ... レイアウトが終了する ('}'が挿入される)。 これが case entity of Company name -> ... Person age name -> ... こうなる case entity of {Company name -> ... ;Person age name -> ... } ('}'の位置は自信ナシ) これが let a = 1 b = 2 c = 3 in a * x * x + b * x +c こうなる let{a = 1 ;b = 2 ;c = 3 }in (おなじく'}'の位置は自信ナシ) -------- 多相型と型変数: data 型 型変数1 型変数2 型変数3 ... = データ構築子1 引数型1 引数型2 ... 例: data Point a = Point a data Pair a b = PairData a b data Either a b = Left a | Right b パラメータ(型変数)をもつ型を多相型という。 多相型を使う関数は、型変数を、変数のままでも具体化しても用いることができる。 PairやEither等の、引数をとる型を型構築子とよぶ。 one = PairData 1 "One" :: Pair Int String oneFunc x = PairData 1 x :: a -> Pair Int a Left 100 :: Either Int a -------- リスト型: リストの型は [a] である。例えば整数のリストは [Int]/[Integer]。 また,文字列String型は、型[Char]の別名。 多相関数の有名な例に、map関数がある: map :: (a -> b) -> [a] -> [b] map f (x:xs) = f x : map f xs map f [] = [] -------- 型クラス: 例えば、リストに目的の値があるかどうかを真偽値で返す関数 elem :: a -> [a] -> Bool を作りたいとする。 elem a (x:xs) = if x == a then True else elem a xs elem [] = False のようになると思われるが… ここで == の型は? (==) :: a -> a -> Bool ? 全ての型で定義されているはずはない。 ==> それぞれの型で個別に定義する必要がある? 例: intEqual :: Int -> Int -> Bool floatEqual :: Float -> Float -> Bool これでは elem のような一般のリストについての関数を作れない。 また、 (==) のような一般的な関数はそれぞれの型で「多重定義」したい。 ここで型クラスを使う。 class Eq a where (==) :: a -> a -> Bool これは「型クラスEqに属する型aはメソッド (==) をもつ」と読む。 型クラスを用いた多相は、アドホック多相の一種である。 一方、「ある型anは型クラスCnに属する」という制約を (C1 a1, C2 a2, ...) => ... とかく。これを文脈という。 例: elemの型は、 elem :: (Eq a) => a -> a -> Bool となる。 -------- 型クラスのインスタンス: - 「ある型aは型クラスCに属する」 という宣言は、 instance C a where と書く。このときメソッドの実装も与えなければならない。 例えば、Entity型がEqに属しているためには、 data Entity = Company String | Person Int String instance Eq Entity where Company name1 == Company name2 = name1 == name2 Person age1 name1 == Person age2 name2 = age1 == age2 && name1 == name2 とかく。ここで右辺の(==)はIntやStringのための==であり、 再帰しているわけではないことに注意。 文脈は関数の型シグネチャ以外にも、色々な処で記述できる。 -------- 型クラスの継承: 型クラス宣言に文脈を加えることができる。 class (Eq a) => Ord a where (<=) :: a -> a -> Bool これは,「Ordに属する型aは、Eqにも属する」と読む。 よって、 1) インスタンス宣言では、Eqに属する型を指定しなければならない。  例えば次は誤り(Point2DがEqに属していない) data Point2D = Point2D Int Int instance Ord (Point2D Int Int) where p <= q = ...  instance Eq (Point2D Int Int) をどこかに書く必要がある。 2) 関数やメソッド内では、Eq a => を書かなくても暗黙に指定される。 lessThan :: (Ord a) => a -> a -> Bool lessThan a b = if a <= b then not (a==b) else False   -------- 型コンストラクタと型変数: 「型変数」の項で data Point a = Point a data Pair a b = PairData a b data Either a b = Left a | Right b リストの型 [a] を挙げた。Point, Pair, Either, [] 等は全て型構築子であり、 a b等にはInt, String等の型に置き換えられるが、 型構築子に置き換えられるような型引数もありうる。 例えば、集合の表現として、リストや木などを使いたいとする。 リストは [a], 木は Tree aと、t a の形をしている。 そこで、型構築子を引数にとる型Setを作る: data Set t a = Set (t a) data Tree a = Node a (Tree a) (Tree a) | Leaf 例として Set [1, 2, 3, 4] :: Set [] Int Set (Node "a" (Node "b" Leaf) Leaf) :: Set Tree String 等がある。 同様に、型構築子のための型クラスも定義できる。 上の集合表現では、要素を取り出す関数 get :: Set t a -> a を作る場合に 木とリストに共通する操作がないため記述できない。そこで class Collection t where take :: t a -> a などという型構築子のための型クラスCollectionを宣言する。 インスタンスは、 instance Collection [] where take (x:_) = x instance Collection Tree where take (Node x _ _) = x 等と定義する。 これで、 get :: (Collection t) => Set t a -> a get s = take s のようにできる。 (次回:モナドの解説)