Haskellの文法等,基本的な情報を列挙します.
Haskell習得中の私が学んだ事を,分かりにくかった点を中心に書いていきます.
一応,上から順に読むと分かりやすくなる予定です.
このページを読む上で,関数型言語の知識は必要ありませんが*1
CやJava等,他のプログラミング言語の経験があると,心強いかもしれません.*2

Haskellって何?

About Haskellを読むと何となく外枠が掴めるかと思います.

準備

まずは処理系のインストールをしてください.
Hugsを起動した状態で色々と試してみます.

D:\HaskellProjects>hugs
__   __ __  __  ____   ___      _________________________________________
||   || ||  || ||  || ||__      Hugs 98: Based on the Haskell 98 standard
||___|| ||__|| ||__||  __||     Copyright (c) 1994-2003
||---||         ___||           World Wide Web: http://haskell.org/hugs
||   ||                         Report bugs to: hugs-bugs@haskell.org
||   || Version: 20050113       _________________________________________

Haskell 98 mode: Restart with command line option -98 to enable extensions

Type :? for help
Hugs.Base>

終了するには

:q

と入力すればOKです(:quit)でも可.

式の書き方

Hugsでは対話的にHaskellを扱えます.
対話的なHugsでは式を入力すると結果が出力されます.
まずは取っ付きやすい式の書き方から勉強してみましょう.

演算子

四則演算は+-*/で普通に行えます.

Hugs.Base> 1+1
2
Hugs.Base> 3*2
6
Hugs.Base> 8/2
4.0
Hugs.Base> 5-2
3
Hugs.Base> 3*(2+3)
15

普通.ただ,割り算の結果を見てみると,8/2の結果が
整数ではなく実数で出ているみたいです.
まぁ細かい事は気にせず先に進む事にします.*3
四則演算以外に論理演算子等も使えますが,その辺りは後日.
ここで書いた,1+1等は全て,Haskellで「式」として有効に使える物です.

関数

関数型言語という位なので,Haskellでは関数がプログラムの中心です.
CやJavaに慣れていると,関数は

func(arg1, arg2);

の様にカッコとカンマで呼び出したくなりますが,Haskellでは

func arg1 arg2

のように,関数名と引数をスペースで区切ります.
試してみましょう.

Hugs.Base> div 9 2
4
Hugs.Base> mod 9 2
1
Hugs.Base>

divは割り算をする関数,modは剰余(あまり)を求める関数です.
関数の引数に関数の結果を入れる事もできますが注意が必要です.

div 4 div 4 2

これは

(div 4 div) 4 2

と解釈されてしまい,エラーがでてしまいます.

div 4 (div 4 2)

と書いてあげましょう.

