Haskell2010 Language Report 日本語訳

翻訳元: Haskell2010 Language Report

Simon Marlow (editor)

著作権表示 著者と発行者はこのレポートを Haskell コミュニティ全体へ帰属させることを意図しており、この表示を含め文章全体が再現される限りにおいてはコピーと配布をいかなる目的でも許可している。このレポートの改変も、改変がそうと明示されており、Haskell2010 言語の定義であることを主張しなければ、いかなる目的でもコピーや再配布することができる。

Copyright notice. The authors and publisher intend this Report to belong to the entire Haskell community, and grant permission to copy and distribute it for any purpose, provided that it is reproduced in its entirety, including this Notice. Modified versions of this Report may also be copied and distributed for any purpose, provided that the modified version is clearly presented as such, and that it does not claim to be a definition of the language Haskell 2010.

  • 目次
  • 前書き
    • 目標
    • Haskell2010: 言語とライブラリ
    • Haskell98 からの拡張
    • リソース
    • 言語の作成

I. Haskell 2010 言語

  1. 序説
    • プログラムの構造
    • Haskell のコア
    • 値と型
    • 名前空間
  2. 字句構造
    • 慣習的表記
    • 字句の構造
    • コメント
    • 識別子と演算子
    • 数値リテラル
    • 文字と文字列リテラル
    • レイアウト
    • エラー
    • 変数、コンストラクタ、演算子、リテラル
    • カリー化された適用とラムダ抽象
    • 演算子の適用
    • セクション
    • 条件
    • リスト
    • タプル
    • ユニット式と括弧つき式
    • 算術列
    • リスト内法表記
    • let 式
    • case 式
    • do 式
    • フィールドラベルのついたデータ型
    • 型シグネチャ
    • パターンマッチ
  3. 宣言と束縛
    • 型とクラスの概観
    • ユーザー定義データ型
    • 型クラスとオーバーロード
    • ネストされた宣言
    • 関数とパターン束縛の静的な意味論
    • カインド推論
  4. モジュール
    • モジュール構造
    • export リスト
    • import 宣言
    • インスタンス宣言の import と export
    • 名前衝突とクロージャー
    • 標準 Prelude
    • 分割コンパイル
    • 抽象データ型
  5. 事前定義された型とクラス
    • 標準的な Haskell の型
    • 正格評価
    • 標準的な Haskell のクラス
    • 数値
  6. 入出力の基本
    • 標準的な I/O 関数
    • I/O 操作列
    • I/O モナドにおける例外処理
  7. Foreign Function Interface
    • 外部言語
    • コンテキスト
    • 字句構造
    • 外部宣言
    • 外部エンティティの規格
    • マーシャリング
    • 外部 C インターフェイス
  8. 標準 Prelude
    • Prelude PreludeList
    • Prelude PreludeText
    • Prelude PreludeIO
  9. 文法リファレンス
    • 慣習的表記
    • 字句文法
    • レイアウト
    • 文芸的コメント
    • 文脈自由文法
    • 結合の解決
  10. インスタンス導出の仕様
    • EqOrdのインスタンス導出
    • Enumのインスタンス導出
    • Boundedのインスタンス導出
    • ReadShowのインスタンス導出
  11. コンパイラプラグマ
    • インライン化
    • 限定化
    • 言語拡張

Haskell 2010 ライブラリ

  1. Control.Monad
    • ファンクターとモナドクラス
    • 関数
  2. Data.Array
    • 不変非正格配列
    • 配列の構成
    • 配列へのアクセス
    • 配列の逐次更新
    • 導出された配列
    • 規格
  3. Data.Bits
  4. Data.Char
    • 文字と文字列
    • 文字の分類
    • 複合語の慣習
    • 単一の数値文字
    • 数値の表現
    • 文字列の表現
  5. Data.Complex
    • 直行形式
    • 極形式
    • 共役
    • 規格
  6. Data.Int
    • 符号付き整数型
  7. Data.Ix
    • Ixクラス
    • Ixインスタンスの導出
  8. Data.List
    • 基本的な関数
    • リスト変換
    • リストの縮小(畳み込み)
    • リストの作成
    • 部分リスト
    • リストの検索
    • リストの添え字アクセス
    • リストの zip/unzip
    • 特別なリスト
    • 一般化された関数
  9. Data.Maybe
    • Maybe型と操作
    • 規格
  10. Data.Ratio
    • 規格
  11. Data.Word
    • 符号なし整数型
  12. Foreign
  13. Foreign.C
  14. Foreign.C.Error
    • errno値の Haskell における表現
  15. Foreign.C.String
    • C の文字列
    • C のワイド文字列
  16. Foreign.C.Types
    • C の型の表現
  17. Foreign.ForeignPtr
    • ファイナライズされたデータポインター
  18. Foreign.Marshal
  19. Foreign.Marshal.Alloc
    • メモリ確保
  20. Foregin.Marshal.Array
    • 配列のマーシャリング
  21. Foreign.Marshal.Error
  22. Foreign.Marshal.Utils
    • 一般的なマーシャリングのユーティリティ
  23. Foregin.Ptr
    • データポインタ
    • 関数ポインタ
    • 整数型とポインタのロスのない相互変換
  24. Foregin.StablePtr
    • Haskell の値への安定参照
  25. Foregin.Storable
  26. Numeric
    • 表示
    • 読み取り
    • その他
  27. System.Environment
  28. Sytem.Exit
  29. System.IO
    • IO モナド
    • ファイルとハンドル
    • ファイルのオープンとクローズ
    • ハンドルに対する捜査
    • テキストの入力と出力
  30. System.IO.Error
    • I/O エラー
    • I/O エラーの型
    • I/O エラーの送出と捕捉

参考文献

前書き

『幾多の人々がコンビネーター論理に関する技術的な文書を残しており、さらにその多くは、われわれのものも含めて誤った状態で出版されている。我々の罪を犯した仲間たちはその時代で最も注意深くて有能な論理学者に属する人々であり、我々はこのことがこの分野が手に負えないことの証拠であると考えている。よって十分な解説は正確さのために必要であるし、極端な要約は(通常よりも遥かに)節約にならないであろう。』

ハスケル・B・カリー, ロベール・フェイ
コンビネーター論理[3]の前書きにて 1956年5月31日

