Haskell の文法

Haskell の文法をおおまかに示します。分からないことがあれば気軽に聞いて下さい。説明不足の部分は、授業中に加筆します。

組み込みの型

Bool
真理値。リテラルの例:True、False
Integer
大きさに制限のない整数。リテラルの例:-1000000000000000、0、1000000000000000
Int
大きさに制限のある整数。リテラルの例:-100、0、100
Char
Unicode文字。リテラルは、シングルクォートで囲む。リテラルの例: 'a'
String
文字列 (Charのリスト)。リテラルはダブルクォートで囲む。リテラルの例: "foo"
[a]
リスト。リテラルの例: [1,2,3,4]
(a,b)
組(タプル)。リテラルの例:(1,'a')

代数データ型

代数データ型は、直積型の直和型です。すべてにラベルが付きます。このタグを構成子(constructor)と呼びます。以下は、代数データ型が直和型だと分かる例です。

data Bool = False | True

Bool が型、False と True が構成子です。"|" が直和(または)を表します。

data Int = ... | -2 | -1 | 0 | 1 | 2 | ...

-2、0、2 などは単語ではありませんが、Int 型の構成子です。

以下は、代数データ型が直積型だと分かる例です。直積型を見ると構成子がラベルだということが分かるでしょう。

data Point = Point Int Int

このように構成子の後には型を書きます。これが直積型(かつ)を表します。この例から分かるように型の名前と構成子の名前の空間は区別されます。

以下は、代数データ型が直積の直和だと分かる例です。

data Shape = Circle Int | Rectangle Int Int

型変数を使って、型をパラメータ化することもできます。

data Maybe a = Nothing | Just a

型変数には、当然ですが具体的な型が入ります。

ghci> :type Just 'a'
Just 'a' :: Maybe Char

以下は、再帰的な型を定義する例です。

data List a = Nil | Cons a (List a)
data Tree a = Leaf | Node (Tree a) a (Tree a)

なお、特殊な組み込み型としてリストがあります。

data [a] = [] | a : [a]

"[]" と ":" は特殊な構成子です。

以下で説明するパターンマッチでは、これらの構成子が使えます。

定数定義

"=" で定義します。型注釈(シグニチャ)は上の行に書きます。

numberOfWorkers :: Int
numberOfWorkers = 10

ghci のコマンドラインからは、このような複数行を使う定数定義はできません。

関数定義

定数定義と同様です。引数が丸括弧で囲まれていないことに注意しましょう。型注釈において、引数と返り値の型は "->" で区切ります。

double :: Int -> Int
double x = x * x

引数が3つ以上のときは、こうなります。

distance :: Int -> Int -> Int
distance x y = x * x + y * y

二項演算子を定義することを考えましょう。型注釈の部分では、二項演算子を丸括弧で囲む必要があります。

(*+) :: Int -> Int -> Int
x *+ y = x * x + y * y

二項演算子に使える記号は "!#$%&*+./<=>?@\^|-~:" です。ただし、":" から始まってはいけません。

ghci のコマンドラインからは、このような複数行を使う関数/演算子定義はできません。

大文字小文字

型の名前と構成子は大文字で始めます。

data Tree a = Leaf | Node (Tree a) a (Tree a)

関数、引数、定数は小文字で始めます。

numberOfWorkers :: Int
numberOfWorkers = 10

distance :: Int -> Int -> Int
distance x y = x * x + y * y

Haskell では、定数名や関数名にラクダ文字を使うのがよいとされています。ラクダ文字とは単語の区切りを大文字にしてある単語のことです。

numberOfWorkers :: Int
numberOfWorkers = 10

この授業では、慣習に従わずに単語を "_" で区切ります。

number_of_workers :: Int
number_of_workers = 10

関数適用

関数適用(関数に引数を与えて呼び出す)には丸括弧は不要です。

ghci> distance 2 3
13

二項演算子は直感通りに使えます。

ghci> 2 *+ 3
13

関数適用は二項演算子よりも結合力が高いです。ですから、以下のような例でも丸括弧は不要です。

ap2 :: (Int -> Int) -> Int -> (Int -> Int) -> Int -> Int
ap2 f x g y = f x + g y

二項演算子には結合力が0〜9の数値で定められています。9が最も高い結合力です。関数適用の結合力は10だと思えばいいでしょう。実際の値は ghci の :info で調べるか、仕様書のTable4.1を見て下さい。

関数と二項演算子

二引数の関数は、バッククォートで囲むと二項演算子になります。

ghci> "foo" `isPrefixOf` "foobar"
True

二項演算子は、丸括弧で囲むと関数になります。

ghci> (*+) 1 2
5

パターンマッチ

パターンマッチとは分岐のための手段です。ある型に対して構成子で分岐できます。トップレベルの関数を分けて書くことで、分岐を実現できます。

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