Hugs.Base> div 4 div 4 2
ERROR - Cannot infer instance
*** Instance   : Integral (a -> a -> a) [#n9efdfa2]
*** Expression : (4 `div` div) 4 2 [#wd1cfa30]

Hugs.Base> div 4 (div 4 2)
2
Hugs.Base>

また,演算子と関数が共存する場合,関数の優先度が一番高いので

div 4 2 * 2

(div 4 2) * 2

と解釈されるので,div 4 (2 * 2)としたい場合は

div 4 (2 * 2)

とする必要があります.

プログラムを書いてみる

ここまで,Haskellで使う式の書き方を簡単に説明しました.
ここからは対話式のHaskellでは無く,プログラムを書いてみます.
Haskellのプログラムは*.hsというファイル名のファイルで作ります.
今回は適当に,test.hsという名前のファイルを用意してください.
そのファイルの存在するディレクトリ上でhugsを起動し

:l test.hs

(もしくは :load test.hs)とすると,プログラムをロードする事ができます.

関数

関数を定義する

Haskellの主役の登場です.
まずは2つの引数を足す関数を作ってみます.~ test.hsに下のように書いて保存してください.

add :: Integer -> Integer -> Integer
add x y = x + y

次にHugs上で

:l
add 1 3

としてみましょう.

Hugs.Base> :l test.hs
Main> add 1 3
4

1と3を足した結果が出力されましたね.
関数の定義は,関数の型の定義(省略できる場合有り)と式の定義に分かれ

関数名 :: 型名
関数名 引数 = 式の定義

という形式で行います.型には
整数を表すInt
Float,浮動小数点数を表すFloat
論理型(True False)を表すBool
等があります(全て大文字で始まる).

Integer -> Integer -> Integer

これは,Integerの引数を二つ取り,Integerを返す関数という意味です.
IntegerとFloatの引数を取り,Boolを返すなら

Integer -> Float -> Bool

になります.
Haskellはコンパイル時,この関数の型をチェックして
型の整合性にエラーが無いか確認します.
しかし,関数の型の定義は省略しても型推論によって
コンパイル時にエラーを発見してくれる事がほとんどのようです.

パターンマッチング

Haskellでは型が同じなら,同じ名前で複数の関数を作る事ができます.
これは,引数に応じて関数に特別なバージョンを用意する事に似ています.
例えば

isZero :: Integer->Bool
isZero 0 = True
isZero x = False

のように,型が同じ2つの関数を用意する事ができます.

Main> isZero 0
True
Main> isZero 10
False

このように型が同じ複数の関数を定義した場合
上で定義されている関数から順に引数のパターンマッチングを行い
最初にマッチした関数を適応する事になります.
よって

isZero x = False
isZero 0 = True

と関数を定義してしまうと,isZero 0を呼び出しても
これはisZero x にマッチするので,Falseを返す事になります.

Main> isZero 0
False
Main> isZero 10
False


また,パターンをガードと呼ばれる条件式(Boolを返す関数,演算子)を使って書く事もできます.
形式は

関数名 引数 | 条件式1 = 1にマッチする時の式
            | 条件式2 = 2にマッチする時の式

です.
例えばisZero関数は

isZero x | x==0 = True
         | x/=0 = False

のように書く事もできます.こっちの方がスマートですね.
また,それ以外という意味のotherwiseを使って

isZero x | x==0 = True
         | otherwise = False

のようにも書けます.

リスト

リストとは配列の様な物です.

[0,1,2,3,4,5]::[Integer]

のように定義します.これも,型の宣言は不要です.
また,これは

[0..5]

のように定義する事もできます.

[1,3..10]

のようにすれば,1〜10の奇数を得る事ができます.

Main> [1,3..10]
[1,3,5,7,9]


また,リスト同士は++で接続する事が可能です.式とリストは:で接続できます.

Hugs.Base> [0,1,2,3,4]
[0,1,2,3,4]
Hugs.Base> [0,1,2,3,4]++[5,6,7,8,9]
[0,1,2,3,4,5,6,7,8,9]
Hugs.Base> 4:[5,6,7,8,9]
[4,5,6,7,8,9]
Hugs.Base>

次にxから0までのリストを作る関数を定義してみましょう.

to0 0 = [0]
to0 x = x : to0 (x-1)

この関数は再帰的に定義されています.

x〜0までのリスト = x と x-1〜0までのリスト

ですね.これを実行すると,

Main> to0 10
[10,9,8,7,6,5,4,3,2,1,0]

のようなリストを得る事が出来ます.

無限リスト

面白い事に,Haskellでは無限に続くリストを扱う事ができます.

[1,3..]

これは全ての奇数を表すリストになります.

Main>[1,3..]
[1,3,5,7,9,11,13,15,17,19,21,
23,25,27,29,31,33,35,37,39{Interrupted!}

Ctrl+Cで中断させるまで奇数を出力し続けます.
次に下のような関数を考えてみます.

ones ::[Integer]
ones = 1:ones

この関数を実行すると

Main> ones
[1,1,1,1,1,1,1,1,1,1,1,1,1,1
,1,1,1,1,1,1,1,1,1,1,1,1,1,1
,1,1,1,1,1,1,1,1,1,1,1,1,1,1
,1,1,1,1,1,1,1,1,1,1,1,1,1,1
,...

この関数は無限に1の続くリストを生成します.
この無限に続くリストを計算に使う事はできるのでしょうか?
ここで,リストの先頭の要素だけを抜き出す関数headを使ってみます.
関数headはこのように使います.

Main> head  [1,2,3,4,5]
1

これを先ほど作ったones関数で試してみます.

head ones

この式を評価するには,ones関数を評価しなければいけません.
しかし,ones関数の評価は先ほど試した通り永遠に終わりません.
これではhead onesの式の評価も永遠に終わらないように思います.
ここで,head関数の定義を確認してみます.

head (x:_) = x

_はワイルドカードです.値が何であってもパターンマッチに成功します.
この定義を見ると,headの引数に渡したリストの先頭の要素さえ確定すれば
head関数の値を得る事ができる事と分かります.
Haskellはhead onesを以下の様に計算します.

head ones --onesを展開
head 1:ones --head x:xsにマッチする
1

この様に,Haskellでは式に無限リストが含まれていても,
式を計算するのに必要なだけリストを求め,そこから解を得る事ができます.

内包表記

例を見てみます.

[x | x  <- [1..]]

このリストは,[1..]のリストから要素を一つずつ抜き出した物を集めたリストです.
つまり,[1..]そのままです.
xで構成するリスト,ただしxは[1..]から要素を一つずつ抜き出した物.と読めばOKです.
|の後ろにはガードを書く事もできます.

[x | x  <- [1..], x `mod` 2 == 0]

これは,xで構成するリスト,ただしxは[1..]から要素を一つずつ抜き出した物の内
「x `mod` 2 == 0」を満たす物のみで構成する.と読めます.

[(x,y) | x <-[1..10], x `mod` 2 ==0 , y<-[1..10], y `mod` 2 /=0]

のように,複数のリストを扱う事もできます.

関数内に関数を定義する

a^2+b^2 = a+b+2 を -n〜n の間で満たすa,bの組み合わせを求める関数を考えてみます.

lFunc a b = a^2 + b^2
rFunc a b = a+b+2
getAnsTo n = [(a,b)| a<-[-n..n], b<-[-n..n], lFunc a b == rFunc a b]

この時,lFuncとrFuncのスコープはグローバルになってしまいます.
このままでも,動きますがlFuncとgetAnsToの関連が不明で
他の関数からもこれらの関数を呼び出してしまう等の不満があります.
Haskellではこれを解決する為に,関数を入れ子に定義する事が出来ます.
その為の構文を使って,getAnsToを定義してみます.

let式

getAnsTo2 n =
    let
        lf a b = a^2 + b^2
        rf a b = a+b+2
    in [(a,b)| a<-[-n..n], b<-[-n..n], lf a b == rf a b]

letとinの間で定義した関数を,inの後ろで定義した関数内で使うことが出来ます.
入れ子になっている関数の定義は,letよりもインデントの深い所でしてください.

where節

getAnsTo3 n = [(a,b)| a<-[-n..n], b<-[-n..n], lf a b == rf a b]
    where
        lf a b = a^2 + b^2
        rf a b = a+b+2

where節の直前で定義した関数内でwhereの次で定義した関数を使う事ができます.
また,複数のガードを持つ関数でも使う事ができます.

多相性

今までの関数,例えば

isZero :: Integer->Bool

等は,Integer型しか引数に取れない関数です.
しかし,head関数などは様々な型に対して用いる事ができます.
このような関数は,

head             :: [a] -> a
head (x:_)        = x

のように,具体的な型名を使わず,小文字で始まる仮の型名を入れることで
定義する事が出来ます.

ユーザー定義の型

↓間違いだらけでした.すみませんでした.修正しておきます
data宣言を使う事で,自分で型を定義する事ができます.

data ABC = A | B | C

のような感じです.~ これはABCという名前の,値,A,B,Cのいずれかを持つ事が出来る型を定義するということを表します.

データ構築子

先ほどの例のAやBはデータ構築子と呼ばれる物です.

データ構築子は関数のような形を取ることもできます.

data IntPair = IntPr Int Int

IntPairが型の名前でIntPrがデータ構築子(コンストラクタのような物?)です.
このデータ構築子は,IntPr 1 2のようにして,IntPair型の値を作ります.つまり

IntPr :: Int->Int->IntPair

のような関数として使えます.
また,

data Pair a  = Pr a a

のように,型の名前に引数を追加することで,特定の型に依存しないdataを作ることもできます.

レコード

data Position = Pos { x::Integer, y::Integer }

のように定義できます.これは一つの型で複数の値を保持できる型で
この例では,Positionという型はxとyという値を持つ事ができます.
Position型の値を定義するには,

position=Pos{x=0, y=1}

のようにし,それぞれの値にアクセスする為には

x position
y position

のようにします.(つまり,xやyがそれぞれの値を得る為の関数として働きます.)

複数のデータ構築子を持った型

先ほど見たPr a aのような関数によるデータ構築子も,|(or)で繋ぐ事ができます.

data Point a =
	  Point2D a a
	| Point3D a a a

型の同義名

type宣言で型に別名を持たせる事で,コードの可読性を増す事ができます.
典型的な例がStringです.

type String             = [Char]

多相型に新しい名前を付ける事もできます.

type KeyValue k v = (k,v)

コメント

このページに書かれている範囲での質問にお答えします.

  • Preludeで"data IntPair = IntPr Int Int"を入力するとエラーになってしまうのは何故ですか? -- 名無し 2010-11-16 23:24:57 (火)
  • 数年前のコメントに横レス……GHCIではトップレベル宣言ができない為です。 -- 2013-12-04 18:52:11 (水)

お名前:

*1 私も全く無いです
*2 関数とか配列が分かれば十分
*3 最初の内は細かい事を気にしない