Haskell の GHC で拡張を使うためのオプションとプラグマの指定

言語の拡張

img05-08-2010[3]Haskell の構文を調べるときは The Haskell 98 Language Report を参照する。 Wikipedia で 歴史を見ると、Haskell 98 の前に Haskell 1.0 が 90 年に作成されていたようだ。 この 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 に対する様々な拡張の名前が挙げれている。

(以下を飛ばす。)

 

型クラスの constructor classes による拡張

img05-10-2010[1]型クラス」にしても、現在の構文は 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 Report4.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 により型が一階である制約がなくなった」ってなんのこっちゃ? (@_@; と思ったら、モナドで最初につまづいた点と関係していた。4797336021ふつうの Haskell プログラミング」で Maybe a 型を取り上げてモナドの説明がされていた章のコラム「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 では以下の通り。(記法は バッカス・ナウア記法 文脈自由文法 を参照。)

img05-09-2010[1]

(Report on the Programming Language Haskell Version 1.2, 4.1.1 Syntax of Types, p25 より)

上記 tycontype constructors を表わすので (同上 p8) 、Haskell 1.2 において「型」は型コンストラクタが最初に置かれる。また、型変数を表わす tyvar が来たとしても一つの型変数だけが許される。

これに対して Haskell 1.3 は以下のように型が表現されている。

img05-09-2010[2]

(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 )

 

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)

 

拡張を利用するには

「拡張」について調べようと思った理由は、モナドに関した記事を読んでいると必ず「拡張」について話がでてくるため。例えば、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 より )

 

参考サイト

Haskell の GHC で拡張を使うためのオプションとプラグマの指定

Haskell の GHC で拡張を使うためのオプションとプラグマの指定 | すぐ
2010年5月14日 Haskell の GHC で拡張を使うためのオプションとプラグマの指定. Bookmark and Share .... 念のため constructor classes が導入される前と後のバージョンの「型」の構文を調べてみる。 Haskell 1.2 では以下の通り。

Haskell の read 関数で文字列から代数的データ型へ変換 - 導出
2009年1月25日 このときは、まだ返り値の型を指定する関数について意識しておらず、そういう書き方をするものだと、疑問を持たずに写経。 .... Haskell の GHC で拡張を使うためのオプションとプラグマの指定 · Google スプレッドシートでリンクを挿入

7.13. プラグマ
Haskell.Extension で定義されている型 Extension の構築子ならどれを使っても良い。指定された拡張がGHCでサポートされていないなら、エラーが報告される。 OPTIONS_GHC プラグマは、そのソースファイルをコンパイルするときにコンパイラに与える追加のオプションを指定するのに使う。 つきすぎる」と判断したときは、インライン化は行わないし、他のモジュールで使うために展開候補をエクスポートすることもない。

4.4. 実行モード
単純なHaskellプログラムなら、これはmakeを使うのに比べてずっと簡単で、しかも速い。makeモードは4.4.1. ghc ––make を使うで説明されている。 4.4.1. ghc ––make を使う. ––make オプションが指定されると、GHCは複数のモジュールから成るプログラムをビルドする。 コンパイルのためにGHCを再起動する必要がないので、コンパイル間で情報をキャッシュすることができる。 については OPTIONS_GHC プラグマ(4.1.2. ソースファイル中のコマンド行オプションを見よ)を使う必要があることに注意せよ。

haskell-jp:480
OldException を使っていると Control.Exception を使うよう警告メッセージが出るようになりました。 GHC に -dynamic オプションなどを直接渡すか、Cabal を使ってライブラリをビルドする際に --enable-shared オプションを渡す (なお、リリースノートによると、この形式で記述するためには、拡張機能として GADTs だけではなく TypeOperators も指定しなければならないようです。 アノテーション指示文(ANN (annotation) pragma) GHC にアノテーションを使用するための指示文が加わりました。

第10回 Haskellで学ぶ並列プログラミング(その1) - 本物のプログラマ
2007年5月1日 今のところ,分散プログラミングを行うには,GPHまたはGHCを拡張した他の処理系などを使用する必要があります。 標準HaskellやGHCには,「処理系のバージョンによって違う内容を扱う」ための指示文(pragma)や関数は特に存在しません。 こうしたやり方は,Haskellではよく使われています。GHCでは-cppオプションを使うことでCプリプロセサを利用できます(参考リンク)。 .... N<x>オプションは,プログラムを実行する際にxで指定したのと同じ数のスレッドを同時に動作させます。

すぐに忘れる脳みそのためのメモ - にほんブログ村
2010/05/14 21:59Haskell の GHC で拡張を使うためのオプションとプラグマの指定; 言語の拡張 Haskell の構文を調べるときは The Haskell 98 Language Report を参照する。 Wikipedia で 歴史を見ると、Haskell 98 の前に Haskell 1.0 が 90 年に作成され

外部関数インタフェース - Real World Haskell (jp)
2009年5月9日 Haskellでは、この技術スタックはHaskellレポートの外部関数インタフェース補遺に指定されています。 FFIレポートはHaskellとCを LANGUAGEプラグマはどのHaskell 98拡張モジュールを使うかを示します。 今回はFFI拡張のみを用います。 ..... これらの問題を解決するために、バインディングプリプロセッサhsc2hsがGHCと一緒に配布されています。 .... これを行うには、 PCREのnewtypeが実際には時間のオプションは、コンパイルのためのニュータイプの定義は、 CInt値、などのように:

すぐに忘れる脳みそのためのメモ 10875233 Feedage.com
Haskell の GHC で拡張を使うためのオプションとプラグマの指定. 2010-05-14T22:01:55.204+09:00. 言語の拡張 Haskell の構文を調べるときは The Haskell 98 Language Report を参照する。 Wikipedia で 歴史を見ると、Haskell 98 の前に Haskell 1.0 が

ヒビルテ(2006-06)
サーバ側がaccept-charset属性を指定して最初からUTF-8でデータを受け取れば良いだけのことだ。 .... GHCではリストに関する有用な書き換え規則がRULESプラグマとして大量に定義されている。ただ、最適化のためにRULESプラグマ .... Haskellで書いたパーサをrubyから利用出来ないかと聞かれる。CからHaskellを呼ぶことは出来るので、拡張ライブラリを書けばRubyからHaskellを呼ぶことも出来る。 ..... ぁゃιぃ拡張機能を使うと出来ないこともない気がする。以下のコードの元ネタはOlegさんの Deepest functor 。