Haskellチートシート(翻訳)

  • 71
    Like
  • 0
    Comment
More than 1 year has passed since last update.

http://cheatsheet.codeslower.com/CheatSheet.pdf の翻訳です。
日本語訳版のファイルが無くなっているようなので。
誤字誤訳その他問題があればコメント、編集リクエストください。
また、直訳な部分も多いので良い訳がある場合もコメント、編集リクエストください。

Haskell Cheat Sheet

このチートシートはHaskellの基本的な要素である構文、キーワード、その他の要素を記しています。また、実行可能なHaskellファイル、印刷可能なドキュメントです。お好きなインタプリタでソースを読み込んでサンプルコードを動かしてください。

基本構文

コメント

単一行コメントは--で始まり、行末までがコメントになります。複数行コメントは{-から-}までです。コメントはネストすることができます。
HaskellコードのドキュメントシステムであるHaddockのための関数定義のコメントは{- |で始まり、その型へのコメントは-- ^です。(訳注:より良い訳求む)

予約語

Haskellで以下のワードは予約されています。変数か関数の名前で使うと構文エラーになります。

  • case
  • class
  • data
  • deriving
  • do
  • else
  • if
  • import
  • in
  • infix
  • infixl
  • infixr
  • instance
  • let
  • of
  • module
  • newtype
  • then
  • type
  • where

文字列

  • "abc" - ユニコード文字列で['a', 'b', 'c']の糖衣です。
  • 'a' - 単一文字。

複数行文字列

通常文字列が改行文字を含むと構文エラーになります。従ってこれは構文エラーです。

string1 = "My long
string."

バックスラッシュ('\')は改行を"エスケープ"できます。

string1 = "My long \
\string."

バックスラッシュの間の部分は無視されます。文字列の改行は明示的に表されます。

string2 = "My long \n\
\string."

従って、string1

My long string.

そしてstring2

My long
string.

と評価されます。

エスケープコード

以下のエスケープコードは文字と文字列の中で使うことができます。
* \n, \r, \fなど - 改行、キャリッジリターン、フォームフィードなどの標準的なコードがサポートされています。
* \72, \x48, \o110 - それぞれ10進数、16進数、8進数表記の文字です。
* \& - 数字のエスケープコードが数字リテラルと隣り合うのを許可する"null"のエスケープ文字。 例えば\x2C4は(Unicodeにおける)∧ですが、\x2C\&4,4です。この繋がりは文字リテラルで使うことができません。

数字

  • 1 - 整数か浮動小数点数
  • 1.0, 1e10 - 浮動小数点数
  • 0o1, 001 - 8進数
  • 0x1, 0X1 - 16進数
  • -1 - 負数。マイナス記号("-")を数字と分けることはできません

列挙

  • [1..100] - 数字のリスト - 1, 2, ..., 100
  • [100..] - 数字の無限リスト - 100, 101, 102, ...,
  • [110..100] - 空リストですが、[110, 109 .. 100]は110から100のリストになります
  • [0, -1 ..] - 負数のリスト
  • [-110..-100] - 構文エラー。負数には[-110.. -100]が要求されます。
  • [1, 3..99], [-1, 3..99] - 1から2ごとの99までのリスト、-1から4ごとの99までのリスト

実はEnumクラスのどんな値でも使えます。

  • ['a' .. 'z'] - 文字のリスト - a, b, .., z
  • ['z', 'y' .. 'a'] - z, y, x, .., a
  • [1.0, 1.5 .. 2] - [1.0, 1.5, 2.0]
  • [UppercaseLetter ..] - GeneralCategory値(Data.Charから)のリスト

リストとタプル

  • [] - 空リスト
  • [1, 2, 3] - 3つの数字のリスト
  • 1 : 2 : 3 : [] - "cons"(:)と"nil"([])を使ったリストの別記法
  • "abc" - 三つの文字のリスト(文字列はリスト)
  • 'a' : 'b' : 'c' : [] - 文字のリスト("abc"と同じ)
  • (1, "a") - 数字と文字列の2つの要素のタプル
  • (head, tail, 3, 'a') - 2つの関数と数字と文字の4要素のタプル

レイアウトルール、波括弧とセミコロン

HaskellはCのように波括弧とセミコロンを使って書くことができます。しかし、そうする人はいません。代わりにスペースがスコープを表す"レイアウト"ルールを使います。一般的なルールは常にインデントすることです。コンパイラがエラーを出す時に、よりインデントしてください。(訳注:より良い訳求む)

波括弧とセミコロン

セミコロンは式の終了、波括弧はスコープを表します。これらはいくつかのキーワード、where, let, do, ofの後に使えます。これらは関数本体の定義では使えません。例えば以下はコンパイルされません。

square2 x = { x * x; }

しかし、これはうまくいきます。

square2 x = result
  where { result = x * x; }

関数定義

where節がなければ、関数本体は関数の名前から最低でも1スペースはインデントしてください。

square x =
  x * x

where節は関数名から最低でも1スペース、関数本体はwhere節から最低でも1スペースはインデントしてください。

square x =
    x2
  where x2 =
    x * x

Let

letの本体はletの最初の定義から最低でも1スペースインデントしてください。もしletが自身の行に現れるならば、定義部はletよりインデントされていなければなりません。

square x =
  let x2 =
        x * x
  in x2

上から分かるように、inキーワードはletと同じ列でなければなりません。最後に、複数の定義がある時に全ての識別子は同じ列になくてはなりません。(訳注:より良い訳求む)

宣言など

以下のセクションでは関数宣言のルール、リストの理解、言語の他のことについて詳説します。

関数宣言

関数は関数名、引数、イコール記号の宣言によって定義されます。

square x = x * x

全ての関数は小文字か"_"で始まる必要があります。そうでなければ構文エラーとなります。

パターンマッチング

関数の複数節は引数の値のパターンマッチングによって定義することができます。以下は4つの分けられたケースを持つagree関数です。

-- 文字列の"y"が与えられた時にマッチ
agree1 "y" = "Great!"
-- 文字列の"n"が与えられた時にマッチ
agree1 "n" = "Too bad."
-- 'y'で始まる文字列が与えられた時にマッチ
agree1 ('y':_) = "YAHOO!"
-- 他のどんな値が与えられた時にもマッチ
agree1 _ = "SO SAD."

'_'文字はワイルドカードでどんな値にもマッチします。
パターンマッチングは入れ子の値にも使えます。

このデータ宣言があるとして、

data Bar = Bil (Maybe Int) | Baz

Maybeの定義は7ページを見てください。
Bilがある時入れ子になったMaybeをマッチさせることができます。

f (Bil (Just _)) = ...
f (Bil Nothing) = ...
f Baz = ...

パターンマッチングは変数への代入もできます。例えばこの関数は与えられた文字列が空かどうかを定めます。もし空でなければstrに束縛された値を小文字に変更します。

toLowerStr [] = []
toLowerStr str = map toLower str

上のstrはどんなものにもマッチする点で同じです。唯一の違いはマッチした値に名前が与えられるかどうかです。

n + kパターン

この(しばしば論争になります)パターンマッチングは特定の数字の表現のマッチを簡単にします。アイデアはベースとなるケース("n"の割り当て)にマッチのための定数を定義し、他のマッチ("k"の割り当て)をベースクラスへの添加として定義します。以下はいくぶん非効率な偶数かどうかの試行方法です。

isEven 0 = True
isEven 1 = False
isEven (n + 2) = isEven n

引数捕捉(asパターン)

引数の捕捉は追加の変数の宣言なしでの値のパターンマッチング、かつ、その使用に便利です。マッチするパターンと値を束縛する変数の間で'@'シンボルを使ってください。この機能は以下で表示するためのリストの先頭をlに、長さを計算するためにリスト全体をlsに束縛することに使われています。

len ls@(l:_) = "List starts with " ++
  show l ++ " and is " ++
  show (length ls) ++ " items long."
len [] = "List is empty!"

ガード

真偽関数は"ガード"としてパターンマッチングに伴って関数の定義に使うことができます。パターンマッチングなしの例です。

which n
  | n == 0 = "zero!"
  | even n = "even!"
  | otherwise = "odd!"

otherwiseは常にTrueと評価され、"デフォルト"の場合を明示するのに使われます。

ガードはパターンを伴って使うこともできます。以下は文字列の最初の文字が大文字か小文字か定める関数です。

what [] = "empty string!"
what (c:_)
  | isUpper c = "uper case!"
  | isLower c = "lower case!"
  | otherwise = "not a letter!"

マッチングとガードの順

パターンマッチングは上から下へ進みます。同様に、ガード節も上から下へ試されます。例えばこれらの関数はどれも面白くないです。

allEmpty _ = False
allEmpty [] = True

alwaysEven n
  | otherwise = False
  | n `div` 2 == 0 = True

レコード構文

普通、パターンマッチングはマッチした値の中で引数の位置に基づいて行われます。しかしレコード構文で宣言された型はレコードの名前に基づいてマッチすることができます。この型を与えます。

data Color = C { red
  , green
  , blue :: Int }

greenだけでマッチできます。

isGreenZero (C { green = 0 }) = True
isGreenZero _ = False

引数の捕捉はこの構文でも可能ですが、不恰好です。上のものに続けて、Pixel型と0ではない緑を全て黒に置き換える関数を定義します。

data Pixel = P Color

-- 緑が0なら変更しない
setGreen (P col@(C { green = 0 })) = P col
setGreen _ = P (C 0 0 0)

遅延パターン

この構文は(irrefutable patterns)としても知られており、常に成功するパターンマッチです。エラーを起こすかもしれないマッチした値を使おうとするのでなければ、これはこのパターンを使うどんな節にも成功するということです(訳注:より良い訳求む)。これは特定の値のが取られない、たとえ値がない時に一般的に便利です。
例えば、デフォルト値のためのクラスを定義します。

class Def a where
  defValue :: a -> a

defValue に正しい型を与えて、その型のデフォルト値を返すという考えです。基本的な型のインスタンス定義は簡単です。

instance Def Bool where
  defValue _ = False

instance Def Char where
  defValue _ = ' '

Maybe はもう少しトリッキーです、なぜなら型のデフォルト値が欲しいからです。しかしコンストラクタはNothingかもしれません。以下の定義は動きますが、Nothingが渡された時にNothingを得るので最適ではありません。

instance Def a => Def (Maybe a) where
  defValue (Just x) = Just (defValue x)
  defValue Nothing = Nothing

むしろ代わりにJust (デフォルト値)を返して欲しいのです。ここで遅延パターンの出番です - たとえNothingが与えられてもJust xにマッチし、デフォルト値を使うと決め込むのです。

instance Def a => Def (Maybe a) where
  defValue ~(Just x) = Just (defValue x)

x 値が実際に評価されない限り、安全です。どの基本的な型もxを見る必要がありません(_のマッチを見てください)。よって上手くいきます。
以上のことの1つの小さな問題はNothingコンストラクタを使う時にインタプリタやコードに型注釈を与えないといけないことです。NothingMaybe a型を持っていますが、他の十分な情報がなければHaskellにaは何か教えないといけません。デフォルト値のいくつかの例です。

-- Return "Just False"
defMB = defValue (Nothing :: Maybe Bool)
-- Return "Just ' '"
defMC = defValue (Nothing :: Maybe Char)

リスト内包表記

リスト内包表記は4つのタイプの要素から成ります。ジェネレータとガードとローカル束縛とターゲットです。リスト内包表記は与えられたジェネレータとガードに基づいたターゲットの値のリストを作ります。この内包表記は全ての平方数を生成します。

squares = [x * x | x <- [1..]]

x <- [1..]は全てのIntegerの値のリストを生成し、xに一つ一つ入れます。x * xは自身をかけることによってリストの要素を作ります。

ガードは除外される要素をもたらします。以下は与えられた数の約数(自身を除く)がどのように計算できるかを示します。dがガードとターゲットでどのように使われているか気を付けてください。

divisors n =
  [d | d <- [1..(n `div` 2)]
     , n `mod` d == 0]

ローカル束縛は生成された式や後続のジェネレータとガードで使われる新しい定義を与えます。以下はzabの小さい方を表すのに使われるものです。

strange = [(a,z) | a <-[1..3]
                 , b <-[1..3]
                 , c <- [1..3]
                 , let z = min a b
                 , z < c ]

内包表記は数字に限定されいません。どんなリストでもできます。全ての大文字の文字が生成でき、

ups =
  [c | c <- [minBound..maxBound]
     , isUpper c]

あるいはリストwordから特定のbrの全ての位置を見つけることができます(0からインデックスされる)。

idxs word br =
  [i | (i, c) <- zip [0..] word
     , c == br]

リスト内包表記特有の機能はパターンマッチングの失敗がエラーを起こさないことです。結果のリストから除外されるだけです。

演算子

Haskellでは厳密な"演算子"が予めほとんど定義されていません(予め定義されたほとんどは実は構文です。例えば"=")。代わりに演算子は二つの引数を取るただの関数で、特別な構文のサポートされます。そのように使われる演算子は括弧を使うことで前置関数として使われます。

3 + 4 == (+) 3 4

新しい演算子を定義するには普通の関数として定義しますが、演算子は二つの引数の間に現れます。これは2つの文字列の間にコンマを入れて余計なスペースが含まれないようにするものです。

first ## last =
  let trim s = dropWhile isSpace
        (reverse (dropWhile isSpace
          (reverse s)))
  in trim last ++ ", " ++ trim first

> " Haskell " ## " Curry "
Curry, Haskell

もちろんこの形でも完全なパターンマッチング、ガード、その他は有効です。型のシグネチャは少し違いますが。演算子の名前は括弧の中になくてはなりません。

(##) :: String -> String -> String

演算子の定義に使える記号は

# $ % & * + . / < = > ? @ \ ^ | - ~

です。
しかし、再定義できない"演算子"がいくつかあります。<- -> =です。最後の=はそれ自身によって再定義することはできませんが、複数文字の演算子の一部としては使うことができます。"bind"関数、>>=は例の1つです。

優先度と結合性

優先度と結合性は合わせてfixityと呼ばれ、その演算子はinfixinfixrinfixlキーワードを通して定められます。これらはトップレベル関数、局所レベル定義両方に適用されます。構文は

{infix | infixr | infixl} 優先度 演算子

で、優先度は0から9です。演算子は実は引数を二つ取るあらゆる関数(例えばバイナリ演算子)です。演算子が左か右の結合性であるかはinfixlなのかinfixrなのかによってそれぞれ明示されます。infixは結合性を持たないことを宣言します。

優先度と結合性は算術的機能のルールの多くを期待します。例えば加算と乗算の優先度を少し更新を考えてみます。

infixl 8 `plus1`
plus1 a b = a + b
infixl 7 `mult1`
mult1 a b = a * b

結果には驚きます。

> 2 + 3 * 5
17
> 2 `plus1` 3 `mult1` 5
25

優先度の逆転は他にも面白い効果を持ちます。割り算を再定義します。

infixr 7 `div1`
div1 a b = a / b

興味深い結果を得ます。

> 20 / 2 / 2
5.0
> 20 `div1` 2 `div1` 2
20.0

カリー化

Haskellでは関数は引数の全てを一度に与える必要はありません。例えば、testに依存する文字列のある要素だけを変換するconvertOnly関数を考えてみます。

convertOnly test change str =
  map (\c -> if test c
              then change c
              else c) str

convertOnlyを使って、特定の文字を数字に変換するl33t関数を書くことができます。

l33t = convertOnly isL33t toL33t
  where
    isL33t 'o' = True
    isL33t 'a' = True
    -- etc.
    isL33t _ = False
    toL33t 'o' = '0'
    toL33t 'a' = '4'
    -- etc.
    toL33t c = c

l33tは明示的な引数を取らないことに気付いてください。また、convertOnlyの最後の引数は与えられていません。しかし、l33tの型シグネチャは全てを物語っています。

l33t :: String -> String

すなわち、l33tは文字列を受け取って文字列を作ります。l33tは常に文字列を受け取り文字列を作る関数という意味で'不変的'です。l33tconvertOnlyの'カリー化'された形で、それは3つの引数の内2つだけを与えられたものです。
更に続けて、大文字だけを変えたい関数を書きたいとします。isUpperをtestに適用するのは分かっていることですが、変換方法は明示したくありません。そんな関数はこう書けます。

convertUpper = convertOnly isUpper

その型シグネチャは

convertUpper :: (Char -> Char)
  -> String -> String

です。
すなわち、convertUpperは2つの引数を取ります。最初は個々の文字を変換する変換関数、二つ目は変換される文字列です。

複数の引数を取るどんな関数のカリー化された形を作ることができます(原文”A curried form of any function which takes multiple arguments can be created”)。これの考え方の1つの方法は関数のシグネチャの中の矢印は1つ以上の引数を与えることによって作ることのできる新しい関数を表しています。

セクション記法

演算子は関数で、他の関数と同じようにカリー化することができます。例えば"+"のカリー化されたバージョンはこう書けます。

add10 = (+) 10

しかし、これは不恰好で読むのが難しいです。"セクション記法"は括弧を使って演算子をカリー化します。セクション記法を使ったadd10はこれです。

add10 = (10 +)

与えられた引数は右か左にあり、これは与えるべき位置を示します。これは連結等の演算子に重要です。

onLeft str = (++ str)
onRight str = (str ++)

これはかなり違った結果を作ります。

> onLeft "foo" "bar"
"barfoo"
> onRight "foo" "bar"
"foobar"

値の更新とレコード構文

Haskellは純粋な言語で、それ自身として可変な状態を持ちません。すなわち、一度値がセットされると変更されません。"更新"は"変更された"フィールドの新しい値による、正にコピー作業です。例えば先に定義したColor型を使って、より簡単にgreenにゼロをセットする関数を書けます。

noGreen1 (C r _ b) = C r 0 b

これは少し冗長で、レコード構文を使って書き直すことができます。この種の"更新"は明示されたフィールドに値をセットし、残りをコピーするだけです。

noGreen2 c = c { green = 0 }

ここでは、cColorの値を捕捉して新しいColorの値を返しています。その値はcredblueと同じ値を持つことになり、green部分は0です。これはredフィールドと等しい値をセットするためにパターンマッチングと組み合わせることができます。

makeGray c@(C { red = r }) =
  c { green = r, blue = r}

Colorの値を手に入れるための引数の捕捉("c@")と内側のredフィールドを手に入れるためのレコード構文("C { red = r}")によるパターンマッチングを使わなくてはならないことに注意してください。

匿名関数

匿名関数(ラムダ式や短くラムダ)は名前のない関数です。これらはいつでもこのように定義できます。

\c -> (c, c)

そしてそれは引数をとって両方の位置に引数を持つタプルを返します。名前の必要ない単純な関数に便利です。以下は文字列が大文字と小文字とスペースが混ざったものだけから成っているかどうか決める関数です。

mixedCase str =
  all (\c -> isSpace c ||
             isLower c ||
             isUpper c) str

もちろんラムダ式は関数から返すこともできます。この古典(原文"This classic")は元より与えられた引数と乗算する関数を返します。

multBy n = \m -> n * m

例えば

> let mult10 = multBy 10
> mult10 10
100

型シグネチャ

Haskellは尽く型推論をします。これはほとんどの場合型を書かなくてもよいということです。しかし、型シグネチャは依然として少なくとも2つの理由で便利です。

ドキュメンテーション - 例えコンパイラが関数の型を分かっても、他のプログラマやあなた自身さえも分からないかもしれません。トップレベルの全ての関数に型シグネチャを書くことはとても良い形式と考えられています。

特化 - 型クラスは関数のオーバーロードをもたらします。例えば数字のリストの全てをマイナスする関数は

negateAll :: Num a => [a] -> [a]

というシグネチャを持ちますが、能率や他の理由のためにInt型のみ許可したいならば、

negateAll :: [Int] -> [Int]

という型シグネチャによって達成できます。

型シグネチャはトップレベル関数とネストしたletwhere定義に現れます。一般にこれはドキュメンテーションに便利ですが、いくつかの場合で多相を防ぐのに必要です。型シグネチャは最初に型付けされるものの名前に続いて、::が続き、型が続きます。この例はすでに上で述べました。

型シグネチャは実装の直上に現れる必要はありません。それを含むモジュールのどこでも明示することができます(たとえそれより下でも!)。同じシグネチャを持つ複数の関数は一緒に定義できます。

pos, neg :: Int -> Int

...

pos x | x < 0 = negate x
      | otherwise = x

neg y | y > 0 = negate y
      | otherwise = y

型注釈

Haskellでは時々型が何を意味しているのか決定できません。この古典的な実演は"show . read"問題です。

canParseInt x = show (read x)

Haskellはread xの型を知らないのでこの関数をコンパイルすることはできません。注釈を通して型を制限しなければなりません。

canParseInt x = show (read x :: Int)

注釈は型シグネチャと同じ構文を持ちますが、どんな式を装飾しても良いです。上の注釈はread xという式に対するもので、xへのものではありません。関数適用(例えばread x)だけは注釈よりも優先的に結合します。もしそうでなければ、上は(read x) :: Intと書かなければならなかったでしょう。

Unit

() - "unit"型と"unit"値。値と型は不要な情報を表します。

キーワード

Haskellのキーワードを以下にアルファベット順で列挙しました。

Case

caseはC#やJavaの switch文と似ていますが、パターンにマッチすることができ、値の形が検査されます。シンプルなデータを考えます。

data Choices = First String | Second |
  Third | Fourth
-- deriving (Show, Eq)

caseはどのchoiseが与えられたか決めるのに使うことができます。

whichChoice ch =
  case ch of
    First _ -> "1st!"
    Second -> "2nd!"
    _ -> "Something else."

関数定義のパターンマッチングと同様に'_'トークンはどんな値にもマッチするワイルドカードです。

入れ子と捕捉

入れ子のマッチングと束縛は許可されています。例えばこのMaybe型の定義

data Maybe a = Just a | Nothing

Maybeによって入れ子になったマッチを使ってchoiceが与えられたかどうか決定することができます。

anyChoice1 ch =
  case ch of
    Nothing -> "No choice!"
    Just (First _) -> "First!"
    Just Second -> "Second!"
    _ -> "Something else."

束縛はマッチした値の操作にも使われます。

anyChoice2 ch =
  case ch of
    Nothing -> "No choice!"
    Just score@(First "gold") ->
      "First with gold!"
    Just score@(First _) ->
      "First with something else: "
      ++ show score
    _ -> "Not first."

マッチの順序

マッチングは上から下へ進みます。もしanyChoice1が以下のように並び替えられると、常に最初のパターンが成功します。

anyChoice3 ch =
  _ -> "Something else."
  Nothing -> "No choice!"
  Just (First _) -> "First!"
  Just Second -> "Second!"

ガード

ガード、あるいは条件付きマッチングは関数定義のような時に使われます。ただ一つの違いは=の代わりに->を使うことです。これは大文字小文字を気にしない文字列マッチをするシンプルな関数です。

strcmp s1 s2 = case (s1, s2) of
  ([], []) -> True
  (s1:ss1, s2:ss2)
    | toUpper s1 == toUpper s2 ->
      strcmp ss1 ss2
    | otherwise -> False
  _ -> False

クラス

Haskellの関数は特定の型か型の集合に対して動くように定義され、一度以上定義することはできません。ほとんどの言語は"オーバーロード"の概念をサポートしており、引数の型に依存した異なった振る舞いを持つことができます。Haskellはclassinstance宣言を通してオーバーロードを達成します。class定義はクラスのメンバー(例えばインスタンス)ならどんな型でも適用できる1つ以上の関数を定義します。クラスはJavaやC#のインターフェイスに似ており、インスタンスはインターフェイスの具体的な実装に似ています。

クラスは1つ以上の型変数を宣言しなければなりません。技術的にHaskell98は1つの型変数だけを許しますが、ほとんどのHaskell実装はいわゆるマルチパラメータをサポートします。それは1つ以上の型変数を許可します。

与えられた型に味を付けるクラスを定義します。

class Flavor a where
  flavor :: a -> String

関数の型シグネチャだけを与える宣言をしていることに注意してください。ここでは実装を与えていません(いくつかの例外はデフォルト実装を見てください)。続いて、いくつかのインスタンスを定義します。

instance Flavor Bool where
  flavor _ = "sweet"

instance Flavor Char where
  flavor _ = "sour"

flavor Trueを評価します。

> flavor True
"sweet"

flavor 'x'

> flavor 'x'
"sour"

デフォルト実装

クラスの関数にデフォルト実装を与えることができます。クラスの他の表現で特定の関数を定義出来る時に便利です。デフォルト実装は他の関数の1つに関数本体を与えることで定義されます。標準的な例はEqで、これは/=(不等号)を==で定義しています。

class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool
  (/=) a b = not (a == b)

再帰的定義も作ることができます。引き続きEqを例に、==/=で定義されています。

(==) a b = not (a /= b)

しかし、もしインスタンスがメンバー関数の十分な具体的な実装を与えられないと、そのインスタンスを使うプログラムはループします。

データ

いわゆる代数的データ型は以下のように定義できます。

data MyType = MyValue1 | MyValue2

MyValue は型の名前です。MyValue1MyValue2は型ので、コンストラクタと呼ばれます。複数のコンストラクタは'|'の文字で仕切られています。型とコンストラクタの名前は大文字で始まっていなければなりません。そうでなければ構文エラーです。

引数のあるコンストラクタ

先の型は列挙型として以外は面白くありません。引数を取るコンストラクタは宣言できて、より多くの情報を持つことが許されています。

data Point = TwoD Int Int
  | Three Int Int Int

それぞれのコンストラクタの引数はの名前ですで、コンストラクタではありません。この種の宣言は間違いです。

data Poly = Triangle TwoD TwoD TwoD

代わりにPoint型を使わなければなりません。

data Poly = Triangle Point Point Point

型とコンストラクタの名前

型とコンストラクタの名前は同じにすることができます。なぜなら混乱を起こす所で使われることはないからです。例えば

data User = User String | Admin String

これは2つのコンストラクタ、UserAdminを持つUserという名前の型です。関数でこの型を使うことは違いを明確にします。

withUser (User _) = "normal user."
withUser (Admin _) = "admin user."

いつくかの文献ではこの習慣を洒落として参照します。

型変数

いわゆる多相的なデータ型の宣言をすることは宣言に型変数を追加することと同じくらい簡単です。

data Slot1 a = Slot1 a | Empty1

これは2つのコンストラクタ、Slot1Empty1を持つSlot1型を宣言します。Slot1コンストラクタはあらゆる型の引数でも取ることができます。そしてそれをaという型変数で表しています。

コンストラクタに型変数と明示的な型を混ぜることもできます。

data Slot2 a = Slot2 a Int | Empty2

上記のSlot2コンストラクタはあらゆる型の値とIntの値を取ります。

レコード構文

コンストラクタの引数は上記のような位置的なものか、それぞれの引数に名前を与えたレコード構文を使ったもののどちらかで宣言することができます。例えばこれは妥当な名前の引数のContact型の宣言です。

data Contact = Contact { ctName :: String
      , ctEmail :: String
      , ctPhone :: String }

これらの名前はセレクタアクセサ関数として参照され、ただの関数です。小文字かアンダースコアで始まっていなくてはならず、スコープの他の関数と同じ名前を持つことはできません。従って"ct"をそれぞれに付けています。(同じ型の)複数のコンストラクタは同じ型の値に同じ名前のアクセサ関数を使うことができますが、もしアクセサが全てのコンストラクタに使われていないと危険かもしれません。このわざとらしい例を考えてください。

data Con = Cont { conValue :: String }
  | Uncon { conValue :: String }
  | Noncon

whichCon con = "convalue is " ++
  conValue con

もしwhichConNoncon値で呼ばれるとランタイムエラーが起きます。

最後に、他のところで説明したように、これらの名前はパターンマッチング、引数捕捉、"更新"に使うことができます。

導出

多くの型は退屈な定義が必要になる共通の演算があります。文字列に変換したり文字列から変換したり、同値性の比較、シークエンスの処理。これらの能力はHaskellでは型クラスを使って定義します。

これら7つの演算はとてもありふれたものなので、Haskellは関連する型に型クラスを自動的に実装するderivingキーワードを提供しています。EqReadShowOrdEnumIxBoundedです。

2つのderivingの形式が可能です。1つ目は1つのクラスだけ導出する型の時に使われます。

data Priority = Low | Medium | High
  deriving Show

2つ目は複数クラスを導出する時に使われます。

data Alarm = Soft | Loud | Deafening
  deriving (Read, Show)

上記の7つ以外のderivingは構文エラーです。

クラス制約

データ型は型変数でのクラス制約を付けて宣言することができますが、この慣例は非推奨です。モジュールシステムを使った"生の"データコンストラクタを隠し、妥当な制約を適用する代わりに"スマートな"コンストラクタをエクスポートする方が良いです。どんな場合でも、使われる構文は

data (Num a) => SomeNumber a = Two a a
  | Three a a a

これは1つの型変数の引数を取るSomeNumber型です。妥当な型はNumクラスです。

導出

dtaderivingを見てください。

Do

doキーワードはそれに続くコードがモナドの文脈にあることを表します。文は改行で分けられ、割り当ては<-であることを表し、letinキーワードを必要としないことを表します。

IfとIO

ifはIOと使う時にはトリッキーです。概念的に他の文脈におけるifと違いはないですが、直感的に開発するのは難しいです。System.DirectorydoesFileExists関数を考えます。

doesFileExist :: FilePath -> IO Bool

if 文はこの"シグネチャ"を持ちます。

if-then-else :: Bool -> a -> a -> a

つまり、Bool値を取って情況に応じた値を評価します。この型シグネチャからdoesFileExistifに直接使えないことがはっきりとします。

wrong fileName =
  if doesFileExist fileName
    then ...
    else ...

つまり、doesFileExistの結果はIO Bool値になり、ifBool値を求めます。代わりにIOアクションによって正しい値を"抽出"しなければなりません。

right1 fileName = do
  exists <- doesFileExist fileName
  if exists
    then return 1
    else return 0

returnの使い方に気を付けてください。doIOモナドの中に入れるので、returnを通して以外、"外に出る"ことができません。ifを直列で使う必要はありません。letを使って状況を評価し値を手にいれることができます。

right2 fileName = do
  exists <- doesFileExist fileName
  let result =
        if exists
          then 1
          else 0
  return result

もう一度returnに気を付けてください。これをletの中に入れることはできません。代わりに関数の最後に一度使うことができます。

複数のdo

doifcaseと一緒に使う時、枝のどちらかが文を持つならばもう一つのdoが必要になります。ifを使った例は

countBytes1 f =
  do
    putStrLn "Enter a filename."
    args <- getLine
    if length args == 0
      -- 'do'なし
      then putStrLn "No filename given."
      else
        -- 複数行が必要
        -- 新しい'do'
        do
          f <- readFile args
          putStrLn ("The file is " ++
            show (length f)
            ++ " bytes long.")

そしてcaseを使ったものは

countBytes2 =
  do
    putStrLn "Enter a filename."
    args <- getLine
    case args of
      [] -> putStrLn "No args given."
      file -> do
        f <- readFile file
        putStrLn ("The file is " ++
          show (length f)
          ++ " bytes long.")

代わりになる構文はセミコロンと波括弧です。doは必要なのは同じですが、インデントは不要です。このコードはcaseの例ですが、ifも原理は同じです。

countBytes3 =
  do
    putStrLn "Enter a filename."
    args <- getLine
    case args of
      [] -> putStrLn "No args given."
      file -> do { f <- readFile file;
        putStrLn ("The file is " ++
          show (length f)
          ++ " bytes long."; }

エクスポート

モジュールのセクションを見てください。

If, Then, Else

ifは常に値を"返します"。式であって、フローを操作する文ではありません。この関数は与えられた文字列が小文字で始まっているかテストし、もしそうであれば大文字に変換します。

-- 最初の文字を取得するために
-- パターンマッチングを使う
sentenceCase (s:rest) =
  if isLower s
  then toUpper s : rest
  else s : rest
-- 他の場合は空文字列
sentenceCase _ = []

インポート

モジュールのセクションを見てください。

In

let のセクションを見てください。

Infix, infixl, infixr

演算子のセクションを見てください。

インスタンス

型クラスのセクションを見てください。

Let

局所関数は関数の中でletを使って定義することができます。letキーワードには常にinが続かなければいけません。inキーワードはletキーワードと同じインデントになくてはなりません。関数は同じスコープ内の他の全ての関数と変数にアクセスできるように定義されます(letで定義されたものも含みます)。この例では、multは引数nxを乗算します。そしてそれはオリジナルのmultiplesに渡され、multはmapによって10までの数にxを乗算するのに使われます。

multiples x =
  let mult n = n * x
  in map mult [1..10]

引数の無いlet "関数"は実際に定数で、一度評価されると外側の関数の呼び出しのために再度評価されることはありません。これは関数の共通部分の捕捉と再利用に便利です。これは数のリストの合計とその平均を与える馬鹿馬鹿しい例です。number1からmまでのリストとし、一度のlistStatsの呼び出しに対して一度しか評価されません。同様に、totalavgも一度の呼び出しに対して一度しか評価されません。

listStats m =
  let numbers = [1..m]
      total = sum numbers
      avg = total / m
  in "total: " ++ show total ++
     ", avg: " ++ show avg

分解(Deconstruction)

let 定義の左辺は引数がサブコンポーネントにアクセスできる時、分解もできます。この定義は文字列から最初の3文字を抽出します。

firstThree str =
  let (a:b:c:_) = str
  in "Initial three characters are: " ++
     show a ++ ", " ++
     show b ++ ", and " ++
     show c

以下のものとは違うことに注目してください。これはちょうど3文字の文字列の時だけ動きます。

onlyThree str =
  let (a:b:c:[]) = str
  in "The characters given are: " ++
     show a ++ ", " ++
     show b ++ ", and " ++
     show c

Of

case のセクションを見てください。

モジュール

モジュールは関数、型、クラス、インスタンス、その他モジュールををエクスポートする要約(compilation)の単位です。モジュールは1つのファイルだけに定義されますが、そのエクスポートは複数のソースに由来します。Haskellのモジュールを作るには、一番上にモジュール宣言を加えるだけです。

module MyModule where

モジュールの名前は大文字で始まっていなくてはなりませんが、それ以外はピリオドと数字とアンダースコアを含めることができます。ピリオドは階層を意味し、Haskellコンパイラはそれらをファイルのディレクトリの印として使いますが、それ以外の意味はありません。

HaskellコミュニティはDataSystemNetworkなどの標準化されたトップレベルモジュールを持っています。もし公にリリースするならば、自身のモジュールが適切な場所にあるのか相談してください。

インポート

Haskellの標準ライブラリはいくつかのモジュールに分けられています。ソースファイルにインポートすることでそれらのライブラリの機能を使うことができます。ライブラリによってエクスポートされているもの全てをインポートするには、モジュールの名前を使います。

import Text.Read

全ては正に全てを意味します。関数、データ型、コンストラクタ、クラス宣言、モジュールによってインポートされエクスポートされたその他のモジュールでさえです。

インポートする名前の列挙によって選択的にインポートされます。例えば、Text.Readからいくつかの関数をインポートするには、

import Text.Read (readParen, lex)

データ型はいくつかの方法によってインポートされます。コンストラクタなしに型をインポートするには、

import Text.Read (Lexme)

もちろん、これはLexme型の値でパターンマッチすることを妨げます。1つ以上のコンストラクタを明示的にインポートすることができます。

import Text.Read (Lexme(Ident, Symbol))

与えられた型の全てのコンストラクタをインポートするには、

import Text.Read (Lexme(..))

クラスの場合、データ型のコンストラクタをインポートするのに似た構文を使って、クラスに定義された関数をインポートすることができます。

import Text.Read (Read(readsPrec
                     , readList))

データ型とは違って、クラスの全ての関数は明示的に除外しなければインポートされることに注意してください。クラスだけをインポートするには、この構文を使ってください。

import Text.Read (Read())

除外インポート

モジュールから全てではないが、ほとんどの名前をインポートする場合、全てを並べるのは退屈なものです。そのため、hidingキーワードを通して明示したインポートもできます。

import Data.Char hiding (isControl
                       , isMark)

インスタンス宣言、あらゆる型、関数、コンストラクタ、クラスを除外できます。

インスタンス宣言

instance 宣言はインポートから除外することはできないことに注意してください。モジュールの全てのinstance宣言はモジュールがインポートされた時にインポートされます。

修飾名付きインポート

モジュールからエクスポートされた名前(例えば関数、型、演算子など)は修飾名付きインポート(qualified import)を通して付けられた接頭辞を持つことができます。これはPreludeの関数と同じ名前の大量の関数を持つモジュールにおいて特に便利です。Data.Setは良い例です。

import qualified Data.Set as Set

この形式はData.Setにエクスポートされた関数、型、コンストラクタ、その他の名前を与えられたエイリアス(例えばSet)で修飾されることを要求します。これはリストからすべての重複を除く1つの方法です。

removeDup a =
  Set.toList (Set.fromList a)

2つ目の形式はエイリアスを作りません。代わりに、接頭辞はモジュールの名前となります。文字列が全て大文字かどうかをチェックする単純な関数を書くことができます。

import qualified Char

allUpper str =
  all Char.isUpper str

接頭辞の明示を除いて、修飾名付きインポートは普通のインポートと同じ構文をサポートします。インポートされた名前は上記と同じように制限されます。

エクスポート

もしエクスポートするもののリストが与えられなかった場合、全ての関数、型、コンストラクタなどがモジュールをインポートしたところで有効になります。インポートされたモジュールはこの場合エクスポートされないことに注意してください。エクスポートされる名前の制限はwhereキーワードの後に括弧で囲まれた名前のリストを加えることで行うことができます。

module MyModule (MyType
  , MyClass
  , myFunc1
  ...)
where

ここではエクスポートされた関数、型、コンストラクタ、クラスを明示するのに、少しの違いを除いてインポートに使われる構文と同じ構文が使うことができます。もしモジュールが他のモジュールをインポートしているなら、モジュールもエクスポートできます。

module MyBigModule (module Data.Set
  , module Data.Char)
where

import Data.Set
import Data.Char

モジュールは自身さえも再エクスポートでき、それは全てのローカル定義とインポートされたモジュールをエクスポートする時に便利です。以下は自身とData.Setをエクスポートしますが、Data.Charはしません。

module AnotherBigModule (module Data.Set
  , module AnotherBigModule)
where

import Data.Set
import Data.Char

Newtype

data は新しい値、typeはシノニムを作るだけですが、newtypeはその間辺りのものです。newtypeの構文はかなり制限されていて、ただ1つのコンストラクタしか定義できず、コンストラクタは1つだけ引数を取ることができます。前述の例に続いて、以下のようにPhone型を定義します。

newtype Home = H String
newtype Work = W String
data Phone = Phone Home Work

type と違って、PhoneHWString値ではありません。型チェッカーはこれらを全く新しい型として扱います。これは前述のlowerName関数はコンパイルされないことを意味しています。以下は型エラーとなります。

lPhone (Phone hm wk) =
  Phone (lower hm) (lower wk)

代わりに、lowerを適用する値を得るためにパターンマッチングを使わないといけません。

lPhone (Phone (H hm) (W wk)) =
  Phone ((H (lower hm)) ((W (lower wk))

重要な見解はこのキーワードは新しい値を導入しないことです。代わりに新しい型を導入します。これは2つのとても便利な性質をもたらします。

  • 実際に新しい値を作らないので、新しい型(new type)はランタイムコストがありません。言い換えるとnewtypeは完全にタダです!
  • 型チェッカーがIntStringのような共通の型がプログラマによって明示される制限された方法で使われることを強制することができます。

最後に、data宣言に付けられるどんなderiving節もnewtype宣言に使えることに注意してください。

Return

do を見てください。

Type

このキーワードは型シノニム(すなわちエイリアス)を定義します。このキーワードはdatanewtypeのような新しい型を定義しません。コードのドキュメントには便利ですが、その他の点では関数や値で与えられた実際の型には影響を与えません。例えば、Person型はこのように定義できますが、

data Person = Person String String

ここででコンストラクタの最初の引数はファーストネームを表し、その次はラストネームを表します。しかし、2つの順番と意味は全く明白ではありません。type宣言が役に立ちます。

type FirstName = String
type LastName = String
data Person = Person FirstName LastName

type はシノニムを導入するので、型チェックは少しも影響を受けません。lower関数は

lower s = map toLower s

と定義され、

lower :: String -> String

という型を持ち、FirstName型かLastName型の値を難なく使うことができます。

Where

let と似ていて、whereは局所関数と定数を定義します。where定義のスコープは今の関数です。もし関数がパターンマッチを通して複数の定義に分割されていているなら、where節はその定義だけに適用されます。例えば、以下のresult関数はstrlen関数に与えられた引数によって違う意味を持ちます。

strlen [] = result
  where result = "No string given!"
strlen f = result ++ " characters long!"
  where result = show (length f)

Where vs Let

where 節は関数定義のレベルだけに定義されます。通常、let 定義のスコープと同一です。唯一の違いはガードが使われる時です。where節のスコープは全てのガードに渡ります。対照的にlet式のスコープは現在の関数節もしあるならガードです。

コントリビューター

パッチと有用な提案に寄与した方々に感謝します。Dave Bayer, Evgenij Belikov, Paul Butler, Elisa Firth, Marc Fontaine, Brian Gianforcaro, Cale Gibbard, Andrew Harris, Stephen Hicks, Kurt Hutchinson, Johan Kiviniemi, Patrik Jansson, Adrian Neumann, Barak Pearlmutter, Lanny Ripple, Markus Roberts, Holger Siegel, Falko Spiller, Adam Vogt, Leif Warner, and Jeff Zaroyko

バージョン

バージョン2.9です。ソースはGitHubにあります(http://github.com/m4dc4p/cheatsheet )。最新のリリースされたバージョンのPDFは http://cheatsheet.codeslower.com からダウンロードできます。他のプロジェクトと執筆にCodeSlower.com( http://blog.codeslower.com/ )を訪れてください。