式
この章では、私たちはHaskellの式の構文と非形式的な意味論を説明する。また必要であらばHaskellカーネルへの変換についても説明する。let
式の場合を除いて、これらの変換は静的、動的な意味論の両方を保存する。これらの変換を使った束縛されていない変数とコンストラクタは常にPrelude
によって定義された実体を参照する。例えば、リスト内包表記の変換(セクション[3.11])で使われる"concatMap"
はPrelude
によって定義されたconcatMap
を意味する。これは識別子"concatMap"
がリスト内包表記で使われているスコープ内にあるかないかは関係なく、また、(もしスコープ内にあったとしても)束縛されていても関係はない。
exp | → | infixexp :: [context =>] type | (expression type signature) |
| | infixexp | (expression type signature) | |
infixexp | → | lexp qop infixexp | (infix operator application) |
| | - infixexp | (prefix negation) | |
| | lexp | ||
lexp | → | \ apat1 … apatn -> exp | (lambda abstraction, n ≥ 1) |
| | let decls in exp | (let expression) | |
| | if exp [;] then exp [;] else exp | (conditional) | |
| | case exp of { alts } | (case expression) | |
| | do { stmts } | (do expression) | |
| | fexp | ||
fexp | → | [fexp] aexp | (function application) |
aexp | → | qvar | (variable) |
| | gcon | (general constructor) | |
| | literal | ||
| | ( exp ) | (parenthesized expression) | |
| | ( exp1 , … , expk ) | (tuple, k ≥ 2) | |
| | [ exp1 , … , expk ] | (list, k ≥ 1) | |
| | [ exp1 [, exp2] .. [exp3] ] | (arithmetic sequence) | |
| | [ exp | qual1 , … , qualn ] | (list comprehension, n ≥ 1) | |
| | ( infixexp qop ) | (left section) | |
| | ( qop⟨-⟩ infixexp ) | (right section) | |
| | qcon { fbind1 , … , fbindn } | (labeled construction, n ≥ 0) | |
| | aexp⟨qcon⟩ { fbind1 , … , fbindn } | (labeled update, n ≥ 1) |
中置演算子を含む式は演算子の結合性によって曖昧さを排除されている(セクション4.4.2参照)。同じ優先度をもつ連続した括弧を持たない演算子は構文エラーを避けるためにどちらも左または右のどちらかに結合しなければならない。括弧を持たない式"x qop(a,i) y qop(b,j) z" ( qop(a,i)はaと優先順位iに関連付いた演算子を意味する)が与えられた場合、括弧はi = jでかつa = b = lかa = b = rでない時は、"x qop(a,i) y"か"y qop(b,i) z"のどちらかを囲むよう追加されなければいけない。
中置演算子を含む式の解決するためのアルゴリズムの例はセクション10.6にある。
符号反転演算子はHaskellにおいて唯一の接頭語になる。中置と同じ優先順位を持ち、演算子はPreludeの中に定義されている(セクション4.4.2, 図4.1)。
この文法は条件式、let式、ラムダ抽象の拡張については曖昧だ。その曖昧さは各構成ができるだけ右へ拡張されるメタ規則により解決される。
構文解析の例を以下に示す。
これが | このように解析される |
---|---|
f x + g y | (f x) + (g y) |
- f x + y | (- (f x)) + y |
let { ... } in x + y | let { ... } in (x + y) |
z + let { ... } in x + y | z + (let { ... } in (x + y)) |
f x y :: Int | (f x y) :: Int |
\ x -> a+b :: Int | \ x -> ((a+b) :: Int) |
わかりやすくするため、以後このセクションでは中置演算子を含む式が演算子の結合性に従って解決されているということにする。
エラー
式の評価中のエラーは、⊥("bottom")
と表記されるが、停止しないことからHaskellプログラムには区別できない。Haskellは非正格評価の言語なことから、全てのHaskellの型は⊥
を含む。つまり、いかなる型の値もユーザーが望めばエラーを返す計算になる可能性がある。評価されたときエラーは直ちにプログラムを停止させ、ユーザーが捕捉されることはできない。Preludeは直接そのようなエラーを引き起こす二つの関数を提供している。
error :: String -> a
undefined :: a
error
の呼び出しはプログラムの実行を終了させ、OSに適切なエラー表示を返す。そのエラー表示にはシステム依存の方法で文字列を画面に表示するべきである。undefined
が使われたとき、そのエラーメッセージはコンパイラーによって作成される。
Haskellの式の変換は実行時エラーが発生したことを明示的に表示するためerror
とundefined
を使用する。エラーが発生した際の実際のプログラムの振舞は実装次第である。そのメッセージはこれらの変換のみ提案するためerror
関数へ渡される。エラー発生時、詳しい情報または乏しい情報を表示することを実装側は選択するかもしれない。
変数、コンストラクタ、演算子、リテラル
aexp | → | qvar | (variable) |
| | gcon | (general constructor) | |
| | literal | ||
gcon | → | () | |
| | [] | ||
| | (,{,}) | ||
| | qcon | ||
var | → | varid | ( varsym ) | (variable) |
qvar | → | qvarid | ( qvarsym ) | (qualified variable) |
con | → | conid | ( consym ) | (constructor) |
qcon | → | qconid | ( gconsym ) | (qualified constructor) |
varop | → | varsym | ` varid ` | (variable operator) |
qvarop | → | qvarsym | ` qvarid ` | (qualified variable operator) |
conop | → | consym | ` conid ` | (constructor operator) |
qconop | → | gconsym | ` qconid ` | (qualified constructor operator) |
op | → | varop | conop | (operator) |
qop | → | qvarop | qconop | (qualified operator) |
gconsym | → | : | qconsym |
Haskellは中置記法に対応するため特別な構文を提供している。 演算子 は中置構文を用いて適用が可能である(セクション3.4)か、 セクション (セクション3.5)を用いて部分的に適用が可能な関数のことである。
演算子 は、+
や$$
といった 演算子シンボル か、` op `
のようにグレイブ・アクセント(バッククォート)で囲まれた通常の識別子かのいずれかである。例えば、op x y
という前置適用を書く代わりに、x `op` y
という中置適用を書くことができる。もし、` op `
に対して結合性が宣言されていない場合には、優先順位は最高で左結合をデフォルトとする。(セクション4.4.2参照)。
対照的に、演算子シンボルは括弧で閉じられた普通の識別子へ変換可能である。例として、(+) x y
はx + y
に等しく、foldr (⋆) 1 xs
はfoldr (\x y -> x⋆y) 1 xs
に等しくなる。
一部の組み込み型のコンストラクタの名前をつけるのに特別な構文がつかわれているものがあり、実際にgconやliteralで見ることができる。これらについてはセクション6.1で説明される。
整数リテラルはfromInteger
関数をInteger
型の適した値への適用を表す。同様に、浮動小数点リテラルはRational
型(つまり、Ratio Integer
)の値にfromRational
を適用することを表す。
変換: 整数リテラルiはfromInteger i
に等しく、fromInteger
はNum
クラスのメソッドである。(セクション6.4.1)
浮動小数点リテラルfはfromRational (n Ratio.% d)
に等しく、fromRational
はFractional
クラスのメソッドで、Ratio.%
はRatio
ライブラリで定義されており、2つの整数から有理数を構築する。整数nとdはn/d = fを満たすものとして選ばれる。
カリー化された適用とラムダ抽象
fexp | → | [fexp] aexp | (function application) |
lexp | → | \ apat1 … apatn -> exp | (lambda abstraction, n ≥ 1) |
関数適用はe1 e2と書く。適用は左結合性をもつので、(f x) y
の括弧は省略することができる。e1はデータ構成子である可能性もあるため、データ構成子の部分的な適用は許されている。
ラムダ抽象 は\ p1 … pn -> e
と書き、pi
はパターンである。\x:xs->x
のような式は構文的に正しくない。(x:xs)->x
と書くのが正しい。
パターンの集合は 線形 でなければならない。つまり、変数は集合の中で2回以上出現してはいけない。
変換: 以下の等式が成り立つ。
p1 … pn -> e = \ X1 … Xn -> case (X1, …, Xn) of (p1, …, pn) -> e
Xiは新しい識別子である。
この変換がセクション3.17.3で説明するcase式とパターンマッチの意味論と組み合わさって与えられたとき、もしもパターンマッチに失敗すれば結果は⊥
となる。
演算子適用
infixexp | → | lexp qop infixexp | |
| | - infixexp | (prefix negation) | |
| | lexp | ||
qop | → | qvarop | qconop | (qualified operator) |
e1 qop e2という形式は二項演算子qopの式e1とe2への中置適用である。
特殊な形式-eは前置の符号反転演算子を表す。この演算子はHaskellにおける唯一の前置演算子であり、negate (e)
という意味の構文である。二項演算子-
はPrelude内の-
の定義への参照を必要とせず、モジュールシステムによって再束縛されるかもしれない。しかしながら、単項演算子-
はPrelude内で定義されたnegate
関数を常に参照する。-
演算子の局所的な意味と単項の符号反転演算との間には何の関連もない。
前置の符号反転演算子はPrelude内(表4.1を参照)で定義された中置演算子-
と同じ優先順位を持つ。e1-e2
は二項演算子-
の中置表現解析されるため、前置の符号反転演算子を使うには構文解析に代わってe1(-e2)
と書かなければいけない。同様に、(-)
は中置演算子と同様に(\ x y -> x-y)
のための構文であるが、(\ x -> -x)
を表せず、そのためにはnegate
を使う必要がある。
変換: 以下の等式が成り立つ。
e1 op e2 = (op) e1 e2
-e = negate (e)
セクション
aexp | → | ( infixexp qop ) | (left section) |
| | ( qop⟨-⟩ infixexp ) | (right section) |
セクション は( op e )や( e op )のように書かれる。このときのopは二項演算子でeは式である。セクションは二項演算子を部分的に適用する便利な構文である。
シンタックスの先行ルールは次のとおりのセクションへ適用する。(op e)は(x op e)が(x op (e))と同じ方法でパースする場合に限り正当であり、(e op)も同様である。例えば、(⋆a+b)
は構文的に不当であるが、(+a⋆b)
と(⋆(a+b))
は有効である。なぜなら(+)
は左結合であり、(a+b+)
は構文的に正しいが、(+a+b)
はそうではない。後者は(+(a+b))
のように書かれるのが正当である。他の例として、次の式は
(let n = 10 in n +)
セクション3にあるように、let/ラムダに関するメタルールにより誤りである。次の式は
(let n = 10 in n + x)
以下のように解析され
(let n = 10 in (n + x))
次のようにはならない
((let n = 10 in n) + x)
なぜなら、-
は文法内で特別に扱われるからだ。前のセクションで説明したように、(- exp)はセクションではなく、前置の符号反転演算子の適用である。しかしながら、Prelude名で定義されたsubtract
関数があり、それによって(subtract exp)
が不正なセクション(訳注: (- exp)のこと)と同じ意味となる。式(+ (- exp))は同じ用途で役立つことができる。
変換: 以下の等式が成り立つ。
(op e) = \ x -> x op e
(e op) = \ X -> e op x
opは二項演算子で、eは式であり、xはeの中で自由出現ではない変数である。
条件文
lexp | → | if exp [;] then exp [;] else exp |
条件式はif e1 then e2 else e3
の形式をとり、もしe1がTrue
なら、e2を返し、e1がFalse
ならe3を返し、それ以外なら⊥
を返す。
変換: 以下の等式が成り立つ。
if e1 then e2 else e3(op e) = case e1 of { True -> e2 ; False -> e3 }
True
とFalse
はPrelude内で定義されているBool
型の2つの引数のないコンストラクタである。e1はBool
型でなければならず、e2とe3も同じ型でなければならない。条件式全体の型も同様である。
リスト
infixexp | → | exp1 qop exp2 | |
aexp | → | [ exp1 , … , expk ] | (k ≥ 1) |
| | gcon | ||
gcon | → | [] | |
| | qcon | ||
qcon | → | ( gconsym ) | |
qop | → | qconop | |
qconop | → | gconsym | |
gconsym | → | : |
List はk ≥ 1として、[e1, …, ek]のように書く。リストコンストラクタは :
であり、空リストは[]
で表記される。リストの標準操作はPrelude内で与えられる(セクション6.1.3と9章の特にセクション9.1を参照)。
変換: 以下の等式が成り立つ。
[e1, …, ek] = e1 : (e2 : ( … (ek : [])))
:
と[]
はPredule内(セクション6.1.3)で定義されたリストのコンストラクタである。e1からekまでの型は同じでなければならない(それをtと呼ぶ)。式全体の型は[t]になる(セクション4.1.2)。
コンストラクタ":"
は[]
のようにリストコンストラクタとしてのみ予約されており、言語構文の一部と見做されている。また、それは隠すことも再定義することもできない。:
は優先順位レベル5の右結合演算子である(セクション4.4.2)。
タプル
aexp | → | ( exp1 , … , expk ) | (k ≥ 2) |
| | qcon | ||
qcon | → | (,{,}) |
タプル はk ≥ 2以上の(e1, …, ek)のように書く。n-tupleのコンストラクタは(,…,)
と表記され、n - 1のコンマがある。従って、(a,b,c)
と(,,) a b c
は同じ値を表す。タプルの標準操作はPrelude内で定義されている(セクション6.1.4と9章)。
変換: k ≥ 2のときの(e1, …, ek)はPrelude内で定義されたk-tupleのインスタンスになり、変換は要求されない。もし、t1からtkはそれぞれe1からekの型があり、最終的なタプルの型は(t1,…,tk)になる(セクション4.1.2)。
単位式と括弧付き式
aexp | → | gcon | |
| | ( exp ) | ||
gcon | → | () |
(e)の形式はシンプルに 括弧付き式 であり、eと等しい。ユニット(unit)式()
は()
型を持つ(セクション4.1.2を参照)。それは⊥
以外の型のメンバのみで、"引数のないタプル"のように考えられる(セクション6.1.5を参照)。
変換: (e)はeと等しい。
数列
aexp | → | [ exp1 [ , exp2] .. [ exp3] ] |
数列 [e1,e2 .. e3]は型tの値のリストを表し、各eiは型tを持ち、tはEnum
クラスのインスタンスである。
変換: 数列はこれらの等式を満たす。
[ e1.. ] | = | enumFrom e1 |
[ e1,e2.. ] | = | enumFromThen e1 e2 |
[ e1..e3 ] | = | enumFromTo e1 e3 |
[ e1,e2..e3 ] | = | enumFromThenTo e1 e2 e3 |
enumForm
、enumFormThen
、enumFormTo
、enumFormThenTo
はPrelude内で定義されているEnum
クラスのクラスメソッドになる。
故に数列の意味論は型tのインスタンス宣言に完全に依存している。どのPrelude
型がEnum
型にあるか、そしてそれらの意味論についてのより詳しいことについてはセクション6.3.4を参照すること。
リスト内包表記
aexp | → | [ exp | qual1 , … , qualn ] | (list comprehension, n ≥ 1) |
qual | → | pat <- exp | (generator) |
| | let decls | (local declaration) | |
| | exp | (boolean guard) |
リスト内包表記 は[ e | q1, …, qn ]、n ≥ 1形式を持ち、qi修飾子は次のいずれかである。
- 形式p <- eの ジェネレータ 。pは型tのパターン(セクション3.17)であり、eは型
[t]
の式である。 - 生成された式eで、あるいは後方のブーリアンガードとジェネレータで使われる新しい定義を提供する ローカル束縛 。
- ブーリアンガード 。
Bool
型の任意の式を表すことができる。
このようなリスト内包表記は修飾子リスト内のジェネレータのネストされた深さ優先探索の評価によって作成された連続した環境でeを評価することによって生成された要素のリストを返す。変数の束縛は通常のパターンマッチングルール(セクション3.17)に従って発生し、もし一致に失敗したら、その時はそのリストの要素は単純にスキップされる。従って、
[ x | xs <- [ [(1,2),(3,4)], [(5,4),(3,2)] ],
(3,x) <- xs ]
リスト[4,2]
を返す。もし修飾子がブーリアンガードなら、成功した前のパターンマッチのためにTrue
と評価しなけれないけない。通常通り、リスト内法表記における束縛は外部スコープの束縛をシャドーイングできる。例えば以下のようになる。
[ x | x <- x, x <- x ] = [ z | y <- x, z <- y]
変換: リスト内包表記はこれらの等式を満たし、これらの等式はカーネルへの変換として使われる可能性がある。
[ e | True ] | = | [e] |
[ e | q ] | = | [ e | q, True ] |
[ e | b, Q ] | = | if b then [ e | Q ] else [] |
[ e | p <- l, Q ] | = | let ok p = [ e | Q ] |
ok _ = [] | ||
in concatMap ok l | ||
[ e | let decls, Q ] | = | let decls in [ e | Q ] |
eは式にわたる範囲で、pはパターンにわたり、lはリスト値式にわたり、bはブーリアン式にわたり、decls は宣言リストにわたり、qは修飾子にわたり、Qは修飾子の列にわたる範囲をもつ。ok
は新しい変数である。関数concatMap
とブーリアン値True
はPrelude内で定義されている。
リスト内法表記の変換で示した通り、letによって束縛された変数は最大限多相的な型を持つ一方で<-によって束縛されたものはラムダ束縛であり、よって単相的になる。 (セクション4.5.4を参照).
Let式
lexp | → | let decls in exp |
let 式は一般的な形式
を持ち、ネストされたレキシカルスコープをもつ相互再帰的な宣言のリストを導入する(let
{ d1 ; … ; dn } in
elet
は他の言語でletrc
としばしば呼ばれる)。宣言の範囲は式eと宣言の右側である。宣言は4章で説明される。パターン束縛のマッチは遅延され、暗黙的な~
がこれらのパターンを反駁不可にする。 例えば、
let (x,y) = undefined in e
はx
またはy
が評価されるまでランタイムエラーをもたらさない。
変換: 式
の動的な意味論は次の変換によって捕捉される。全ての型シグネチャを取り除いた後、それぞれの宣言diはlet
{ d1 ; … ; dn} in e0pi = ei
の形の等式へと変換される。pi
とei
はセクション4.4.3での変換を使用する、各々のパターンと式である。一度この変換が終われば、次のような等式が成り立つ。この等式はカーネルへの変換として使われる場合がある。
let {p1 = e1; ... ; pn = en} in e0 | = | let (~ p1, ... ,~ pn) = (e1, ... ,en) in e0 |
let p = e1 in e0 | = | case e1 of ~ p -> e0 |
where no variable in p appears free in e1 | ||
let p = e1 in e0 | = | let p = fix ( \ ~ p -> e1) in e0 |
fix
は最小不動点演算子である。反駁不可パターン~pの使用は注意すべきだ。この変換は静的な意味論を保存しない。なぜなら、case
を使用すると束縛変数が完全な多相型へ型付けされなくなるからである。let
式で束縛された静的な意味論はセクション4.4.3で説明される。
Case式
lexp | → | case exp of { alts } | |
alts | → | alt1 ; … ; altn | (n ≥ 1) |
alt | → | pat -> exp [where decls] | |
| | pat gdpat [where decls] | ||
| | (empty alternative) | ||
gdpat | → | guards -> exp [ gdpat ] | |
guards | → | | guard1, …, guardn | (n ≥ 1) |
guard | → | pat <- infixexp | (pattern guard) |
| | let decls | (local declaration) | |
| | infixexp | (boolean guard) |
case 式は一般的な形式case e of { p1 match1 ; … ; pn matchn }
を持つ。各matchiは一般的な形式
| gsi1 -> ei1
…
| gsimi -> eimi
where declsi
( ガード の構文ルールについて注目して欲しい。|
は区切りを表す構文的なメタシンボルではなく終端記号である。)各選択子pi matchiはパターンpiから成り、matchiと一致する。各マッチは順繰りにガードgsijと本体eijのペアの列から成り、代替となる全てのガードと式上の範囲での付加的な束縛(declsi)に従う。
ガード は次の形式をの一つを持つ。
- パターンガード は形式p <- eで、pは型tのパターンで、eは式の種類tである。もし、式eがパターンpに一致するなら成功し、パターンの束縛をその環境にもたらす。
- 局地的束縛 は形式
let decls
である。それらは常に成功し、その環境にdeclsと定義した名前をもたらす。 - ブーリアンガード は
Bool
型の数式である。もし、式がTrue
と評価するなら成功し、その環境に新しい名前をもたらさない。ブーリアンガードgはパターンガードTrue <- g
に意味的に等しい。
形式pat -> exp where decls
の代わりの以下の簡略記法が扱われる。
pat | True -> exp
where decls
ケース式は少なくとも1つの選択句を持たなければならず、各選択句は一つの実体を持たないといけない。各実体は同じ型を持たなければならず、式全体の型はその型になる。
ケース式は式eが個々の選択句に反するパターンマッチングによって評価される。その選択子は上から下へ連続的に試される。もし、eが選択句のパターンと一致したら、そのとき選択句のガード式は始めにパターンの一致の間に生成された束縛によって展開されたケース式の環境内で上から下へ連続的に試される。その時、where
句内のdeclsiによって、その選択句は関連付けられる。
各ガード式のためにコンマ区切りのガードは左から右へ連続的に試される。もしそのすべてに成功したなら、そのときは対応する式はガードによって生成された束縛で展開された環境で評価される。すなわち、(let句かパターンガードのいずれかを使った)ガードによって生成された束縛は続くガードと対応する式のスコープ内にある。もしあらゆるガードが失敗したら、その時はこのガード式は失敗し次のガード式を試す。
もし与えられた選択句のどのガード式も成功しなかったら、その時マッチングは次の選択句へ継続する。もしどの選択句も成功しなければ、そのときの結果は⊥
となる。パターンマッチングはセクション3.17で説明され、ケース式の正式な意味論はセクション3.17.3で説明される。
パースについての注意点 。以下の式は
case x of { (a,_) | let b = not a in b :: Bool -> a }
これを正しく構文解析するには用心しなければならない。ただ一つの曖昧さのない構文解析は、すなわち次のようにすることである。
case x of { (a,_) | (let b = not a in b :: Bool) -> a }
しかしながら、Bool -> a
というフレーズは型として構文的に正当であり、先読みが制限されているパーサーはこの選択に誤ってコミットする可能性があり、それゆえプログラムは拒否する。故に、プログラマーは型シグネチャで終わるガードを避けるように勧められる。これは実際に ガード がexpではなくinfixexpを含んでいる理由になる。
Do式
lexp | → | do { stmts } | (do expression) |
stmts | → | stmt1 … stmtn exp [; ] | (n ≥ 0) |
stmt | → | exp ; | |
| | pat <- exp ; | ||
| | let decls ; | ||
| | ; | (empty statement) |
do式はモナドのプログラミングのためのより従来的な構文を提供する。それは以下のような式を許す。
putStr "x: " >>
getLine >>= \l ->
return (words l)
より、旧来の方法による書き方は次のものになる。
do putStr "x: "
l <- getLine
return (words l)
変換: Do式はこれらの等式を満たし、排除した空のstmtsの後にカーネルの中への変換のように使われるかもしれない。
do {e} | = | e |
do {e;stmts} | = | e >> do {stmts} |
do {p <- e; stmts} | = | let ok p = do {stmts} |
ok _ = fail "..." | ||
in e >>= ok | ||
do {let decls; stmts} | = | let decls in do {stmts} |
コンパイラが生成したエラーメッセージを表す省略記号"..."
の部分はfail
へ渡され、そして可能であればパターンマッチに失敗した場所を表示する。関数>>
,>>=
とfail
はPreludeで定義されたクラスMonad
の操作であり、ok
は新しい識別子である。
do
の変換でも示したように、let
に束縛された変数は完全に多相的な型をもつ一方で<-
によって定義された変数はラムダ束縛であり、ゆえに単相的である。
フィールドラベル付きのデータ型
データ型の宣言はフィールドラベルを必要に応じて定義してもよい。(セクション4.2.1を参照)これらのフィールドラベルは構築、形式の選択、データ型全体の構造に依存した方法でのフィールドの更新することに使用される。
異なるデータ型は同じスコープの共通のフィールドラベルを共有することはできない。フィールドラベルはコンストラクタ内で高々一度だけ、使用することができる。しかしながら、データ型の中で、あるフィールドがすべてのコンストラクタ内で同じ型を持つときに限り1つのフィールドを複数のコンストラクタで使用することができる。最後の点については次が良い例である:
data S = S1 { x :: Int } | S2 { x :: Int } -- OK
data T = T1 { y :: Int } | T2 { y :: Bool } -- BAD
ここでのs
は正当であるがT
はそうではない。またy
は後者では矛盾する型付けが与えられている。
フィールドセレクション
aexp | → | qvar |
フィールドラベルはセレクタ関数のように使用される。変数のように使われる際は、フィールドラベルはオブジェクトからフィールドを抽出する関数のように振る舞う。セレクタはトップレベルの束縛であり、よってローカル変数によってシャドーイングされる場合があるが、しかし他のトップレベルの束縛で同じ名前のものと衝突してはならない。この覆いはセレクタ関数にのみ影響を及ぼし、レコード作成(セクション3.15.2)及びに更新(セクション3.15.3)、フィールドラベルは通常の変数と混合されることはない。
変換: フィールドラベルfは次のようなセレクタ関数を生成する。
f x | = | case x of { C1 p11 … p1k -> e1 ;… ; Cn pn1 … pnk -> en } |
C1 ... Cnは全てfとラベルされたフィールドを含むデータ型のコンストラクタで、pijはfがCiの要素のj番目、または_
をラベルした時のy
であり、eiはCiのフィールドがfまたはundefined
のラベルを持つ時のy
である。
フィールドラベルを用いた生成
aexp | → | qcon { fbind1 , … , fbindn } | (labeled construction, n ≥ 0) |
fbind | → | qvar = exp |
ラベル付けされたフィールドを使うコンストラクタが値の生成に使われる場合があるが、その時には各コンポーネントは位置ではなく名前によって指定する。宣言リストの中で使われる中括弧とは異なりレイアウトの対象にならない。{
と}
の文字は明示しなければならない。(これはフィールドの更新、フィールドパターンにおいても正しい。)フィールドラベルを使用する構築は次の制約に応じる。
- 指定されたコンストラクタで宣言されたフィールドラベルのみ言及してよい。
- フィールドラベルは複数回言及してはならない。
- 言及されないフィールドは
⊥
で初期化される。 - 正格なフィールド(宣言された型のフィールドの接頭語に
!
が付けられている)が生成の際に省略された時はコンパイルエラーが発生する。厳格なフィールドはセクション4.2.1で説明される。
式F {}
は、F
はデータコンストラクタであり、F
がレコード構文により宣言されたかどうかに関わらず、正当である(ただしF
が正格フィールドを持たない時に限る。上の4番目の箇条書きを参照)。それはF ⊥1 … ⊥n
を表し、nはF
の引数の数である。
変換: f = vの束縛で、フィールドfはvでラベルする。
C { bs } | = | C (pick1C bs undefined) … (pickkC bs undefined) |
kはCの引数の数である。
補助関数pickiC bs dは次にように定義される。
もし、コンストラクタCのi番目の要素がフィールドラベルfを持ち、if f=vは束縛されたbsに表示されるなら、その時はpickiC bs dはvである。言い換えるとpickiC bs dはデフォルト値dである。
フィールドラベルを使用した更新
aexp | → | aexp⟨qcon⟩ { fbind1 , … , fbindn } | (labeled update, n ≥ 1) |
フィールドラベルを使ったデータ型に所属する値は非破壊的に更新されるかもしれない。これは元々存在していた値を指定されたフィールドの値で書き換えた新しい値を生成する。更新は次の方法に制限される。
- 全てのラベルは同じデータ型から取られなければいけない。
- 少なくともあるコンストラクタは更新の中で全ての言及されたラベルを定義しなければいけない。
- 2回以上言及されるラベルがあってはならない。
- 実行エラーは更新された値が全ての明記されたラベルを含まない時に発生する。
変換: 以下は以前のpickの定義を使用する。
e { bs } | = | case e of |
C1 v1 … vk1 -> C1 (pick1C1 bs v1) … (pickk 1C1 bs v k1) | ||
... | ||
Cj v1 … vkj -> Cj (pick1Cj bs v1) … (pickk jCj bs v kj) | ||
_ -> error "Update error" |
{ C1,...,Cj}はbs内の全てのラベルを含むコンストラクタの集合で、kiはCiの引数の数である。
これはラベル付けされたフィールドを使用している例である。
data T = C1 {f1,f2 :: Int}
| C2 {f1 :: Int,
f3,f4 :: Char}
式 | 変換 |
---|---|
C1 {f1 = 3} | C1 3 undefined |
C2 {f1 = 1, f4 = 'A', f3 = 'B'} | C2 1 'B' 'A' |
x {f1 = 1} | case x of C1 _ f2 -> C1 1 f2 |
C2 _ f3 f4 -> C2 1 f3 f4 |
フィールドf1
は両方のT
のコンストラクタに共通である。この例では、フィールドラベル表記でコンストラクタを使った式をフィールドラベルを使わない同じコンストラクタを使った同値な式へと変換している。もし、x {f2 = 1, f3 = 'x'}
のように、どのコンストラクタも、更新で使われたフィールドラベルの集合を定義していないのであれば、コンパイル時エラーという結果になるだろう。
式の型シグネチャ
exp | → | exp :: [context =>] type |
式の型シグネチャ は形式e :: tを持つ。eは式で、tは型(セクション4.1.2)であり、それらは明示的に式を分類することに使用され、オーバーロード(セクション4.1.2を参照)するために曖昧な型付けを解決することに使われるかもしれない。式の値はexpの値である。通常の型シグネチャと同様に(セクション4.4.1を参照)、宣言された型はexpから導出可能な主要な型より具体的になるかもしれないが、主要な型より一般的なまたは同程度な型を与えることはエラーである。
変換:
e :: t = let { v :: t; v = e } in v
パターンマッチング
パターン はラムダ抽象や関数定義、パターン束縛、リスト内包表記、do式、case式内で現れる。しかしながら、はじめの5つは最終的にcase式に変換されるので、パターンマッチの意味論はcase式のときのみ定めれば十分である。
パターン
パターンはこの構文を持つ。
pat | → | lpat qconop pat | (infix constructor) |
| | lpat | ||
lpat | → | apat | |
| | - (integer | float) | (negative literal) | |
| | gcon apat1 … apatk | (arity gcon = k, k ≥ 1) | |
apat | → | var [ @ apat] | (as pattern) |
| | gcon | (arity gcon = 0) | |
| | qcon { fpat1 , … , fpatk } | (labeled pattern, k ≥ 0) | |
| | literal | ||
| | _ | (wildcard) | |
| | ( pat ) | (parenthesized pattern) | |
| | ( pat1 , … , patk ) | (tuple pattern, k ≥ 2) | |
| | [ pat1 , … , patk ] | (list pattern, k ≥ 1) | |
| | ~ apat | (irrefutable pattern) | |
fpat | → | qvar = pat |
コンストラクタの引数の数はそれに関係するサブパターンの数と一致しなければいけない。部分的に適用されるコンストラクタに反して一致することはできない。
全てのパターンは 線形 でなければならない。変数は2回以上現れないかもしれない。例として、この定義は不正である。
f (x,x) = x -- ILLEGAL; x used twice in pattern
形式var@patのパターンはas-patternsと呼ばれ、varをpatによってマッチされた値に付ける名前として使うことができる。例えば以下のものは、
case e of { xs@(x:rest) -> if x==0 then rest else xs }
は次のものと等しい。
let { xs = e } in
case xs of { (x:rest) -> if x==0 then rest else xs }
形式_
のパターンは ワイルドカード であり、パターンのいくつかの部分が右手側で参照されない時に便利である。それは他の場所で使われない識別子がその場所に置かれているかのようである。例えば、以下は、
case e of { [x,_,_] -> if x==0 then True else False }
は次のものと等しい。
case e of { [x,y,z] -> if x==0 then True else False }
パターンマッチングの非形式的の意味論
パターンは値に対してマッチが行われる。パターンマッチを行おうとした場合、次の3つのいずれかの結果を得る。 失敗 かもしれない、 成功 かもしれず、その時はパターン内の各変数に束縛を返す、 分岐する かもしれない(例:⊥
を返す)。パターンマッチングは次のルールによって外から内へ、左から右へ進行する。
-
値vに対してマッチするパターンvarのマッチングは常に成功し、varをvに束縛する。
-
値vに対してマッチするパターン~apatのマッチングは常に成功する。もしvに対してマッチするapatのマッチングが別の方法で成功するならば、apat内の束縛されていない変数は適切な値に束縛される。vに対してマッチするapatのマッチングが失敗または分岐するなら⊥ に束縛される(束縛は評価を ほのめかさない )。
運用上、これはあるapat内の変数が使われるまで、パターン~apatが何とも一致しないことを意味する。その時点でパターン全体はその値に対してマッチし、もし一致が失敗または分岐するなら、全体の計算を行う。 -
あらゆる値に対してマッチするワイルドパターン
_
のマッチングは常に成功し、束縛は行われない。 -
値に対してマッチするパターンcon patのマッチングは、conは
newtype
によって定義されたコンストラクタである、以下の項目でその値に依存する。- もし値が形式con vであるなら、その時patはvに対してマッチされる。
- もし値が
⊥
なら、その時patは⊥
に対してマッチする。
すなわち
newtype
と関連するコンストラクタが値の型を変更することのみに務める。 -
値に対してのcon pat1 ... patnのマッチングは、conは
data
によって定義されるコンストラクタである、依存するその値に依存する。- もし値が形式con pat1 ... patnであるなら、サブパターンはそのデータ値の要素に対して左から右へ一致される。もし、全てのマッチングが成功したなら、マッチング全体は成功し、はじめの失敗または分岐はマッチング全体を各々、失敗または分岐へともたらす。
- もし値が形式con' v1 ... vmであるなら、conはcon'への異なるコンストラクタである、そのマッチングは失敗する。
- もし値が
⊥
なら、そのマッチングは分岐する。
-
ラベル付きフィールドを使ったコンストラクタに対してのマッチングはそのフィールドがフィールドリスト内で指定された順序で照合されることを除いて、通常のコンストラクタパターンのマッチングと同じである。全てのリストされたフィールドはコンストラクタによって宣言されなければならず、フィールドは2回以上指定されないかもしれない。パターンによって指定されたフィールドは無視される(
_
に対して一致する)。 -
値v対する数値、文字、文字列リテラルパターンkのマッチングはもし、v == kなら成功する。
==
はパターンの型を元にオーバロードされる。マッチングはもしこのテストが分岐するなら分岐する。
数値リテラルの解釈はまさにセクション3.2で記載のとおりである。即ち、オーバロードされた関数fromInteger
またはfromRational
は(それぞれ)適切な型へ変換することによってInteger
またはRational
リテラルに適用される。
静的型の制約(例えば、文字とbooleanを一致させる静的なエラー)は別として、次の静的クラスの制約は保持する。
- 整数リテラルパターンはクラス
Num
の値とのみ照合できる。 - 浮動小数点リテラルパターンはクラス
Factional
の値とのみ照合できる。
2種類のパターンの区別することはしばしば有用である。 反駁できない パターンの照合は厳密ではなく、そのパターンはもし、照合された値が⊥
なら一致する。 反駁できる パターンは厳密であり、その一致される値が⊥
なら分岐する。反駁できないパターンは次のものである。変数やワイルドカード、Nがnewtype
とapatによって定義されたコンストラクタN apatは反駁できず(セクション4.2.3)、var@apatのapatは反駁できない、または形式~apat(apatが反駁できないかどうか)である。他の全てのパターンは反駁できる。
ここにいくつかの例をだす。
-
もし、パターン
['a','b']
が['x',⊥]
と一致されるなら、その時、'a'
はx
との一致に 失敗し 、その結果は失敗と一致する。しかし、もし['a','b']
が[⊥,'x']
と一致されるなら、その時、'a'
と⊥
を一致するよう試みることは 分岐 と一致することをもたらす。 -
これらの例は反駁できるものとできないもののマッチングの実演である。
(\ ~(x,y) -> 0) ⊥ ⇒ 0 (\ (x,y) -> 0) ⊥ ⇒ ⊥ (\ ~[x] -> 0) [] ⇒ 0 (\ ~[x] -> x) [] ⇒ ⊥ (\ ~[x,~(a,b)] -> x) [(0,1),⊥] ⇒ (0,1) (\ ~[x, (a,b)] -> x) [(0,1),⊥] ⇒ ⊥ (\ (x:xs) -> x:x:xs) ⊥ ⇒ ⊥ (\ ~(x:xs) -> x:x:xs) ⊥ ⇒ ⊥:⊥:⊥ -
次の宣言を考えてほしい。
newtype N = N Bool data D = D !Bool
これらの例は
data
とnewtype
によって定義された型においてのパターンマッチングの違いを説明する。(\ (N True) -> True) ⊥ ⇒ ⊥ (\ (D True) -> True) ⊥ ⇒ ⊥ (\ ~(D True) -> True) ⊥ ⇒ True 追加の例はセクション4.2.3で見つかるだろう。
関数内のcase式内の最上位パターンと最上位パターンの集合またはパターン束縛は0以上の ガード に関係する持つかもしれない。ガードの構文と意味論についてはセクション3.13を参照してもらいたい。
ガード意味論は関数またはcase式の厳密な特徴への影響を持つ。特に、他の反駁できないパターンがガードのために評価されるかもしれない。例えば、次の
f :: (Int,Int,Int) -> [Int] -> Int
f ~(x,y,z) [a] | (a == y) = 1
a
とy
の両方はガードの==
によって評価される。
パターンマッチングの正式な意味論
case式を除くすべてのパターンマッチの意味論は、パターンマッチの構成とcase
式との間を関連付ける等式を与えることで定められる( 訳注 : パターンマッチの意味論は一旦case式を使って定義し、そのあとcase式の意味論に従って処理を行う)。case
式の意味論自体は図3.1、3.3の、一連の識別子のように順番に与えられる。どんな実装でもこれらの識別子を保持するために振る舞わなければならず、かなり非効率的なコードを生成することから、それはそれらを直接使用することは期待されない。
(a) |
|
(b) |
|
(c) |
|
(d) |
|
(e) |
|
(f) |
|
図 3.1: case式の意味論、パート1
(g) |
|
(h) |
|
(i) |
|
(j) |
|
(k) |
|
(l) |
|
(m) |
|
(n) |
|
(o) |
|
(p) |
|
(q) |
|
(r) |
|
図 3.2: case式の意味論、パート2
(s) |
|
(t) |
|
(u) |
|
(v) |
|
図 3.3: case式の意味論、パート3
図3.1-3.3のe, e'とeiは式で、giとgsiはガードと各々のガードの並びであり、pとpiはパターン、v, x, xiは変数、K,K'は代数的データ型(data)
コンストラクタ(タプルコンストラクタを含む)で、Nはnewtype
コンストラクタである。
ルール(b)は実際にガードを含むかどうかにはかかわらず、一般的な表層ソース言語のcase
式に適合するものである。もしガードが書かれていなければ、その時、True
が形式matchi内のガードgsi,jに代用される。各々の識別子はもっと簡単な形式へとcase
式の結果を操作する。
図3.2のルール(h)はオーバロードされた==
演算子を起動し、パターンマッチングの意味をオーバーロードされた定数に対して定義するというルールである。
これらの識別子は静的な意味論を全て保存する。ルール(d)、(e)、(j)、(q)はlet
ではなくラムダを使っていて、これはcase
によって束縛された変数が単相型ということを示す(セクション4.1.4を参照)。