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
コンストラクタを使う時にインタプリタやコードに型注釈を与えないといけないことです。Nothing
はMaybe 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]
ローカル束縛は生成された式や後続のジェネレータとガードで使われる新しい定義を与えます。以下はz
がa
とb
の小さい方を表すのに使われるものです。
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と呼ばれ、その演算子は_infix_、infixr、_infixl_キーワードを通して定められます。これらはトップレベル関数、局所レベル定義両方に適用されます。構文は
{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
は常に文字列を受け取り文字列を作る関数という意味で'不変的'です。l33t
はconvertOnly
の'カリー化'された形で、それは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 }
ここでは、c
でColor
の値を捕捉して新しいColor
の値を返しています。その値はc
のred
とblue
と同じ値を持つことになり、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]
という型シグネチャによって達成できます。
型シグネチャはトップレベル関数とネストしたlet
、where
定義に現れます。一般にこれはドキュメンテーションに便利ですが、いくつかの場合で多相を防ぐのに必要です。型シグネチャは最初に型付けされるものの名前に続いて、::
が続き、型が続きます。この例はすでに上で述べました。
型シグネチャは実装の直上に現れる必要はありません。それを含むモジュールのどこでも明示することができます(たとえそれより下でも!)。同じシグネチャを持つ複数の関数は一緒に定義できます。
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はclass
とinstance
宣言を通してオーバーロードを達成します。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
は型の_名前_です。MyValue1
とMyValue2
は型の_値_で、コンストラクタと呼ばれます。複数のコンストラクタは'|'の文字で仕切られています。型とコンストラクタの名前は大文字で始まって_いなければなりません_。そうでなければ構文エラーです。
引数のあるコンストラクタ
先の型は列挙型として以外は面白くありません。引数を取るコンストラクタは宣言できて、より多くの情報を持つことが許されています。
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つのコンストラクタ、User
とAdmin
を持つUser
という名前の型です。関数でこの型を使うことは違いを明確にします。
withUser (User _) = "normal user."
withUser (Admin _) = "admin user."
いつくかの文献ではこの習慣を_洒落_として参照します。
型変数
いわゆる_多相的な_データ型の宣言をすることは宣言に型変数を追加することと同じくらい簡単です。
data Slot1 a = Slot1 a | Empty1
これは2つのコンストラクタ、Slot1
とEmpty1
を持つ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
もしwhichCon
がNoncon
値で呼ばれるとランタイムエラーが起きます。
最後に、他のところで説明したように、これらの名前はパターンマッチング、引数捕捉、"更新"に使うことができます。
導出
多くの型は退屈な定義が必要になる共通の演算があります。文字列に変換したり文字列から変換したり、同値性の比較、シークエンスの処理。これらの能力はHaskellでは型クラスを使って定義します。
これら7つの演算はとてもありふれたものなので、Haskellは関連する型に型クラスを自動的に実装するderiving
キーワードを提供しています。Eq
、Read
、Show
、Ord
、Enum
、Ix
、Bounded
です。
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
クラスです。
導出
dta
のderiving
を見てください。
Do
do
キーワードはそれに続くコードがモナドの文脈にあることを表します。文は改行で分けられ、割り当ては<-
であることを表し、let
はin
キーワードを必要としないことを表します。
IfとIO
if
はIOと使う時にはトリッキーです。概念的に他の文脈におけるif
と違いはないですが、直感的に開発するのは難しいです。System.Directory
のdoesFileExists
関数を考えます。
doesFileExist :: FilePath -> IO Bool
if
文はこの"シグネチャ"を持ちます。
if-then-else :: Bool -> a -> a -> a
つまり、Bool
値を取って情況に応じた値を評価します。この型シグネチャからdoesFileExist
はif
に直接使えないことがはっきりとします。
wrong fileName =
if doesFileExist fileName
then ...
else ...
つまり、doesFileExist
の結果はIO Bool
値になり、if
はBool
値を求めます。代わりにIOアクションによって正しい値を"抽出"しなければなりません。
right1 fileName = do
exists <- doesFileExist fileName
if exists
then return 1
else return 0
return
の使い方に気を付けてください。do
はIO
モナドの中に入れるので、return
を通して以外、"外に出る"ことができません。if
を直列で使う必要はありません。let
を使って状況を評価し値を手にいれることができます。
right2 fileName = do
exists <- doesFileExist fileName
let result =
if exists
then 1
else 0
return result
もう一度return
に気を付けてください。これをlet
の中に入れることはできません。代わりに関数の最後に一度使うことができます。
複数のdo
do
をif
かcase
と一緒に使う時、枝のどちらかが文を持つならばもう一つの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
は引数n
にx
を乗算します。そしてそれはオリジナルのmultiples
に渡され、mult
はmapによって10までの数にxを乗算するのに使われます。
multiples x =
let mult n = n * x
in map mult [1..10]
引数の無いlet
"関数"は実際に定数で、一度評価されると外側の関数の呼び出しのために再度評価されることはありません。これは関数の共通部分の捕捉と再利用に便利です。これは数のリストの合計とその平均を与える馬鹿馬鹿しい例です。number
を1
からm
までのリストとし、一度のlistStats
の呼び出しに対して一度しか評価されません。同様に、total
とavg
も一度の呼び出しに対して一度しか評価されません。
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コミュニティはData
、System
、Network
などの標準化されたトップレベルモジュールを持っています。もし公にリリースするならば、自身のモジュールが適切な場所にあるのか相談してください。
インポート
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
と違って、Phone
のH
とW
はString
値では_ありません_。型チェッカーはこれらを全く新しい型として扱います。これは前述の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は完全にタダです!
- 型チェッカーが
Int
やString
のような共通の型がプログラマによって明示される制限された方法で使われることを強制することができます。
最後に、data
宣言に付けられるどんなderiving
節もnewtype
宣言に使えることに注意してください。
Return
do
を見てください。
Type
このキーワードは型シノニム(すなわちエイリアス)を定義します。このキーワードはdata
やnewtype
のような新しい型を定義しません。コードのドキュメントには便利ですが、その他の点では関数や値で与えられた実際の型には影響を与えません。例えば、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/ )を訪れてください。