1987年の7月、オレゴン州のポートランドでの関数型言語とコンピューターの設計(FPCA '87)学会にて会議が開かれ、関数型言語コミュニティが置かれている不幸な状況について話し合われた。すなわち、それまで10を超える非正格な純粋関数型言語が実装されており、それらの表現力とベースとなる意味論がどれも似かよっていたことである。その会議で強く意見が一致したこととして、共通となる言語が欠けていることはこの種の関数型言語がより広く使われるようになることの妨げとなっているということであった。新しいアイデアの素早いやりとりの場、現実のアプリケーション開発のための安定した基盤、そして人々が関数型言語を利用することを推奨する売り込みの手段を提供する、共通言語を設計するための委員会が結成されることが決定された。。この文書はその委員会(とそれに続く)の努力の結果である、Haskellと呼ばれる純粋関数型言語を説明したものである。これは我々の多くの部分の論理学的な基礎を提供する仕事を成した論理学者ハスケル・B・カリーにちなんでつけられた。

目的

委員会の主たる目的は次の条件を満たすような言語をデザインすることであった。

  1. 教育、研究、そして大規模なシステムを作ることも含めたアプリケーションに適したものであること。
  2. 公表された形式的な文法および意味論により完全に説明されるものであること。
  3. 自由に利用可能なものであること。誰でも言語を実装し、それを配りたい人に配ることが許可されていること。
  4. 広く認められたアイデアに基づいているものであること。
  5. 関数型プログラミング言語の不要な多様性を減らすこと。

Haskell 2010: 言語とライブラリ

委員会はHaskellが言語デザインにおいて将来の研究の礎の役割を果たすことを目的とし、実験的な機能を盛り込んだその拡張や変種たる言語が登場することを望んでいた。

Haskellは実際には最初の発表から進化を続けてきている。1997年の中頃までには、言語デザインに5つのバージョンが存在していた(Haskell 1.0から1.4)。1997年にアムステルダムで開かれたHaskellのワークショップにおいて、Haskellの安定した変種が必要であることが決定され、これが"Haskell 98"となり、1999年の2月に発表された。小さなバグを修正したものが2002年に 改訂版 Haskell 98レポートとされた。

2005年のHaskellワークショップにおいて、公式の言語に対する多くの拡張が広く使われているということ(そしてそれらは複数の実装でサポートされていること)と、言語標準を定期的に定義することは意味のあることであることが合意された。それには現在の状態の明文化(とルール化)が必須であった。

このためHaskell Primeの取り組みがHaskell 98の相対的に保守的な拡張であると理解された、新しい機能は皆によく理解され広く合意されたものにしか立脚しないのである。それは"安定した"言語であることも意図されていたが、近年の言語デザインの研究でのかなりの進歩も反映したものであった。

色々な言語デザインを調査し数年の後には、唯一の一枚岩な言語の版でやるには仕事が大きすぎること、進捗を生む最良の方法は言語を小さくゆっくりと段階を踏んだ言語の進化であるので、各版はよく理解された拡張と変更のほんの一部だけを統一したものにすることが決定された。Haskell 2010はこのようにして作られた最初の版であり、新しい版は毎年1つずつ出される見込みである。

Haskell 98への拡張

Haskell 98と比較してHaskell 2010に入った最も重要な言語の変更点をここに並べる。

新しい言語機能:

  • 外部関数インターフェイス(FFI)
  • 階層的モジュール名(例: Data.Bool)
  • パターンガード

削除された言語機能:

  • (n + k)パターン文法

Haskellのリソース

Haskellのウェブサイト https://www.haskell.org はたくさんの有益なリソースへのアクセスを可能にしている。次はその一部である:

  • オンライン版の言語とライブラリの定義
  • Haskellのチュートリアルの資料
  • Haskellメーリングリストの詳細
  • Haskellの実装
  • Haskellに貢献したツールやライブラリ
  • Haskellのアプリケーション
  • ユーザーが貢献するwikiの記事
  • Haskellコミュニティにおけるニュースとイベント

是非あなたもHaskellのメーリングリストやHaskell wikiにて、コメントし、改善を提案し、言語やレポートの現状について批評をしてみてください。

言語の構築

Haskellは研究者やアプリケーションプログラマーたちの活発なコミュニティによって作られ、維持されてきた。言語とライブラリ委員会の一員を務める人々が、特に多量の時間とエネルギーをこの言語に捧げてきた。彼らの名前と、その期間での所属をここに記す。

(訳注 ここでは名前と所属について翻訳をせず原文のまま表示します)

Arvind (MIT)
Lennart Augustsson (Chalmers University)
Dave Barton (Mitre Corp)
Brian Boutel (Victoria University of Wellington)
Warren Burton (Simon Fraser University)
Manuel M T Chakravarty (University of New South Wales)
Duncan Coutts (Well-Typed LLP)
Jon Fairbairn (University of Cambridge)
Joseph Fasel (Los Alamos National Laboratory)
John Goerzen
Andy Gordon (University of Cambridge)
Maria Guzman (Yale University)
Kevin Hammond [編集者] (University of Glasgow)
Bastiaan Heeren (Utrecht University)
Ralf Hinze (University of Bonn)
Paul Hudak [編集者] (Yale University)
John Hughes [編集者] (University of Glasgow; Chalmers University)
Thomas Johnsson (Chalmers University)
Isaac Jones (Galois, inc.)
Mark Jones (Yale University, University of Nottingham, Oregon Graduate Institute)
Dick Kieburtz (Oregon Graduate Institute)
John Launchbury (University of Glasgow; Oregon Graduate Institute; Galois, inc.)
Andres Löh (Utrecht University)
Ian Lynagh (Well-Typed LLP)
Simon Marlow [編集者] (Microsoft Research)
John Meacham
Erik Meijer (Utrecht University)
Ravi Nanavati (Bluespec, inc.)
Rishiyur Nikhil (MIT)
Henrik Nilsson (University of Nottingham)
Ross Paterson (City University, London)
John Peterson [編集者] (Yale University)
Simon Peyton Jones [編集者] (University of Glasgow; Microsoft Research Ltd)
Mike Reeve (Imperial College)
Alastair Reid (University of Glasgow)
Colin Runciman (University of York)
Don Stewart (Galois, inc.)
Martin Sulzmann (Informatik Consulting Systems AG)
Audrey Tang
Simon Thompson (University of Kent)
Philip Wadler [編集者] (University of Glasgow)
Malcolm Wallace (University of York)
Stephanie Weirich (University of Pennsylvania)
David Wise (Indiana University)
Jonathan Young (Yale University)

[編集者]と記された人々はこの言語の1つ以上の版での編集に協力してくれている。

加えて、他にも多くの人々が多かれ少なかれ有益な貢献をしてくれている。以下の人々である。 Hans Aberg, Kris Aerts, Sten Anderson, Richard Bird, Tom Blenko, Stephen Blott, Duke Briscoe, Paul Callaghan, Magnus Carlsson, Mark Carroll, Franklin Chen, Olaf Chitil, Chris Clack, Guy Cousineau, Tony Davie, Craig Dickson, Chris Dornan, Laura Dutton, Chris Fasel, Pat Fasel, Sigbjorn Finne, Michael Fryers, Peter Gammie, Andy Gill, Mike Gunter, Cordy Hall, Mark Hall, Thomas Hallgren, Matt Harden, Klemens Hemm, Fergus Henderson, Dean Herington, Bob Hiromoto, Nic Holt, Ian Holyer, Randy Hudson, Alexander Jacobson, Patrik Jansson, Robert Jeschofnik, Orjan Johansen, Simon B. Jones, Stef Joosten, Mike Joy, Wolfram Kahl, Stefan Kahrs, Antti-Juhani Kaijanaho, Jerzy Karczmarczuk, Kent Karlsson, Martin D. Kealey, Richard Kelsey, Siau-Cheng Khoo, Amir Kishon, Feliks Kluzniak, Jan Kort, Marcin Kowalczyk, Jose Labra, Jeff Lewis, Mark Lillibridge, Bjorn Lisper, Sandra Loosemore, Pablo Lopez, Olaf Lubeck, Christian Maeder, Ketil Malde, Michael Marte, Jim Mattson, John Meacham, Sergey Mechveliani, Gary Memovich, Randy Michelsen, Rick Mohr, Andy Moran, Graeme Moss, Arthur Norman, Nick North, Chris Okasaki, Bjarte M. Østvold, Paul Otto, Sven Panne, Dave Parrott, Larne Pekowsky, Rinus Plasmeijer, Ian Poole, Stephen Price, John Robson, Andreas Rossberg, George Russell, Patrick Sansom, Michael Schneider, Felix Schroeter, Julian Seward, Nimish Shah, Christian Sievers, Libor Skarvada, Jan Skibinski, Lauren Smith, Raman Sundaresh, Josef Svenningsson, Ken Takusagawa, Wolfgang Thaller, Satish Thatte, Tom Thomson, Tommy Thorn, Dylan Thurston, Mike Thyer, Mark Tullsen, David Tweed, Pradeep Varma, Keith Wansbrough, Tony Warnock, Michael Webber, Carl Witty, Stuart Wray, and Bonnie Yantis.

最後に、チャーチ、ロッサー、カリーによる重要で基礎的な仕事とほかの人々のラムダ計算に関する仕事はもちろんのこととして、長年にわたって開発されてきた注目に値する多くのプログラミング言語の影響を認めるのが正しい。様々なアイデアの源をピンポイントに挙げるのは難しいが、次の言語は特に影響を与えている: Lisp (とその現在での姿であるCommon LispとScheme)、ランディンのISWIM、APL、バッカスのFP[1]、MLとStandard ML、Hopeと Hope+、Clean、Id、Gofer、Sisal、そしてターナーのMiranda(Mirandaは有限会社リサーチソフトウェアのトレードマークである)が最後を飾った一連の言語。これらの先駆者たちなしにはHaskellは不可能であっただろう。

サイモン・マーロー
2010年4月 ケンブリッジ大学にて

序説

Haskellは汎用の純粋関数型言語であり、近年のプログラミング言語のデザインにおける多くの革新を取り入れている。Haskellは高階関数、非正格意味論、静的多相型付け、ユーザー定義代数的データ型、パターンマッチ、リスト内法表記、モジュールシステム、モナドを用いたI/Oシステム、リスト、配列、任意精度と固定精度整数、浮動小数点数などを含む豊富な基本データ型を備えている。Haskellは非正格関数型言語に対する長年の研究の極致であり結実である。

このレポートはHaskellプログラムの文法と、それらプログラムの意味づけを行うための形式的でない抽象意味論を定義するものである。Haskellプログラムがどのように操作され、実行され、コンパイルされるかなどについては実行依存のこととしておく。このことは、プログラミング環境の特質や、未定義のプログラムに対して返却されるエラーメッセージ(すなわち、形式的には⊥に評価されるプログラム)などに関する事項を含んでいる。

プログラムの構造

このセクションでは、Haskellの大まかな文法と意味論の構造及びそれらがレポートの残りの章の構造とどのように関係しているかについて説明する。

  1. Haskellプログラムの一番トップレベルは5章で説明される モジュール の集合である。モジュールは名前空間の管理と大規模なプログラムにおいてソフトウェアが再利用できる手段を提供するものである。
  2. モジュールのトップレベルは 宣言 の集まりであり、これらは複数の種類のものからなる。すべて4章で説明される。宣言は通常の値、データ型、型クラス、結合性情報などのものを定義するものである。
  3. 次に来るレベルは であり、3章で説明される。式は を表現し、 静的型 を持つ。式は"小規模な"Haskellプログラミングにおいて中心的な役割を果たすものである。
  4. 一番下のレベルにくるものはHaskellの 文法構造 であり、2章で説明される。文法構造はテキストファイルに書かれたHaskellプログラムの具体的な表現を捉えるものである。

このレポートはHaskellの文法的な構造を下から上に向かって進めていく。

上で触れられていない章は、Haskellの標準的な組み込みデータ型と組み込みクラスについて説明している6章と、HaskellにおけるI/O機能(すなわち、Haskellが外の世界とどのようにやりとりするか)について述べる7章である。また、Prelude、具体的な文法、文芸的プログラミング、インスタンス導出の仕様、多くのHaskellコンパイラによってサポートされるプラグマについても章を割いている。

本文ではHaskellプログラム断片の例をタイプライターフォント(訳注: このドキュメントではタイプライターフォントまたはコードブロックとして表示する)で表示する:

let x = 1
    z = x+y
in  z+1

任意の部分Haskellコードを表現しているプログラム断片における「穴」はイタリック体で表示され、例えば if e1 then e2 else e3 となる。一般的にはイタリック体になっている部分はニーモニック(訳注: 覚えやすいようにそれらしい名前を付けること)であり、例えば e は式、 d は宣言、 t は型といった具合である。

Haskellのカーネル

Haskellは関数型プログラミングで有名になった多くの便利な文法構造を採用している。このレポートではそういった構文糖衣の意味はより簡単な構成に変換されて与えられる。これら変換が漏れなく適用された場合、結果としてプログラムはHaskellの小さなサブセットによって書かれたことになるが、これを我々はHaskellの カーネル と呼ぶ。

カーネルは正確に規定されたものではないが、これは本質的には標準的な表示的意味論をもつラムダ計算を少し構文糖衣によって変えたものである。各文法構造からカーネルへの変換はその文法の導入と同様にして与えることができる。このように(訳注: カーネルがHaskellから切り離されているという)設計はHaskellプログラムに対する推論を容易にし、言語の実装者に対して便利なガイドラインを提供する。

値と型

式は に評価され、 静的型 を持つ。値と型はHaskellでは混合されていない。しかし、型システムは色々な種類のユーザー定義データ型を許容しており、パラメータ多相(伝統的なHindley-Milner型推論を用いている)だけでなく アドホック多相 あるいは(型クラスを用いた) オーバーロード と呼ばれるものも利用可能である。

Haskellでのエラーは意味論的には⊥ (「ボトム」)と同値である。技術的には、これは停止しないことと区別不可能であり、よって言語はエラーを検知したりそれに作用するような機構を持たない。しかし実装者はエラーに対して有用な情報を提供しようとするかもしれない。セクション3.1をみよ。

名前空間

Haskellには名前が6種類ある: 変数コンストラクタ の名前は値を表現し、型変数型コンストラクタ型クラス の名前は型システムに関連したエンティティを表し、モジュール名 はモジュールを表す。名前には2つの制約がある:

  1. 変数と型変数の名前は小文字またはアンダースコアから始まる識別子、他の4種類の名前は大文字から始まる識別子である
  2. 識別子は同じスコープ内で、型コンストラクタの名前かつクラスの名前として使われてはならない

制約はこれだけである。例えば、Intは1つのスコープで同時にモジュール、クラス、コンストラクタの名前として使われる可能性がある。

字句構造

この章では、Haskellの低レベルな字句構造を説明する。このレポートを初めて読む場合には、詳細はほとんど読み飛ばしてもよいだろう。

表記法

以下の表記法は構文を表すために使用される。

[pattern] 任意
{pattern} 0、またはそれ以上の繰り返し
(pattern) グルーピング
pat1 | pat2 選択
pat(pat') 相違 ー pat'によって生成されたものを除いた、patによって生成された要素
fibonacci タイプライターフォントの終端構文

このセクション内の構文は字句構造を説明しているため、全ての空白は明示的に表現されているが、並置されたシンボル間には暗黙的な空白はない。BNFのような構文は今後使われ、次の形式を取る。

nonterm alt1 | alt2 | … | altn

通常は文脈によって区別が明確になるが、 |[…]のような(タイプライターフォントで指定された)具体的な終端構文から|や[…]のようなメタデータ構文の区別には注意が必要である。

HaskellはUnicode[2]文字セットを使っている。しかしながら、プログラムソースは現在、以前のHaskellバージョンで使われていたASCII文字セットに偏っている。

この構文はUnicodeコンソーシアムによって定義されているUnicode文字の文字符号化スキームによって異なる。Haskellコンパイラーは新しいバージョンのUnicodeが利用可能になるにつれてそれらを利用されることが期待されている。

字句プログラム構造

program {lexeme | whietespace}
lexeme qvarid | qconid | qvarsym | qconsym | literal | special | reservedop | reservedid
literal integer | float | char | string
special ( | ) | , | ; | [ | ] | ` | { | }
whitespace whitestuff {whitestuff}
whitestuff whitechar | comment | ncomment
whitechar newline | vertab | space | tab | uniWhite
newline return linefeed | return | linefeed | formfeed
return キャレッジ⏎
linefeed 改行
vertab 垂直タブ
formfeed 改ページ
space 空白
tab 水平タブ
uniWhite 空白として定義されたUnicode文字
comment dashes [ anysymbol {any} ] newline
dashes -- {-}
opencom {-
closecom -}
ncomment opencom ANY seq {ncomment ANY seq} closecom
ANY seq {ANY}⟨{ANY} ( opencom | closecom ) {ANY}⟩
ANY graphic | whitechar
any graphic | space | tab
graphic small | large | symbol | digit | special | " | '
small ascSmall | uniSmall | _
ascSmall a | b | … | z
uniSmall 小文字Unicode
large ascLarge | uniLarge
ascLarge A | B | … | Z
uniLarge 任意の大文字またはタイトルケース(訳注: 先頭のみ大文字で後は小文字にするスタイル)のユニコード文字
symbol ascSymbol | uniSymbolspecial | _ | " | '
ascSymbol ! | # | $ | % | & | | + | . | / | < | = | > | ? | @ | \ | ^ | | | - | ~ | :
uniSymbol Unicodeのシンボル、または句読点
digit ascDigit | uniDigit
ascDigit 0 | 1 | … | 9
uniDigit 10進数Unicode
octit 0 | 1 | … | 7
hexit digit | A | … | F | a | … | f

字句解析は"maximal munch"規則に従うべきである。すなわち、語彙素生成規則を満たす可能な限り長くとった語彙素が読み取られる([訳注]:日本語訳、最長一致。"longest match" ともいう)。したがって、caseは予約語だが、casesは予約語ではない。同様に=は予約されているが、==~=は予約されていない。

空白はどんなものでも正しい字句の区切り文字である。

Any カテゴリではない文字はHaskellプログラム内では有効ではなく、字句解析エラーを結果にすべきである。

コメント

コメントは有効な空白である。

普通のコメントは二つ以上の連続したダッシュ(--など)で始まり、次の改行まで及ぶ。連続したダッシュは正当な語彙素の一部を形成してはいけない。例として、"-->"や"|--"はコメントの開始としては見なさない。なぜならこれらは正当な語彙素だからだ。しかしながら、"--foo"はコメントの開始として見なされる。

ネストされたコメントは"{-"で始まり、"-}"で終わる。"{-"は違法な語彙素ではない。それゆえ、例えば"{---"は末尾に余分なダッシュがあるがネストされたコメントの始まりである。

コメントそれ自体は語彙的に解析されない。代わりに、初めに文字列"-}"が現れた前の部分までがネストされたコメントの範囲である。ネストされたコメントは任意の深さにネストできる。ネストされたコメント内に文字列"{-"があると新しいネストされたコメントが始まり、"-}"によって閉じられる。ネストされたコメント内では、各"{-"は対応する"-}"の出現によって照合される。

普通のコメント内では"{-""-}"の文字の並びは特別な意味を持たず、一方でネストされたコメント内ではダッシュの並びは特別な意味を持たない。

ネストされたコメントはコンパイラープラグマのためにも使われる。それについては12章で説明される。

もし、いくつかのコードがネストされたコメントによってコメントアウトされていたら、その時、そのコード内の文字列内または行末コメントに"{-""-}"があるとネストされたコメントに干渉する。

識別子と演算子

varid (small {small | large | digit | ' })⟨reservedid⟩
conid large {small | large | digit | ' }
reservedid case | class | data | default | deriving | do | else
| foreign | if | import | in | infix | infixl
| infixr | instance | let | module | newtype | of
| then | type | where | _

識別子は0個以上の文字、数字、アンダースコア、およびシングルクォートで構成される。識別子は字句的に小文字で始まる字句(変数識別子)と大文字から始まる字句(コンストラクタ識別子)の二つの名前空間に区別される。(セクション1.4)これらの識別子は大文字と小文字を区別する。namenaMeNameは3つの判然たる識別子である。(初め2つは変数識別子で、最後のはコンストラクタ識別子である。)

アンダースコア("_")は小文字として扱われ、小文字が許されるところならどこでも使用可能だ。しかしながら、全て"_"なものはパターンのワイルドカードのように使われる識別子として予約されている。未使用の識別子に対して警告を出すコンパイラーはアンダースコアで始まる識別子に対しては警告を抑制することが推奨される。これはプログラマーが未使用であると予想されるパラメータに"_foo"を使うことを許可している。

varsym ( symbol⟨:⟩ {symbol} )reservedop | dashes
consym ( : {symbol})reservedop
reservedop> .. | : | :: | = | \ | | | <- | -> | @ | ~ | =>

演算子シンボルは上で定義したように、1つ以上の記号文字から形成され、2つの名前空間に字句的に区別される。(セクション1.4)

  • コロンから始まる演算子シンボルはコンストラクタである。
  • 他の文字から始まる演算子シンボルは普通の識別子である。

コロン(":")はHaskellリストのコンストラクタとして使用されるためだけに予約されている。これにより、"[]""[a,b]"のようなリスト構文の他の部分との扱いが統一される。

接頭辞否定の特殊な構文を除き、全ての演算子は中置である。ただし、各中置演算子をセクション内で使用して、部分的に適応される演算子を生成することができる。(セクション3.5を参照)標準の中置演算子はすべて定義済みのシンボルであり、リバウンドすることがある。

レポートの残りの部分では、6種類の名前が使用される。

varid (variables)
conid (constructors)
tyvar varid (type variables)
tycon conid (type constructors)
tycls conid (type classes)
modid {conid .} conid (modules)

変数と型変数は小文字で始まる識別子によって表され、そのほかは大文字で始まる識別子によって表される。また、変数とコンストラクタには中置形式があるが、他の4つにはない。モジュール名はドットで区切られた一連のconidである。名前空間についてはセクション1.4でも説明している。

特定の状況では、名前の前にモジュール識別子を付けることで、名前をオプションで修飾できる。これは変数、コンストラクタ、型コンストラクタ、型クラス、型クラス名に適応されるが、型変数やモジュール名には適応されない。修飾名については、チャプター5で詳しく説明する。

qvarid [modid .] varid
qconid [modid .] conid
qtycon [modid .] tycon
qtycls [modid .] tycls
qvarsym [modid .] varsym
qconsym [modid .] consym

修飾名は語彙素なので、修飾子と名前の間には空白を入れることはできない。サンプルの語彙解析を以下に示す。

これは このような語彙
f.g f . g (3トークン)
F.g F.g (修飾された'g')
f.. f .. (2トークン)
F.. F.. (修飾された'.')
F. F . (2トークン)

修飾子は名前の構文上の扱いを変更しない。例えば、Prelude.+はPrelude(セクション4.4.2)での+の定義と同じ固定性を持つ中置演算子である。

数値リテラル

decimal digit{digit}
octal octit{octit}
hexadecimal hexit{hexit}
integer decimal
| 0o octal | 0O octal
| 0x hexadecimal | 0X hexadecimal
float decimal . decimal [exponent]
| decimal exponent
exponent (e | E) [+ | -] decimal

数値リテラルには整数と浮動小数点の2種類ある。整数リテラルは10進数(デフォルト)、8進数(0oまたは0Oを先頭に付ける)、16進数表記(0x0Xを先頭に付ける)で指定できる。浮動小数点リテラルは常に10進数である。浮動小数点リテラルは小数点の前後に数値を含まなければいけない。これは小数点が他のドット文字の使い方に誤って認識されないことを保証するためだ。負数リテラルはセクション3.4で論じられる。数値リテラルの型はセクション6.4.1で論じられる。

文字と文字列リテラル

char ' (graphic⟨' | \⟩ | space | escape⟨\&⟩) '
string " {graphic⟨" | \⟩ | space | escape | gap} "
escape \ ( charesc | ascii | decimal | o octal | x hexadecimal )
charesc a | b | f | n | r | t | v | \ | " | ' | &
ascii ^cntrl | NUL | SOH | STX | ETX | EOT | ENQ | ACK
| BEL | BS | HT | LF | VT | FF | CR | SO | SI | DLE
| DC1 | DC2 | DC3 | DC4 | NAK | SYN | ETB | CAN
| EM | SUB | ESC | FS | GS | RS | US | SP | DEL
cntrl ascLarge | @ | [ | \ | ] | ^ | _
gap \ whitechar {whitechar} \

文字リテラルは'a'のようにシングルクォーテーションで囲まれたものであり、文字列リテラルは"Hello"のようにダブルクォーテーションで囲まれたものである。

エスケープコードは特殊文字の表現のために文字と文字列で使われる。注意すべきことはシングルクォーテーション(')は文字列においても使われるが、文字でエスケープする必要があることだ。同様に、ダブルクォーテーションは文字の中で使われるが、その際は文字列でエスケープする必要がある。\は常にエスケープしないといけない。charescカテゴリには"アラート"(\a)、"バックスペース"(\b)、"改ページ"(\f)、"改行"(\n)、"キャリッジ・リターン"(\r)、"水平タブ"(\t)、"垂直タブ"(\v)といった文字のポータブル表現も含まれている。

\^Xのような制御文字を含むUnicode文字セットのエスケープ文字も用意している。\137のような数値エスケープは10進数表現の137で文字を指定するために使用され、同様に8進数(例:\o137)や16進数(例:\x37)の表現も可能である。

“maximal munch”に従って、文字列内の数字のエスケープ文字は全て連続した数字で構成され、任意の長さにすることができる。同様に、"\SOH"のような奇妙なASCIIエスケープコードは長さ1の文字列としてパ-スされる。'\&'エスケープ文字は"\137\&9""SO\&H"のような文字列(共に長さは2)が構成できるように"ヌル文字"として提供される。その代わり"\&"""に等しく、'\&'文字は許されない。文字の等価性はセクション6.1.2で定義されている。

文字列は無視される"ギャップ"(白い文字を囲む2つのバックスラント)を含むかもしれない。これにより1行の終わりと次の行の始めにバックスラントを書くことによって、複数の行に長い文字列を書くことが可能だ。例としては以下のものになる。

"Here is a backslant \\ as well as \137, \  
    \a numeric escape character, and \^X, a control character."

文字列リテラルは実際には文字のリストの略記である。(セクション3.7を参照)

レイアウト

Haskellはレイアウトを使用して同じ情報を伝えることによって、いくつかの文法で使用されている中括弧とセミコロンの省略を許可している。これによりレイアウトに依存するもの、しないものの両方のコーディングスタイルが可能になり、1つのプログラム内で自由に混在させることができる。レイアウトが必要ではないため、Haskellプログラムは他のプログラムによって簡単に作成することができる。

レイアウトがHaskellプログラムの意味に与える影響は、レイアウトによって決定される場所に中括弧とセミコロンを追加することによって完全に指定できる。この拡張プログラムの意味はレイアウトに影響されなくなった。

非公式には中括弧とセミコロンは次のように挿入される。キーワードのwhereletdo、またはofの後に開き括弧が省略されると、レイアウト(または"オフサイド")規則が有効になる。これが起こると、次の語彙素のインデントが(新しい行にあるかどうかにかかわらず)記録され、省略された開き括弧が挿入される(語彙素の前の空白にはコメントが含まれる場合がある)。後続の行について、空白のみが含まれている場合、またはそれ以上のインデントされている場合は、前の項目が続行される。(何も挿入されない)同じ量だけインデントされている場合は新しい項目が始まる(セミコロンが挿入される)。またインデントが小さくなると、レイアウトリストは終了する(閉じ括弧が挿入される)。whereletdoまたはofの直後のノンブレース語彙素のインデントが現在のインデントレベル以下である場合は、レイアウトを開始する代わりに、空のリスト"{}"が挿入され、現在のレベルでレイアウト処理が発生する(つまり、セミコロンまたは閉じ括弧を挿入する)。レイアウトリストを含む構文カテゴリが終了するたびに、閉じ括弧も挿入される。つまり、閉じ括弧が合法となる点で違法な語彙素が検出された場合は閉じ括弧が挿入される。レイアウトルールはそれが挿入した開いている中括弧にだけ一致する。明示的な開き括弧は、明示的に閉じ括弧と一致しなければならない。これらの明示的な開き括弧内では、たとえ行が以前の暗黙の開き括弧の左側に字下げされていても、括弧の外側の構成要素に対してレイアウト処理は実行されない。

セクション10.3ではレイアウトルールのより正確な定義を示す。

これらの規則を考えると、1つの改行で実際に複数のレイアウトリストを終了させることができる。 これらの規則は以下のコードを許す。

f x = let a = 1; b = 2  
          g y = exp2  
       in exp1

生成したa, b, gは全て同じレイアウトリストの一部である。

例として、図2.1は(ややわざとらしい)モジュールを示し、図2.2はそのレイアウトルールを適応した結果を示している。次の部分に注意: (a) }};popで行が開始している個所において、前の行が終了すると、ネストしたwhere区の深さ(3)に対応する3つレイアウトルールの利用が呼び出される。(b)where句の閉じ括弧はタプルとcase式にネストされており、タプルの終了を検出されたため挿入された。(c)一番最後の閉じ括弧は、Eofトークンの0列のインデントにより挿入された。

module AStack( Stack, push, pop, top, size ) where  
data Stack a = Empty  
             | MkStack a (Stack a)  

push :: a -> Stack a -> Stack a  
push x s = MkStack x s  

size :: Stack a -> Int  
size s = length (stkToLst s)  where  
           stkToLst  Empty         = []  
           stkToLst (MkStack x s)  = x:xs where xs = stkToLst s  

pop :: Stack a -> (a, Stack a)  
pop (MkStack x s)  
  = (x, case s of r -> i r where i x = x) -- (pop Empty) is an error  

top :: Stack a -> a  
top (MkStack x s) = x                     -- (top Empty) is an error

図2.1: サンプルプログラム

module AStack( Stack, push, pop, top, size ) where  
{data Stack a = Empty  
             | MkStack a (Stack a)  

;push :: a -> Stack a -> Stack a  
;push x s = MkStack x s  

;size :: Stack a -> Int  
;size s = length (stkToLst s)  where  
           {stkToLst  Empty         = []  
           ;stkToLst (MkStack x s)  = x:xs where {xs = stkToLst s  

}};pop :: Stack a -> (a, Stack a)  
;pop (MkStack x s)  
  = (x, case s of {r -> i r where {i x = x}}) -- (pop Empty) is an error  

;top :: Stack a -> a  
;top (MkStack x s) = x                        -- (top Empty) is an error  
}

図2.2: レイアウトを展開したサンプルプログラム

この章では、私たちはHaskellの式の構文と非形式的な意味論を説明する。また必要であらばHaskellカーネルへの変換についても説明する。let式の場合を除いて、これらの変換は静的、動的な意味論の両方を保存する。これらの変換を使った束縛されていない変数とコンストラクタは常にPreludeによって定義された実体を参照する。例えば、リスト内包表記の変換(セクション[3.11])で使われる"concatMap"Preludeによって定義されたconcatMapを意味する。これは識別子"concatMap"がリスト内包表記で使われているスコープ内にあるかないかは関係なく、また、(もしスコープ内にあったとしても)束縛されていても関係はない。

expinfixexp :: [context =>] type(expression type signature)
|infixexp(expression type signature)
infixexplexp qop infixexp(infix operator application)
|- infixexp(prefix negation)
|lexp
lexp\ apat1apatn -> 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)
aexpqvar(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 = la = 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の式の変換は実行時エラーが発生したことを明示的に表示するためerrorundefinedを使用する。エラーが発生した際の実際のプログラムの振舞は実装次第である。そのメッセージはこれらの変換のみ提案するためerror関数へ渡される。エラー発生時、詳しい情報または乏しい情報を表示することを実装側は選択するかもしれない。

変数、コンストラクタ、演算子、リテラル

aexpqvar(variable)
|gcon(general constructor)
|literal
gcon()
|[]
|(,{,})
|qcon
varvarid | ( varsym )(variable)
qvarqvarid | ( qvarsym )(qualified variable)
conconid | ( consym )(constructor)
qconqconid | ( gconsym )(qualified constructor)
varopvarsym | ` varid `(variable operator)
qvaropqvarsym | ` qvarid `(qualified variable operator)
conopconsym | ` conid `(constructor operator)
qconopgconsym | ` qconid `(qualified constructor operator)
opvarop | conop(operator)
qopqvarop | qconop(qualified operator)
gconsym: | qconsym

Haskellは中置記法に対応するため特別な構文を提供している。 演算子 は中置構文を用いて適用が可能である(セクション3.4)か、 セクション (セクション3.5)を用いて部分的に適用が可能な関数のことである。

演算子 は、+$$といった 演算子シンボル か、` op `のようにグレイブ・アクセント(バッククォート)で囲まれた通常の識別子かのいずれかである。例えば、op x yという前置適用を書く代わりに、x `op` yという中置適用を書くことができる。もし、` op `に対して結合性が宣言されていない場合には、優先順位は最高で左結合をデフォルトとする。(セクション4.4.2参照)。

対照的に、演算子シンボルは括弧で閉じられた普通の識別子へ変換可能である。例として、(+) x yx + yに等しく、foldr (⋆) 1 xsfoldr (\x y -> x⋆y) 1 xsに等しくなる。

一部の組み込み型のコンストラクタの名前をつけるのに特別な構文がつかわれているものがあり、実際にgconliteralで見ることができる。これらについてはセクション6.1で説明される。

整数リテラルはfromInteger関数をInteger型の適した値への適用を表す。同様に、浮動小数点リテラルはRational型(つまり、Ratio Integer)の値にfromRationalを適用することを表す。

変換: 整数リテラルifromInteger iに等しく、fromIntegerNumクラスのメソッドである。(セクション6.4.1)

浮動小数点リテラルffromRational (n Ratio.% d)に等しく、fromRationalFractionalクラスのメソッドで、Ratio.%Ratioライブラリで定義されており、2つの整数から有理数を構築する。整数ndn/d = fを満たすものとして選ばれる。

カリー化された適用とラムダ抽象

fexp[fexp] aexp(function application)
lexp\ apat1apatn -> exp(lambda abstraction, n ≥ 1)

関数適用はe1 e2と書く。適用は左結合性をもつので、(f x) yの括弧は省略することができる。e1はデータ構成子である可能性もあるため、データ構成子の部分的な適用は許されている。

ラムダ抽象\ p1pn -> eと書き、piはパターンである。\x:xs->xのような式は構文的に正しくない。(x:xs)->xと書くのが正しい。

パターンの集合は 線形 でなければならない。つまり、変数は集合の中で2回以上出現してはいけない。

変換: 以下の等式が成り立つ。

p1pn -> e = \ X1 … Xn -> case (X1, …, Xn) of (p1, …, pn) -> e

Xiは新しい識別子である。

この変換がセクション3.17.3で説明するcase式とパターンマッチの意味論と組み合わさって与えられたとき、もしもパターンマッチに失敗すれば結果はとなる。

演算子適用

infixexplexp qop infixexp
|- infixexp(prefix negation)
|lexp
qopqvarop | qconop(qualified operator)

e1 qop e2という形式は二項演算子qopの式e1e2への中置適用である。

特殊な形式-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は式であり、xeの中で自由出現ではない変数である。

条件文

lexpif exp [;] then exp [;] else exp

条件式はif e1 then e2 else e3の形式をとり、もしe1Trueなら、e2を返し、e1Falseならe3を返し、それ以外ならを返す。

変換: 以下の等式が成り立つ。

if e1 then e2 else e3(op e) = case e1 of { True -> e2 ; False -> e3 }

TrueFalseはPrelude内で定義されているBool型の2つの引数のないコンストラクタである。e1Bool型でなければならず、e2e3も同じ型でなければならない。条件式全体の型も同様である。

リスト

infixexpexp1 qop exp2
aexp[ exp1 , … , expk ](k ≥ 1)
|gcon
gcon[]
|qcon
qcon( gconsym )
qopqconop
qconopgconsym
gconsym:

Listk ≥ 1として、[e1, …, ek]のように書く。リストコンストラクタは :であり、空リストは[]で表記される。リストの標準操作はPrelude内で与えられる(セクション6.1.39章の特にセクション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.49章)。

変換: k ≥ 2のときの(e1, …, ek)はPrelude内で定義されたk-tupleのインスタンスになり、変換は要求されない。もし、t1からtkはそれぞれe1からekの型があり、最終的なタプルの型は(t1,…,tk)になる(セクション4.1.2)。

単位式と括弧付き式

aexpgcon
|( exp )
gcon()

(e)の形式はシンプルに 括弧付き式 であり、eと等しい。ユニット(unit)()()型を持つ(セクション4.1.2を参照)。それは以外の型のメンバのみで、"引数のないタプル"のように考えられる(セクション6.1.5を参照)。

変換: (e)eと等しい。

数列

aexp[ exp1 [, exp2] .. [exp3] ]

数列 [e1,e2 .. e3]は型tの値のリストを表し、各eiは型tを持ち、tEnumクラスのインスタンスである。

変換: 数列はこれらの等式を満たす。

[ e1.. ] = enumFrom e1
[ e1,e2.. ] = enumFromThen e1 e2
[ e1..e3 ] = enumFromTo e1 e3
[ e1,e2..e3 ] = enumFromThenTo e1 e2 e3

enumFormenumFormThenenumFormToenumFormThenToはPrelude内で定義されているEnumクラスのクラスメソッドになる。

故に数列の意味論は型tのインスタンス宣言に完全に依存している。どのPrelude型がEnum型にあるか、そしてそれらの意味論についてのより詳しいことについてはセクション6.3.4を参照すること。

リスト内包表記

aexp[ exp | qual1 , … , qualn ](list comprehension, n ≥ 1)
qualpat <- 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式

lexplet decls in exp

let 式は一般的な形式let { d1 ; … ; dn } in eを持ち、ネストされたレキシカルスコープをもつ相互再帰的な宣言のリストを導入する(letは他の言語でletrcとしばしば呼ばれる)。宣言の範囲は式eと宣言の右側である。宣言は4章で説明される。パターン束縛のマッチは遅延され、暗黙的な~がこれらのパターンを反駁不可にする。 例えば、

let (x,y) = undefined in e

xまたはyが評価されるまでランタイムエラーをもたらさない。

変換:let { d1 ; … ; dn} in e0の動的な意味論は次の変換によって捕捉される。全ての型シグネチャを取り除いた後、それぞれの宣言dipi = eiの形の等式へと変換される。pieiはセクション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式

lexpcase exp of { alts }
altsalt1 ; … ; altn(n ≥ 1)
altpat -> exp [where decls]
| pat gdpat [where decls]
| (empty alternative)
gdpatguards -> exp [ gdpat ]
guards | guard1, …, guardn(n ≥ 1)
guardpat <- 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式

lexpdo { stmts }(do expression)
stmtsstmt1stmtn exp [;](n ≥ 0)
stmtexp ;
|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は後者では矛盾する型付けが与えられている。

フィールドセレクション

aexpqvar

フィールドラベルはセレクタ関数のように使用される。変数のように使われる際は、フィールドラベルはオブジェクトからフィールドを抽出する関数のように振る舞う。セレクタはトップレベルの束縛であり、よってローカル変数によってシャドーイングされる場合があるが、しかし他のトップレベルの束縛で同じ名前のものと衝突してはならない。この覆いはセレクタ関数にのみ影響を及ぼし、レコード作成(セクション3.15.2)及びに更新(セクション3.15.3)、フィールドラベルは通常の変数と混合されることはない。

変換: フィールドラベルfは次のようなセレクタ関数を生成する。

f x=case x of { C1 p11p1k -> e1 ;… ; Cn pn1pnk -> en }

C1 ... Cnは全てfとラベルされたフィールドを含むデータ型のコンストラクタで、pijfCiの要素のj番目、または_をラベルした時のyであり、eiCiのフィールドがfまたはundefinedのラベルを持つ時のyである。

フィールドラベルを用いた生成

aexpqcon { fbind1 , … , fbindn }(labeled construction, n ≥ 0)
fbindqvar = exp

ラベル付けされたフィールドを使うコンストラクタが値の生成に使われる場合があるが、その時には各コンポーネントは位置ではなく名前によって指定する。宣言リストの中で使われる中括弧とは異なりレイアウトの対象にならない。{}の文字は明示しなければならない。(これはフィールドの更新、フィールドパターンにおいても正しい。)フィールドラベルを使用する構築は次の制約に応じる。

  • 指定されたコンストラクタで宣言されたフィールドラベルのみ言及してよい。
  • フィールドラベルは複数回言及してはならない。
  • 言及されないフィールドはで初期化される。
  • 正格なフィールド(宣言された型のフィールドの接頭語に!が付けられている)が生成の際に省略された時はコンパイルエラーが発生する。厳格なフィールドはセクション4.2.1で説明される。

F {}は、Fはデータコンストラクタであり、Fがレコード構文により宣言されたかどうかに関わらず、正当である(ただしFが正格フィールドを持たない時に限る。上の4番目の箇条書きを参照)。それはF ⊥1 … ⊥nを表し、nFの引数の数である。

変換: f = vの束縛で、フィールドfvでラベルする。

C { bs }=C (pick1C bs undefined) … (pickkC bs undefined)

kCの引数の数である。

補助関数pickiC bs dは次にように定義される。

もし、コンストラクタCi番目の要素がフィールドラベルfを持ち、if f=vは束縛されたbsに表示されるなら、その時はpickiC bs dvである。言い換えるとpickiC bs dはデフォルト値dである。

フィールドラベルを使用した更新

aexpaexp⟨qcon⟩ { fbind1 , … , fbindn }(labeled update, n ≥ 1)

フィールドラベルを使ったデータ型に所属する値は非破壊的に更新されるかもしれない。これは元々存在していた値を指定されたフィールドの値で書き換えた新しい値を生成する。更新は次の方法に制限される。

  • 全てのラベルは同じデータ型から取られなければいけない。
  • 少なくともあるコンストラクタは更新の中で全ての言及されたラベルを定義しなければいけない。
  • 2回以上言及されるラベルがあってはならない。
  • 実行エラーは更新された値が全ての明記されたラベルを含まない時に発生する。

変換: 以下は以前のpickの定義を使用する。

e { bs }=case e of
    C1 v1vk1 -> C1 (pick1C1 bs v1) … (pickk 1C1 bs v k1)
      ...
    Cj v1vkj -> Cj (pick1Cj bs v1) … (pickk jCj bs v kj)
    _ -> error "Update error"

{ C1,...,Cj}bs内の全てのラベルを含むコンストラクタの集合で、iCiの引数の数である。

これはラベル付けされたフィールドを使用している例である。

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'}のように、どのコンストラクタも、更新で使われたフィールドラベルの集合を定義していないのであれば、コンパイル時エラーという結果になるだろう。

式の型シグネチャ

expexp :: [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式のときのみ定めれば十分である。

パターン

パターンはこの構文を持つ。

patlpat qconop pat(infix constructor)
|lpat
lpatapat
|- (integer | float)(negative literal)
|gcon apat1apatk(arity gcon = k, k ≥ 1)
apatvar [ @ 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)
fpatqvar = pat

コンストラクタの引数の数はそれに関係するサブパターンの数と一致しなければいけない。部分的に適用されるコンストラクタに反して一致することはできない。

全てのパターンは 線形 でなければならない。変数は2回以上現れないかもしれない。例として、この定義は不正である。

f (x,x) = x     -- ILLEGAL; x used twice in pattern

形式var@patのパターンはas-patternsと呼ばれ、varpatによってマッチされた値に付ける名前として使うことができる。例えば以下のものは、

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つのいずれかの結果を得る。 失敗 かもしれない、 成功 かもしれず、その時はパターン内の各変数に束縛を返す、 分岐する かもしれない(例:を返す)。パターンマッチングは次のルールによって外から内へ、左から右へ進行する。

  1. vに対してマッチするパターンvarのマッチングは常に成功し、varvに束縛する。

  2. vに対してマッチするパターン~apatのマッチングは常に成功する。もしvに対してマッチするapatのマッチングが別の方法で成功するならば、apat内の束縛されていない変数は適切な値に束縛される。vに対してマッチするapatのマッチングが失敗または分岐するならに束縛される(束縛は評価を ほのめかさない )。

    運用上、これはあるapat内の変数が使われるまで、パターン~apatが何とも一致しないことを意味する。その時点でパターン全体はその値に対してマッチし、もし一致が失敗または分岐するなら、全体の計算を行う。

  3. あらゆる値に対してマッチするワイルドパターン_のマッチングは常に成功し、束縛は行われない。

  4. 値に対してマッチするパターンcon patのマッチングは、connewtypeによって定義されたコンストラクタである、以下の項目でその値に依存する。

    • もし値が形式con vであるなら、その時patvに対してマッチされる。
    • もし値がなら、その時patに対してマッチする。

    すなわちnewtypeと関連するコンストラクタが値の型を変更することのみに務める。

  5. 値に対してのcon pat1 ... patnのマッチングは、condataによって定義されるコンストラクタである、依存するその値に依存する。

    • もし値が形式con pat1 ... patnであるなら、サブパターンはそのデータ値の要素に対して左から右へ一致される。もし、全てのマッチングが成功したなら、マッチング全体は成功し、はじめの失敗または分岐はマッチング全体を各々、失敗または分岐へともたらす。
    • もし値が形式con' v1 ... vmであるなら、concon'への異なるコンストラクタである、そのマッチングは失敗する。
    • もし値がなら、そのマッチングは分岐する。
  6. ラベル付きフィールドを使ったコンストラクタに対してのマッチングはそのフィールドがフィールドリスト内で指定された順序で照合されることを除いて、通常のコンストラクタパターンのマッチングと同じである。全てのリストされたフィールドはコンストラクタによって宣言されなければならず、フィールドは2回以上指定されないかもしれない。パターンによって指定されたフィールドは無視される(_に対して一致する)。

  7. v対する数値、文字、文字列リテラルパターンkのマッチングはもし、v == kなら成功する。==はパターンの型を元にオーバロードされる。マッチングはもしこのテストが分岐するなら分岐する。

    数値リテラルの解釈はまさにセクション3.2で記載のとおりである。即ち、オーバロードされた関数fromIntegerまたはfromRationalは(それぞれ)適切な型へ変換することによってIntegerまたはRationalリテラルに適用される。

静的型の制約(例えば、文字とbooleanを一致させる静的なエラー)は別として、次の静的クラスの制約は保持する。

  • 整数リテラルパターンはクラスNumの値とのみ照合できる。
  • 浮動小数点リテラルパターンはクラスFactionalの値とのみ照合できる。

2種類のパターンの区別することはしばしば有用である。 反駁できない パターンの照合は厳密ではなく、そのパターンはもし、照合された値がなら一致する。 反駁できる パターンは厳密であり、その一致される値がなら分岐する。反駁できないパターンは次のものである。変数やワイルドカード、Nnewtypeapatによって定義されたコンストラクタN apatは反駁できず(セクション4.2.3)、var@apatapatは反駁できない、または形式~apat(apatが反駁できないかどうか)である。他の全てのパターンは反駁できる。

ここにいくつかの例をだす。

  1. もし、パターン['a','b']['x',⊥]と一致されるなら、その時、'a'xとの一致に 失敗し 、その結果は失敗と一致する。しかし、もし['a','b'][⊥,'x']と一致されるなら、その時、'a'を一致するよう試みることは 分岐 と一致することをもたらす。

  2. これらの例は反駁できるものとできないもののマッチングの実演である。

    (\ ~(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) ⊥⊥:⊥:⊥
  3. 次の宣言を考えてほしい。

    newtype N = N Bool  
    data    D = D !Bool
    

    これらの例はdatanewtypeによって定義された型においてのパターンマッチングの違いを説明する。

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

ayの両方はガードの==によって評価される。

パターンマッチングの正式な意味論

case式を除くすべてのパターンマッチの意味論は、パターンマッチの構成とcase式との間を関連付ける等式を与えることで定められる( 訳注 : パターンマッチの意味論は一旦case式を使って定義し、そのあとcase式の意味論に従って処理を行う)。case式の意味論自体は図3.13.3の、一連の識別子のように順番に与えられる。どんな実装でもこれらの識別子を保持するために振る舞わなければならず、かなり非効率的なコードを生成することから、それはそれらを直接使用することは期待されない。

(a)
case e of { alts } = (\v -> case v of { alts }) e
where v is a new variable
(b)
case  v of {  p 1  match1;  … ; pn  matchn }
=  case v of { p1  match1 ;
               _  -> … case v of {
                         pn  matchn ;
                         _  -> error "No match" }…}
where each matchi has the form:
 | gsi,1  -> ei,1 ; … ; | gsi,mi -> ei,mi where { declsi } 
(c)
case v of { p | gs1 -> e1 ; …
             | gsn -> en where { decls }
            _     -> e′ }
= case e′ of { y ->
   case v of {
     p -> let { decls } in
          case () of {
            () | gs1 -> e1;
            _ -> … case () of {
                       () | gsn -> en;
                       _  -> y } … }
     _ -> y }}
where y is a new variable
(d)
case v of { ~p -> e; _ -> e′ }
= (\x1 … xn -> e ) (case v of { p-> x1 })… (case v of { p -> xn})
where x1,…,xn are all the variables in p
(e)
case v of { x@p -> e; _ -> e′ }
=  case v of { p -> ( \ x -> e ) v ; _ -> e′ }
(f)
case v of { _ -> e; _ -> e′ } = e 

図 3.1: case式の意味論、パート1

(g)
case v of { K p1…pn -> e; _ -> e′ }
    = case v of {
         K x1…xn -> case x1 of {
                        p1 -> … case xn of { pn -> e ; _ -> e′ } …
                        _  -> e′ }
         _ -> e′ }
    at least one of p1,…,pn is not a variable; x1,…,xn are new variables
(h)
case v of { k -> e; _ -> e′ } = if (v==k) then e else e′
    where k is a numeric, character, or string literal
(i)
case v of { x -> e; _ -> e′ } = case v of { x -> e }
(j)
case v of { x -> e } = ( \ x -> e ) v 
(k)
case N v of { N p -> e; _ -> e′ }
    = case v of { p -> e; _ -> e′ }
    where N is a newtype constructor 
(l)
case ⊥ of { N p -> e; _ -> e′ } = case ⊥ of { p -> e }
    where N is a newtype constructor 
(m)
case  v  of {  K  { f1  =  p1  ,  f2  =  p2  , … } ->  e ; _ ->  e′ }
    =  case e′ of {
        y ->
        case  v  of {
          K  {  f1  =  p1  } ->
                case  v  of { K  { f2  =  p2  , …  } ->  e ; _ ->  y  };
                _ ->  y  }}
    where f1, f2, … are fields of constructor K; y is a new variable 
(n)
case  v  of {  K  { f  =  p } ->  e ; _ ->  e′ }
  = case  v  of {
       K p1pn  ->  e ; _ ->  e′ }
  where pi is p if f labels the ith component of K, _ otherwise 
(o)
case  v  of {  K  {} ->  e ; _ ->  e′ }
  = case  v  of {
       K _ … _ ->  e ; _ ->  e′ }
(p)
case (K′ e1em) of { K x1 … xn -> e; _ -> e′ } = e′
  where K and K′ are distinct data constructors of arity n and m, respectively
(q)
case (K e1en) of { K x1 … xn -> e; _ -> e′ }
  = (\x1 … xn -> e) e1en
  where K is a data constructor of arity n 
(r)
case ⊥ of { K x1 … xn -> e; _ -> e′ } =  ⊥
  where K is a data constructor of arity n

図 3.2: case式の意味論、パート2

(s)
case () of { () | g1, …, gn -> e; _ -> e′ }
= case () of {
     () | g1 -> … case () of {
                    () | gn -> e;
                    _ -> e′ } …
     _ -> e′ }
 where y is a new variable 
(t)
case() of { () | p <- e0 -> e; _ -> e′ }
= case e0 of { p -> e; _ -> e′ }
(u)
case () of { () | let decls -> e; _ -> e′ }
= let decls in e 
(v)
case () of { () | e0 -> e; _ -> e′ }
  = if e0 then e else e′ 

図 3.3: case式の意味論、パート3

3.1-3.3e, e'eiは式で、gigsiはガードと各々のガードの並びであり、ppiはパターン、v, x, xiは変数、K,K'は代数的データ型(data)コンストラクタ(タプルコンストラクタを含む)で、Nnewtypeコンストラクタである。

ルール(b)は実際にガードを含むかどうかにはかかわらず、一般的な表層ソース言語のcase式に適合するものである。もしガードが書かれていなければ、その時、Trueが形式matchi内のガードgsi,jに代用される。各々の識別子はもっと簡単な形式へとcase式の結果を操作する。

3.2のルール(h)はオーバロードされた==演算子を起動し、パターンマッチングの意味をオーバーロードされた定数に対して定義するというルールである。

これらの識別子は静的な意味論を全て保存する。ルール(d)、(e)、(j)、(q)はletではなくラムダを使っていて、これはcaseによって束縛された変数が単相型ということを示す(セクション4.1.4を参照)。

宣言と束縛

この章では、Haskellの 宣言 の構文と簡略した意味論を説明する。

modulemodule modid [exports] where body
|body
body{ impdecls ; topdecls }
|{ impdecls }
|{ topdecls }
topdeclstopdecl1 ; … ; topdecln(n ≥ 1)
topdecltype simpletype = type
|data [context =>] simpletype [= constrs] [deriving]
|newtype [context =>] simpletype = newconstr [deriving]
|class [scontext =>] tycls tyvar [where cdecls]
|instance [scontext =>] qtycls inst [where idecls]
|default (type1 , … , typen)(n ≥ 0)
|foreign fdecl
|decl
decls{ decl1 ; … ; decln }(n ≥ 0)
declgendecl
|(funlhs | pat) rhs
cdecls{ cdecl1 ; … ; cdecln }(n ≥ 0)
cdeclgendecl
|(funlhs | var) rhs
idecls{ idecl1 ; … ; idecln }(n ≥ 0)
idecl(funlhs | var) rhs
| (empty)
gendeclvars :: [context =>] type(type signature)
|fixity [integer] ops(fixity declaration)
| (empty declaration)
opsop1 , … , opn(n ≥ 1)
varsvar1 , … , varn(n ≥ 1)
fixityinfixl | infixr | infix

構文的カテゴリtopdeclsに属する宣言はHaskellモジュール(5章)の最上位のみ許す一方で declsは最上位またはネストされたスコープのいずれかで使ってもよい(例えば、letwhereの内でtopdeclsを構築する)。

説明のため、typenewtypedata宣言からなるユーザー定義のデータ型(セクション4.2)とclassinstancedefault宣言からなる型クラスとオーバーロード(セクション4.3)、値束縛と型シグネチャ、固定の宣言からなるネストされた宣言(セクション4.4)の3つのグループに宣言を分割する。

haskellは(整数や浮動小数点数のような)"ハードウェアで実現された"であるいくつかのプリミティブなデータ型を持つ。しかし、多くの"ビルドイン"なデータ型は通常のHaskellコードによって定義されていて、通常typedata宣言に使われる。これらの"ビルドイン"のデータ型はセクション6.1で詳細に説明される。

型とクラスの概要

Haskellは静的型意味論4,6を提供するために伝統的なHindley-Milner多相型システムを使用するが、その型システムは構造化された手法にオーバーロード関数を導入するために提供する 型クラス (または クラス )で拡張されている。

class宣言(セクション4.3.1)は新しい 型クラス とあらゆるそのクラスのインスタンスの型によってもサポートされなければいけないオーバーロードされた操作を導入する。instance宣言(セクション4.3.2)は型がクラスの インスタンス であり、名付けられた型でインスタンス化されるオーバーロードされたオペレーション、クラスメソッドと呼ばれる、の定義を含むということを宣言する。

例えば、型IntFloatで操作(+)negateをオーバーロードしたいと考えたとしよう。Numと呼ばれる新しい型クラスを導入する。

class Num a  where          -- Numの単純化されたクラス宣言
  (+)    :: a -> a -> a     -- (NumはPreludeで定義されている)  
  negate :: a -> a

この宣言は「型aがもし与えられた型で定義されたクラスメソッド(+)negateがあるならクラスNumのインスタンスである」と読めるであろう。

このクラスのインスタンス化のときにIntFloatをその際に宣言できる。

instance Num Int  where     -- Num Intの単純化されたインスタンス
   x + y       =  addInt x y  
   negate x    =  negateInt x  

 instance Num Float  where   -- Num Floatの単純化されたインスタンス
   x + y       =  addFloat x y  
   negate x    =  negateFloat x

addIntnegateIntaddFloatnegateFloatはこのケースでプリミティブ関数で想定されるが、一般的にはユーザー定義関数になり得る。 上のはじめの宣言は「IntはクラスNumのインスタンスであり、その証拠として(クラスメソッド)(+)negateが定義されている」と読まれる。

型クラスのより多くの例はJones8かWadlerとBlott13による論文で見つけられる。用語'型クラス'はオリジナルのHaskell1.0型システムを記述するために使われてあって、'コンストラクタクラス'はオリジナルの型クラスへ拡張を記述することに使われていた。ふたつの異なる用語を使う理由はもはやなく、この規格書において、'型クラス'という単語は元々のHaskell型クラスとJonesによって導入されたコンストラクタクラスの両方を含んでいる。

カインド

型の表現が有効である確証を得るために、型の表現を異なるカインド(カインド, kind)へと分類され、以下の2つの可能な形式の内、1つを取る。

  • シンボル*は全ての引数のない型コンストラクタのカインドを意味する。
  • もしK1K2がカインドならば、K1 → K2はカインドK1の型を取り、K2の型を返す型のカインドである。

型推論が値の表現の正当性をチェックするのと同様にして、カインド推論は型の表現の正当性をチェックする。しかしながら、型とは違い、カインドは完全に暗黙的であり、言語の見て分かる部分には存在しない。カインドの推論はセクション4.6で議論される。

型の構文

typebtype [-> type](function type)
btype[btype] atype(type application)
atypegtycon
|tyvar
|( type1 , … , typek )(tuple type, k ≥ 2)
|[ type ](list type)
|( type )(parenthesised constructor)
gtyconqtycon
|()(unit type)
|[](list constructor)
|(->)(function constructor)
|(,{,})(tupling constructors)

Haskellの型の表現のための構文は上に与えられる。データ値と同じようにデータコンストラクタを使って作られ、型の値(訳注: 型の表現でそれ以上簡約出来ないもののこと)は 型コンストラクタ から作られる。データコンストラクタと同様に、型コンストラクタの名前は大文字で始められる。データコンストラクタとは違い、中置型コンストラクタは許されない((->)以外)。

型の表現の主要な形式は次のものになる。

  1. 小文字で始まる識別子のように書かれた型変数。変数のカインドは現れた文脈によって暗黙的に決定される。

  2. 型コンストラクタ。多くの型コンストラクタは大文字から始まる識別子のように書かれている。 例えば、

    • CharInt,Float,DoubleBoolはカインド*で構築される型である。
    • MaybeIOは単項型コンストラクタで、カインド*→*をもつ型として扱われる。
    • 宣言data T ...またはnewtype T ...は型のボキャブラリーに型コンストラクタTを追加する。Tのカインドはカインドの推論によって決定される。

    特殊な構文は特定のビルドインの型コンストラクタに提供される。

    • 自明型()のように書かれ、カインド*を持つ。それは"引数のないタプル"型を示し、()と書かれるが、値をちゃんと持つ(セクション3.96.1.5を参照)。
    • 関数型(->)のように書かれ、カインド∗→∗→∗を持つ。
    • リスト型[]のように書かれ、カインド∗→∗を持つ。
    • タプル型(,), (,,)等のように書かれる。それらのカインドは∗→∗→∗, ∗→∗→∗→ ∗などである。

    (->)[]の定数の使用は下でより詳しく説明される。

  3. 型適用。もし、t1がカインドK1 → K2の型でt2がカインドK1の型であるなら、その時t1, t2はカインドK2の型の表現である。

  4. 括弧つき型 、形式(t)を持つ、型tと同一である。

例えば、型の表現IO aは変数aに定数IOへの適用のように理解されることが可能だ。IO型コンストラクタはカインド∗→∗を持ち、変数aと式全体の両方を従え、式IO aはカインド*を持たなければならない。一般的に 型の推論 (セクション4.6)の処理は適切なカインドをユーザー定義のデータ型や型のシノニム、クラスへ決定することを必要とされる。

特別な構文は特定の型の表現がより伝統的なスタイルで書かれることを許すために提供される。

  1. 関数型 は形式t1 -> t2を持ち、型(->) t1 t2に等しい。アロー関数は左に関連づける。例えば、Int -> Int -> FloatInt -> (Int -> Float)を意味する。
  2. タプル型K ≥ 2である形式t1, ..., tkを持ち、括弧の間にk-1個のカンマがある型(,…,) t1 … tkと等しい。それは型t1をはじめの要素に、型t2を2番目の要素に持つなど、k要素のタプルの型を示す(セクション3.86.1.4を参照)。
  3. リスト型 は形式[t]を持ち、型[] tと等しい。それは型tの要素を伴うリストの型を示す(セクション3.76.1.3を参照)。

これらの特別な構文的形式は何がスコープに入っているかにかかわらず関数、タプル、リストのビルドイン型のコンストラクタを常に示す。同様な方法で、プリフィックスな型コンストラクタ(->), [], (), (,)等はビルドインの型コンストラクタを常に示す。それらは修飾子を付けることはできず、そしてまたリストのimport/exportするもののリストに入れることもできない(5章)。(上述の特定の生成規則、"gtycon"から)

リストとタプル型が特別な構文を持つのだが、それらの意味論はユーザー定義された代数データ型と同じである。

式と型は一貫した構文を持つことに注意すること。もし、tiは式またはパターンeiの型ならその時、式(\ e1 -> e2), [e1](e1,e2)は各々、型(t1 -> t2), [t1](t1,t2)を持つ。

ひとつの例外(クラス宣言内の区別された型変数のこと(セクション4.3.1))を除いて、Haskellの型の表現内の型変数は一般に全て全称量化されていると仮定され、全称量化のための明示的な文法はない4。例えば、型の表現a -> aは型∀ a. a → aを示す。明確にするために、しかしながら、Haskellプログラムの型を議論する時に明示的な個々の区分をしばしば書く。明示的に個々に区分された型を書く時、のスコープは可能な限り左側へ拡張する。例として、∀ a. a → a∀ a. (a → a)を意味する。

クラス表明と文脈の構文

contextclass
| ( class1 , … , classn )(n ≥ 0)
class qtycls tyvar
| qtycls ( tyvar atype1atypen )(n ≥ 1)
qtycls[ modid . ] tycls
tyclsconid
tyvarvarid

クラス表明 は形式qtycls tyvarを持ち、クラスqtyclsの型tyvarのメンバを示す。クラス識別子は大文字で始める。 文脈 は0個以上のクラス表明からなり、一般に( C1 u1, …, Cn un )の形式をもつ。ここでC1, …, Cnはクラス識別子であり、( u1, …, un)はそれぞれ変数型または一つ以上の型への変数型の適用のいずれかである。n = 1のとき括弧の外側は省かれるであろう。一般的に、文脈を示すためにcxを使用し、cx => tを文脈cxによって制限された型tを示すために書く。文脈cxtによって参照される変数型のみを含まなければいけない。利便性のために、文脈cxが空であっても、具体的な構文は=>を含まないケースであるが、cx => tを書く。

型とクラスの意味論

このセクションは、型システムの簡略的な詳細を提供する。(WadlerとBlott[13]、Jones[8]は各々より詳細に型とコンストラクタクラスを議論している。)

Haskellの型システムは をプログラム内の各式に割り当てる。一般的に、型は形式u. cx ⇒ tである。uは変数型の集合u1, ..., unである。どのような型であっても、cxに束縛がない一般的な個々に区別された変数型uitでも束縛がないものでなければならない。その上、内容cxはセクション4.1.3上で与えられた形式でなければならない。例として、ここにいくつかの正常な型がある。

Eq a => a -> a  
(Eq a, Show a, Eq b) => [a] -> [b] -> String  
(Eq (f a), Functor f) => (a -> b) -> f a -> f b -> Bool

3つの型で、制約Eq (f a)fが全称量化されているためもっと単純にはできない。

eの型は型をeに束縛のない変数へ与えるため 型環境 に依存し、いずれかの型を宣言する クラス環境 はいずれかのクラスのインスタンスである。(型はインスタンス宣言または派生節の存在によってのみクラスのインスタンスになる。)

型は一般化による半順序集合(下に明記される)で関連する。多くの一般的な型は、一般化の先行順によって同等まで導かれ、(与えられた環境の)個々の式は 主要な型 と呼ばれるものに割り当てられる。Haskellの拡張されたHindley-Milner型システムは全式の主要な型を推論でき、オーバーロードされたクラスメソッドの妥当な使用を含んでいる(セクション4.3.4で説明するように、確実に曖昧なオーバーロードが起こり得るのだが)。したがって、明示的な型付け(型シグネチャと呼ぶ)は通常、オプションである(セクション3.164.4.1を参照)。

u. cx1 ⇒ t1は領域が以下のようなuの代用Sがある場合に限り、型w. cx2 ⇒ t2 より一般的 である。

  • t2S(t1)と同じである。
  • cx2はそのクラスの環境を保持し、S(cx1)も保持する。

u. cx ⇒ tの値は内容cx[s;/u]を保持する場合に限り型sでインスタンス化されるかもしれない。例えば、関数doubleについて考えてみる。

double x = x + x

doubleの最も一般的な型はa. Num a⇒ a → aである。doubleは(Intにインスタンス化する)型Intの値に適用されるかもしれない、なぜならNum Intが成り立つ、すなわちIntはクラスNumのインスタンスだからである。しかしながら、doubleが型Charの値に通常の意味で適用されることはないであろう。なぜなら、Charは通常クラスNumのインスタンスではないからだ。ユーザーはインスタンスのような宣言を選択するかもしれない。その場合、doubleが型Charの値に通常の意味で適用されることはないであろう。

ユーザー定義のデータ型

このセクションでは、代数のデータ型(data宣言)や新たに命名するデータ型(newtype宣言)、型のシノニム(type宣言)を説明する。これらの宣言はモジュールのトップレベルでのみ現れてよい。

代数データ型宣言

topdecldata [context =>] simpletype [= constrs] [deriving]
simpletypetycon tyvar1tyvark(k ≥ 0)
constrsconstr1 | … | constrn(n ≥ 1)
constrcon [!] atype1 … [!] atypek(arity con = k, k ≥ 0)
|(btype | ! atype) conop (btype | ! atype)(infix conop)
|con { fielddecl1 , … , fielddecln }(n ≥ 0)
fielddeclvars :: (type | ! atype)
derivingderiving (dclass | (dclass1, … , dclassn))(n ≥ 0)
dclassqtycls

constrの優先順位は式と同じである。通常のコンストラクタの適用が中置コンストラクタの適用より高い優先順位を持つ(そのためa : Foo aa : (Foo a)のように解析する)。

代数的なデータ型の宣言は、cxが内容である形式data cs => T u1 ... uk = K1 t1 1 ... t1k1 | ... | Kn tn1 ... tnknを持つ。 この宣言は0個以上の構成要素であるデータコンストラクタ K1, …, Knをもつような新しい型コンストラクタTを導入する。 このリポートで、修飾されていない用語"コンストラクタ"は"データコンストラクタ"を常に意味する。

データコンストラクタの型はKi :: ∀ u1 … uk. cxi ⇒ ti1 → ⋅⋅⋅ → tiki → (T u1 … uk)によって与えられる。 ここでcxiは、ti1, …, tikiの型に自由に出現する型変数たちのみを含むようなcxの部分集合の中で最大なものである。 型変数u1からukは互いに異なるものでなければならず、cxtijに出現してもよい。 また、他の型変数がcxやそれより右側に出現すると静的なエラーとなる。 新しい型定数Tは引数の変数uiのカインドκiがセクション4.6で説明されるカインド推論によって決定される形式κ1 →… → κk →∗のカインドを持つ。 これはTが0からk引数のどこでも型の表現に使われるかもしれないということを意味する。

例えば、以下の宣言は

data Eq a => Set a = NilSet | ConsSet a (Set a)

カインド∗→∗の型コンストラクタSetを導入し、型ありのコンストラクタNilSetConsSetは以下のものである。

NilSet  ::  ∀ a.  Set  a
ConsSet ::  ∀ a.  Eq   a  ⇒  a  →  Set   a  →  Set   a

与えられた例では、ConsSetにオーバーロードされた型はConsSetは型がクラスEqのインスタンスである値に提供されることのみ可能であることを保証する。ConsSetに対照したパターンマッチングはEq a拘束にも発生する。例えば、

f (ConsSet a s) = a

関数fは推論された型Eq a => Set a -> aを持つ。data宣言の内容は他に何も効果を持たない。

データ型のコンストラクタの可視性(すなわちデータ型の"抽象度")は、それが定義されたモジュールの外では、セクション5.8で説明されるエクスポートリスト内のデータ型の名前の形式によって制御される。

data宣言の内容は他に何も効果を持たない付加的なderivingの部分は 派生されたインスタンス と関係しており、セクション4.3.3で説明される。

ラベル付けされたフィールド 引数k個とるデータコンストラクタはk要素のオブジェクトを作成する。これらの要素は通常、式またはパターンの中のコンストラクタへの引数のように位置付けして呼び出される。巨大なデータ型のために、データオブジェクトの要素に フィールドラベル を割り当てることは便利である。これはコンストラクタ内でその位置を独立して参照されるために明記するフィールドを許す。

data宣言のコンストラクタ定義はラベルを記録構文(C { ... })を使用するコンストラクタのフィールドに割り当てられるだろう。フィールドラベルを使用するコンストラクタはそれらなしにコンストラクタを自由に組み合わされるかもしれない。フィールドラベルに関係するコンストラクタは通常のコンストラクタのようにまだ使われるだろう。ラベルを使う機能は基礎となる位置上のコンストラクタを使う操作のための単純な簡略記法である。その位置上のコンストラクタの引数はラベル付けされたフィールドのように同じ順序で発生する。例えば、以下の宣言は

data C = F { f1,f2 :: Int, f3 :: Bool }

下のように生成されるものと同一な型とコンストラクタを定義する。

data C = F Int Int Bool

フィールドラベルを使用する操作はセクション3.15で説明される。data宣言においては、型シノニムを展開した後にそのフィールドが使われている場所すべてで同じ型がつく場合に限り、同じフィールドラベルを複数のコンストラクタで使ってもよい。ラベルはスコープ内の型以上で共有されることはできない。フィールド名は通常の変数とクラスメソッドを使う最上位の名前空間を共有し、スコープ内の他の最上位の名前と衝突してはいけない。

パターンF {}はコンストラクタF記録構文を使って宣言されているかどうかにかかわらずFによって構築された任意の値と一致する。

正格なフラグ データコンストラクタが適用されるたびに、代数的データ型の宣言に対応する型が感嘆符!で表される正格なフラグを持つ場合に限り、コンストラクタの各引数は評価される。 "!"は通常のvarsymであって、reservedopとしては字句解析されない。 それはデータ宣言の引数の型の内容にのみ特別な意味を持つ。

変換:siが形式!titiのいずれかである形式data cx => T u1 … uk = … | K s1 … sn | …の宣言は(\ x1 … xn -> ( ((K op1 x1) op2 x2) … ) opn xn)という式の中のKの全ての発生を置き換える。 opiはもしsiが形式tiなら、正格ではない適用関数$であり、opiはもしsiが形式tiであるなら正格に適用する関数$!である(セクション6.2を参照)。 K上のパターンマッチングは正格なフラグによる影響を受けられない。

型シノニムの宣言

topdecltype simpletype = type
simpletypetycon tyvar1tyvark(k ≥ 0)

型シノニムの宣言は古い型と等しい新しい型を生成する。 それは新しいコンストラクタTを生成する形式type T u1 ... uk = tを持つ。 型(T t1 …tk)は型t[t1∕u1, …, tk∕uk]に等しい。 型変数u1からukは互いに異なるものでなければならず、t上のみにスコープされる。 そしてそのtの中に他の型変数が現れたら静的エラーになる。 新しい型コンストラクタTのカインドは引数uiのカインドκiは形式κ1 →… → κk → κであり、tの右側のκはセクション4.6で説明されるカインドの推論によって決定される。 例えば、次の定義はリスト型のコンストラクタを書く方法の代替案を提供することに使用されることができる。

type List = []

型シノニムの宣言によって生成された型コンストラクタのシンボルTは一部のみを提供されることはできない。十分な数の引数なしにTを使うことは静的エラーになる。

再帰的と相互再帰的なデータ型は許されるのだが、 代数的データ型 が入り込む限り、型シノニムではそうではない。例えば、

type Rec a   =  [Circ a]  
data Circ a  =  Tag [Rec a]

は許されるが、それに反して、

type Rec a   =  [Circ a]        -- invalid  
type Circ a  =  [Rec a]         -- invalid

はそうではない。似たもので、type Rec a = [Rec a]も許されない。

型シノニムはより読みやすい型シグネチャを作る便利な、しかし厳密な構文的仕組みである。同義語とその定義はinstance宣言のインスタンス型を除いて、完全に置き換えできる(セクション4.3.2を参照)。

データ型の改名

topdeclnewtype [context =>] simpletype = newconstr [deriving]
newconstrcon atype
|con { var :: type }
simpletypetycon tyvar1tyvark(k ≥ 0)

newtype cs => T u1 ... uk = N tの形の宣言は新しい型を導入し、その表現はすでに存在している型と同じである(訳注: TはNから新たに作られた型であるが、実行時表現が等しい)。 型(T u1… uk)はデータ型tを改名する。 それは型シノニムからオリジナルな型からまたはその型へ明示的に強制されなければならない厳密な型を作成することとは異なる。 また型シノニムと異なり、newtypeは再帰的な型を定義することに使用されるかもしれない。 式の中のコンストラクタNは型tから型(T u1 … uk)へ値を強制する。 パターンの中のNは型(T u1 … uk)から型tへ値を強制する。 これらの強制は実行時のオーバーヘッドなしに実装されるかもしれない。 newtypeはオブジェクトの根底にある表現を変更しない。

新しいインスタンス(セクション4.3.2を参照)はnewtypeによって定義された型に定義されることができるが、型シノニムに定義されることはないかもしれない。 newtypeによって作成された型は代数的データ型が追加の間接レベルを持つ表現内の代数的データ型とは異なる。 この差は効率が悪い表現にアクセスするかもしれない。 この差はパターンマッチングのための異なるルールに反映される(セクション3.17を参照)。 代数的なデータ型とは異なり、新しい型コンストラクタNリフトしない 、そのため、Nと同じである。

次の例はdata(代数的データ型)とtype(型シノニム)、newtype(型の改名)との差を明確にする。以下の宣言が与えられる。

data D1 = D1 Int  
data D2 = D2 !Int  
type S = Int  
newtype N = N Int  
d1 (D1 i) = 42  
d2 (D2 i) = 42  
s i = 42  
n (N i) = 42

(d1 ⊥)(d2 ⊥)(d2 (D2 ⊥))と全て等しい。一方で、(n ⊥)(n (N ⊥))(d1 (D1 ⊥))(s ⊥)42と全て等しくなる。特別に、(N ⊥)(D1 ⊥)と等しくないときはと等しくなる。

newtype宣言のオプション的に派生部分はdata宣言の派生要素と同じ方法で扱われる。セクション4.3.3を参照すること。

newtype宣言はフィールド名をつける構文を使用するかもしれず、もちろんそのフィールドしかないかもしれない。従って、

newtype Age = Age { unAge :: Int }

はコンストラクタとデコンストラクタの両方をスコープに持ち込む。

Age   :: Int -> Age  
unAge :: Age -> Int

型クラスとオーバーロード

クラス宣言

topdeclclass [scontext =>] tycls tyvar [where cdecls]
scontextsimpleclass
|( simpleclass1 ,, simpleclassn )(n ≥ 0)
simpleclassqtycls tyvar
cdecls{ cdecl1 ;; cdecln }(n ≥ 0)
cdeclgendecl
|(funlhs | var) rhs

クラス宣言 は新しいクラスとその中のオペレーション(クラスメソッド)を生成する。クラス宣言は次の一般的な形式を持つ。

class cx => C u where cdecls

これは新しいクラスの名前Cを生成し、型変数uはそのクラスの本体のクラスメソッドシグネチャ上でのみスコープされる。内容cxは下で説明するCのスーパークラスを明記する。cxの中で参照されるであろう型変数のみがuである。

スーパークラスの関係は循環してはいけない。例)指示された非環式のグラフを構成しなければいけない。

class宣言のcdecls部分は3種類の宣言を含む。

  • クラス宣言は新しいクラスメソッドviを生成し、スコープはclass宣言の外側に展開する。 クラス宣言のクラスメソッドはcdecls内の明示的な型シグネチャvi :: cxi => tiにあるviそのものである。 クラスメソッドは変数束縛とフィールド名と一緒に最上位の名前空間を共有する。 それらはスコープの他の最上位の束縛と衝突してはならない。 そのため、クラスメソッドは最上位の定義やフィールド名、他のクラスメソッドのように同じ名前を持つことはできない。

    トップレベルのクラスメソッドviの型はvi :: ∀u,w.(Cu,cxi) ⇒ tiである。 tiuを言及しなければいけないし、uより型変数wを言及するかもしれない。 その場合、viの型はuwの両方に多相的である。 cxiwのみ束縛するだろう。 特に、cxiuを束縛しなくともよい。 例えば、
    class Foo a where
    op :: Num b => a -> b -> a
    

    ここでのop型はa, b.(Foo a, Num b) ⇒ aba.である。
  • cdeclsは(他の値ではなく)そのクラスのメソッドに対する 結合性宣言 を含んでもよい。 しかしながら、クラスメソッドはトップレベルの値を宣言することから、他の選択肢としてクラスメソッドの結合性宣言はクラス宣言の外側であるトップレベルに現れてもよい。
  • 最後に、cdeclsviのいずれかの デフォルトクラスメソッド を含められる(セクション4.3.2)。 デフォルトメソッドの宣言は通常、左手側が変数か関数定義のみであろうことを除いて値の定義である。 例えば、
    class Foo a where  
    op1, op2 :: a -> a  
    (op1, op2) = ...
    

    は、許可されない。デフォルト宣言の左手側がパターンだからだ。

これらのケース以外に、cdecls内の宣言は許されない。

where部を伴わないclass宣言はオリジナルの全てのクラスメソッドを継承する巨大なクラスのコレクションを合成することに便利かもしれない。 例えば、

class  (Read a, Show a) => Textual a

このような場合、型が全スーパークラスのインスタンスであるなら、たとえサブクラスが直ちにクラスメソッドを持たなくても、サブクラスのインスタンスに 自動的にはならずinstance宣言はwhere部を伴わず明示的に与えられなければならない。

インスタンス宣言

topdeclinstance [scontext =>] qtycls inst [where idecls]
instgtycon
|( gtycon tyvar1tyvark )(k ≥ 0, tyvars distinct)
|( tyvar1 , … , tyvark )(k ≥ 2, tyvars distinct)
|[ tyvar ]
|( tyvar1 -> tyvar2 )(tyvar1 and tyvar2 distinct)
idecls{ idecl1 ; … ; idecln }(n ≥ 0)
idecl(funlhs | var) rhs
| (empty)

インスタンス宣言 はクラスのインスタンスを生成する。クラス宣言はclass cx => C u where { cbody }という風に行う。対応するインスタンス宣言の一般的な形式は次のものになる:instance cx′ => C (T u1 … uk) where { d } where k ≥ 0。型(T u1 … uk)はシンプルな型変数u1, … ukに提供される型コンストラクタTの形式を取らなければいけない。さらにTは型シノニムであってはならず、uiは全て互いに異ならなければならない。

以下のようなインスタンス宣言は禁止である。

instance C (a,a) where ...  
instance C (Int,a) where ...  
instance C [[a]] where ...

宣言dCのクラスメソッドのみの束縛を含められる。スコープにないクラスメソッドへの束縛を与えることは不正であるが、スコープ内にあるものの名前は重要ではない。特に、それは修飾子付きの名前でもよいであろう。(このルールはセクション5.2のエクスポートリスト内に従属する名前に使われることと同一である。)例として、rangeは修飾子付きの名前Data.Ix.rangeのみがスコープ内にあるが、これは正当である。

module A where  
  import qualified Data.Ix  

  instance Data.Ix.Ix T where  
    range = ...

class宣言内にすでに与えられているゆえに、宣言はあらゆる型シグネチャまたは固定宣言を含めないであろう。デフォルトのクラスメソッド(セクション4.3.1)の場合のように、メソッド宣言は変数または関数定義の形式を取らなければならない。

もし、いくつかのクラスメソッドに束縛が与えられなければ、その時、class宣言内の対応するデフォルトのクラスメソッドは(提供しているなら)使われる。デフォルトが存在しないなら、その時、このインスタンスのクラスメソッドはundefinedに束縛され、コンパイル時エラーは発生しない。

TをクラスCのインスタンスであるよう生成するinstance宣言はC-Tインスタンス宣言と呼ばれ、以下の静的な制約に従うべきである。

  • 型はプログラム上で1回以上個々のクラスのインスタンスのように宣言されるだろう。

  • クラスと型は同じカインドを持たなければいけない。これはセクション4.6で説明される使用するカインドの推論を決定されることが可能だ。

  • インスタンス型(T u1 … uk)内の型変数がインスタンス内容cx'に束縛を満たすということを推測するべきだ。この推測の元、次の2つの状態も満たされなければならない。

    1. Cのスーパークラスの内容cx[(T u1 … uk)∕u] によって表現された束縛が満たされなければならない。言い換えると、TCのスーパークラスの各インスタンスでなければならず、全スーパークラスのインスタンスの内容はcx'によって暗示されなければいけない。
    2. 正しく型付けされたd内のクラスメソッド宣言に要求されるインスタンス型内の型変数上の束縛も満たされなければいけない。

    実際に異常なケースを除いては、インスタンス宣言から上記2つの制約を満たす最も一般的なインスタンス文脈cx'を推論することが可能である。だが、それでも明示的なインスタンスの内容を書くことは強制される。

次の例はスーパークラスインスタンスによって強いられた制限事項を説明する。

class Foo a => Bar a where ...  

instance (Eq a, Show a) => Foo [a] where ...  

instance Num a => Bar [a] where ...

この例はHaskellにおいて正常である。FooBarのスーパークラスであるため、2つ目のインスタンス宣言は[a]が仮定Num aの下でFooのインスタンスである時に正常である。1つめのインスタンス宣言はこの仮定の下で[a]Fooのインスタンスであると実際に告げる。なぜなら、EqShowNumのスーパークラスだからだ。

もし2つのインスタンス宣言が代わりにこのように読むなら。

instance Num a => Foo [a] where ...  

instance (Eq a, Show a) => Bar [a] where ...

そのとき、そのプログラムは不正である。2つ目のインスタンス宣言は[a]が仮定(Eq a, Show a)の下、Fooのインスタンスであるときのみ正常である。しかし、[a]がもっと強い仮定Num aの下でFooのインスタンスのみであることから、これは保持しない。

instance宣言のさらに進んだ例は9章で見つけられるだろう。

導出されたインスタンス

セクション4.2.1で言及したように、datanewtypeの宣言は任意のderivingの形式を含んでいる。 もしその形式が含まれていたら、そのデータ型に対して 導出されたインスタンス宣言 が指定されたクラスのそれぞれについて自動的に生成される。 これらのインスタンスはユーザー定義されたインスタンスと同じ制限事項に従うべきである。 型TへクラスCを導出している時、Cの全スーパークラスのインスタンスはTのために存在しなければならず、明示的なinstance宣言を経由またはderiving句にスーパークラスを含むことを経由するかのいずれかである。

導出されたインスタンスはユーザー定義のデータ型へ便利なよく使われるオペレーションを提供する。 例えば、クラスEqの中のデータ型に導出されたインスタンスが==/=オペレーションを定義すると、それらを定義する必要からプログラマーを解放する。

導出されたインスタンスが許されるPreludeのクラスはEqOrdEnumBoundedShowReadのみであり、図6.1で全て図示される。 これらの句それぞれに生成される導出されたインスタンスの様相の精密な詳細は11章に提供されており、そこにはそのような導出されたインスタンスが可能である時の仕様書を含んでいる。 標準ライブラリによって定義された句も導出可能であろう。

もしderivingの形式で名付けられたクラス上でinstance宣言を導出することが可能でないなら、静的エラーが結果となる。 例えば、すべてのデータ型が正しくEnumのクラスメソッドをサポートできるわけではない。 それはまた導出されたクラスに明示的なinstance宣言を与えるため静的エラーになる。

もしderiving形式がdataまたはnewtype宣言から省略されたなら、そのときそのデータ型への いかなる インスタンス宣言も導出されない。 すなわち、deriving形式を省くことは空の導出の形式: deriving()を含んでいることと同等である。

曖昧な型とオーバーロードされた数値演算子の既定値

topdecldefault (type1 ,, typen)(n ≥ 0)

Haskellスタイルのオーバーロード固有の問題は 曖昧な型 の可能性があるということである。 例えば、11章で定義されたreadshow関数を使い、もし単なるIntBoolReadShowのメンバーなら、その時の次の式

let x = read "..." in show x  -- invalid

は曖昧である。なぜならshowreadの型は、下の2つの式の

show :: ∀ a. Show  a  ⇒  a  →  String
read :: ∀ a. Read  a  ⇒  String  →  a

両方のケースでaIntまたはBoolのどちらでもインスタンス化で満たすことが可能だからだ。 そのような式は不適切な型付けだと考えられ、静的エラーである。

u. cx ⇒ t内で、もしtではなくexの中に存在するuに型変数uがあるなら、式e曖昧な型 を持つと言う。 そのような型は不正である。

例えば、先程のshowreadを伴う式はその型がa. Show a, Read a ⇒ Stringであるから曖昧な型を持つ。

曖昧な型はユーザーからの入力によってのみ回避できる。 その方法のひとつはセクション3.16で説明された 式の型シグネチャ の使用を通じてである。 例として、先程与えられた曖昧な式において、以下のように書くことで、

let x = read "..." in show (x::Bool)

型から曖昧さを取り除く。

型シグネチャにより固定された型を与える以外にも、曖昧な式はある変数と同じ型にしなければならない場合がしばしばある。 これは関数asTypeOf:x asTypeOf y(9章)の用途がxの値を持つということであるが、xyは同じ型を持つように強制される。 例えば、

approxSqrt x = encodeFloat 1 (exponent x ‘div‘ 2) ‘asTypeOf‘ x

(encodeFloatexponentの説明についてはセクション6.4.6を参照)

Numクラスの曖昧さはとてもありふれているので、Haskellはこれを解決する別の方法を提供している。 それはデフォルト宣言である。 n ≥ 0で、各tiNum tiが保持するdefault (t1 , … , tn)。 曖昧な型が発見された状態で、もし以下の条件を満たすなら、曖昧な型変数vはデフォルト可能である。

  • vC vの形の制約の中でのみ出現し(ただしCはクラスである)、かつ
  • 少なくともこれらのクラスの一つが数値クラス(Num、またはNumのサブクラス)であり、
  • これらのクラスの全てがPreludeまたは標準ライブラリの中で定義されている(図6.2-6.3は数値クラスを示し、図6.1はPrelude内で定義されたクラスを示す)。

各デフォルト可能な変数は全ての曖昧な変数のクラスのインスタンスであるデフォルトリストの初めの型によって置き換えられる。 そのような型が見つからなければ、静的エラーである。

ひとつのデフォルト宣言のみがモジュールごとに許可され、その効果はそのモジュールに制限される。 もしデフォルト宣言がモジュール内で与えられないなら、その時は次のようなものであると仮定する。

default (Integer, Double)

空のデフォルト宣言default ()はモジュール内の全てのデフォルトをオフにする。

ネストされた宣言

次の宣言はモジュールのトップレベルを含むどんな宣言リストでも使用される。

型シグネチャ

gendeclvars :: [context =>] type
varsvar1 ,, varn (n ≥ 1)

型シグネチャは可能な限り内容を尊重して変数の型を明示する。型シグネチャはiがそれぞれ1からnを取るvi :: cx => tを表明することと同等な形式: v1, …, vn :: cx => tを持つ。 各viは型シグネチャを含む同じ宣言リストに束縛する値を持たなければいけない。 例えば、型シグネチャをスコープ外へ束縛された変数へ与えることは不正である。 その上、たとえそのシグネチャが同一であっても、一つの変数に一つ以上の型シグネチャを与えることは不正である。

セクション4.1.2で言及されたように、シグネチャに現れた全ての型変数はそのシグネチャ上で全称量化され、従って型変数のスコープはそれを含む型シグネチャに制限される。 例として、次の宣言、

f :: a -> a  
f x = x :: a                  -- invalid

2つの型シグネチャ内のaは完全に異なる。 それにまたxが型a. aを持たないことから、これらの宣言は静的エラーを含む。 (xの型はfの型次第であり、現状ではHaskellで依存型をもつ変数のシグネチャを指定する方法はない。このことはセクション4.5.4で説明される)

もし、与えられた問題が変数fのシグネチャを含むなら、その時各々のfの使用は宣言された型を持つように扱われる。 もし同じ型が定義しているfの実体へ推論されることもできなければ、静的エラーである。

もし変数fが対応する型シグネチャ宣言を提供しないで定義されるなら、その時それ自身の宣言グループ(セクション4.5)の外側の各fの使用は対応する推論される型、または 主要な 型を持つように扱われる。 しかしながら、型推論がまだ可能であることを保証するために、定義する実体とその宣言グループを伴う全てのfの使用が同じ単相型を持たなければいけない。(セクション4.5.2で述べるように、主要な型は一般化によって得られる。)

例えば、もし以下のように定義したら、

sqr x  =  x⋆x

その時その主要な型はsqr :: ∀ a. Num a ⇒ a → aであり、sqr 5またはsqr 0.1のような適用を許す。 次のようにより明確に型を宣言することも正常である。

sqr :: Int -> Int

しかし、この場合sqr 0.1のような適用は不正である。 次のような型シグネチャは

sqr :: (Num a, Num b) => a -> b     -- invalid  
sqr :: a -> a                       -- invalid

それらはsqrの主要な型より一般的であるが、不正である。

型シグネチャは 多相的再帰 をサポートすることにも使われることができる。 次の定義は異常であるが、型シグネチャを使って推論されるものよりも一般的な型をどのようにして指定すればよいか、ということのよい説明である。

data T a  =  K (T Int) (T a)
f         :: T a -> a
f (K x y) =  if f x == 1 then f y else undefined

もしシグネチャ宣言を取り除くなら、fの型はfへの引数がT Intである初めの再帰的な呼び出しの結果、T Int -> Intのように推論される。 多相的再帰はユーザーがT a -> aのように、より一般的な型シグネチャを供給することを許可する。

結合性宣言

gendeclfixity [integer] ops
fixityinfixl | infixr | infix
ops op1 , … , opn (n ≥ 1)
op varop | conop

結合性宣言は一つ以上の演算子の結合性と束縛の優先順位を与える。 結合性宣言の中のintegerは0から9の範囲でなければならない。 結合性宣言は型シグネチャが現れるところや、型シグネチャのように各自の演算子のプロパティを宣言する場所ならどこでも現れることができる。 また型シグネチャのように、結合性宣言は演算子の宣言と同じ宣言の列でのみ出現することができ、そして演算子に対しては高々1つの結合性宣言のみ与えてよい。 (クラスメソッドは小さな例外であり、それらの結合性宣言はクラス宣言の中またはトップレベルに出現できる。)

non-とleft-、right-結合(それぞれinfixinfixlinfixr)の3種類の結合性と、0から9を含んだ(レベル0は最も小さい結合を束縛し、レベル9は最も大きい結合を束縛する)10の優先順位のレベルがある。 もし が省略されるなら、レベル9と仮定される。 結合性宣言を欠いた演算子はinfixl 9と仮定される(結合性の使い方のより詳細はセクション3を参照)。

テーブル4.1はPreduleに定義された演算子の結合性と優先順位をリストアップする。


優先順位 左結合演算子 非結合演算子 右結合演算子
9 !! .
8 ^, ^^,⋆⋆
7 ,/,'div',
'mod','rem', 'quot'
6 +,-
5 :, ++
4 ==, /=, <, <=, >, >=,
'elem', 'notElem'
3 &&
2 ||
1 >>, >>=
0 $, $!, 'seq'

テーブル4.1 Prelude演算子の優先順位と結合性


結合性は型と同様に特定の実体(コンストラクタまたは変数)の性質であり、実体がもつ 名前 のプロパティではない。 例えば、

module Bar( op ) where  
  infixr 7 ‘op‘  
  op = ...  

module Foo where  
  import qualified Bar  
  infix 3 ‘op‘  

  a ‘op‘ b = (a ‘Bar.op‘ b) + 1  

  f x = let  
            p ‘op‘ q = (p ‘Foo.op‘ q) ⋆ 2  
        in ...

ここでの、'Bar.op'infixr 7で、‘Foo.op‘infix 3であり、fの右手側のopのネストされた定義はinfixl 9のデフォルトの結合性を持つ。 (ネストされた結合性宣言を伴うopのネストされた定義に結合性を与えることもまた可能であろう。)

関数とパターン束縛

decl(funlhs | pat) rhs
funlhsvar apat { apat }
|pat varop pat
| ( funlhs ) apat { apat }
rhs= exp [where decls]
| gdrhs [where decls]
gdrhsguards = exp [gdrhs]
guards | guard1, …, guardn(n ≥ 1)
guardpat <- infixexp (pattern guard)
|let decls (local declaration)
|infixexp (boolean guard)

この文法に置いて我々は次の2つのケースを区別する: パターンの束縛 がおきているとき(そのとき左手側はpatである)と、それ以外の 関数束縛 と呼ばれる束縛がおきているときである。 どちらの束縛もモジュールのトップレベルでまたはwhereletの構成物の範囲で現れることができる。

関数束縛

関数束縛は関数の値に変数を束縛する。 変数xへ束縛する関数の一般的な形式は以下のものである。

x	p11 … p1k	match1x	pn1 … pnk	matchn

上の各pijはパターンで、各matchiは次の一般的形式

= ei where { declsi }

または、

| gsi1  =  ei1 
…
 | gsimi  =  eimi
            where { declsi }

であり、n ≥ 1, 1 ≤ in, mi ≥ 1である。 前者は後者の特別な場合であるので、簡略記法として扱われる。 すなわち、

 | `True` = ei `where` { declsi } 

注意点として関数を定義している全ての句は隣接しなければならず、各句の中のパターンの数は同じでなければならない。 各適合に対応するパターンのセットは線形でなければならず、変数がその実体のセットの中に一回以上現れることは許されていない。

関数の値を中置演算子に束縛するための代わりの構文が与えられている。 例えば、これらの3つの関数定義は全て等しい。

plus x y z = x+y+z
x ‘plus‘ y = \ z -> x+y+z
(x ‘plus‘ y) z = x+y+z

結合性の解決は、関数の束縛を中置にしたものにも式の場合と同様に適用することに注意せよ(セクション10.6)。 関数束縛に等しい左側に適用している結合性の解決はトップレベルで定義されているvaropを残さなければいけない。 例えば、もし優先度6で新しい演算子##を定義しているなら、その時この定義は不正である。

a ## b : xs = exp

なぜなら、:は優先度5を持ち、従って左手側は(a ## x) : xsに解決され、この式は(a ## x)が正常なパターンでないためパターン束縛にはできない

変換: 関数への一般的な束縛の形式はこの等式に構文的に等しい(すなわち:シンプルなパターン束縛)。

x = \ x1xk -> case (x1, …, xk) of
(p11, …, p1k) match1
…
(pn1, …, pnk) matchn

xiは新しい識別子である。

パターン束縛

パターン束縛は値に変数を束縛する。 シンプルな パターン束縛はp = eの形式を持つ。 まるでその前に暗黙の~があるかのように、パターンpは反駁できないパターンとして「遅延的に」適合される。 セクション3.12にその変換を確かめられる。

パターン束縛の 一般的な 形式は p適合 であり、適合 は上記の関数束縛において同じ構造である。 言い換えると、パターン束縛は以下のものである。

p | gs1 = e1
  | gs2 = e2
    …
  | gsm = em
  where { decls }

変換: 上記のパターン束縛はこのシンプルなパターン束縛と構文的に等しい。

p = let decls in
    case () of
      () | gs1 -> e1
         | gs2 -> e2
           …
         | gsm -> em
       _ -> error "Unmatched pattern"

関数とパターン束縛の静的な意味論

関数の静的意味論とlet式またはwhere句のパターン束縛はこのセクションで論じる。

依存の解析

一般的に静的意味論は通常のHindley-Milner推論規則を適用することによって与えられる。 多相性を増加するために、これらの規則は 依存の解析 によって見分けられる束縛のグループに適用される。

もし次のいずれかであるなら、束縛b1は同じ宣言のリストの中の束縛b2に依存する。

  1. b1は自由識別子で、型シグネチャを持たずb2によって束縛されるようなものを含む。または、
  2. b1b2に依存する束縛に依存する。

宣言のグループ は相互依存の束縛の最小限のひと組である。 Hindley-Milner型推論は依存の順序に各宣言グループに適用される。 where/letの構成物の中の宣言の順序は無意味である。

一般化

Hindley-Milner型システムは次の2段階でlet式に型を割り当てる。

  1. 宣言グループは依存している順に考慮される。 各グループにおいて、全称量化を伴わない型がそのグループに束縛される各変数へ推論される。 その時、これらの型に現れる全ての型変数は型環境に束縛された変数を連帯されるにも限らず全称量化される。 このことを一般化と呼ばれる。
  2. 最後に、let式の本体は型付けされる。

例えば、次の宣言を考えてほしい。

f x = let g y = (y,y)  
    in ...

gの定義の型はa → (a,a)である。 一般化は、gに対して多相型∀ a. a → (a,a)を紐づけ、そのあと"..."の部分の型付けが続けられる。

オーバーロードされた定義に型を付ける時、単一の宣言グループにおけるすべてのオーバーロードしている制約は集められる。それはグループで宣言された各変数の型の文脈を作るためである。 例えば、以下の定義において

f x = let g1 x y = if x>y then show x else g2 y x  
          g2 p q = g1 q p  
      in ...

g1g2の定義の型は両方aa → Stringであり、 累算された制約はOrd a(>の使用から発生する)と、Show a(showの使用から発生する)である。 この制約の収集に現れる型変数は制約された型変数と呼ばれる。

一般化の手順は型a. (Ord a, Show a) ⇒ aa → Stringというg1g2両方に帰属する。 >showg1の定義にあるのに、g2g1と同じ方法にオーバーロードされることに注目してほしい。

プログラマがある宣言グループの2つ以上の変数に明示的な型シグネチャを与えたならば、それらのシグネチャの文脈は型変数の名前の取り換えを除いて一致しなければならない。

文脈の簡約エラー

セクション4.1.4で言及されたように、型の文脈は型変数または一つ以上の型変数の適用のみ制約できる。 よって、一般化によって生成された型は全ての文脈の制約が「頭部正規形」に簡約されなければいけない形式に表現されなければならない。 例として、次の定義を考えてもらいたい。

xs y  =  xs == [y]

その型は次のもので与えられ、

f :: Eq a => [a] -> a -> Bool

以下のものではない。

f :: Eq [a] => [a] -> a -> Bool

等号はリストに対して使われているが、文脈は簡単な形にしなければならず、一般化の前にリストに対するEqのインスタンス宣言を用いて行う。 もしそのようなインスタンスがスコープ内に無いなら、静的エラーが起きる。

ここにはmが一般化された型変数のひとつである形式C (m t)の制約のための要求を示す例がある。 すなわち、クラスCを型変数または型コンストラクタではない型式に適用する場合である。 次のものを考えてもらいたい。

f :: (Monad m, Eq (m a)) => a -> m a -> Bool  
f x y = return x == y

returnの型はMonad m => a -> m aであり、(==)の型はEq a => a -> a -> Boolである。 fの型は従って(Monad m, Eq (m a)) => a -> m a -> Boolであるべきで、その文脈はそれ以上簡易化されることはできない。

データ型deriving句(セクション4.3.3)から派生したインスタンス宣言は、あらゆるインスタンス宣言のように、簡単な文脈を持たなければならない。 すなわち、全ての制約はaが型変数である形式C aでなければならない。 例えば、以下の型において

data Apply a b = App (a b)  deriving Show

導出されたShowインスタンスは文脈Show (a b)を生成し、そしてそれは簡約されることはできず、かつシンプルではないゆえに静的エラーが結果として生じる。

単相性

時々、その定義の型の中に使用されるすべての型変数にわたる一般化ができないことがある。 例えば、次の宣言を考える。

f x = let g y z = ([x,y], z)  
      in ...

xが型aを持ち、gの定義の型はa → b → ([a],b)という環境である。 一般化の手順は型b. ab([a],b)というgに帰属する。 aが型環境に現れているため、bのみが全称量化されることが可能である。 このことをgの型が型変数aに単相的であると言う。

そのような単相性の結果としてgの全ての適用の初めの引数が単一の型でなければならない。 例えば、次の式は”...”に有効であろう。

(g True, g False)

(ついでに言うとxBool型を持つよう強制させる。) しかし次のものは不正である。

(g True, g 'c')

一般的にもしau. cx ⇒ t.の中で自由なら、型u. cx ⇒ tは型変数a単相的であると言われる。

Haskellによって与えられる明示的な型シグネチャは単相的な型変数を含む型を表現するのに十分な力がないことには注意すべきである。 例えば、次のものは書くことができない。

f x = let  
        g :: a -> b -> ([a],b)  
        g y z = ([x,y], z)  
      in ...

なぜなら、あれはgabの両方に多相的であることを要求されるからだ(セクション4.4.1)。 このプログラムでは、gは最初の引数が型変数を含まないものに制限された場合にのみ型シグネチャを与えられる。 例えば、

g :: Int -> b -> ([Int],b)

このシグネチャはInt型を持つxももたらすだろう。

単相性の制限

Haskellは上記の標準Hindley-Milner制限の範囲を超えて、一般化の手順に一定の余分な制限を配置し、特定の場合において多相性をさらに削減する。

単相性の制限は変数の束縛する構文に依存する。 変数は 関数束縛 または パターン束縛 のどちらかによって束縛されることを思い出してほしい。 しかも、 単純な パターン束縛はそのパターンが単一の変数のみで構成するパターン束縛である(セクション4.4.3)。

次の2つのルールは単相性の制限を定義する。

単相性の制限

  • ルール1
    次の条件を満たすとき、与えられた宣言グループが 非制限的 であるという。

    • (a):
      グループ内の各変数は関数束縛または単純なパターン束縛によって束縛される(セクション4.4.3.2)。かつ、
    • (b):
      単純なパターン束縛によって束縛されるようなグループ内のすべての変数に対して明示的な型シグネチャが与えられている。

    Hindley-Milnerの多相性に関する通常の制限とは、環境に自由に出現しない型変数のみが一般化されるというものである。 それに加えて、該当するグループの一般化のステップにおいて、制限的な宣言グループの制約された型変数は一般化されない場合がある。 (もし同じ型クラスに属さないといけないなら、型変数が制約されていることを思い出すこと。セクション4.5.2を参照。)

  • ルール2
    全部のモジュールへの型推論が完了した時に残るあらゆる単相的な型変数は曖昧だと考えられ、そしてデフォルトのルールを使用した各々の型に解決される(セクション4.3.4)。

動機 ルール1は2つの理由により要求され、両方ともかなり巧妙である。

  • ルール1は思いがけなく繰り返されることから計算を防止する。 例えば、genericLengthは型が次のもので与えられる(Data.Listライブラリの)標準関数である。

    genericLength :: Num a => [b] -> a
    

    では、次の式を考えて欲しい。

    let { len = genericLength xs } in (len, len)
    

    まるでlenが一度だけ計算されるべきかのように見れるが、ルール1がなければ2回の異なるオーバーロード毎に一回づつで、計二回計算されるであろう。 もしプログラマが実際に計算が繰り返されてほしいと考えているなら、明示的な型シグネチャを追加するだろう。

    let { len :: Num a => a; len = genericLength xs } in (len, len)
    
  • ルール1は曖昧さを防止する。例えば、次の宣言グループを考えてほしい。

    [(n,s)] = reads t
    

    readsは型がシグネチャによって与えられる標準関数であることを思い出してほしい。

    reads :: (Read a) => String -> [(a,String)]
    

    ルール1なしで、nは型a. Read a ⇒ aを割り当てられるであろう。そして、sは型a. Read a ⇒ Stringである。 後者は不正な型である、なぜなら本質的に曖昧であるからだ。 sを使うオーバーロードするもので決定することは可能ではなく、そしてsに型シグネチャを追加することで解決されることもできない。 このゆえに、単純ではないパターン束縛が使われている時(セクション4.4.3.2)、型シグネチャが与えられるかどうかにかかわりなく、推論された型はそれらの制約された型変数の中で常に単相的である。 この場合において、nsの両者はaに単相的である。

    同じ制約はパターン束縛関数に適用する。 例えば、次の中の

    (f,g) = ((+),(-))
    

    fgの両者はfまたはgに与えられる型シグネチャにかかわらず単相的である。

ルール2は現在のモジュールの外側のモジュール上で型推論を行うことを除いて、エクスポートされた束縛の単相的な使用を強いるための方法がないことから要求される。 ルール2はモジュール内に束縛された全変数の正確な型がそのモジュールのみで決定されなければならないことを述べ、それをインポートするモジュールによってではないことを述べている。

module M1(len1) where
default( Int, Double )
len1 = genericLength "Hello"

module M2 where
  import M1(len1)
  len2 = (2⋆len1) :: Rational

モジュールM1上の型推論が完了した時、len1は(ルール1によって)単相的な型Num a => aを持つ。 ルール2はそのとき単相的な型変数aが曖昧であると述べ、セクション4.3.4の既定のルールを使い解決されなければならない。 それゆえに、len1が型Intを取得し、len2の中の使用は誤った型である。 (もし上記のコードが実際に求められるなら、len1上の型シグネチャはその問題を解決するであろう。)

この問題はネストされた束縛では起きない。なぜなら、それらの全体のスコープがコンパイラに見えているからだ。

結果 単相性ルールはプログラマに多くの結果をもたらす。 関数構文を用いて定義されたどんなものも、通常期待されるような関数に一般化される。 例として、

f x y = x+y

関数fはクラスNumにオーバーロードするもので使われる。 ここに再計算の危険はない。 しかしながら、次のパターン構文を伴い定義される同じ関数は、

f = \x -> \y -> x+y

もしfが完全にオーバーロードされるなら型シグネチャを要求する。 多くの関数は単純なパターン束縛を使用してたいてい自然に定義される。 ユーザーは、完全なオーバーローディングを保つためには型シグネチャを添えることを心掛けなければならない。 標準Preludeはこのことのたくさんの例を含む。

sum  :: (Num a) => [a] -> a  
sum  =  foldl (+) 0   

ルール1はトップレベルとネストされた定義の両方に適用する。 次のものを考えてほしい。

module M where  
  len1 = genericLength "Hello"  
  len2 = (2⋆len1) :: Rational

ここで、型推論はlen1が単相的な型(Num a => a)を持つことを探し、その型変数alen2上の型推論を行う時にRationalに解決される。

カインドの推論

このセクションは カインドの推論 、すなわち与えられたプログラムに現れる各型コンストラクタとクラスに適切なカインドを計算することに使われるルールを述べる。

カインドの推論の工程の最初の手順は依存グループデータ型やシノニム、クラス定義のセットを整理することである。 これはセクション4.5で述べられた値宣言の依存解析のように同じ方法を多く成し遂げられることができる。 例えば、次のプログラムの断片はデータ型コンストラクタDとシノニムS、クラスCの定義を含み、 その全ては同じ依存グループに含まれているであろう。

data C a => D a = Foo (S a)  
type S a = [D a]  
class C a where  
    bar :: a -> D a -> Bool

各グループ内の変数やコンストラクタ、クラスのカインドは型推論とカインドを保存するユニフィケーションの標準的なテクニックを用いて決定される[8]。 例えば上で定義した中で、パラメータabarの型の関数コンストラクタ(->)の引数のように現れ、これゆえにカインドを持たなければならない。 それはDSの両方がカインド∗→∗を、かつクラスCの全インスタンスがカインドを持たなければならないということを結果として生ずる。

推論されたカインドのいくつかの部分は対応する定義によっておそらく完全に決定されないであろうことも起こりうる。 そのような場合、の既定値は推測される。 例えば、次の例の各々のaパラメータに任意のカインドκを推測できるだろう。

data App f a = A (f a)  
data Tree a  = Leaf | Fork (Tree a) (Tree a)

これは任意のカインドκに対して、それぞれAppTreeにカインド(κ →∗) → κ →∗κ →∗を与えるであろう。 そして、多相的なカインドを許すように拡張を要求するであろう。 代わりに、デフォルト束縛κ = ∗を用いれば、これら2つのコンストラクタへ与えられる実際のカインドはおのおの、(∗→∗) →∗→∗∗→∗になる。

既定値は特別な型コンストラクタ定数またはクラスが後ろの依存グループまたはプログラムの他の場所で使われる方法の検討なしで各依存グループに与えられる。 例として、上のソースコードに次に続く定義を追加することは(例えば(∗→∗) →∗に変化することによって)Treeへ推論されたカインドに影響を及ぼさない。 そして代わりに静的エラーを生成する。 なぜなら[], ∗→∗,のカインドはTreeの引数へ期待されるカインドと適合しないからだ。

type FunnyTree = Tree []     -- invalid

これは各コンストラクタとクラスがスコープ内にある時はいつでも同じカインドで一貫して使われていることを保証するため重要である。

モジュール

モジュールは(他のモジュールからスコープの中へもたらされたリソースの) インポート の一式によって作成された環境の中の値やデータ型、型シノニム、クラスなど(章を参照)の集まりを定義する。 これらのリソースのいくつかを エクスポート し、他のモジュールでそれらを利用できるようにする。 モジュールの中で定義された、またはその中へインポートされた、あるいはエクスポートされた値または型クラスに参照するために項の エンティティ を使用する。

Haskell プログラム はモジュールの集まりであり、その中の一つから規約により、Mainが呼ばれなければならず、かつmain値をエクスポートしなければいけない。 プログラムの はモジュールMainの中の識別子mainの値であり、そしていくつかの型τのための型IO τの計算結果でなければいけない(章を参照)。 プログラムが実行されたとき、計算結果mainは行わられ、(型τの)その結果は捨てられる。

モジュールは明示的なimport宣言により他のモジュールを参照することができ、 その各々でインポートされたモジュールの名前を与え、かつインポートされるためにそのエンティティを明記する。 モジュールは互いに再帰的にインポートされることもできる。

モジュールは名前空間の制御に使われ、かつファーストクラス値ではない。 複数のモジュールからなるHaskellのプログラムは、単一のモジュールのプログラムに、各エンティティに一意な名前を与え、その参照が出現する全ての場所を適切に一意な名前に変えて全てのモジュールの本体1の名前を連結することにより変換することができる。

例えば、ここに3つのモジュールプログラムがある。

module Main where  
    import A  
    import B  
    main = A.f >> B.f  

module A where  
    f = ...  

module B where  
    f = ... 

それは次の単一モジュールプログラムに等しい。

module Main where  
    main = af >> bf  

    af = ...  

    bf = ... 

なぜならそれらは互いに再帰的であることが許可され、モジュールはプログラムを依存の状態に注意することなく自由に分割することを許す。

モジュールの名前(語彙素modid)は大文字で始まり、ドットで区切られ、空白をはさまない一つ以上の識別子の列である。 例えば、Data.BoolMainForeign.Marshal.Allocはすべて有効なモジュール名である。

modid{conid .} conid(modules)

モジュール名は新しいコンポーネントを追加すると、元々のモジュール名の子を階層の中に作成し、その階層の中に配置されているように考えることができる。 例えば、モジュールContorl.Monad.STContorl.Monad下の階層の子である。 しかしこれは単なる慣習であり、言語の定義には含まれない。このリポートではmodidは平坦な名前空間を占有する単一の識別子のように扱われる。

中でも特別なモジュール Prelude が存在し、これはデフォルトで全てのモジュールにインポートされる(セクション5.6)。 加えて、必要に応じてインポートされる標準ライブラリのモジュールの集合も特別である(Part2を参照)。

(訳注:上のリンク先であるPart2はHaskell2010のライブラリの部分でこのリポートの13章から42章までを指す。)

1 この文に2つの小さな例外がある。 一つは、デフォルト宣言のスコープは単一のモジュール内のみに及ぶ(セクション4.3.4)。 二つめは、単相性の制約のルール2がモジュールの境界によって影響を受ける。

モジュールの構造

モジュールは値束縛やデータ型、型シノニム、クラスなどへの宣言を含む相互再帰的なスコープを定義する(4章を参照)。

module module modid [exports] where body
|body
body { impdecls ; topdecls }
|{ impdecls }
|{ topdecls }
impdecls impdecl1 ; … ; impdecln(n ≥ 1)
topdecls topdecl1 ; … ; topdecln(n ≥ 1)

モジュールはmoduleキーワードとその名前、エクスポートされる(丸括弧で囲まれた)エンティティのリストをヘッダーに伴って始まる。 そのヘッダーの次にはインポートされるモジュールを明記する空かもしれないimport宣言(impdecls、セクション5.3)のリストが続き、 必要に応じてインポートされる束縛を制限する。 これには空かもしれないトップレベルの宣言のリストが次に続くであろう(topdecls4章)。

モジュールの本体のみで成るモジュールの短縮形式は許される。 もしこれが使われるなら、そのヘッダーは‘module Main(main) where’であると推測される。 もし短縮されたモジュールの初めの語彙素が{でなければ、そのときそのレイアウトルールはそのモジュールのトップレベルへ適用される。

エクスポートリスト

exports ( export1 , … , exportn [ , ] ) (n ≥ 0)
export qvar
| qtycon [(..) | ( cname1 , … , cnamen )] (n ≥ 0)
| qtycls [(..) | ( var1 , … , varn )] (n ≥ 0)
| module modid
cname var | con

エクスポートリストはモジュール宣言によってエクスポートされるエンティティを識別する。 モジュールの実装はそのモジュールで宣言しているまたは他のモジュールからインポートしているエンティティのみをエクスポートできる。 もしエクスポートリストが省略されるなら、モジュールの中で定義されたすべての値や型、クラスはインポートされるものを除いてエクスポートされる。

エクスポートリストのエンティティは次に従う名前である。

  1. 値、フィールド名またはクラスメソッドはそれらがモジュールの本体またはインポートによって宣言されたものかどうかにかかわらず、 qvaridのように値の名前を与えることによって名前をつけることができ、そしてそれはスコープ内でなければならない。 演算子はそれらをqvaridsに変えるために丸括弧で閉じられなければならない。

  2. dataまたはnewtype宣言で宣言された代数データ型Tは次の3つの方法の一つで名前をつけることができる。

    • 形式Tコンストラクタまたはフィールド名によってではなく、 型によって名前がつけられる。 コンストラクタなしに型をエクスポート出来るという仕様は、抽象データ型のコンストラクタについても同様である(セクション5.8)。
    • 形式T(c1,...,cn)は型とそのコンストラクタとフィールド名の複数または全ての名前である。
    • 略された形式T(..)は(修飾されたかされていないかのどちらにしろ)現在のスコープにある型と全てのそのコンストラクタとフィールド名の名前である。

    全てのケースにおいて(修飾されているかもしれない)型コンストラクタTはスコープになければならない。 2番目の形式の中のciの名前であるそのコンストラクタとフィールドは修飾されない。 これらの付随する名前の一つは次の場合にのみ正当である。 (a) Tのコンストラクタまたはフィールドの名前である。かつ、 (b) コンストラクタまたはフィールドが 修飾されるかされない名前の下のスコープ内にあるかどうかにかかわらず モジュール本体のスコープ内にある。 例えば、次のコードは正当である。

    module A( Mb.Maybe( Nothing, Just ) ) where  
    import qualified Data.Maybe as Mb 
    

    データコンストラクタは付随する名前のようなものを除いてエクスポートリストの中で名前をつけることはできない。 なぜならそうでなければ型コンストラクタから見分けられないからだ。

  3. data宣言によって宣言される型シノニムTは形式Tによって名前をつけられることができ、Tはスコープ内にある。

  4. class宣言で宣言される演算f1,...,fnを伴うクラスCは次の3つの方法のひとつから名前をつけることができる。

    • 形式Cクラスメソッドを除いて クラスの名前である。
    • 形式C(f1,...,fn)はクラスとそのメソッドのいくつかまたは全ての名前である。
    • 略された形式C(..)は(修飾されるされないか関係なく)スコープにあるクラスとその全てのメソッドの名前である。

    全てのケースにおいて、Cはスコープになければならない。 2番目の形式の中で、(修飾されない)付随する名前fiのひとつは次の場合にのみ正当である。 (a) Cのクラスメソッドの名前である。かつ (b) そのクラスメソッドが修飾されるされない名前の下のスコープ内にあるかどうかにかかわらずモジュール本体のスコープ内にある。

  5. 形式module Mは修飾されない名前"e"と修飾される名前"M.e"の両方を伴うスコープ内にある全エンティティのセットの名前である。 このセットは空でもよい。 例えば、

    module Queue( module Stack, enqueue, dequeue ) where  
        import Stack  
        ... 
    

    ここのモジュールQueueStackからインポートされた全エンティティを略して書くためそのエクスポートリスト内のモジュール名Stackを使う。 モジュールは構文"module M"内のそれが保有する名前を使うエクスポートリストの中で保有するローカルな定義の名前をつけることができる、 なぜなら、ローカル宣言は修飾されるされない名前の両方をスコープの中へもたらす(セクション5.5.1)。 例えば、

    module Mod1( module Mod1, module Mod2 ) where  
    import Mod2  
    import Mod3 
    

    ここのモジュールMod1Mod2からインポートされたそれらと同様に全てのローカル定義をエクスポートするが、Mod3からインポートされたものは異なる。

    Mがエクスポートリストをもつモジュールでない限り、または少なくとも1つのインポート宣言によって(修飾されるかどうかにかかわらず)インポートされたモジュールでない限り、module Mをエクスポートリストで使うことはエラーとなる。

エクスポートリストは累積される。 すなわち、エクスポートリストによってエクスポートされるエンティティのセットはリストの個々のアイテムによってエクスポートされたエンティティの和集合である。

エンティティがどのようにエクスポートされているものであっても、インポートしているモジュールには違いがない。 例えば、データ型Tからフィールド名fは個々に(f、上記のアイテム(1))エクスポートしてよい。 あるいはそのデータ型(T(f)、アイテム(2))の明示的に名前をつけられたメンバーのようにしても、 あるいは暗黙的に名前をつけられたメンバー(T(..)、アイテム(2))のようにしても、 モジュール本体(module M、アイテム(5))をエクスポートすることによっても、同様にエクスポートしてもよい。

モジュールによってエクスポートされたエンティティの 修飾される 名前は(それら各々の名前空間の範囲で)全て異ならなければならない。 例えば、

module A ( C.f, C.g, g, module B ) where   -- an invalid module  
import B(f)  
import qualified C(f,g)  
g = f True

モジュールAそれ自身の範囲で衝突する名前はないが、 C.ggの間(C.ggは異なる実体であると仮定する。モジュールは相互再帰的にインポートできることを思い出してほしい。)と module BC.fの間(B.fC.fは異なる実体であると仮定する)にエクスポートリスト内で衝突する名前がある。

インポート宣言

impdeclimport [qualified] modid [as modid] [impspec]
| (empty declaration)
impspec( import1 , … , importn [ , ] )(n ≥ 0)
|hiding ( import1 , … , importn [ , ] )(n ≥ 0)
importvar
| tycon [ (..) | ( cname1 , … , cnamen )](n ≥ 0)
| tycls [(..) | ( var1 , … , varn )](n ≥ 0)
cnamevar | con

モジュールによってエクスポートされたエンティティはモジュールのはじめのimport宣言を伴って他のモジュールのスコープの中へもたらされる。 import宣言はインポートされるモジュールの名前をつけ、任意でインポートされるエンティティを明示する。 単一モジュールは一つ以上のimport宣言によってインポートされるかもしれない。 インポートされた名前はトップレベルの宣言のように扱い、 そのスコープはモジュールの実体全体に及ぶが、ローカルのトップレベルではない束縛によってシャドーイングされることもある。

複数のimport宣言の効果は厳密に累積し、 もしモジュール内のimport宣言のいずれかでインポートされるなら、エンティティはスコープ内にある。 インポート宣言の順序は無関係である。

語彙的に、終端期号"as""qualified""hiding"はそれぞれreservedidではなくvaridである。 それらはimport宣言の文脈内でのみ特別な意味を持ち、変数のようにも使われることが出来る。

何がインポートされるか

正確にどのエンティティがインポートされるかは次に3つの方法の一つで明示されることが出来る。

  1. インポートされるエンティティは丸括弧の中のリスト化しているものによってはっきりと明示されることが出来る。 そのリストの中のアイテムは修飾子が許可されないこととmodule <em>modid</em>エンティティが許可されないことを除いてエクスポートリスト内のものと同様に同じ形式を持つ。 インポートの形式(..)が型またはクラスで使われるとき、(..)はモジュールからエクスポートされるコンストラクタまたはメソッド、フィールド名の全てを参照する。

    そのリストはインポートされるモジュールによってエクスポートされるエンティティのみに名前をつけないといけない。 リストは空でもよく、その場合はインスタンス以外はインポートされない。

  2. エンティティは形式hiding(inport1, ..., importn)を使うことによって除外されることができる。 そして名前がつけられたモジュールによってエクスポートされる全エンティティはリスト内で名前を付けられたものを除いてインポートされるべきことを明示する。 データコンストラクタは関連する型で接頭辞をつけられることなく隠れているリスト内で直接名前をつけることが出来る。 例えば、

    import M hiding (C)
    

    Cと名前を付けられるあらゆるコンストラクタまたはクラス、型は除外される。 対象的にインポートリスト内でCを使うことはクラスまたは型のみに名前をつける。

    インポートされたモジュールによって実際にエクスポートされないエンティティを隠すことはエラーである。

  3. 最後にもしimpspecは省かれるなら、そのとき明示されたモジュールによってエクスポートされるエンティティはインポートされる。

修飾されるインポート

セクション5.3.1のルールの下で各エンティティに対して、トップレベルの環境は拡張される。 もしインポート宣言がqualifiedキーワードを使っていたなら、エンティティの修飾された名前のみがスコープ内へともたらされる。 もしqualifiedキーワードが省略されているなら、そのときエンティティの修飾される名前 修飾されない名前の 両者はスコープの中へともたらされる。 セクション5.5.1では修飾される名前のより詳細を述べていく。

インポートされる名前の修飾子はインポートされるモジュールの名前かimport文のas句に与えられるローカルな別名のどちらかである。 このゆえに、修飾子は必ずしもインポートするエンティティが元々宣言されていたモジュールの名前でなくとも良い。

修飾されない名前を除外するための機能は修飾されない名前空間のプログラマによる完全な制御を許し、 ローカルで定義されたエンティティは修飾子つきでインポートされたものと同じ名前を共有することが出来る。

module Ring where  
import qualified Prelude    -- All Prelude names must be qualified  
import Data.List( nub )  

l1 + l2 = l1 Prelude.++ l2  -- This + differs from the one in the Prelude  
l1 ⋆ l2 = nub (l1 + l2)     -- This ⋆ differs from the one in the Prelude  

succ = (Prelude.+ 1)

ローカルな別名

インポートされるモジュールはas句を使いインポートするモジュールの中でローカルな別名を割り当てられることができる。 例えば、次の

import qualified VeryLongModuleName as C

モジュールの中ではエンティティはVeryLongModuleName.ではなくC.という修飾子を用いて参照されなければならない。 これはインポートされるモジュールで使われる修飾子を変更せずに異なるモジュールをVeryLongModuleNameの代わりにされることも許す。 全ての名前が曖昧さなく解決される限りにおいては、2つ以上のモジュールがスコープ内で同じ修飾子を使用することは正当である。 例えば、

module M where  
    import qualified Foo as A  
    import qualified Baz as A  
    x = A.f 

このモジュールはFooBazが両方fをエクスポートしないときに限り正当である。

as句はqualifiedimportではない式でも使われることができる。

import Foo as A(f)

この宣言はfA.fをスコープの中にもたらす。

上記のインポートルールを明らかにするため、xyをエクスポートするモジュールAを想定してほしい。 そのときこの表は名前が明記されたインポート式によってスコープの中へもたらされることを示す。

インポート宣言スコープの中にもたらされる名前
import A x, y, A.x, A.y
import A() (nothing)
import A(x) x, A.x
import qualified A A.x, A.y
import qualified A() (nothing)
import qualified A(x) A.x
import A hiding () x, y, A.x, A.y
import A hiding (x) y, A.y
import qualified A hiding () A.x, A.y
import qualified A hiding (x) A.y
import A as B x, y, B.x, B.y
import A as B(x) x, B.x
import qualified A as B B.x, B.y

全てのケースで、モジュールAのスコープの中の全インスタンス宣言はインポートされる(セクション5.4)。

事前定義された型とクラス

入出力の基本

Foreign Function Interface

標準 Prelude

文法リファレンス

慣習的表記

以下の慣習的表記が文法を表現するのにつかわれる:

  • [pattern] 省略可能
  • {pattern} 0回以上の繰り返し
  • (pattern) グループ化
  • pat1 | pat2 選択
  • pat<pat'> 差(patによって生成された要素で、pat'で生成されたものを除いたもの)
  • fibonacci タイプライターフォントで表記される終端文法

BNFのような文法をレポートを通して用いる。文法の生成は次のような形をしている:

nonterm -> alt1 | alt2 | .. | altn

字句文法、文脈自由文法いずれも曖昧さが残るが、これは文法語句をできる限り長く取り、左から右に進む(shift-reduceパースでは、shift/reduceコンフリクトはシフトを取ることで解決する)ことで解決するものとする。字句文法では、これは「最長一致」と呼ばれるルールである。文脈自由文法では、これは条件式やlet式、ラムダ抽象などが右方向に取れるだけ長くとることを表す。

字句文法

program {lexeme | whietespace}
lexemeqvarid | qconid | qvarsym | qconsym
| literal | special | reservedop | reservedid
literal integer | float | char | string
special ( | ) | , | ; | [ | ] | ` | { | }
whitespace whitestuff {whitestuff}
whitestuff whitechar | comment | ncomment
whitechar newline | vertab | space | tab | uniWhite
newline return linefeed | return | linefeed | formfeed
return キャレッジ⏎
linefeed 改行
vertab 垂直タブ
formfeed 改ページ
space 空白
tab 水平タブ
uniWhite 空白として定義されたUnicode文字
comment dashes [ anysymbol {any} ] newline
dashes -- {-}
opencom {-
closecom -}
ncomment opencom ANY seq {ncomment ANY seq} closecom
ANY seq {ANY}⟨{ANY} ( opencom | closecom ) {ANY}⟩
ANY graphic | whitechar
any graphic | space | tab
graphic small | large | symbol | digit | special | " | '
small ascSmall | uniSmall | _
ascSmall a | b | … | z
uniSmall 小文字Unicode
large ascLarge | uniLarge
ascLarge A | B | … | Z
uniLarge 任意の大文字またはタイトルケースのユニコード文字
(訳注: タイトルケース 先頭のみ大文字で後は小文字にするスタイル)
symbol ascSymbol | uniSymbolspecial | _ | " | '
ascSymbol! | # | $ | % | & | | + | . | / | < | = | > | ? | @
| \ | ^ | | | - | ~ | :
uniSymbol Unicodeのシンボル、または句読点
digit ascDigit | uniDigit
ascDigit 0 | 1 | … | 9
uniDigit 10進数Unicode
octit 0 | 1 | … | 7
hexit digit | A | … | F | a | … | f
varid (small {small | large | digit | ' })reservedid
conid large {small | large | digit | ' }
reservedid case | class | data | default | deriving | do | else
foreign | if | import | in | infix | infixl
infixr | instance | let | module | newtype | of
then | type | where | _
varsym( symbol: {symbol} )⟨reservedop | dashes⟩
consym( : {symbol})⟨reservedop⟩
reservedop.. | : | :: | = | \ | | | <- | -> | @ | ~ | =>
varid (変数)
conid (コンストラクタ)
tyvarvarid(型変数)
tyconconid(型コンストラクタ)
tyclsconid(型クラス)
modid{conid .} conid(モジュール)
qvarid[ modid . ] varid
qconid[ modid . ] conid
qtycon[ modid . ] tycon
qtycls[ modid . ] tycls
qvarsym[ modid . ] varsym
qconsym[ modid . ] consym
decimaldigit{digit}
octaloctit{octit}
hexadecimalhexit{hexit}
integerdecimal
| 0o octal | 0O octal
| 0x hexadecimal | 0X hexadecimal
floatdecimal . decimal [exponent
| decimal exponent
exponent(e | E) [+ | -] decimal
char' (graphic' | \ | space | escape\&) '
string" {graphic" | \\ | space | escape | gap} "
escape\\ ( charesc | ascii | decimal | o octal | x hexadecimal )
charesca | b | f | n | r | t | v | \\ | " | ' | &
ascii^cntrl | NUL | SOH | STX | ETX | EOT | ENQ | ACK
| BEL | BS | HT | LF | VT | FF | CR | SO | SI | DLE
| DC1 | DC2 | DC3 | DC4 | NAK | SYN | ETB | CAN
| EM | SUB | ESC | FS | GS | RS | US | SP | DEL
cntrlascLarge | @ | [ | \\ | ] | ^ | _
gap\ whitechar {whitechar} \

レイアウト

セクション2.7([訳注] TODO:リンク)ではレイアウトルールに対する非形式的な議論を見た。このセクションではより正確に定義をする。

Haskellプログラムの意味はそのレイアウトに依存する場合がある。レイアウトが意味に与える効果は波括弧とセミコロンをレイアウトによって決定される位置に追加することで完全に説明できる。このようにして追加されたプログラムの意味は今やレイアウトによって影響を受けない。

レイアウトがプログラムに対して与える影響は、このセクションで波括弧とセミコロンをどのように追加するかを説明することで指定される。仕様は、プログラムの返還を行う関数Lの形で与えられる。Lの入力は次のようなものである:

  • Haskellレポートにある字句文法によって定められた語句の列であって、さらに次のような追加の語句を含む:
    • let, where, do, ofキーワードの後に{が続かない場合、トークン{n}がキーワードの後に挿入される。ただしnは、続くトークンがあればそのインデントを表し、ファイルの終端に達した場合は0を表す。
    • モジュールの最初のトークンが{でもmoduleでもない場合、そのトークンのインデントをnとすると、{n}が先行する。
    • 同じ行で空白のみが最初のトークンに先行する場合、<n>がこの語句に先行する。ここでnは語句のインデントであり、もしインデントが存在しない場合には先の2つのルールの結果として{n}が先行することとなる。(注意: 文字列リテラルはセクション2.6で説明したように、複数行に及ぶこともある。よって次のコードにおいては、\Billの前に<n>が挿入されることはない。なぜなら、これは完全な語句の開始でもなければ,の前でもなく、単純に空白文字によって先行されているだけだからである。)
f = ("Hello \  
        \Bill", "Jake")    
  • "レイアウト文脈"のスタックで、各要素が次のいずれかであるもの:
    • 文脈の囲みを明示することを表すゼロ(すなわち、プログラマは開いた波括弧を書いていた場合) 最も内側の文脈が0である場合、レイアウトトークンは文脈の囲いが終了するか新しい文脈が追加されるまで挿入されない。
    • レイアウト文脈を囲うインデントの段数を表す正の整数

語句の"インデント"とは、語句の最初の文字の段数である。そしてある行のインデントとは、最も左にある語句のインデントである。段数を決定するには、次の慣習に従う固定幅フォントを使っていると仮定する。 - 次の文字 newline, return, linefeed, formfeed は新しい行を開始する。 - 最初の段数は0ではなく、1とする。 - タブは8文字分の幅だけ次の文字位置までの間が空く。 - タブ文字は現在の位置をタブの次の文字位置まで揃えるためにそれに足りるだけのスペースを挿入させる。

レイアウトルールのために、ソースコード中のユニコード文字もASCII文字と同じ固定された幅をもつものとみなされる。しかし見た目の混乱を防ぐために、プログラマーは見た目に分からないレイアウトの意味を、空白でない文字幅に依存させるようなプログラムを書くことは避けるべきである。

関数適用 L tokens [] は、tokensをレイアウトに依存しないものへの変換である。ここでtokensはモジュールを字句解析して得られた結果で、上で説明したような段数を表す数字を追加している。Lの定義は次のようになっている。ここで、:をストリームのコンストラクタ演算子として、[]を空のストリームとして使っている。

L (< n >: ts) (m : ms)  = ;  :  (L ts (m : ms))             if m = n
                        = }  :  (L (< n >: ts) ms)          if n < m
L (< n >: ts) ms        = L ts ms
L ({n} : ts) (m : ms)   = {  :  (L ts (n : m : ms))         if n > m (ノート) 1)
L ({n} : ts) []         = {  :  (L ts [n])                  if n > 0 (ノート 1)
L ({n} : ts) ms         = {  :  }  :  (L (< n >: ts) ms)    (ノート 2)
L (} : ts) (0 : ms)     = }  :  (L ts ms)                   (ノート 3)
L (} : ts) ms           = parse-error                       (ノート 3)
L ({ : ts) ms           = {  :  (L ts (0 : ms))             (ノート 4)
L (t : ts) (m : ms)     = }  :  (L (t : ts) ms)             if m≠0 and parse-error(t)
                                                            (ノート 5)
L (t : ts) ms           = t  :  (L ts ms)
L [] []                 = []
L [] (m : ms)           = }  :  L [] ms                     if m≠0 (ノート 6)

ノート1 ネストされたコンテキストは文脈の囲い(n > m)よりも深くインデントされなければならない。そうでなければ、Lは失敗し、コンパイラはレイアウトエラーを示すだろう。次は例である。

  f x = let  
           h y = let  
    p z = z  
                 in p  
        in h

ここで、pの定義は、ここではhの定義によって定まっている文脈の囲いのインデントより浅くインデントされている。

ノート2 (例えば)whereの後に出現する最初のトークンがレイアウト文脈の囲いよりもインデントされていなかった場合、そのwhereのブロックは空でなければならず、よって中身のないの波括弧が挿入される。{n}トークンは中身のない波括弧が明示されたを模倣して<n>によって置き換えられる。

ノート3 現在のレイアウト文脈を0と比べることで、明示された閉じ波括弧が明示された開き波括弧のみと対応していることを保証できる。明示された閉じ波括弧が明示されていない開き波括弧と対応している場合はパースエラーが出力される。

ノート4 この句は、ラベル付き構成と更新を含めた(セクション3.15)すべての波括弧の組が明示的なレイアウト文脈として扱われるようにするためのものである。この式はHaskell1.4とは異なっている。

ノート5 横の条件parse-error(t)は次のように解釈される: Lによってこれまでに生成された次のトークンtをもつトークン列がHaskell文法において無効なものから始まっていることを表しており、またLによってこれまでに生成された"}"に続くトークン列がHaskell文法において有効なものから始まっていることを表している場合、parse-error(t)は真である。

m≠0は暗黙的に追加された閉じ波括弧が明示されていない開き波括弧と対応することを確認している。

ノート6 入力に終わりに、保留されている閉じ波括弧が挿入される。非レイアウト文脈に含まれている場合(すなわち、m = 0)、ここでエラーになる。

上のルールのいずれもマッチしない場合、このアルゴリズムは失敗する。例えば入力の最後に到達したときに、非レイアウト文脈が有効であれば、閉じ波括弧が存在しないので失敗することになる。このアルゴリズムによって検知されないエラー条件も一部存在する。例えば、let }である。

ノート1はレイアウト処理がパースエラーによって途中で停止する可能性があることを言っている。例えば

let x = e; y = x in e'

は有効である。なぜならこれは次のように変換されるからである。

let { x = e; y = x } in e'

閉じ波括弧は上のパースエラーのルールにより挿入される。

文芸的コメント

「文芸的コメント」の慣習は、リチャード・バードとフィリップ・ワドラーらがOrwell言語のために初めて導入し、そして次にドナルド・クヌースの「文芸的プログラミング」に影響を与えたものであるが、Haskellのソースコードを記述するためのもう一つのスタイルである。文芸的スタイルはコメントを書くことを、それをデフォルトとすることで推奨している。始めの文字が">"である行はプログラムの一部として扱われ、それ以外の行はすべてコメントとなる。

プログラムの本文は">"で始まる行のみを拾い、">"とそれに続く空白を置き換えることで復元することができる。その結果残る本文の中では、レイアウトやコメントは10章で説明したとおりに適用される。

">"を間違って省略してしまった場合に備えて、空でないコメント行に隣接するプログラム行はエラーになる。ここで空のコメント行とは、空白しか含まないもののことである。

慣習的に、コメントのスタイルはファイル拡張子によって指定される。".hs"であれば通常のHaskellファイルであり、".lhs"であれば文芸的Haskellファイルである。このスタイルを用いると、階乗の簡単なプログラムは次のようになる。

(訳注: 文芸的Haskellに対応するsyntax highlighterがないので通常のHaskellハイライトで代用しています。本来コメントとして扱われるThis literate...This is the factorial...などに色が付いていますがここは上でも説明があった通りコメントです。)

   This literate program prompts the user for a number  
   and prints the factorial of that number:  

> main :: IO ()  

> main = do putStr "Enter a number: "  
>           l <- readLine  
>           putStr "n!= "  
>           print (fact (read l))  

  This is the factorial function.  

> fact :: Integer -> Integer  
> fact 0 = 1  
> fact n = n ⋆ fact (n-1)

文芸的プログラミングという代替スタイルは、文章処理システムのLaTeXを使う際に特に適している。この慣習の下では、\begin{code}...\end{code}デリミタで囲まれた部分全体が文芸的プログラムのプログラム本文として扱われ、その他の行はすべてコメントである。より正確には:

  • プログラムコードは\begin{code}に続く次の行から始まり
  • プログラムコードは\end{code}で始まる行の直前で終わる (文字列リテラルは当然除く)

これらデリミタの前後に余分な空白行を挿入する必要は必ずしもないが、スタイルとしてはそれが望ましいであろう。例えば次のようになる。

\documentstyle{article}  

\begin{document}  

\chapter{Introduction}  

This is a trivial program that prints the first 20 factorials.  

\begin{code}  
main :: IO ()  
main =  print [ (n, product [1..n]) | n <- [1..20]]  
\end{code}  

\end{document}

このスタイルは同じファイル拡張子を用いる。同じファイルに対してこれら2つのスタイルを混ぜるのはおすすめできない。

文脈自由構文

modulemodule modid [exports] where body
| body
body{ impdecls ; topdecls }
| { impdecls }
| { topdecls }
impdeclsimpdecl1 ;; impdecln(n ≥ 1)
exports( export1 ,, exportn [ , ] )(n ≥ 0)
exportqvar
| qtycon [(..) | ( cname1 ,, cnamen )](n ≥ 0)
| qtycls [(..) | ( qvar1 ,, qvarn )](n ≥ 0)
| module modid
impdeclimport [qualified] modid [as modid] [impspec]
| (empty declaration)
impspec( import1 ,, importn [ , ] ) (n ≥ 0)
| hiding ( import1 ,, importn [ , ] ) (n ≥ 0)
importvar
| tycon [ (..) | ( cname1 ,, cnamen )] (n ≥ 0)
| tycls [(..) | ( var1 ,, varn )] (n ≥ 0)
cnamevar | con
topdeclstopdecl1 ;; topdecln (n ≥ 0)
topdecltype simpletype = type
| data [context =>] simpletype [= constrs] [deriving]
| newtype [context =>] simpletype = newconstr [deriving]
| class [scontext =>] tycls tyvar [where cdecls]
| instance [scontext =>] qtycls inst [where idecls]
| default (type1 ,, typen) (n ≥ 0)
| foreign fdecl
| decl
decls{ decl1 ;; decln } (n ≥ 0)
declgendecl
| (funlhs | pat) rhs
cdecls{ cdecl1 ;; cdecln } (n ≥ 0)
cdeclgendecl
| (funlhs | var) rhs
idecls{ idecl1 ;; idecln } (n ≥ 0)
idecl(funlhs | var) rhs
| (empty)
gendeclvars :: [context =>] type (type signature)
| fixity [integer] ops (fixity declaration)
| (empty declaration)
opsop1 ,, opn (n ≥ 1)
varsvar1 , …, varn (n ≥ 1)
fixityinfixl | infixr | infix
typebtype [-> type] (function type)
btype[btype] atype (type application)
atypegtycon
| tyvar
| ( type1 ,, typek ) (tuple type, k ≥ 2)
| [ type ] (list type)
| ( type ) (parenthesized constructor)
gtyconqtycon
| () (unit type)
| [] (list constructor)
| (->) (function constructor)
| (,{,}) (tupling constructors)
contextclass
| ( class1 ,, classn ) (n ≥ 0)
classqtycls tyvar
| qtycls ( tyvar atype1atypen ) (n ≥ 1)
scontextsimpleclass
| ( simpleclass1 ,, simpleclassn ) (n ≥ 0)
simpleclassqtycls tyvar
simpletypetycon tyvar1tyvark (k ≥ 0)
constrsconstr1 | … | constrn (n ≥ 1)
constrcon [!] atype1 … [!] atypek (arity con = k, k ≥ 0)
| (btype | ! atype) conop (btype | ! atype) (infix conop)
| con { fielddecl1 ,, fielddecln } (n ≥ 0)
newconstrcon atype
| con { var :: type }
fielddeclvars :: (type | ! atype)
derivingderiving (dclass | (dclass1, … , dclassn)) (n ≥ 0)
dclassqtycls
instgtycon
| ( gtycon tyvar1tyvark ) (k ≥ 0, tyvars distinct)
| ( tyvar1 ,, tyvark ) (k ≥ 2, tyvars distinct)
| [ tyvar ]
| ( tyvar1 -> tyvar2 ) tyvar1 and tyvar2 distinct
fdeclimport callconv [safety] impent var :: ftype (define variable)
| export callconv expent var :: ftype (expose variable)
callconvccall | stdcall | cplusplus (calling convention)
| jvm | dotnet
| system-specific calling conventions
impent[string] (see Section 8.5.1)
expent[string] (see Section 8.5.1)
safetyunsafe | safe
ftypefrtype
| fatypeftype
frtypefatype
| ()
fatypeqtycon atype1atypek (k ≥ 0)
funlhsvar apat { apat }
| pat varop pat
| ( funlhs ) apat { apat }
rhs= exp [where decls]
| gdrhs [where decls]
gdrhsguards = exp [gdrhs]
guards| guard1, …, guardn (n ≥ 1)
guardpat <- infixexp (pattern guard)
| let decls (local declaration)
| infixexp (boolean guard)
expinfixexp :: [context =>] type (expression type signature)
| infixexp
infixexplexp qop infixexp (infix operator application)
| - infixexp (prefix negation)
| lexp
lexp\ apat1apatn -> 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)
aexpqvar (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)
qualpat <- exp (generator)
| let decls (local declaration)
| exp (guard)
altsalt1 ;; altn (n ≥ 1)
altpat -> exp [where decls]
| pat gdpat [where decls]
| (empty alternative)
gdpatguards -> exp [ gdpat ]
stmtsstmt1stmtn exp [;] (n ≥ 0)
stmtexp ;
| pat <- exp ;
| let decls ;
| ; (empty statement)
fbindqvar = exp
patlpat qconop pat (infix constructor)
| lpat
lpatapat
| - (integer | float) (negative literal)
| gcon apat1apatk (arity gcon = k, k ≥ 1)
apatvar [ @ 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)
fpatqvar = pat
gcon()
| []
| (,{,})
| qcon
varvarid | ( varsym ) (variable)
qvarqvarid | ( qvarsym ) (qualified variable)
conconid | ( consym ) (constructor)
qconqconid | ( gconsym ) (qualified constructor)
varopvarsym | ` varid ` (variable operator)
qvaropqvarsym | ` qvarid ` (qualified variable operator)
conopconsym | ` conid ` (constructor operator)
qconopgconsym | ` qconid ` (qualified constructor operator)
opvarop | conop (operator)
qopqvarop | qconop (qualified operator)
gconsym: | qconsym

結合性解決

次はHaskellの式における結合性解決の実装例である。結合性解決はHaskellのパターンに対しても適用可能であるが、パターンは式のサブセットであるので、以下では簡単のため式のみを考えることにする。

resolve関数はリストであって、それぞれの要素が式または演算子であるもの、すなわち、文脈自由文法において非終端記号infixexpのインスタンスとなるもの(訳注: 上の文法表でinfixexpに当てはまるもの、の意味)を受け取る。resolveJust e(ここでeは解決された式)または、入力が意味のある式を表現していない場合にはNothingを返す。もちろん、コンパイラにおいては有益なエラーメッセージを生成するという目的のためには関係する演算子についての情報をより多く返す方がよいであろうが、ここではアルゴリズムを説明するのにMaybe型で十分である。

import Control.Monad  

type Prec   = Int  
type Var    = String  

data Op = Op String Prec Fixity  
  deriving (Eq,Show)  

data Fixity = Leftfix | Rightfix | Nonfix  
  deriving (Eq,Show)  

data Exp = Var Var | OpApp Exp Op Exp | Neg Exp  
  deriving (Eq,Show)  

data Tok = TExp Exp | TOp Op | TNeg  
  deriving (Eq,Show)  

resolve :: [Tok] -> Maybe Exp  
resolve tokens = fmap fst $ parseNeg (Op "" (-1) Nonfix) tokens  
  where  
    parseNeg :: Op -> [Tok] -> Maybe (Exp,[Tok])  
    parseNeg op1 (TExp e1 : rest)  
       = parse op1 e1 rest  
    parseNeg op1 (TNeg : rest)  
       = do guard (prec1 < 6)  
            (r, rest') <- parseNeg (Op "-" 6 Leftfix) rest  
            parse op1 (Neg r) rest'  
       where  
          Op _ prec1 fix1 = op1  

    parse :: Op -> Exp -> [Tok] -> Maybe (Exp, [Tok])  
    parse _   e1 [] = Just (e1, [])  
    parse op1 e1 (TOp op2 : rest)  
       -- case (1): 不当な式をチェック
       | prec1 == prec2 && (fix1 /= fix2 || fix1 == Nonfix)  
       = Nothing  

       -- case (2): op1とop2は左結合であるべきである
       | prec1 > prec2 || (prec1 == prec2 && fix1 == Leftfix)  
       = Just (e1, TOp op2 : rest)  

       -- case (3): op1とop2は右結合であるべきである
       | otherwise  
       = do (r,rest') <- parseNeg op2 rest  
            parse op1 (OpApp e1 op2 r) rest'  
       where  
         Op _ prec1 fix1 = op1  
         Op _ prec2 fix2 = op2

このアルゴリズムは次のように働く。各段階において関数呼び出し

parse op1 E1 (op2 : tokens)

があるが、これは次のような式であることを確かめていることを意味している。(ここで、呼び出し側がE0を保持している)

E0 ‘op1‘ E1 ‘op2‘ ...     (1)

parseの仕事はop1の右側の式を構築し、入力の残りの部分を返すことである。

3つの場合を考慮する必要がある:

  1. op1op2が同じ優先度であるが、同じ結合性を持たない場合あるいは不結合(nonfix)であることが宣言されている場合には、式は不当である。
  2. op1op2よりも高い優先度であるかop1op2が左結合である場合には、op1の右側の式はE1であることがわかり、よってこれを呼び出し側に返却する。
  3. いずれでもない場合、E1 `op2` Rの形の式が求めているものであることがわかる。Rを見つけるには、parseNeg op2 tokensを呼び出してop2の右側の式を計算し、これを今はRと名前を付ける(parseNeg以下の部分についての詳細は以下に示すが、本質的には、もしもtokens(E2 : tokens)の形をしている場合、parseNegparse op2 E2 restと同値である)。さて、E0 `op1` (E1 `op2` R) `op3` ...の形を得た、ここでop3は入力に出現する次の演算子である。これは上の(1)の場合の具体的な例になっているから、E1 == (E1 `op2` R)と新たに置いてparseを続いて呼び出す。

アルゴリズムを初期化するには、op1は他の何よりも優先度の低い仮想的な演算子であるとおく。そしてparseは入力全体を消費し結果の式を返す。

前置マイナス演算子の-の扱いは若干込み入っている。前置のマイナスは中置のマイナスと同じ結合性であったことを思い出そう、いずれも左結合で優先度は6だ。-の左にくる演算子は、もし存在すれば、式が正当であるためには優先度が6より低くなければならない。マイナス演算子そのものは同じ結合性を持つ演算子に対しては左結合的にはたらく(例: +)。よって例えば-a + bは正当であり(-a) + bとして解決されるが、a + -bは不当である。

関数parseNegは前置マイナスを処理する。前置演算子に遭遇して、それがその位置で正当であれば(左側にある演算子の優先度が6より低ければ)、上の(3)のケースと同様にして進めていく。つまり、-の引数をparseNegを再帰的に呼ぶことで計算し、そしてparseを呼んで続けていく。

このアルゴリズムは優先度の範囲と解決には影響されないことに注意せよ。原則として、Haskellの優先度が1から10までの範囲の整数に制限される理由は何もない。より広い範囲や分数の値を使うことで特別に難しくなるわけではない。

インスタンス導出の仕様

インスタンス導出とは、データまたはnewtype宣言から自動的に生成されるインスタンス宣言である。インスタンス導出のコード本体は対応する型の定義から文法的に導出される。インスタンス導出はコンパイラが知っているクラスに対してのみ可能である。このようなクラスはPreludeまたは標準ライブラリで定義されている。この章では、Preludeによって定義されたクラスの導出について説明する。

Tが次のように定義された代数的データ型であるとする:

data cx => T u1 ... uk = K1 t11 ... t1k1 | ... | Kn tn1 ... tnkn
    deriving (C1, ..., Cm)

(ここで、m ≥ 0であるとする。また、m = 1のときは括弧は省略できる)

そしてクラスCに対するインスタンス導出宣言は次の条件を満たすときに可能である:

  1. CEq, Ord, Enum, Bounded, Show, Readのいずれかである
  2. 文脈cx'であって、cx' => C tijが構成に使われているすべての型tijについて成り立つようなものが存在する
  3. CBoundedであるとき、型は列挙型である(どのコンストラクタもパラメーターを持たない)か、唯一のコンストラクタをもつ
  4. CEnumであるとき、型は列挙型である
  5. T u1 ... ukCのインスタンスになるような明示的なインスタンス宣言がプログラムのどこにも存在しない
  6. データ宣言がコンストラクタをもたないとき(上でn = 0のとき)、どのクラスも導出可能でない(m = 0である)

インスタンスを導出するという目的から、newtype宣言はdata宣言で1つコンストラクタをもつものとして扱われる。

derivingの形があるとき、各クラスCiに対するT u1 ... ukのインスタンス宣言は自動的に生成される。どれかのCiに対してインスタンス宣言が導出不可能であるとき、静的エラーが返る。インスタンス導出が必要でないときは、derivingの形は省略されるか、deriving ()が使われることもある。

それぞれの導出されたインスタンス宣言は instance (cx, cx') => Ci (T u1 ... uk) where { d } の形をしている。ただしdCiTのデータ型宣言に依存して自動的に導出される(これについてはこのセクションの残りで説明する))。

コンテキスト cx'は上の(2)を満たす最小のコンテキストである。相互再帰データ型でこれを計算するためにコンパイラは不動点の計算を行う必要があることがある。

Preludeのクラスで導出可能なそれぞれについてのインスタンス導出の詳細をここで与える。ここでの説明で登場する自由変数とコンストラクタはすべてPreludeで定義されたものを指している。

EqOrdのインスタンス導出

EqOrdのインスタンス導出によって自動的に導入されるクラスメソッドは (==), (/=), compare, (<), (<=), (>), (>=), max, minである。最後の7つの演算子は各コンストラクタの集合ごとに辞書順で比較を行うように定義される。すなわち、データ型宣言で先に書かれたコンストラクタは後に書かれたコンストラクタよりも小さい値として扱われる。例えば、Boolデータ型に対して、(True > False) == Trueが成り立つ。

導出された比較はコンストラクタを常に左から右に向かって走査する。このことは次の例から確かめられる:

(1,undefined) == (2,undefined) =>    False
(undefined,1) == (undefined,2) =>    ⊥

導出されたクラスEqOrdの演算はいずれも引数に対して正格である。例えば、FalseBool型の最初のコンストラクタであるがFalse <= ⊥である。

Enumのインスタンス導出

Enumのインスタンス導出宣言は列挙型(すべてのコンストラクタが引数をとらないようなデータ型)に対してのみ可能である。

引数をとらないコンストラクタはは左から右に向かって0からn-1までナンバリングされていると仮定する。このナンバリングの仕組みの下でsuccpredは値の次の値と前の値を与える演算子である。succを最大の要素に適用したときとpredを最小の要素に適用したときはエラーになる。

toEnumfromEnumは列挙された値をIntへ、またはIntから変換する演算子である。toEnumIntの引数に一致するコンストラクタの番号がない場合は実行時エラーになる。

他のメソッドの定義は次である:

enumFrom x           = enumFromTo x lastCon
enumFromThen x y     = enumFromThenTo x y bound
    where  
        bound
            | fromEnum y >= fromEnum x = lastCon
            | otherwise                = firstCon
enumFromTo x y       = map toEnum [fromEnum x .. fromEnum y]
enumFromThenTo x y z = map toEnum [fromEnum x, fromEnum y .. fromEnum z]

ここで、firstConlastConはそれぞれdata宣言にある最初と最後のコンストラクタである。例として、データ型

data Color = Red | Orange | Yellow | Green deriving (Enum)

が与えられたとき、次のようになる:

[Orange ..]         ==  [Orange, Yellow, Green]  
fromEnum Yellow     ==  2

Boundedのインスタンス導出

BoundedクラスはminBoundmaxBoundをクラスメソッドとして導入するが、これはそれぞれその型の最小、最大の要素を定めるメソッドである。列挙型に対しては、data宣言にある最初と最後のコンストラクタが境界になる。コンストラクタが1つの型に対しては、コンストラクタを構成型の教会に適用したものになる。例として、次のデータ型を考える。

data Pair a b = Pair a b deriving Bounded

これは次のようなBoundedインスタンスを生成する:

instance (Bounded a,Bounded b) => Bounded (Pair a b) where  
    minBound = Pair minBound minBound  
    maxBound = Pair maxBound maxBound

ReadShowのインスタンス導出

ReadShowのインスタンス導出で自動的に導入されるクラスメソッドはshowsPrec, readsPrec, showList, readListである。これらは値を文字列に変換し、文字列をパースして値にするために使われる。

関数showsPrec d x rは優先度d(0から11の間の数値をとる)、値x、文字列rを受ける。これはrxの文字列表現を結合したものを返す。showsPrecは次の規則を満たす: showsPrec d x r ++ s == showsPred d x (r ++ s) この表現は、xのトップレベルコンストラクタの優先度がdよりも小さいときは括弧で囲われる。追加のパラメータrは木などの構造を木のサイズに対して二次関数的でなく線形時間で出力するためには必須になる。

関数readsPrec d sは優先度d(0から10の数値をとる)と文字列sを受け取り、文字列の先頭から値をパースすることを試み、(パースされた値, 残りの文字列)なるペアのリストを返す。パースに成功することがない場合、返却されるリストは空になる。括弧のない中置演算子の適用のパースが成功するのは演算子の優先度がd以上の時だけである。

次が成り立つ。

(x,"") は (readsPrec d (showsPrec d x "")) の要素である

つまり、readsPrecshowsPrecによって生成された文字列をパースでき、showsPrecに初めに渡された値が得られるべきである。

showListreadListは標準的でない表示を使ってオブジェクトのリストを表現できるようにする。

readsPrecは文字列以外の標準的な型のどんな有効な表現もパースできる。文字列に対してはクォートされた文字列のみが許され、他にはリストに対しては、ブラケットで囲まれた形[...]のみが許される。詳細は9章([訳注] リンク)を見よ。

showの結果は、結合性の宣言がその型が宣言された時点で有効になっている場合であれば、定数のみを含んだHaskellの文法的に正しい式になる。戻り値は、データ型で定義されたコンストラクタの名前と括弧、スペースのみを含む。ラベル付きコンストラクタフィールドが使われているときは、波括弧、コンマ、フィールド名、等号も使われる。括弧は結合性を無視して必要なところでだけ追加される。showの戻り値は、すべてのコンポーネント型がread可能ならばreadできる。(これはPreludeで定義されたすべてのインスタンスに対しては正しいが、ユーザーによって定義されたインスタンスではそうとは限らない。)

Readのインスタンス導出によって、次のようなことが仮定される。そしてこれらの過程はShowのインスタンス導出も従う:

  • コンストラクタが中置演算子として定義されているならば、Readインスタンスの導出はコンストラクタの(前置形ではなく)中置適用のみをパースする。

  • 結合性は括弧を減らすためには使われないが、優先度はそうと使われる場合がある。例として

    infixr 4 :$
    data T = Int :$ T | NT
    

    があったとき、

    • show (1 :$ 2 :$ NT)は文字列"1 :$ (2 :$ NT)"を生成する。
    • read "1 :$ (2 :$ NT)"は成功し、当然の結果を返す。
    • read 1 :$ 2 :$ NTは失敗する。
  • コンストラクタがレコード構文により定義されている場合、導出されたReadはレコード構文の形のみパースし、さらにフィールドは元々の宣言と同じ順番でしか与えることはできない。

  • Readインスタンスの導出は入力文字列のトークンの間に任意のHaskellの空白を許す。余分な括弧も許される。

ReadShowインスタンスの導出が不適切なケースもある。次のような問題がある:

  • 循環構造はこれらのインスタンスによっては出力、読み取りできない。
  • 出力によって部分構造の共有が失われる。つまり、オブジェクトが出力された表現は必要より遥かに大きくなる場合がある。
  • readで使われるパースの方法は非常に非効率的であるので、巨大な構造の読み取りは非常に遅い場合がある。
  • Preludeで定義された型の出力はユーザーによる制御ができない。例えば、浮動小数点数のフォーマットを変える方法がない。

完結した例として木構造を考えよう:

data Tree a = Leaf a | Tree a :^: Tree a
    deriving (Eq, Ord, Read, Show)

Treeは列挙型でもなければコンストラクタが1つでもないので、BoundedEnumのインスタンスの自動導出はできない。Treeのインスタンス宣言の完全なものはFigure 11.1にある。デフォルトクラスメソッド定義に注意せよ。例えば、Ordに対しては<=のみが定義されており、ほかのクラスメソッド(<,>,>=,max,min)はFigure 6.1に示されている、クラス宣言で与えられたデフォルト値によって与えられている。

Figure11.1: インスタンス導出の例

infixr 5 :^:  
data Tree a =  Leaf a  |  Tree a :^: Tree a  
 
instance (Eq a) => Eq (Tree a) where  
        Leaf m == Leaf n  =  m==n  
        u:^:v  == x:^:y   =  u==x && v==y  
             _ == _       =  False  
 
instance (Ord a) => Ord (Tree a) where  
        Leaf m <= Leaf n  =  m<=n  
        Leaf m <= x:^:y   =  True  
        u:^:v  <= Leaf n  =  False  
        u:^:v  <= x:^:y   =  u<x || u==x && v<=y  
 
instance (Show a) => Show (Tree a) where  
 
        showsPrec d (Leaf m) = showParen (d > app_prec) showStr  
          where  
             showStr = showString "Leaf " . showsPrec (app_prec+1) m  
 
        showsPrec d (u :^: v) = showParen (d > up_prec) showStr  
          where  
             showStr = showsPrec (up_prec+1) u .  
                       showString " :^: "      .  
                       showsPrec (up_prec+1) v  
                -- Note: right-associativity of :^: ignored  
 
instance (Read a) => Read (Tree a) where  
 
        readsPrec d r =  readParen (d > up_prec)  
                         (\r -> [(u:^:v,w) |  
                                 (u,s) <- readsPrec (up_prec+1) r,  
                                 (":^:",t) <- lex s,  
                                 (v,w) <- readsPrec (up_prec+1) t]) r  
 
                      ++ readParen (d > app_prec)  
                         (\r -> [(Leaf m,t) |  
                                 ("Leaf",s) <- lex r,  
                                 (m,t) <- readsPrec (app_prec+1) s]) r  
 
up_prec  = 5    -- :^: の結合優先度
app_prec = 10   -- 適用はどんな結合優先度の高い演算子よりも優先度が高い

コンパイラプラグマ

一部のコンパイラ実装は コンパイラプラグマ と呼ばれる仕組みを備えている。これはコンパイラに対して命令を追加したりヒントを与えるようなものであるが、これは正確にはHaskell言語の規格には含まれず、よってプログラムの意味を変えない。この章では実際に使われてきた実践をまとめたものである。実装が各プラグマを提供することは必須ではないが、実装によって認識されないプラグマは無視されるべきである。実際に多くの言語拡張が使われていることもあり、実装は以下に述べるLANGUAGEプラグマをサポートすることが強く推奨されている。

トークンとしては、プラグマは {-##-} で囲まれる文法であることを除いてはコメントと同じ方法で記される。

インライン化

decl -> {-# INLINE qvars #-}
decl -> {-# NOINLINE qvars #-}

INLINEプラグマは、コンパイラが指定の変数を使用する際にインライン化するよう指示する。コンパイラは単純な式のインライン化をしばしば自動的に行う。NOINLINEプラグマによってインライン化を止めることもある。

特殊化

decl -> {-# SPECIALIZE spec_1, ..., spec_k #-}   (k >= 1)
spec -> vars :: type

特殊化はオーバーロードされた関数が非効率に実行されることを防ぐ目的で使われる。例えば、次のプログラム

factorial :: Num a => a -> a  
factorial 0 = 0  
factorial n = n ⋆ factorial (n-1)  
{-# SPECIALIZE factorial :: Int -> Int,  
               factorial :: Integer -> Integer #-}

では、factorialの呼び出しで、コンパイラが渡されたパラメーターがIntまたはIntegerであることを検出すると、数値オーバーロードされた方ではなく特殊化されたfactorialを用いる。

言語拡張

LANGUAGEプラグマはファイルの先頭に記述するプラグマ(ファイルヘッダープラグマ)である。ファイルヘッダープラグマはソースファイルでモジュールキーワードよりも前に置かなければならない。ファイルヘッダープラグマは好きなだけ書くことができるし、コメントよりも先に書いても後に書いてもよい。個々のLANGUAGEプラグマはLANGUAGEキーワードで始まり、カンマで区切られた、言語機能の名前の列を続けて書く。

例えば、スコープ付き型変数とCPPによるプリプロセッシングを有効にしたいのであれば、使っているHaskell実装がそれらをサポートしていれば、

{-# LANGUAGE ScopedTypeVariables, CPP #-}

と書くことができる。

Haskellの実装がソースファイルで要求された特定の言語機能を認識またはサポートしない(または要求された言語機能を組み合わてサポートできない)場合は、いかなる方法によるコンパイルでも、あるいはいかなる方法でそのファイルとHaskell実装を用いても、エラーによって失敗しなければならない。

移植性の観点から、サポートされた同じ言語機能を有効にする場合はどのような仕方でも明確に認められている(例: コマンドライン引数、あるいは実装が指定する依存関係や標準的でないプラグマを経由するなど)。LANGUAGEプラグマをサポートするHaskell 2010実装は

{-# LANGUAGE Haskell2010 #-}

をサポートしなければならない。

そのような実装はさらに次のような名前の言語機能をサポートすることが推奨されている:

PatternGuards, NoNPlusKPatterns, RelaxedPolyRec,  
EmptyDataDecls, ForeignFunctionInterface

これらはHaskell 2010以前、一部の実装でサポートされていたものがこのレポートに入ることになった言語拡張である。

Control.Monad

module Control.Monad (  
    Functor(fmap),  Monad((>>=), (>>), return, fail),  MonadPlus(mzero, mplus),  
    mapM,  mapM_,  forM,  forM_,  sequence,  sequence_,  (=<<),  (>=>),  (<=<),  
    forever,  void,  join,  msum,  filterM,  mapAndUnzipM,  zipWithM,  
    zipWithM_,  foldM,  foldM_,  replicateM,  replicateM_,  guard,  when,  
    unless,  liftM,  liftM2,  liftM3,  liftM4,  liftM5,  ap  
  ) where

Control.MonadモジュールはFunctor, Monad, MonadPlusクラスと、いくつかのモナド上の便利な演算子を提供する。

(訳注: 以下の説明は現状のGHCの提供するものとは異なっています。)

FunctorとMonadクラス

型クラス

class Functor f where

Functorクラスはそれ上にマップできるような型に使われる。Functorインスタンスは次の法則を満たすべきである:

  • fmap id == id
  • fmap (f . g) == fmap f . fmap g

リスト、Data.Maybe.MaybeSystem.IO.IOに対するFunctorのインスタンスはこの法則を満たす。

メソッド

fmap :: (a -> b) -> f a -> f b

インスタンス

instance Functor []
instance Functor IO
instance Functor Maybe
instance Ix i => Functor (Array i)

型クラス

class Monad f where

Monadクラスはモナド上の基本的な演算子を定義する。モナド圏論と呼ばれる数学の一分野から来た概念である。しかしながら、Haskellプログラマの観点からはモナドはアクションの抽象データ型のことであると思うのが最適である。Haskellのdo式はモナディックな式を書くための便利な構文を提供している。

最小完全定義: >>=return

Monadのインスタンスは次の法則をみたすべきである:

  • return a >>= k == k a
  • m >>= return == m
  • m >>= (\x -> k x >>= h) == (m >>= k) >>= h

MonadFunctorのインスタンスは追加で次の法則も満たすべきである。

  • fmap f xs == xs >>= return . f

Preludeで定義されているリスト、Data.Maybe.MaybeSystem.IO.IOに対するMonadのインスタンスはこの法則を満たす。

メソッド

(>>=) :: m a -> (a -> m b) -> m b

最初のアクションによって生成された値を2つ目のアクションの引数として渡すことで連続して2つのアクションを合成する。

(>>) :: m a -> m b -> m b

最初のアクションによって生成された値を捨てることで連続して2つのアクションを合成する。これは手続き型言語での順列を表す演算子(例えばセミコロン)のようなものである。

return :: a -> m a

値をモナディックな型に注入する。

fail :: String -> m a

メッセージとともに失敗する。この演算子はモナドの数学的な定義の一部ではないが、do式の中でパターンマッチに失敗した際に呼ばれる。

インスタンス

instance Monad []
instance Monad IO
instance Monad Maybe

型クラス

class Monad m => MonadPlus m where

モナドであって、選択と失敗を備えたもの。

メソッド

mzero :: m a

mplusの単位元。さらに次の法則も満たすべきである:

  • mzero >>= f = mzero
  • v >> mzero = mzero
mplus :: m a -> m a -> m a

結合的な演算子。

インスタンス

instance MonadPlus []
instance MonadPlus Maybe

関数

名前付けの慣習

このライブラリの関数は次のような名前付けの慣習に従っている。

  • Mが後ろにつくものはクライスリ圏での関数を表す。すなわち、モナド型コンストラクタmが関数の結果(カリー化していない形)に対して付けられ、それ以外にはつかない。よって、例としては次:
filter  ::              (a ->   Bool) -> [a] ->   [a]  
filterM :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
  • _が後ろにつくものは結果の型が(m a)から(m ())になる。よって、例としては次:
sequence  :: Monad m => [m a] -> m [a]  
sequence_ :: Monad m => [m a] -> m ()
  • mが先頭につくものは、すでに存在する関数をモナディックな形に一般化したものである。よって、例としては次:
sum  :: Num a       => [a]   -> a  
msum :: MonadPlus m => [m a] -> m a

Monadの基本的な関数

mapM :: Monad m => (a -> m b) -> [a] -> m [b]

mapM fsequence . map fと同値である。

mapM_ :: Monad m => (a -> m b) -> [a] -> m ()

mapM_ fsequence_ . map fと同値である。

forM :: Monad m => [a] -> (a -> m b) -> m [b]

forMmapMの引数を逆にしたものである。

forM_ :: Monad m => [a] -> (a -> m b) -> m ()

forM_mapM_の引数を逆にしたものである。

sequence :: Monad m => [m a] -> m [a]

各アクションを左から右に順番に評価し、結果を集める。

sequence_ :: Monad m => [m a] -> m ()

各アクションを左から右に順番に評価し、結果を無視する。

(=<<) :: Monad m => (a -> m b) -> m a -> m b

>>=と同じで、引数が入れ替えたもの。

(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c

左から右へのモナドのクライスリ合成。

(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> a -> m c

右から左へのモナドのクライスリ合成。>>=の引数を逆にしたもの。

forever :: Monad m => m a -> m b

forever actはアクションを無限に繰り返す。

void :: Functor f => f a -> f ()

void valueIOアクションの結果の値といった評価の結果を捨て、または無視する。

リストの関数の一般化

join :: Monad m => m (m a) -> m a

join関数はモナドの従来のjoin演算子である。モナドの構造を1レベル取り除き、(束縛されている)引数をより外側のレベルへ射影するために使われる。

msum :: MonadPlus m => [m a] -> m a

リストに対するconcat関数の一般化である。

filterM :: Monad m => (a -> m Bool) -> [a] -> m [a]

リストに対するfilter関数の一般化である。

mapAndUnzipM :: Monad m => (a -> m (b, c)) -> [a] -> m ([b], [c])

mapAndUnzipM関数は第一引数をリストに適用し、ペアのリストを結果として返す。この関数は複雑なデータや状態変化を行うようなモナドで主に使われる。

zipWithM :: Monad m => (a -> b -> m c) -> [a] -> [b] -> m [c]

zipWithM関数はzipWithを任意のモナドに一般化したものである。

zipWithM_ :: Monad m => (a -> b -> m c) -> [a] -> [b] -> m ()

zipWithM_zipWithMの拡張で、最後の結果を無視するものである。

foldM :: Monad m => (a -> b -> m a) -> a -> [b] -> m a

foldM関数はfoldlに似たものであるが、結果がモナドに包まれているところが異なる。foldMは引数であるリストを左から右に向かって走査することに注意せよ。このことは、>>と"畳み込み関数"が可換でない場合に問題になりうる。

       foldM f a1 [x1, x2, ..., xm]
==

       do  
         a2 <- f a1 x1  
         a3 <- f a2 x2  
         ...  
         f am xm

右から左に向かった評価が必要であれば、入力のリストを反転させればよい。

foldM_ :: Monad m => (a -> b -> m a) -> a -> [b] -> m ()

foldMに近いが、結果を捨てる。

replicateM :: Monad m => Int -> m a -> m [a]

replicateM n actはアクションをn回行い、結果を集める。

replicateM_ :: Monad m => Int -> m a -> m ()

replicateMに近いが、結果を捨てる。

モナディックな式の条件付き実行

guard :: MonadPlus m => Bool -> m ()

guard bbTrueであればreturn ()であり、bFalseであればmzeroである。

when :: Monad m => Bool -> m () -> m ()

モナディックな式の条件付き実行である。例えば、

       when debug (putStr "Debugging\n")

はブール値debugTrueであれば文字列Debugging\nを出力し、そうでなければ何もしない。

unless :: Monad m => Bool -> m () -> m ()

whenの逆である。

モナディックな持ち上げ演算子

liftM :: Monad m => (a1 -> r) -> m a1 -> m r

関数をモナドに持ち上げる。

liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r

関数をモナドに持ち上げ、モナディックな引数を左から右へと走査する。例えば、

    liftM2 (+) [0,1] [0,2] = [0,2,1,3]  
    liftM2 (+) (Just 1) Nothing = Nothing
liftM3 :: Monad m => (a1 -> a2 -> a3 -> r)
                     -> m a1 -> m a2 -> m a3 -> m r

関数をモナドに持ち上げ、モナディックな引数を左から右へと走査する。(liftM2を参照)

liftM4 :: Monad m => (a1 -> a2 -> a3 -> a4 -> r)
                     -> m a1 -> m a2 -> m a3 -> m a4 -> m r

関数をモナドに持ち上げ、モナディックな引数を左から右へと走査する。(liftM2を参照)

liftM5 :: Monad m => (a1 -> a2 -> a3 -> a4 -> a5 -> r)
                     -> m a1 -> m a2 -> m a3 -> m a4 -> m a5 -> m r

関数をモナドに持ち上げ、モナディックな引数を左から右へと走査する。(liftM2を参照)

ap :: Monad m => m (a -> b) -> m a -> m b

多く場合liftM演算子は、(関数適用を持ち上げる)apを使用したものに置き換えることができる。

       return f ‘ap‘ x1 ‘ap‘ ... ‘ap‘ xn

は次と等しい。

       liftMn f x1 x2 ... xn

Data.Array

Data.Bits

Data.Char

Data.Complex

Data.Int

Data.Ix

Data.List

Data.Maybe

Data.Ratio

Data.Word

Foreign

Foreign.C

Foreign.C.Error

Foreign.C.String

Foreign.C.Types

Foreign.ForeignPtr

Foreign.Marshal

Foreign.Marshal.Alloc

Foreign.Marshal.Array

Foreign.Marshal.Error

Foreign.Marshal.Utils

Foreign.Ptr

Foregin.StablePtr

Foregin.Storable

Numeric

System.Environment

Sytem.Exit

System.IO

System.IO.Error

参考文献