上から順に調べていって最初にマッチした式が使われます。パターンマッチに変数(引数)を利用すると、必ずマッチします。

利用しない引数は、その値を捨てることを明示するために "_" を使います。

isZero :: Int -> Bool
isZero 0 = True
isZero _ = False

他の例:

area :: Shape -> Int
area (Circle r)      = 3 * r * r
area (Rectangle x y) = x * y

Circle r を丸括弧で囲っていることに注意して下さい。囲まないと、2引数の関数という意味になります。

リストの構成子は "[]" と ":" でしたので、以下のようにパターンパッチが使えます。

length :: [a] -> Int
length []     = 0
length (_:xs) = 1 + length xs

ガード

構成子よる分岐の後で、"|" に続いて Bool の式を書けば、さらに分岐できます。

data Sign = Minus | Zero | Plus

sign :: Int -> Sign
sign 0       = Zero
sign n
  | n > 0     = Plus
  | otherwise = Minus

otherwise は True の別名です。ガードは2文字下げて書きましょう。

ガードは、いろいろな使い方が考えられます。

sign :: Int -> Sign
sign n
  | n > 0     = Plus
  | n == 0    = Zero
  | otherwise = Minus
sign :: Int -> Sign
sign n
  | n > 0  = Plus
  | n == 0 = Zero
  | n < 0  = Minus
sign :: Int -> Sign
sign 0         = Zero
sign n | n > 0 = Plus
sign _         = Minus

ローカル定数とローカル関数 (where)

where の後にローカル定数やローカル関数を定義できます。

abs :: Int -> Int
abs n = abs' s
  where
    s = sign n
    abs' Minus = n * (-1)
    abs' Zero  = n
    abs' Plus  = n

where は2文字下げ、ローカル定数とローカル関数は4文字下げて書きましょう。

ローカル変数とローカル関数を定義する別の方法として let がありますが、この授業では使いません。

if then else

Haskell の if then else は else を省略できません。

collatz :: Int -> Int
collatz x = if x `mod` 2 == 0 then x `div` 2 else x * 3 + 1

この授業では if then else は使いません。ガードを使えばよいからです。

collatz :: Int -> Int
collatz x
  | x `mod` 2 == 0 = x `div` 2
  | otherwise      = x * 3 + 1

case of

case of はパターンマッチのための基本的な構文です。

abs :: Int -> Int
abs n = case sign n of
    Minus -> n * (-1)
    _     -> n

トップレベルでのパターンマッチでは左と右を "=" でつないでいましたが、case of では "->" でつなぐことに注意しましょう。case of の後の構成子は、4文字下げて書きましょう。

構成子の分岐の後に、さらにガードも書けます。

check_str :: String -> String
check_str xs = case xs of
    []            -> "Null string"
    (x:_)
      | isUpper x -> "Capitalized string"
      | otherwise -> "Somthing else"

トップレベルでのパターンマッチは、case of の構文糖衣です。

check_str :: String -> String
check_str []    = "Null string"
check_str (x:_)
  | isUpper x -> "Capitalized string"
  | otherwise -> "Somthing else"

if then else も case of の構文糖衣です。

collatz :: Int -> Int
collatz x = if x `mod` 2 == 0 then x `div` 2 else x * 3 + 1
collatz :: Int -> Int
collatz x = case x `mod` 2 == 0 of
    True  -> x `div` 2
    False -> x * 3 + 1

部分適用

関数は、一部の引数を与えることで、カスタマイズした関数にすることができます。

ghci> :type distance 
distance :: Int -> Int -> Int

ghci> :type distance 3
distance 3 :: Int -> Int

ghci> let distance3 = distance 3

ghci> distance3 4
25

セクション

二項演算子は、左右どちらかの引数を与え丸括弧で囲むことで、カスタマイズした関数にすることができます。

ghci> :type (+)
(+) :: Num a => a -> a -> a

ghci> :type (+ 1)
(+ 1) :: Num a => a -> a

ghci> let plus1 = (+ 1)

ghci> plus1 2
3

モジュール

1ファイルが1モジュールです。モジュール名とファイル名は一致さます。ファイルの最初には、以下が必要です。

module モジュール名 where

この文がないと、以下が省略されているとして取り扱われます。

module Main where

Main モジュールには main という関数が必要です。

モジュール名の横に、どの関数、どの型を公開するか指定できます。

module Example(func, Type(..)) where

func = (+1)

data Type = Boo | Hoo | Woo

Type(..)のように指定すると、型名に加えて構成子が公開されます。型だけ公開すると、構成子にはアクセスできない抽象データ型になります。つまり、その型に対してパターンマッチできませn。(もちろん、このモジュール内では、構成子にアクセスできます。)

module Example(func, Type) where

func = (+1)

data Type = Boo | Hoo | Woo