1. 言語の拡張
Haskell の構文を調べるときは The Haskell 98 Language Report を参照する。
Wikipedia で Haskell の歴史を見ると、Haskell 98 の前に Haskell 1.0 が 90 年に作成されている。
Haskell 1.0 の説明に、次のように書かれていた。
… 1997年後半にそのシリーズは、安定しており小さく可搬なバージョンの言語仕様と、学習用および将来の拡張の基礎としての基本ライブラリなどを定義した Haskell 98 に到達した。実験的な機能の付加や統合を通じて Haskell98 の拡張や派生物を作成することを、委員会ははっきりと歓迎した[1]。
(太字は引用者による)
上記の出典を確認すると、
Haskell continues to evolve, going well beyond Haskell 98.For example, …
Type system innovations, including: - multi-parameter type classes;
- functional dependencies;
- …
(Preface の `Extensions to Haskell98’ より)
Haskell 98 に対する、様々な拡張の名前が挙げれている。
(以下を飛ばす。)
2. 型クラスの constructor classes による拡張の例
Haskell 1.0 から見たら、「型クラス」も言語の拡張となる。
Type classes are one of the most distinctive features of Haskell (Hudak et al. [1992]). They have been used for an impressive variety of applications, and Haskell 1.3 significantly extended their expressiveness by introducing constructor classes (Jones [1995a]).
(Type Classes: An Exploration of the Design Space、太字は引用者による)
Haskell 1.3 より constructor classes が導入された。
The Haskell 98 Language Report の 4.1 Overview of Types and Classes には、
More examples of type classes can be found in the papers by Jones [7] or Wadler and Blott [12]. The term `type class' was used to describe the original Haskell 1.0 type system; `constructor class' was used to describe an extension to the original type classes. There is no longer any reason to use two different terms: in this report, `type class' includes both the original Haskell type classes and the constructor classes introduced by Jones.
上記の訳、4.1 型およびクラスの概要 によると、
型と構成子クラスのさらなる例は、Jones の論文 [7]、あるいは、Wadler と Blott の論文[12] に見ること ができる。「型クラス」という用語は、元々は Haskell 1.0 の型システムを 記述するのに用いられていたものであり、「構成子クラス」の用語は元々の 型クラスを拡張を記述するのに用いられていたものである。このレポートで はすでに、これらの用語を分けて用いる理由はなく、「型クラス」は元々の Haskell 型クラスとJones によって導入された構成子クラスの両方を含んで いる。
(太字は引用者による)
constructor classes とは
Haskell の仕様の歴史を Language and library specification – HaskellWiki で辿ると、Changes from Haskell 1.2 to Haskell 1.3 に constructor class の説明が書かれている。
constructor classes remove the restriction that types be `first order'. That is, `T a' is a valid Haskell type, but `t a' is not since `t' is a type variable.
(Constructor Classes より)
「constructor classes により、型が一階である制約がなくなった」ってなんのこっちゃ? (@_@; これはモナドで最初につまづいた点と関係していた。
「ふつうの Haskell プログラミング」で、コラム「Monad クラスのインスタンスは型じゃない」 (p269) に、次のように述べられている。
… Maybe a は型で、Maybe は型コンストラクタです。ですから、Monad クラスのインスタンスであるのは型コンストラクタ Maybe であって、型ではありません。
Constructor class - The Mail Archive にも、同様の内容が書かれている。
The term `constructor class' is meant to include classes like Functor and Monad, whose instances are type constructors but not types.
「型をクラスのインスタンスにする」という意味は、すぐに理解できた。しかし、
「型コンストラクタをクラスのインスタンスにする」
という意味が、なかなか分からなかった。
先ほど Haskell 1.3 の変更の説明で使われていた言葉を用いるなら、first-order な型をクラスのインスタンスにするのは理解しやすいが、higer-order な型がクラス宣言で使われているのを理解するのは難しい。 (cf. A Gentle Introduction to Haskell: Classes )
Meet the Monads の説明では、以下の記述が上記に相当する。
Haskell ではこのコンテナの型も多相にすることができます。それゆえ、「m a
」と書いて、ある型の値を保持するある型のコンテナを表現することができます。
この文章を読んだとき、Haskell では、そういう書き方を元々できるのだと理解した。しかし、Haskell 1.3 よりも前の時代には、Moand クラスの (>>=) メソッドの型、
(>>=) :: m a -> (a -> m b) -> m b
における `m’ のような型変数の使い方ができなかったらしい。
型の構文の変化
constructor classes が導入される前と後のバージョンの、「型」の構文を調べてみる。
Haskell 1.2 では、以下のように規定されている。(記法は バッカス・ナウア記法 、 文脈自由文法 を参照。)
(Report on the Programming Language Haskell Version 1.2, 4.1.1 Syntax of Types, p25 より)
上記の tycon は type constructors を表す。(同上 p8) Haskell 1.2 において、「型」は型コンストラクタが、最初に置かれる。また、型変数を表わす tyvar が来たとしても、一つの型変数だけが許される。
これに対して、Haskell 1.3 では、以下のように型が表現されている。
(Report on the Programming Language Haskell Version 1.3, 4.1.1 Syntax of Types, pp.32-33 より)
tyvar は型変数なので、t, a がそれぞれ型変数であるなら、t a は型の表現として正しいことがわかる。
constructor classes の例
constructor classes について知りたければ、Functor と Monad の定義を読めばいい。
慣れるために、自分で何か適当な例を考えねば … と思ったけれど、良い例が思い浮かばなかった。具体性に乏しい意味不明な例を挙げる。 ^^;
例えば、Hoge という入れ物があるとする。この入れ物には、どんな型の値でも、一つだけ入れることができるとする。
data Hoge a = Hoge a
ここから中身を取り出す get 関数を定義する。
get (Hoge x) = x
更に Piyo という入れ物があり、こちらは左右二箇所に、値を入れる場所があるとする。
data Piyo a = Piyo { left, right :: a }
同じく、中身を取り出す get 関数を定義したい。
その前に、Hoge も Piyo も入れ物だから、「入れ物」を表わす型クラス Container を定義する。そして、入れ物の中身を取り出す関数 get の型を宣言。
class Container c where
get :: c a -> a
get 関数において、型の第1引数を c a のように型変数 2 つを用いて表現した。これが constructor classes 。
次に、型コンストラクタ Hoge, Piyo を型クラス Container のインスタンスにする。ただし、Piyo は get で中身を取り出すとき、左側にある値だけを取り出し、右は無視すると想定した。
instance Container Hoge where
get (Hoge x) = x
instance Container Piyo where
get (Piyo l r) = l
これを使い、
main = do print $ get $ Hoge 100 -- 100
print $ get $ Piyo "p1" "p2" -- “p1”
ついでに、入れ物から取り出した後、関数を適用する getf 関数の型を、型クラス Container に宣言する。
getf :: (a -> b) -> c a -> b
上記の宣言に対応して、Hoge で getf 関数を実装
getf f (Hoge x) = f x
piyo の方は、先ほどと同じく左側の値を取り出し、それに対して関数を適用し、右側は無視するとする。
getf f (Piyo l r) = f l
これを使い、
print $ getf (*2) h -- 200
print $ getf (replicate 2) p -- ["piyo", "piyo"]
全体はこちら。
Hoge とか Piyo とか意味不明な例で試したけれど、おかげで Functor が理解しやすくなった。 ^^; ( cf. Haskell の fmap )
3. Haskell 2010 の拡張
2009.11 には、Haskell 2010 と名付けられた改訂版がでている。
Haskell 2010 に導入された拡張の名前は、DoAndIfThenElse、HierarchicalModules、EmptyDataDeclarations、 FixityResolution、ForeignFunctionInterface、LineCommentSyntax、 PatternGuards、RelaxedDependencyAnalysis、LanguagePragma、NoNPlusKPatterns である。
( Haskell – Wikipedia より)
新たな拡張が導入されている。 (cf. changes in Haskell 2010 , via Haskell Prime)
4. 拡張を利用するには
「拡張」について調べた理由は、モナドに関した記事に、必ず「拡張」の話がでてくるため。
例えば、All About Monads では、
… ここで示した定義では Haskell 98 の標準にはない、複数パラメータ型クラスおよび funDeps が使われています。この State モナドを利用するのに、これの詳細を完全に理解する必要はありません。
(The State monad より, 太字は引用者による)
同様の記述が Reader モナド, Writer モナド, Error モナド にある。
モナド以外でも、Existentially quantified types, GADT に `extention’ という単語がある。
コマンドラインのオプションで指定
7.1. Language options によると、拡張を利用するには次の2 種類の方法がある。
- Every language option can switched on by a command-line flag "
-X...
",
- Language options recognised by Cabal can also be enabled using the
LANGUAGE
pragma,
一つは、コマンドラインのオプション。もう一つは、LANGUAGE プラグマの指定。
Haskell 2010 で導入された EmptyDataDecls を試してみる。
“Fun with Functional Dependencies” には、次のように、データコンストラクタが書かれていない記述がある。
data Zero
これを実行すると、以下のメッセージが表示される。
`Zero' has no constructors (-XEmptyDataDecls permits this)
In the data type declaration for `Zero'
拡張を実行するときの方法の一つ「オプションを付けて実行しなさい」ということ。
main 関数を適当に書いた後、
ghc ファイル名 -XEmptyDataDecls
のようにオプションを指定するとコンパイルできる。
プラグマによる指定
Use of language extensions – HaskellWiki では、
We recommend to explicitly switch on language extensions that are needed using the LANGUAGE pragma instead of switching them on all at once using the -fglasgow-exts
compiler flag.
(太字は引用者による)
上記のコマンドラインよりも、プラグマによる指定が勧められている。
プラグマとは The Haskell 98 Report: Compiler Pragmas によると、
プラグマは 付加的指令やヒントをコンパイラに与えるために使用される。しかし、 これは Haskell 言語そのものの正式な部分ではなく、プログラムの意味を 変えるものではない。 …
字句としてはプラグマはコメントと看倣される。
GHC のマニュアルでは 7.13. Pragmas に、
Pragmas all take the form {-# word
... #-}
where word
indicates the type of pragma, and is followed optionally by information specific to that type of pragma.
つまり、コメント として見なされるように
{- -}
で囲み、プラグマであることを示すのに # を使い、
{-# #-}
プラグマのタイプと、それに対する指示をオプションとして指定。
{-# プラグマのタイプ [指示] #-}
「言語の拡張」の場合は、プラグマのタイプに LANGUAGE と記述する。
{-# LANGUAGE [指示] #-}
先ほどの EmptyDataDecls をプラグマを使って書くなら、
{-# LANGUAGE EmptyDataDecls #-}
data Zero
オプションで指定する拡張の名前は、先ほど実行したときに表示されたメッセージ中の
-XEmptyDataDecls
から先頭の –X をとった文字列を使えば良い。一々覚えておく必要はない。
その他
サポートされているプラグマの一覧を表示するには、
ghc --supported-languages
( 7.13. Pragmas より )
参考サイト