言語拡張-XUnicodeSyntax
は、特定のASCII文字列をUnicode文字を使って表すことを可能にする。以下の代替記法が提供される。
ASCII | Unicodeによる代替 | コードポイント | 名前 |
---|---|---|---|
::
|
:: | 0x2237 | PROPORTION |
=>
|
⇒ | 0x21D2 | RIGHTWARDS DOUBLE ARROW |
forall
|
∀ | 0x2200 | FOR ALL |
->
|
→ | 0x2192 | RIGHTWARDS ARROW |
<-
|
← | 0x2190 | LEFTWARDS ARROW |
-< | ↢ | 0x2919 | LEFTWARDS ARROW-TAIL |
>- | ↣ | 0x291A | RIGHTWARDS ARROW-TAIL |
-<< | 0x291B | LEFTWARDS DOUBLE ARROW-TAIL | |
>>- | 0x291C | RIGHTWARDS DOUBLE ARROW-TAIL | |
* | ★ | 0x2605 | BLACK STAR |
-XMagicHash
という言語拡張は、識別子に対する後置修飾子として「#」を認めるものである。つまり、「x#」が変数として有効に、「T#」が型構築子やデータ構築子として有効になる。
この井桁記号はまったく意味論に影響を与えない。非ボックス化された値や型に「#」で終わる名前を付ける(たとえばInt#
)傾向があるが、必須ではない。これらはただの通常の変数に過ぎないのである。また、拡張-XMagicHash
が何かをスコープに導入することもない。例えば、Int#
をスコープに導入するためにはGHC.Prim
(7.2. 非ボックス化型とプリミティブ演算を見よ)をインポートしなければならない。その後で、スコープに導入されたInt#
に言及することを可能にするのが-XMagicHash
オプションである。
また、-XMagicHash
は新しい形式のリテラルを何種類か有効にする。(7.2.1. 非ボックス化型
を見よ)
'x'#
の型はChar#
"foo"#
の型はAddr#
3#
の型はInt#
である。一般に、なんらかのHaskellの整数lexemeの後に#
が付いたものはInt#
になる。例えば-0x3A#
や32#
がそうである。
3##
の型はWord#
である。一般に、なんらかの非負なHaskellの整数lexemeの後に##
が付いたものはWord#
になる。
3.2#
の型はFloat#
3.2##
の型はDouble#
GHCは、モジュール名の構文について、ある小さな拡張をサポートしている。すなわち、モジュール名はドット「.
」を含むことができる。これは「階層的モジュール名前空間」拡張とも呼ばれる。これは、通常平坦なHaskellモジュールの名前空間を拡張して、より柔軟な、モジュールの階層をつくり出すからである。
この拡張は言語そのものにはほとんど影響を与えない。モジュール名は常に完全修飾されるので、完全修飾されたモジュール名を「真のモジュール名」だと考えることができる。従って、特に、モジュールの先頭のmodule
キーワードの後には、完全なモジュール名を与えなければならない。例えば、A.B.C
というモジュールは次のように始まらなければならない。
module A.B.C
階層的モジュールを使っていて、修飾名を使いたいときは、as
キーワードを使ってタイプ数を節約するのが常套手段である。例えば、次のようにである。
import qualified Control.Monad.ST.Strict as ST
階層的モジュールが使われているときにGHCがどのようにソースファイルやインタフェースファイルを探索するかについては、4.7.3. 探索パスを見よ。
GHCには、階層的に配置された大規模なライブラリ群が付属している。これについては、付属のライブラリ説明書(訳注: 未訳。web上の最新版)を見てほしい。また、HackageDBから別のライブラリを入手してインストールすることもできる。
以下の議論はSimon Peyton Jonesの元提案を短くしたものである。(この提案はパターンガードが実装される前に書かれたので、これを未実装の機能として扱っていることに注意)
有限写像を表す抽象データ型と、それについてのlookup操作があったとしよう。
lookup :: FiniteMap -> Int -> Maybe Int
lookupは、与えられたキーが写像の定義域に含まれなければNothing
を返し、そうでなければ(Just v)
を返す。ここでv
はそのキーが対応する値である。ここで次の定義を考えよう。
clunky env var1 var2 | ok1 && ok2 = val1 + val2 | otherwise = var1 + var2 where m1 = lookup env var1 m2 = lookup env var2 ok1 = maybeToBool m1 ok2 = maybeToBool m2 val1 = expectJust m1 val2 = expectJust m2
補助関数は次のとおりである。
maybeToBool :: Maybe a -> Bool maybeToBool (Just x) = True maybeToBool Nothing = False expectJust :: Maybe a -> a expectJust (Just x) = x expectJust Nothing = error "Unexpected Nothing"
clunky
は何をしているのか?ガードであるok1 && ok2
は両方のlookupが成功したことを確かめている。このために、maybeToBool
を使ってMaybe
を真偽値に変換している。expectJust
の呼び出し(遅延評価される)は、lookupの結果から値を抽出し、返った値をval1
とval2
にそれぞれ束縛している。もしどちらかのlookupが失敗すると、clunkyはotherwise
の選択肢を選び、引数の和を返す。
これは確かに合法なHaskellだが、欲する結果を得るのに非常に冗長で自明でないやりかたをしている。おそらく、case式を使った方がclunkyをもっと直接的に書けるだろう。
clunky env var1 var2 = case lookup env var1 of Nothing -> fail Just val1 -> case lookup env var2 of Nothing -> fail Just val2 -> val1 + val2 where fail = var1 + var2
これで少し短くなったが、改善とは言えないだろう。もちろん、パターン照合やガードのついた等式をcase式に書き換えることは常にできる。これはまさに、複数の等式を持つ定義をコンパイルするときにコンパイラが行っていることである。Haskellにガードつきの等式があるのは、場合分けを一つ一つ独立に書き下していくことができるようにである。この構造はcaseを使った版では明らかでない。右辺のうちふたつは同じ(fail
)だし、式全体がどんどんインデントされていっている。
私ならclunkyを次のように書く。
clunky env var1 var2 | Just val1 <- lookup env var1 , Just val2 <- lookup env var2 = val1 + val2 ...clunkyの他の等式...
意味は十分明快だろう。修飾子は順番に照合される。<-
修飾子(パターンガードと呼ぼう)については、右辺が評価され、左辺のパターンと照合される。照合が失敗するとガードが全体として失敗し、次の等式が試みられる。成功すると、それに沿った束縛が行われ、次の修飾子が、拡張された環境の下で照合される。ただし、リスト内包表記の場合と違って、<-
の右辺の式の型は左辺のパターンの型と同じである。パターンガードによって導入された束縛のスコープは、残りのガード修飾子と、その等式の右辺にわたる。
リスト内包表記の場合と同様に、パターンガード間に自由に真偽式を混ぜることができる。例えば、次のようにである。
f x | [y] <- x , y > 3 , Just z <- h y = ...
従って、現在のHaskellのガードは、修飾子がただ一つの要素からなり、その要素が真偽式であるような、特別な場合とみなされる。
ビューパターンを有効にするフラグは-XViewPatterns
である。ビューパターンに関するさらなる情報と実例はWikiのページにある。
ビューパターンは、他のパターンの中に入れ子にして使えるパターンガードに少々似ていて、抽象型に対するパターン照合の方法として便利である。例えば、プログラミング言語の実装において、その言語の型の構文を以下のように表現することがあるかもしれない。
type Typ data TypView = Unit | Arrow Typ Typ view :: Type -> TypeView -- さらに、Typを構築するための演算が続く...
Typの表現は抽象的なままにされているので、実装では手の込んだ表現(例えば共有を管理するためのhash-consing)を使うこともできる。 ビューパターンがないと、このシグネチャを使うのは少々不便である。
size :: Typ -> Integer size t = case view t of Unit -> 1 Arrow t1 t2 -> size t1 + size t2
等式を使った関数定義は使えず、このcaseを繰り返すしかない。さらに、t
に関する照合が別のパターンの深くに埋まっている場合、状況はもっと悪くなる。
ビューパターンを使うと、関数viewをパターンの中で呼んで、その結果に対して照合を行うことができる。
size (view -> Unit) = 1 size (view -> Arrow t1 t2) = size t1 + size t2
つまり、expression
->
pattern
と書かれる新しい形式のパターンを追加したのである。これは、「照合対象にexpressionを適用し、その適用の結果をpatternに対して照合せよ」という意味である。expressionは関数の型を持つ任意のHaskellの式であり、ビューパターンはパターンが使えるところならどこにでも使える。
(
exp
->
pat
)
というパターンの意味論は以下の通り。
このビューパターンによって束縛される変数は、pat
によって束縛される変数である。
exp
中の変数は全て束縛された出現である(訳注: 変数はどれも束縛されていなければいけない、換言すればスコープにある変数しか使ってはいけないということ)が、「左の方」のパターン中で束縛された変数はスコープにある。この特徴によって、例えば、ある関数のある引数を、別の引数のビューの中で使うことができる。例えば、7.3.4. パターンガードに登場した関数clunky
は、ビューパターンを使って次のように書ける。
clunky env (lookup env -> Just val1) (lookup env -> Just val2) = val1 + val2 ...clunkyのその他の等式...
より精密に言うと、スコープ規則は以下の通りである。
単一のパターンの中で、ビューパターンの左にあるパターンで束縛された変数はスコープにある。例。
example :: Maybe ((String -> Integer,Integer), String) -> Bool example Just ((f,_), f -> 4) = True
さらに、関数定義において、カリー化された引数を照合することで束縛された変数は、その後の引数におけるビューパターン内で使うことができる。
example :: (String -> Integer) -> String -> Bool example f (f -> 4) = True
つまり、この場合のスコープ割り当ては、カリー化された引数をタプルにまとめた場合と同じになる。
let
やwhere
、もしくは最上位のような相互再帰的な束縛において、ある宣言中のビューパターンが別の宣言によって束縛された変数に言及することはできない。つまり、それぞれの宣言が自己完結していなければならない。例えば、以下のプログラムは許されない。
let {(x -> y) = e1 ; (y -> x) = e2 } in x
(この設計上の決定について、よりはっきりと述べたものがTrac #4061にある。)
型付け: もしexp
の型がT1
->
T2
でpat
がT2
型の値に照合するなら、ビューパターン全体でT1
型の値に照合する。
照合: Haskell 98レポートの3.17.3節(和訳)の等式群に、以下のものを加える。
case v of { (e -> p) -> e1 ; _ -> e2 } = case (e v) of { p -> e1 ; _ -> e2 }
つまり、(
exp
->
pat
)
というパターンに変数v
を照合するには、(
exp
v
)
を評価し、その結果をpat
に対して照合する。
効率性: ある関数定義やcase式の中で、同じビュー関数が複数の選択肢の中で使われている(例えば上のsize
)場合、ビュー関数が一回しか適用されないように、GHCはその関数の適用を集約して一つのネストしたcase式にしようとする。GHCのパターンのコンパイルは、The Implementation of Functional Programming Languagesの第四章にある行列アルゴリズムに従っている。ある行列の最初の列の上部の行いくつか(訳注: top rows)が全て「同じ」式を持つビューパターンであった場合、それらのパターンは一つのネストされたcaseに変換される。これには、例えば、tuple中で整列したの隣接ビューパターンが含まれる。以下のような場合である。
f ((view -> A, p1), p2) = e1 f ((view -> B, p3), p4) = e2
二つのビューパターンがどんなときに「同じ」であるかの現在の描像は非常に制限されたもので、完全な構文的同値性ですらない。それでも、変数、リテラル、適用、それにタプルを含んでいる。例えば、view ("hi", "there")
が二つあった場合、それはまとめられる。一方、現在の実装はα同値性に従った比較を行わないので、(x, view x -> y)
が二つあっても合体することはない。
n+k
パターンへの対応はデフォルトで有効になっている。無効にするには、-XNoNPlusKPatterns
フラグが使える。
Haskell 98のdo記法では、再帰的な束縛が許されない。すなわち、do式中で束縛された変数は、テキスト中でそれより後ろのコードブロックからのみ可視である。これに対して、let式では、束縛変数がその束縛グループ全体から可視である。do記法でも再帰的束縛ができると恩恵を被る応用がいくつかあると分かった。これに必要な構文的なサポートが-XDoRec
によって提供される。
以下は(人為的だが)単純な例である。
{-# LANGUAGE DoRec #-} justOnes = do { rec { xs <- Just (1:xs) } ; return (map negate xs) }
推測できるだろうが、justOnes
はJust [-1,-1,-1,...
に評価される。
再帰的do記法の動機と背景は、A recursive do for Haskell, by Levent Erkok, John Launchbury, Haskell Workshop 2002, pages: 29-37. Pittsburgh, Pennsylvaniaに記されている。モナドの値再帰の背後にある理論については、ErkokのthesisであるValue Recursion in Monadic Computationsがさらなる説明を与えている。ただし、GHCの使う構文はこれらの文書にあるものと異なっていることに注意。
再帰的do記法は、-XDoRec
フラグか、同等の言語プラグマであるDoRec
を使って有効にする。すると、一つの新しいキーワード「rec
」が導入される。これは、相互再帰的なモナド文の集りをまとめて、一つの文を作るものである。
let
文と同様に、rec
で束縛された変数はそのrec
グループの全体と、そのrec
の下で可視である。例として、次の二つを比較せよ。
do { a <- getChar do { a <- getChar ; let { r1 = f a r2 ; rec { r1 <- f a r2 ; r2 = g r1 } ; r2 <- g r1 } ; return (r1 ++ r2) } ; return (r1 ++ r2) }
どちらの場合でも、r1
とr2
はlet
やrec
のブロック全体と、それ以降の文の中で使える。違いは、let
が非モナド的であるのに対して、rec
はモナド的であることだ。(周知のように、Haskellにおいてlet
とは実際にはletrec
のことである)
rec
の静的および動的な意味は以下のように述べることができる。
まず、let束縛と同様に、rec
は再帰的な極小のまとまりに分割(segmentation)される。
rec { a <- getChar ===> a <- getChar ; b <- f a c rec { b <- f a c ; c <- f b a ; c <- f b a } ; putChar c } putChar c
分割の詳細はA recursive do for Haskellの3.2節に記述がある。分割によってよりよい多相性が得られ、再帰的な「結び目」の大きさが軽減される。さらに、この論文にあるように、意味論的な影響もある(当該モナドがright-shrinking則を満たしている場合を除く)。
次に、Control.Monad.Fix.mfix
の呼び出しを使って、こうしてできたそれぞれのrec
が脱糖(desugar)される。例えば、前の例のrec
グループはこのように脱糖される。
rec { b <- f a c ===> (b,c) <- mfix (\~(b,c) -> do { b <- f a c ; c <- f b a } ; c <- f b a ; return (b,c) })
一般に、rec
という文は、脱糖されて次のような文になる。
ss
vs
<- mfix (\~vs
-> do {ss
; returnvs
})
ただし、vs
はss
によって束縛される変数群からなるタプルである。
元々のrec
は、この脱糖された形とまったく同じように型検査される。つまり、例えば、変数群vs
はラムダによって束縛されているので、rec
に続く文においてすべて単相的になる。
関数mfix
は、Control.Monad.Fix
のMonadFix
で次のように定義されている。
class Monad m => MonadFix m where mfix :: (a -> m a) -> m a
再帰的do記法を使うにあたって、他にもいくつか重要な点がある。
これは-XDoRec
フラグで有効になる。このフラグは-fglasgow-exts
で有効になる。
あるモナドに再帰的な束縛を使う必要があるなら、そのモナドはMonadFix
クラスのインスタンスとして宣言されていなければならない。
次のMonadFix
インスタンスは自動的に提供される。List, Maybe, IO。さらに、Control.Monad.STとControl.Monad.ST.Lazyモジュールは、Haskellの(それぞれ正格と遅延の)内部的な状態モナドについてのMonadFixインスタンスを提供する。
let
束縛やwhere
束縛と同様に、一つのrec
の中での名前の覆い隠しは許されない。つまり、一つのrec
で束縛される名前は全て異なっていなければならない(論文の3.3節)。
これは構文の再束縛(7.3.11. 再束縛可能な構文とPreludeの暗黙インポート)に対応している。
GHCは、A recursive do for Haskellに記述されている通りのmdo
キーワードを有効にする-XRecursiveDo
フラグをサポートしていたが、これは現在非推奨である。mdo { Q; e }
の代わりにdo { rec Q; e }
と書いてほしい。
歴史的な参考事項: mdo記法の古い実装(および既存の文書の大部分)は、ライブラリとクラスの名としてMonadRec
を使っていたが、GHCはこの名前をサポートしない。
並行リスト内包表記はリスト内包表記を自然に拡張したものである。リスト内包表記は、mapとfilterを書くための扱いやすい構文と捉えることができる。並行内包表記はこれをzipWith系関数を含むように拡張するものである。
並行リスト内包表記は複数の独立した枝からなり、「|」で区切られる。それぞれの枝には修飾子が並べられる。例えば、以下のものは二つのリストをzipする。
[ (x, y) | x <- xs | y <- ys ]
結果のリストは、最も短い枝と同じ長さになる。この点で、並行リスト内包表記の振る舞いはzipのものを踏襲している。
通常の内包表記への変換を規定することによって並行リスト内包表記を定義することができる。以下に示すのは基本的な考え方である。
次のような並行内包表記があったとする。
[ e | p1 <- e11, p2 <- e12, ... | q1 <- e21, q2 <- e22, ... ... ]
これは次のように変換される。
[ e | ((p1,p2), (q1,q2), ...) <- zipN [(p1,p2) | p1 <- e11, p2 <- e12, ...] [(q1,q2) | q1 <- e21, q2 <- e22, ...] ... ]
ここで、「zipN」は、枝の数に応じた適切なzipである。
一般化リスト内包表記は、SQLでおなじみのソートやグループ化といった操作を可能にするための、リスト内包表記という構文糖に対するさらなる強化である。これは、論文Comprehensive comprehensions: comprehensions with "order by" and "group by"で完全に記述されている。ただし、我々が使う構文は論文のものと僅かに異なる。
この拡張は-XTransformListComp
というフラグによって有効になる。
例を示す。
employees = [ ("Simon", "MS", 80) , ("Erik", "MS", 100) , ("Phil", "Ed", 40) , ("Gordon", "Ed", 45) , ("Paul", "Yale", 60)] output = [ (the dept, sum salary) | (name, dept, salary) <- employees , then group by dept , then sortWith by (sum salary) , then take 5 ]
この例では、リストoutput
の値は次のようになる。
[("Yale", 60), ("Ed", 85), ("MS", 180)]
新しいキーワードが三つある。group
、by
、using
である。(関数sortWith
はキーワードではない。GHC.Exts
からエクスポートされている普通の関数である。)
内包表記修飾子の新しい形式が五つあり、すべて(既存の)キーワードthen
で導入される。
then fこの文は、
f
が型forall a. [a] -> [a]
を持つことを要求する。これの使用例として、最初にあげた例ではtake 5
を適用するのに使われている。
then f by e
この形式は上のものに似ているが、fの最初の引数として渡される関数を作ることができる。そのため、fの型はforall a. (a -> t) -> [a] -> [a]
でなければならない。型から分かるように、この関数は、変形対象のリストの要素からfがなんらかの情報を「射影抽出(project out)」できるようにするものである。
ひとつの例が最初の例にある。この例では、変換されるリストの任意の要素についてsortWith
がsum salary
を見つけ出すのに使われる関数が、sortWith
への引数として与えられている。
then group by e using f
グループ化系統の文のうち、最も一般的な形がこれである。この形式では、fの型がforall a. (a -> t) -> [a] -> [[a]]
であることが要求される。上のthen f by e
の場合と同様、最初の引数はコンパイラによってfに与えられる。これは、変換対象のリストの各要素についてfがeを計算することができるようにする関数である。しかし、グループ化以外の場合と異なり、fはさらに対象のリストをいくつかの部分リストに分割する。これによって、この文以降のあらゆる点において、内包表記中でこれ以前に現われた束縛は、単一の値ではなく可能な値のリストを指すようになる。これを理解する助けになるように、ひとつの例を見てみよう。
-- これはGHC.ExtsのgroupWithと同様に働くが、最初に入力をソートしない groupRuns :: Eq b => (a -> b) -> [a] -> [[a]] groupRuns f = groupBy (\x y -> f x == f y) output = [ (the x, y) | x <- ([1..3] ++ [1..2]) , y <- [4..6] , then group by x using groupRuns ]
結果として、変数output
は次に示す値を取る。
[(1, [4, 5, 6]), (2, [4, 5, 6]), (3, [4, 5, 6]), (1, [4, 5, 6]), (2, [4, 5, 6])]
関数the
を使って、xの型をリストから元の数値型に戻したのに注意。対照的に、変数yは、グループ化によって導入されたリスト形式のままにしてある。
then group by e
この形式のグループ化は、要するに上述のものと同じだが、グループ化に用いられる関数が与えられていないので、代わりにGHC.Exts
で定義されているgroupWith
が用いられる。最初の例で使ったグループ化の形式はこれである。
then group using f
この形式のgroup文では、fの型は単純にforall a. [a] -> [[a]]
である必要があり、ここまでの内包を直接グループ化するのに使われる。この形式の例を以下に示す。
output = [ x | y <- [1..5] , x <- "hello" , then group using inits]
結果は、「hello」という単語を五回並べた文字列の、すべての前方部分列(prefix)を含むリストになる。
["","h","he","hel","hell","hello","helloh","hellohe","hellohel","hellohell","hellohello","hellohelloh",...]
モナド内包表記はリスト内包表記をあらゆるモナドに一般化したものである。これには並列内包表記(7.3.8. 並行リスト内包表記)と変換内包表記(7.3.9. 一般化(SQL風)リスト内包表記)を含む。
モナド内包表記は以下のものに対応する。
束縛:
[ x + y | x <- Just 1, y <- Just 2 ]
束縛は、(>>=)
とreturn
を使って、次のような通常のdo記法に翻訳される。
do x <- Just 1 y <- Just 2 return (x+y)
ガード:
[ x | x <- [1..10], x <= 5 ]
ガードはguard
関数を使って翻訳される。これにはMonadPlus
インスタンスが必要である。
do x <- [1..10] guard (x <= 5) return x
変換文(-XTransformListComp
を付けた場合と同様)。
[ x+y | x <- [1..10], y <- [1..x], then take 2 ]
これは、以下のものに翻訳される。
do (x,y) <- take 2 (do x <- [1..10] y <- [1..x] return (x,y)) return (x+y)
グループ化文(-XTransformListComp
を付けた場合と同様)
[ x | x <- [1,1,2,2,3], then group by x ] [ x | x <- [1,1,2,2,3], then group by x using GHC.Exts.groupWith ] [ x | x <- [1,1,2,2,3], then group using myGroup ]
基本形のthen group by e
はmgroupWith
関数を使って翻訳される。これにはControl.Monad.Group
で定義されているMonadGroup
のインスタンスが必要である。
do x <- mgroupWith (do x <- [1,1,2,2,3] return x) return x
グループ化文によってx
の型が変えられていることに注意。
グループ化関数をusing
を使って定義することもできる。
並列な文(-XParallelListComp
付きの場合と同様)
[ (x+y) | x <- [1..10] | y <- [11..20] ]
並列な文はmzip
関数を使って翻訳される。これにはControl.Monad.Zip
で定義されているMonadZip
のインスタンスが必要である。
do (x,y) <- mzip (do x <- [1..10] return x) (do y <- [11..20] return y) return (x+y)
MonadComprehensions
拡張が有効なら、これらの機能が全て有効になる。内包表記の種類と、より詳細な使い方については、前の章、7.3.9. 一般化(SQL風)リスト内包表記と7.3.8. 並行リスト内包表記に説明がある。一般に、モナド内包表記用に型[a]
を型Monad m => m a
に置き換えるだけで良い。
注意: これらの例示ではほとんどリストモナドしか使っていないが、モナド内包表記はあらゆるモナドに対して働く。リストに関して必要なインスタンスは全てbase
パッケージが提供する。これによってMonadComprehensions
が組み込みのリスト内包表記、変換内包表記、並列内包表記と後方互換になる。
より形式的には、脱糖は以下のように行われる。以下ではモナド内包表記[ e | Q]
を脱糖したものをD[ e | Q]
と書く。
式: e 宣言: d 修飾子リスト: Q,R,S -- 基本形 D[ e | ] = return e D[ e | p <- e, Q ] = e >>= \p -> D[ e | Q ] D[ e | e, Q ] = guard e >> \p -> D[ e | Q ] D[ e | let d, Q ] = let d in D[ e | Q ] -- 並列内包表記 (並列な枝が複数あるならこれを繰り返す) D[ e | (Q | R), S ] = mzip D[ Qv | Q ] D[ Rv | R ] >>= \(Qv,Rv) -> D[ e | S ] -- 変換内包表記 D[ e | Q then f, R ] = f D[ Qv | Q ] >>= \Qv -> D[ e | R ] D[ e | Q then f by b, R ] = f (\Qv -> b) D[ Qv | Q ] >>= \Qv -> D[ e | R ] D[ e | Q then group using f, R ] = f D[ Qv | Q ] >>= \ys -> case (fmap selQv1 ys, ..., fmap selQvn ys) of Qv -> D[ e | R ] D[ e | Q then group by b, R ] = D[ e | Q then group by b using mgroupWith, R ] D[ e | Q then group by b using f, R ] = f (\Qv -> b) D[ Qv | Q ] >>= \ys -> case (fmap selQv1 ys, ..., fmap selQvn ys) of Qv -> D[ e | R ] ただし、QvはQで束縛された変数(のうち後で使われるもの)のタプルであり、 selQviはQvをその第i成分に写すセレクタである 演算子 標準の束縛 期待される型 -------------------------------------------------------------------- return GHC.Base t1 -> m t2 (>>=) GHC.Base m1 t1 -> (t2 -> m2 t3) -> m3 t3 (>>) GHC.Base m1 t1 -> m2 t2 -> m3 t3 guard Control.Monad t1 -> m t2 fmap GHC.Base forall a b. (a->b) -> n a -> n b mgroupWith Control.Monad.Group forall a. (a -> t) -> m1 a -> m2 (n a) mzip Control.Monad.Zip forall a b. m a -> m b -> m (a,b)
内包表記は、それを脱糖したものが型検査に通るなら型検査に通るべきである。
モナド内包表記は構文の再束縛(7.3.11. 再束縛可能な構文とPreludeの暗黙インポート)に対応している。構文の再束縛なしの場合、「標準の束縛」で定義された演算子が使われる。構文の再束縛が有効な場合、各演算子は現在の字句的スコープから引かれる。例えば、並列内包表記は、スコープにある"mzip
"をなんであれ使って型検査・脱糖される。
再束縛される演算子は上の表にある「期待される型」を持っていなければならない。これらの型は驚くほど一般的である。例えば、次のような型を持つバインド演算子を使うことができる。
(>>=) :: T x y a -> (a -> T y z b) -> T x z b
変換内包表記の場合には、内包表記が任意のモナドに関する物であるだけでなく、グループが任意の(fmap
を持つ)型n
に関してパラメタ化されている。
GHCは通常Prelude.hi
を自動的にインポートする。これが嫌なら、-XNoImplicitPrelude
を使うと良い。こうすれば、自分自身のPreludeをインポートすることができる。(ただし、それにPrelude
という名前をつけてはいけない。Haskellではモジュールの名前空間は平坦なので、Preludeモジュールと衝突を起こしてはならないのだ)
自分で数値クラスの階層を定義するために、自作のプレリュードを実装しているとしよう。しかし、リテラルの「1」が、Haskellレポートの指定通りにPrelude.fromInteger 1
を意味するとしたら、これは完全な無駄骨である。このため、-XRebindableSyntax
フラグを使った場合には、以下に挙げる組込み構文は(Preludeのものではなく)スコープにあるものならなんでも使うようになる。
整数リテラル368
の意味は「fromInteger (368::Integer)
」であり、「Prelude.fromInteger (368::Integer)
」ではない。
小数リテラルも全く同じように扱われる。変換はfromRational (3.68::Rational)
である。
多重定義された数値的パターンでの等値比較では、とにかくスコープにある(==)
を使う。
n+k
パターンにおける減算演算およびだいなりいこーる比較では、とにかくスコープにある(-)
と(>=)
を使う。
符号反転(例えば「- (f x)
」)は、数値パターンでも式中でも「negate (f x)
」を意味する。
条件分岐(例えば、 "if
e1 then
e2 else
e3")は"ifThenElse
e1 e2 e3"を意味する。ただしcase
式は影響を受けない。
do記法の変換時にはとにかくスコープにある(>>=)
、(>>)
、fail
が使われる。リスト内包表記、mdo(7.3.7.2. mdo記法 (廃止予定))、並行配列内包表記は影響を受けない。
アロー記法(7.10. アロー記法
を見よ)では、とにかくスコープにあるarr
、(>>>)
、first
、app
、(|||)
、loop
の各関数が使われる。ただし、他の構文要素の場合と異なり、これらの関数の型はPreludeのものとかなり良く近似していなければならない。詳細は固まっていので、もしこれを使いたいなら、声を掛けてほしい。
-XRebindableSyntax
は、-XNoImplicitPrelude
を自動的に有効にする。
どの場合でも(アロー記法は例外)、コードの静的意味は脱糖された形でのそれと等しくなるはずである。これは少々予想に反するかもしれない。例えば、368
というリテラルの静的意味はfromInteger (368::Integer)
のそれとまったく同じである。従って、fromInteger
は下に挙げるどんな型をもっていても良い。
fromInteger :: Integer -> Integer fromInteger :: forall a. Foo a => Integer -> a fromInteger :: Num a => a -> Integer fromInteger :: Integer -> Bool -> Bool
警告: これは実験的な機能であり、通常ほど検査がなされない。脱糖されたプログラムを型検査するには-dcore-lint
を使う。Core Lintが満足しているなら、問題ないはずである。
-XPostfixOperators
フラグを使うと、演算子の左セクションの構文に小さな拡張が有効になり、これを使って後置演算子を定義することができるようになる。拡張とはこうである。以下のような左セクションがあったとしよう。
(e !)
これは、(型検査と実行の両面において)以下の式と等しい。
((!) e)
(これは式e
が何であっても、また演算子(!)
が何であっても成り立つ)。Haskell 98の厳密な解釈では、このセクションは以下と同等だとされる。
(\y -> (!) e y)
つまり、演算子は二引数の関数でなければならない。GHCでは一引数の関数であっても良く、結果として、関数を後置記法で書くことができるようになる。
この拡張は関数定義の左辺には及ばない。従って、このような関数を定義するときには前置形を使わなければならない。
-XTupleSections
フラグは、タプルの構築子をPython風に部分適用することを有効にする。例として、以下のプログラム
(, True)
は、次の不恰好な表記と同じ意味の書き方とみなされる。
\x -> (x, True)
タプルの引数はどんな組み合わせで省略してもよい。以下のように。
(, "I", , , "Love", , 1337)
これは、次のように翻訳される。
\a b c d -> (a, "I", b, c, "Love", d, 1337)
unboxed tuplesを有効にしているなら、これについてもセクションが使える。例えば、
(# , True #)
非ボックス化タプルにゼロ項のものはないので、次の式
(# #)
は、変わらず非ボックス化単項タプルの構築子を意味する。
レコードの構築とパターンマッチにおいては、仮に同じフィールド名を持つデータ型が二つスコープにあったとしても、どのフィールドが言及されているのか全く曖昧でない。例えば以下のように。
module M where data S = MkS { x :: Int, y :: Bool } module Foo where import M data T = MkT { x :: Int } ok1 (MkS { x = n }) = n+1 -- 曖昧でない ok2 n = MkT { x = n+1 } -- 曖昧でない bad1 k = k { x = 3 } -- 曖昧 bad2 k = x k -- 曖昧
スコープには二つのx
があるが、ok1
の定義中のパターンにおけるx
は、型S
のフィールドを指す他にないということが明白である。関数ok2
についても同様である。しかし、bad1
におけるレコード更新と、bad2
におけるレコード選択では、どちらの型が意図されているか明確でない。
Haskell 98はこの四つすべてを曖昧であると見做すが、-XDisambiguateRecordFields
が与えられると、GHCは前者二つを認める。この規則は、Haskell 98でのインスタンス宣言の規則(インスタンス宣言中のメソッド束縛の左辺のメソッド名は曖昧さなくそのクラスのメソッドを(スコープにあれば)参照し、スコープに同名の別の変数があっても構わないとする)と全く同じである。これによって、異なるモジュールから同じフィールド名を使う二つのレコードをインポートしたときに、修飾名がそこらに散らばるのを軽減できる。
詳細をいくつか。
フィールドの曖昧性除去は同名利用(7.3.15. レコード同名利用 を見よ)と組み合わせることができる。例。
module Foo where import M x=True ok3 (MkS { x }) = x+1 -- 曖昧性除去と同名利用を両方使っている
-XDisambiguateRecordFields
が有効なら、対応する選択関数が修飾された形でしかスコープにない場合であっても、修飾されていないフィールド名を使うことができる。例えば、先に挙げた例と同じM
モジュールがあると仮定すると、これは合法である。
module Foo where import qualified M -- qualifiedであることに注意 ok4 (M.MkS { x = n }) = n+1 -- 曖昧でない
構築子MkS
は修飾された形でしかスコープにないので、M.MkS
と呼ばなければならない。しかし、スコープにM.x
があってもx
がないにもかかわらず、フィールドx
を修飾する必要はない。(実質的に、これは構築子によって修飾されている)
レコード同名利用(訳注: record puns; punは「駄洒落」「語呂合わせ」の意)は、-XNamedFieldPuns
フラグによって有効になる。
レコードを使うとき、フィールド名と同じ名前の変数を束縛するようなパターンを書くことがよくある。以下のような場合である。
data C = C {a :: Int} f (C {a = a}) = a
レコード同名利用は、この変数名を省略することを可能にする。よって、上と同じことを単に次のように書くことができる。
f (C {a}) = a
つまり、レコードパターン中において、a
というパターンはa = a
というパターン(同じa
という名前についての)に展開される。
以下のことに注意。
レコード同名利用は式中でも使える。例えば、次のように書く。
let a = 1 in C {a}
これは次のものと同等である。
let a = 1 in C {a = a}
この展開は純粋に構文上のものであるため、レコード同名利用の式は、そのフィールド名と同じ綴りを持つなかで、最も内側の変数を参照する。
同じレコード中で、同名利用と通常のパターンを混ぜることができる。
data C = C {a :: Int, b :: Int} f (C {a, b = 4}) = a
同名利用は、レコードパターンが使えるところ(例えばlet
束縛の中や最上位)ならどこでも使うことができる。
フィールド名が修飾されている場合は、その同名利用が展開されるときに修飾が取り除かれる。例えば、
f (C {M.a}) = a
は、
f (M.C {M.a = a}) = a
を意味する。(これは、構築子M.C
のフィールド選択関数a
が修飾された形でのみスコープにある場合に便利である)
レコードワイルドカードは-XRecordWildCards
フラグによって有効になる。このフラグによって-XDisambiguateRecordFields
も有効になる。
沢山のフィールドのあるレコードでは、レコードパターンにおいてフィールドをいちいち書き下すのが面倒なことがある。次のような場合である。
data C = C {a :: Int, b :: Int, c :: Int, d :: Int} f (C {a = 1, b = b, c = c, d = d}) = b + c + d
レコードワイルドカード構文では、レコードパターン中に「..
」を使えるようになる。そうすると、省略したそれぞれのf
がf = f
というパターンに置換される。例として、上のパターンは次のように書ける。
f (C {a = 1, ..}) = b + c + d
さらなる詳細。
ワイルドカードは他のパターン(同名利用(7.3.15. レコード同名利用
)も含む)と併用できる。例えばC {a = 1, b, ..})
というパターンでのように。さらに、レコードワイルドカードはレコードパターンが使えるところならどこでも使えるので、let
束縛や最上位でも使える。例えば、次の最上位の束縛は、b
とc
とd
を定義する。
C {a = 1, ..} = e
レコードワイルドカードは式中でも使える。次のように書くことができる。
let {a = 1; b = 2; c = 3; d = 4} in C {..}
これは以下のように書くのと同じである。
let {a = 1; b = 2; c = 3; d = 4} in C {a=a, b=b, c=c, d=d}
この展開は純粋に構文上のものなので、レコードワイルドカード式は、省略されたフィールド名と同じ綴りの変数のうちもっとも内側にあるものを参照する。
「..
」は省略されたスコープにあるレコードフィールドに展開される。より精密には、"C {..}
"の展開がf
を含むのは、以下の条件が成り立つ場合、そしてその場合に限る。
f
が構築子C
のレコードフィールドである。
なんらかの形でf
がスコープにある(修飾形でも、非修飾形でも)。
(パターンでなく)式の場合、変数f
が、レコードセレクタ自身の束縛とは別に、非修飾形でスコープにある。
例を示す。
module M where data R = R { a,b,c :: Int } module X where import M( R(a,b) ) f b = R { .. }
このR{..}
はR{M.a=a}
に展開される。b
はレコードフィールドがスコープにないので省略される。c
は変数c
がスコープにない(もちろん、レコード選択子c
の束縛を除いて)ので省略される。
Haskell 98レポートを注意深く読むと、let
やwhere
で導入される局所束縛の内部に結合性宣言(infix
、infixl
、infixr
)が出現することが許されていることが分かる。しかし、このような束縛の意味論について、Haskellレポートはあまり詳しく規定していない。
GHCでは、次のように、局所束縛に結合性宣言が付属していてもよい。
let f = ... infixr 3 `f` in ...
そして、この結合性宣言は、この束縛がスコープにあるようなあらゆる場所で適用される。例えば、let
式なら、他のlet
束縛の右辺とlet
式の本体で適用される。また、再帰的do
式(7.3.7. 再帰的do記法
)では、let
文の局所結合性宣言は、束縛される名前と同様に、そのグループの全ての文に渡るスコープを持つ。
さらに、局所結合性宣言にはその名前についての局所束縛が付属「していなければならない」。次のように、別の場所で束縛された名前の結合性を設定しなおすことは不可能である。
let infixr 9 $ in ...
局所結合性宣言は技術的にHaskell 98なので、有効にするのにフラグは必要ない。
-XPackageImports
フラグが有効なら、GHCは、インポート宣言を、インポート先のモジュールが属しているべきパッケージ名で修飾することを認める。例をあげる。
import "network" Network.Socket
こうすると、network
パッケージ(のいずれかのバージョン)からNetwork.Socket
をインポートすることになる。これは、同じモジュールが複数のパッケージから利用できたり、現在ビルド中のパッケージと外部のパッケージの両方にあったりする場合に、インポートの曖昧性を取り除くために使うことができる。
注意: おそらく、あなたがこの機能を使う必要はないだろう。この機能は、主に我々がAPIの変更に際して後方互換なパッケージのバージョンをビルドすることができるように追加されたものである。一般的な事例では、この機能を使うと脆い依存が発生しやすい。モジュールはあるパッケージから別のパッケージに移ることがあり、その場合パッケージ修飾されたインポートは壊れるからである。
-XSafeImports
フラグを付けると、GHCはimport宣言の構文を拡張してimport
キーワードの後に任意でsafe
キーワードを受け取るようにする。この機能はSafe HaskellというGHC拡張の一部である。例えば、
import safe qualified Network.Socket as NS
は、Network.Socket
モジュールをインポートするが、Network.Socketが安全にインポートできる場合にしかコンパイルに成功しない。importがどういう場合に安全だとみなされるかの記述は7.20. Safe Haskellを見よ。
専用の構文を有効にするオプションを使うと、Haskell 98で動作していたコードがコンパイルできなくなる可能性がある。これは大抵、変数名として使われていたものが予約語になることが原因である。この節では、言語拡張によって「盗まれた」構文を列挙する。ここではHaskell 98の字句構文(Haskell 98レポートを見よ)の記法と非終端記号を使っている。構文の変更については、既存の正しいプログラムに影響する可能性のあるもの(「盗まれた」構文)のみを列挙する。多くの拡張は新しい文脈自由構文を導入するが、これらの場合は例外なく、新しい構文を使って書かれたプログラムは適当なオプションなしではコンパイルできない。
専用の構文には次の二種類がある。
新しい予約語や予約シンボル。もはやプログラムで識別子として使うことのできない文字列である。
その他の専用構文。特定のオプションが有効になっていると別の意味を持つような文字の列。
盗まれるのは以下の構文である。
forall
-XExplicitForAll
によって盗まれる(型中において)。従って、-XScopedTypeVariables
、-XLiberalTypeSynonyms
、-XRank2Types
、-XRankNTypes
、-XPolymorphicComponents
、-XExistentialQuantification
によっても盗まれる。
mdo
-XRecursiveDo
に盗まれる。
foreign
-XForeignFunctionInterface
に盗まれる。
rec
、proc
、-<
、>-
、-<<
、>>-
、および(|
、|)
の括弧
-XArrows
によって盗まれる。
?varid
,
%varid
-XImplicitParams
によって盗まれる。
[|
,
[e|
, [p|
,
[d|
, [t|
,
$(
,
$varid
-XTemplateHaskell
によって盗まれる。
[:varid
|
-XQuasiQuotes
によって盗まれる。
varid
{#
},
char
#
,
string
#
,
integer
#
,
float
#
,
float
##
,
(#
, #)
,
-XMagicHash
によって盗まれる。