Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに...

216
論理関数型言語 Mercury 入門 高島 尚希 2014 4 8

Transcript of Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに...

Page 1: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

論理関数型言語Mercury入門

高島 尚希

2014年 4月 8日

Page 2: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。
Page 3: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

3

はじめに

Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。プロ

グラミングを行うときの考え方のことをプログラミングパラダイムとよびます。Mercury は論理

関数型言語とよばれており、Prolog などの論理型のパラダイムと Haskellなどの関数型のパラダ

イムを組み合わせたプログラミング言語です。論理型と関数型という異なる 2つのパラダイムを組

み合わせることによって、従来のプログラミング言語では思いもよらないような方法でプログラム

を記述することが可能になります。論理型と関数型の組み合わせには、論理型をベースに関数型の

機能を加える場合と、関数型をベースに論理型の機能を加える場合とがありえます。Mercury は

前者、すなわち論理型をベースに関数型の機能を追加した言語です。後者、すなわち関数型をベー

スに論理型の機能を追加した言語としては Curryなどがあります。

C 言語や Java をはじめとして、世の中で広く使われているプログラム言語の多くは手続き型

言語とよばれており、プログラマはプログラムの処理の手順、すなわち手続きを明示的に記述す

る必要があります。一方、関数型言語や論理型言語はともに宣言型言語という分類に属しており、

プログラマはプログラムの性質や意味を記述し、具体的な処理手順はプログラミング言語の処理

系に任せてしまうという考え方でプログラムを記述します。残念ながら現在の情報科学の技術は、

性質や意味を記述するだけであらゆるプログラムが記述できるという域には到達していませんが、

Mercuryはこの考え方をかなりのレベルまで実現できているように筆者は感じます。

Mercuryの特徴には、論理型と関数型を融合したということ以外に、非常に強力な型システムと

モードシステム、決定性システムの存在があります。これらはプログラムの誤りを検出するという

役割と、コンパイルによって効率的なコードを生成するという役割を担っています。特にプログラ

ムの誤りを検出するという働きは非常に強力で、筆者はこれまでこの機能に大変お世話になってき

ました。宣言型の機能をフルに活かして、宣言的に書いたプログラムをコンパイラに通し、コンパ

イラに指示されるままにプログラムを修正し、プログラムを実行したら、完璧に動作するプログラ

ムが完成していたということを、これまで何度も経験しています。

本書では他のプログラミング言語の経験者を対象に、プログラミング言語Mercuryの解説を行

います。関数型言語や論理型言語の経験は仮定していないので、そのような経験がない人でも安心

して読み進めることができます。本書では開発環境として Linuxや OS Xなどの UNIX系のオペ

レーティングシステムを使っていることを前提に解説を行っています。Windowsをお使いの方は、

Page 4: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

4 はじめに

Linuxの開発環境を用意することをおすすめします。Mercuryの処理系自体はWindowsでも動作

させることが可能ですが、本書では詳細な手順等は解説しません。本書は新しいプログラミング言

語の学習を通じて、プログラミングに関する新しい考え方を身につけたいと考えている方に特にお

すすめです。Mercury の学習を通じて身につけた論理型言語や関数型言語の考え方は、今後手続

き型言語のプログラミングを行う際にもきっと役に立つことと思います。本書を通じて皆さんが少

しでもMercuryの面白さを感じてもらえれば幸いです。

Page 5: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

5

目次

第 1章 はじめよう 9

1.1 開発環境の構築 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

1.2 プログラムのコンパイルと実行 . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

1.3 Hello, World! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

1.4 複数の出力 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

1.5 状態変数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

1.6 変数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

1.7 成功と失敗 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

1.8 型 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

1.9 文字列 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

1.10 整数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

1.11 浮動小数点数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

1.12 文字 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

1.13 コメント . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

第 2章 述語 25

2.1 宣言 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

2.2 事実 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

2.3 変数を使う . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

2.4 規則を使って述語を定義する . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

2.5 再帰的な述語 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

2.6 関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

第 3章 リスト 47

3.1 リストの構築 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

3.2 リストの分解 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

3.3 多相的な述語 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

3.4 多相的な関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

Page 6: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

6 目次

3.5 タプル . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

3.6 文字のリスト . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

3.7 リストの整列 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

3.8 複数のモード . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

第 4章 型を作る 61

4.1 値を列挙する . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

4.2 引数をもたせる . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

4.3 引数に名前を付ける . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

4.4 自然数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

4.5 型パラメータ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

4.6 数式の計算 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71

第 5章 値としての述語と関数 75

5.1 値としての述語 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

5.2 値としての述語の型とモード . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

5.3 述語を受け取る述語 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

5.4 無名述語 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78

5.5 述語を返す述語 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

5.6 値としての関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80

5.7 述語や関数をデータ構造に格納する . . . . . . . . . . . . . . . . . . . . . . . . . 81

5.8 部分適用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

5.9 リストに関する高階関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84

第 6章 非決定性 91

6.1 基本 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

6.2 ピタゴラス数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95

6.3 結果を 1つだけ取得する . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97

6.4 リストに関する非決定的な述語 . . . . . . . . . . . . . . . . . . . . . . . . . . . 98

6.5 8クイーン問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103

6.6 結果を n個取得する . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104

6.7 入力を受け取る . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107

6.8 インタラクティブに結果を取得する . . . . . . . . . . . . . . . . . . . . . . . . . 111

第 7章 確定節文法 113

7.1 基本 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113

7.2 状態の読み取りと更新 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115

Page 7: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

7

7.3 状態としてリストを受け渡す . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116

7.4 状態としてユーザ定義型を受け渡す . . . . . . . . . . . . . . . . . . . . . . . . . 117

7.5 構文解析 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119

第 8章 型クラスと存在型 123

8.1 型クラスの基本 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123

8.2 型クラス制約 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126

8.3 型クラスの継承 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127

8.4 存在型 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129

8.5 関数従属性 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132

第 9章 Cインターフェース 135

9.1 基本 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135

9.2 データ受け渡し規約 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137

9.3 C言語のオブジェクトをMercury上で扱う . . . . . . . . . . . . . . . . . . . . . 139

9.4 C言語の定数をMercuryのユーザ定義型として扱う . . . . . . . . . . . . . . . . 140

9.5 C言語の関数定義をMercuryのソースに埋め込む . . . . . . . . . . . . . . . . . 142

9.6 C言語からMercuryのコードを呼び出す . . . . . . . . . . . . . . . . . . . . . . 143

9.7 C言語で準決定的な述語を実装する . . . . . . . . . . . . . . . . . . . . . . . . . 144

9.8 オブジェクトファイルをリンクする . . . . . . . . . . . . . . . . . . . . . . . . . 145

第 10章 その他の話題 149

10.1 複数のファイルからなるプログラム . . . . . . . . . . . . . . . . . . . . . . . . . 149

10.2 ライブラリの作成 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150

10.3 抽象型 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151

10.4 例外処理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154

10.5 末尾再帰 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156

10.6 モードとインスト . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158

第 11章 標準ライブラリ概覧 (基本的な型) 163

11.1 整数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163

11.2 浮動小数点数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165

11.3 数学関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167

11.4 文字 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168

11.5 文字列 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169

11.6 文字列の整形 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172

11.7 多倍長整数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173

Page 8: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

8 目次

11.8 真偽値 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176

11.9 maybe型 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177

第 12章 標準ライブラリ概覧 (コレクション) 179

12.1 リスト . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179

12.2 ペア . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185

12.3 連想リスト . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185

12.4 集合 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187

12.5 写像 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189

12.6 univ型 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191

第 13章 標準ライブラリ概覧 (入出力) 193

13.1 コンソール出力 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193

13.2 コンソール入力 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194

13.3 ファイル出力 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196

13.4 ファイル入力 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200

13.5 ファイル操作 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206

13.6 入出力に関するその他の述語・関数 . . . . . . . . . . . . . . . . . . . . . . . . . 206

第 14章 標準ライブラリ概覧 (その他) 209

14.1 時間 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209

14.2 乱数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210

14.3 ストア . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212

14.4 solutionsモジュール . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214

14.5 その他 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215

Page 9: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

9

第 1章

はじめよう

1.1 開発環境の構築

まずは開発環境の構築を行いましょう。本書では Linux や OS X などの UNIX 系のオペレー

ティングシステムを利用していることを前提に解説を行います。Mercury の公式サイト*1 から

ソースコードの tarボールをダウンロードしてきてください。本書執筆時点での最新版のバージョ

ンは 14.01です。以下のように解凍して configureスクリプトを実行し、make、make install

するとコンパイルとインストールが完了します。

$ tar zxvf mercury-srcdist-14.01.tar.gz

$ cd mercury-srcdist-14.01

$ ./configure

$ make PARALLEL=-j4

$ sudo make install PARALLEL=-j4

PARALLEL=-j4の 4は並列にビルドするタスクの数です。指定することで並列にビルドされます。

コンパイルには非常に時間がかかるので時間のあるときに行ってください。インストールが完了す

ると、実行ファイルが/usr/local/mercury-14.01/bin/ 以下に配置されます。シェルからコマ

ンドが実行できるように、このディレクトリにパスを通しておいてください。

1.2 プログラムのコンパイルと実行

Mercury のプログラムのコンパイルには mmc コマンドを使います。ソースファイルの名前が

file.mの場合、

$ mmc --make file

*1 http://www.mercurylang.org/

Page 10: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

10 第 1章 はじめよう

でコンパイルできます。mmcコマンドにファイル名を渡す場合は、拡張子を取り除いた名前を指

定する必要がある点に注意してください。では実際に簡単なプログラムのコンパイルと実行を試し

てみましょう。テキストエディタを起動して、以下のプログラムを入力し、hello.mという名前で

保存してください。

1 :- module hello.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5 :- implementation.

6 main(IO1, IO2) :-

7 write_string("Hello, World!\n", IO1, IO2).

シェルを開いて hello.mを保存したディレクトリに移動して、mmc --make helloと入力してく

ださい。

$ mmc --make hello

Making Mercury/int3s/hello.int3

Making Mercury/ints/hello.int

Making Mercury/cs/hello.c

Making Mercury/os/hello.o

Making hello

プログラムがコンパイルされ、実行可能ファイルが生成されます。実行すると以下のように画面に

Hello, World!と表示されます。

$ ./hello

Hello, World!

1.3 Hello, World!

プログラムをコンパイルできるようになったので、Mercury のプログラムの書き方を学んでい

きましょう。Mercuryのプログラムは以下のような構造になっています。

:- module (モジュール名).

:- interface.

(インターフェイス部)

Page 11: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

1.3 Hello, World! 11

:- implementation.

(実装部)

括弧で囲まれた部分はプログラムによって変わる部分です。モジュール名にはアルファベット小

文字で始まる英数字の列を指定します。ソースコードのファイル名はこのモジュール名に拡張子

(.m) を付けたものになっている必要があります。インターフェイス部にはモジュールの外部に公

開する述語の宣言などを記述します。実装部にはモジュールの外部に公開しない述語の宣言と、述

語の実装などを記述します。

前節に出てきた画面に Hello, World!と表示するプログラムを詳しく見てみましょう。このプ

ログラムではモジュール名に helloを指定したので、ソースコードのファイル名は hello.mにな

ります。

インターフェイス部は以下の 2行です。

:- import_module io.

:- pred main(io::di, io::uo) is det.

最初の行では import_module宣言を用いて標準ライブラリの ioモジュールを読み込んでいます。

これにより入出力に必要な述語が利用可能になります。次に pred宣言で述語 mainを宣言してい

ます。これにより、述語 mainがモジュールの外部から呼び出し可能になります。述語 mainはプ

ログラムで最初に実行される述語で、インターフェイス部で宣言する必要があります。述語の宣言

の詳細は後の節で説明します。

実装部は以下の 2行です。

main(IO1, IO2) :-

write_string("Hello, World!\n", IO1, IO2).

実装部では述語 mainの実装を記述しています。プログラムの実行が開始されると、述語 main が

呼び出されます。その際、述語 mainの 1引数目に入出力の状態を表す値が渡されます。ここでは

その値を変数 IO1で受け取っています。述語の本体では ioモジュールの述語 io.write_string

を呼び出しています。モジュール名の “io.” は省略が可能なので、ここでは省略して単に

write_string と書いています。述語 write_string は第 1 引数で指定した文字列を画面に

出力する働きをします。ここでは表示する文字列として、"Hello, World!\n"を指定しています。

文字列中の\n は改行文字を表しています。第 2 引数には入出力状態を渡します。ここでは main

で受け取った値をそのまま渡しています。write_stringは文字列を表示すると、新しい入出力の

状態を返します。ここでは変数 IO2でその値を受け取っています。受け取った値は mainの呼び出

し元に返しています。

他の多くのプログラミング言語と異なり、文字列出力などの処理に入出力状態の受け渡しが必要

な点、引数が入力の役割をする場合と出力の役割をする場合の両方がある点が特徴的です。

Page 12: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

12 第 1章 はじめよう

1.4 複数の出力

述語 main で write_string を複数回呼び出したい場合はどのようにするのでしょうか。以下

は、そのようなプログラムの例です。

1 :- module hello2.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 main(IO1, IO4) :-

8 write_string("Hello, ", IO1, IO2),

9 write_string("Mercury ", IO2, IO3),

10 write_string("World!\n", IO3, IO4).

実行結果は以下のようになります。

$ ./hello2

Hello, Mercury World!

複数の述語を呼び出すには、述語呼び出しをコンマ (,)で区切って並べます。mainで受け取っ

た状態を最初の write_stringに渡して、それが返した新しい状態を次の write_stringに渡し

ます。そして、最後の write_stringが返した値を mainに返します。状態の値が述語の呼び出し

を逐次化する役割を果たしているので、述語呼び出しの順番に意味はありません。例えば以下のよ

うに逆順に並べても、先ほどと同じ順番で出力されます。

main(IO1, IO4) :-

write_string("World!\n", IO3, IO4),

write_string("Mercury ", IO2, IO3),

write_string("Hello, ", IO1, IO2).

1.5 状態変数

前節の例では入出力の状態に手で名前をつけていましたが、状態変数とよばれる特殊な記法を用

いると、状態の受け渡しを自動化することができます。以下に状態変数を使ったプログラム例を示

Page 13: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

1.6 変数 13

します。

1 :- module hello3.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 main(!IO) :-

8 write_string("Hello, ", !IO),

9 write_string("Mercury ", !IO),

10 write_string("World!\n", !IO).

実行結果は以下のようになります。

$ ./hello3

Hello, Mercury World!

上のプログラムで mainの引数や write_stringの呼び出しに現れる!IOが状態変数です。状態変

数は通常の変数 2 個分を 1 つで表しています。状態変数を使うとそれらを並べた順番で状態を受

け渡すようにプログラムが自動的に変換されます。上のプログラムは前節で出てきた状態に手で名

前をつけたプログラムと全く同じ動きになります。最初に作った、画面に Hello, World!と表示

するプログラムも状態変数を使うと以下のように記述できます。

main(!IO) :-

write_string("Hello, World!\n", !IO).

1.6 変数

変数を使うと値に名前を付けることができます。Mercury では変数名はアルファベットの大文

字で始まる英数字またはアンダースコア (_)の列です。Mercuryの変数は Cや Javaなどのプログ

ラム言語の変数とは大きく異なっています。Cや Javaでは変数はある記憶域を表しており、変数

に対しては値を代入して、中身を書き換えることができます。一方、Mercuryの変数は値に付けた

単なる名前であって、特定の記憶域を指し示しているわけではありません。また一度値に名前を付

けると、その名前が指し示すものが変わることはありません。以下に変数を使ったプログラム例を

示します。

Page 14: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

14 第 1章 はじめよう

1 :- module hello4.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 main(!IO) :-

8 Msg = "Hello, World!\n",

9 write_string(Msg, !IO).

実行結果は以下のようになります。

$ ./hello4

Hello, World!

上のプログラムでは"Hello, World!\n"という値に Msgという名前を付けており、write_string

では値に付けた名前である Msg を引数に指定しています。“=”は組み込みの述語で、左辺と右辺が

等しいということを宣言します。ここでは、変数 Msgと"Hello, World!\n"が等しいと宣言して

います。両者が等しいということは、"Hello, World!\n"と書ける場所で代わりに Msgと書いて

も同じ意味になるということを表しています。“=”の左辺と右辺には区別は無いので、上の

Msg = "Hello, World!\n"

は、左右を入れ替えて、

"Hello, World!\n" = Msg

のように書くこともできます。また、述語の呼び出しの順番に意味は無いので、述語 main の定

義は、

main(!IO) :-

write_string(Msg, !IO),

Msg = "Hello, World!\n".

のように書いても一向に問題ありません。入れ替える前のプログラムは「"Hello, World!\n"と

表示する。」と読むことができ、入れ替え後のプログラムは「表示する。"Hello, World!\n"と。」

と読めます。どのような書き方が最も好ましいかは場合によります。今回のように引数が少ない述

語を呼び出す場合は、先に名前を付けてようが後で名前をつけようが違いがありませんが、多くの

Page 15: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

1.7 成功と失敗 15

パラメータがある述語を呼び出す場合は、述語呼び出しを先に持ってきた方がプログラムが読みや

すくなる場合があります。述語 “=”を使って 2つの項を等しくすることを単一化すると言います。

別の例を見てみましょう。以下は 2つの変数 Aと Bを使って画面に"Hello, World!" と表示す

るプログラム例です。

1 :- module hello5.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 main(!IO) :-

8 A = B,

9 A = "Hello, World!\n",

10 write_string(B, !IO).

実行結果は以下のようになります。

$ ./hello5

Hello, World!

最初に変数 Aと変数 Bを単一化しています。これにより、Aと書ける場所には代わりに Bと書いて

も良いということを宣言しています。これを Cや Javaのように「Aに Bを代入している」と読む

と訳が分からなくなります。次に、変数 Aと"Hello, World!\n"が等しいと宣言しています。最

後に変数 Bの値を write_stringで表示しています。Aと Bと"Hello, World!\n"はこのプログ

ラム中ではすべて同じものなので、画面には"Hello, World!"と表示されます。

1.7 成功と失敗

述語は実行すると、成功する場合と失敗する場合があります。これまで出てきた述語で、

write_stringは必ず成功する述語です。一方、“=”は入力によって成功したり失敗したりする述

語です。“=”が失敗するのは以下のように左辺と右辺を等しくできない場合です。

"hello" = "world"

述語が成功したか失敗したかに応じて分岐するには、if-then-elseゴールを使います。ゴールと

は述語の定義の “:-”の右側に来るもののことをいいます。具体的には述語の呼び出しやそれらを

コンマで連結したものがゴールです。if-then-elseゴールは、

Page 16: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

16 第 1章 はじめよう

if ゴール 1 then ゴール 2 else ゴール 3

もしくは、

ゴール 1 -> ゴール 2 ; ゴール 3

という構文をしており、ゴール 1が成功した場合ゴール 2を実行し、失敗したらゴール 3を実行

します。Cや Javaの条件分岐のようにゴール 3を省略することはできません。

以下は if-then-elseゴールを使ったプログラムの例です。

1 :- module ifthen.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 main(!IO) :-

8 (

9 if

10 "ABC" = "ABC"

11 then

12 write_string("成功\n", !IO)

13 else

14 write_string("失敗\n", !IO)

15 ),

16 (

17 "ABC" = "DEF" ->

18 write_string("成功\n", !IO)

19 ;

20 write_string("失敗\n", !IO)

21 ).

実行結果は以下のようになります。

$ ./ifthen

成功

失敗

Page 17: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

1.8 型 17

このプログラムでは if-then-else ゴールを使って、“=” の呼び出しが成功したか失敗したか

を画面に表示しています。このプログラムをコンパイルすると、"ABC" = "ABC"は必ず成功し、

"ABC" = "DEF"は必ず失敗するという旨の警告が表示されますが、ここでは無視してかまいませ

ん。最初の “=”の呼び出し、

"ABC" = "ABC"

は左辺と右辺が等しいので成功し、if-then-elseゴールの then以下が実行され、画面に成功と

表示されます。2番目の “=”の呼び出し、

"ABC" = "DEF"

は左辺と右辺を等しくできないので失敗し、if-then-elseゴールの “;”以下が実行され、画面に

失敗と表示されます。

1.8 型

型とは値を種類ごとに分類したもので、例えば 123や-6などの整数値であれば int型、"hello"

や"abc"などの文字列値であれば string型であるとよびます。Mercuryは強く型付けされた言語

です。プログラム中の値や変数にはすべて型がついています。Mercury では C や Java のように

変数の型をプログラマが宣言する必要はありません。変数の型は文脈から自動的に判断されます。

Mercuryの組み込みの型には表 1.1のものがあります。

表 1.1 組み込みの型

型 読み方 値の例

string型 文字列型 "hello"

int型 整数型 123

float型 浮動小数点数型 3.14

char型 文字型 ’A’

1.9 文字列

文字列定数は"hello"のように文字列をダブルクォーテーションで囲んで表します。文字列中に

は表 1.2のエスケープシーケンスを含めることができます。例えば、

write_string("abc\t\"def\"\n", !IO)

を実行すると画面に、

Page 18: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

18 第 1章 はじめよう

表 1.2 エスケープシーケンス

エスケープシーケンス 意味

\b バックスペース

\n 改行

\r 復帰

\t 水平タブ

\" ダブルクォーテーション

\’ シングルクォーテーション

\\ バックスラッシュ

abc "def"

と表示され改行されます。改行文字を出力するにはエスケープシーケンスを使う以外に、io モ

ジュールの述語 io.nlを使うこともできます。

nl(!IO)

文字列に関する演算子には表 1.3のものが用意されています。これらの演算子を使うには、string

モジュールをインポートする必要があります。

表 1.3 文字列に関する演算子

演算子 意味

文字列 1 ++ 文字列 2 文字列同士の連結

文字列 ^ elem(整数) 文字列中の整数番目の文字を返す

以下に文字列を使ったプログラム例を示します。

1 :- module string_test.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module string.

8 main(!IO) :-

9 Str1 = "ABC",

10 Str2 = "DEF",

Page 19: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

1.10 整数 19

11 write_string(Str1 ++ Str2, !IO),

12 nl(!IO).

実行結果は以下のようになります。

$ ./string_test

ABCDEF

このプログラムでは変数で名前を付けた文字列"ABC"と"DEF"を文字列を連結する演算子 “++”で

連結して表示しています。また、述語 io.nlを使って改行文字を出力しています。

1.10 整数

整数定数は 123 のように数字を並べて表します。整数の値の範囲は 64bit 環境では −263 から

263 − 1の範囲、32bit環境では −231 から 231 − 1の範囲になります。整数定数は、123のような

10進表記の他、0b1100のような 2進表記、0o14のような 8進表記、0xF3のような 16進表記が

可能です。また、0’xのような表記で文字 xの文字コードを表します。整数に関する演算子には表

1.4のものが用意されています。これらの演算子を使うには、intモジュールインポートする必要

表 1.4 整数に関する演算子

演算子 意味

- 式 整数の符号反転

式 1 + 式 2 整数同士の加算

式 1 - 式 2 整数同士の減算

式 1 * 式 2 整数同士の乗算

式 1 / 式 2 整数同士の除算

式 1 rem 式 2 整数同士の剰余

があります。演算子には優先順位が定まっており、*、/、rem は、+、-よりも優先度が高くなって

います。

整数値を画面に表示するには、ioモジュールの述語 io.write_intを使います。

write_int(123, !IO)

以下に整数を使ったプログラム例を示します。

1 :- module int_test.

2 :- interface.

Page 20: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

20 第 1章 はじめよう

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8 main(!IO) :-

9 write_int(1 + 2 * 3, !IO),

10 nl(!IO).

実行結果は以下のようになります。

$ ./int_test

7

このプログラムでは数式 1 + 2 * 3を計算して結果を表示しています。“+”よりも “*”の方が演

算子の優先順位が高いので、2 * 3 が先に計算されてその結果である 6 と 1 が足されて結果の 7

が表示されています。もしも 1 + 2 を先に計算して欲しければ、(1 + 2) * 3 のように 1 + 2

を括弧で囲む必要があります。

以下のプログラムでは 2進数や 8進数などの整数の異なる表記法を試しています。

1 :- module int_test2.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 main(!IO) :-

8 write_int(0b1100, !IO),

9 nl(!IO),

10 write_int(0o14, !IO),

11 nl(!IO),

12 write_int(0xF3, !IO),

13 nl(!IO),

14 write_int(0’x, !IO),

15 nl(!IO).

Page 21: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

1.11 浮動小数点数 21

実行結果は以下のようになります。

$ ./int_test2

12

12

243

120

述語 io.write_intは整数を 10進数で表示するため、それぞれの値が 10進数で表示されていま

す。10進数以外で表示する必要がある時は、後述の述語 io.formatを使う必要があります。

1.11 浮動小数点数

浮動小数点数の定数は 3.14 のように値を小数点表記します。整数から浮動小数点数への暗黙

の変換は行われないので、浮動小数点数を表したい時は以下のように小数点を含める必要があり

ます。

2.0

浮動小数点数は以下のように科学技術表記をすることもできます。

6.02e23

これは 6.02× 1023 を表しています。浮動小数点数に関する演算子には表 1.5のものが用意されて

います。これらの演算子を使うには、floatモジュールをインポートする必要があります。intモ

表 1.5 浮動小数点数に関する演算子

演算子 意味

- 式 浮動小数点数の符号反転

式 1 + 式 2 浮動小数点数同士の加算

式 1 - 式 2 浮動小数点数同士の減算

式 1 * 式 2 浮動小数点数同士の乗算

式 1 / 式 2 浮動小数点数同士の除算

ジュールと float モジュールの両方をインポートした場合、+ が整数の加算であるか浮動小数点

数の加算であるかは文脈により判断されます。整数から浮動小数点数への暗黙の変換は行われない

ので、

1.2 + 3

Page 22: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

22 第 1章 はじめよう

のような式は型エラーになります。このような式を書きたい場合は、3 を浮動小数点数に変換し

てから加算する必要があります。整数を浮動小数点数に変換するには、float モジュールの関数

float.floatを使います。

1.2 + float(3)

反対に浮動小数点数を整数に変換するには表 1.6の関数を使います。

表 1.6 浮動小数点数を整数に変換する関数

関数 意味

float.ceiling_to_int(値) 引数の値を下回らない最小の整数を返す

float.floor_to_int(値) 引数の値を上回らない最大の整数を返す

float.round_to_int(値) 0.1の位を四捨五入して整数に変換する

float.truncate_to_int(値) 引数の値に最も近い整数を返す

浮動小数点数の値を画面に表示するには ioモジュールの述語 io.write_floatを使います。

write_float(3.14, !IO)

以下に浮動小数点数を使ったプログラム例を示します。

1 :- module float_test.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module float.

8 main(!IO) :-

9 write_float(10.0 / 4.0, !IO),

10 nl(!IO).

実行結果は以下のようになります。

$ ./float_test

2.5

上のプログラムでは 10.0 / 4.0を計算して結果である 2.5を画面に表示しています。

Page 23: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

1.12 文字 23

1.12 文字

文字定数は’A’のように文字をシングルクォーテーションで囲んで表します。文字列と同様に表

1.2のエスケープシーケンスが利用できます。文字定数に関する注意点として、“+”などの演算子

として使われている文字定数を表すときは、括弧で囲んで (’+’) のように表す必要があります。

括弧で囲む必要のある文字定数には以下のものがあります。

. , ! @ ^ : - * / + < = > ~ ; & \

バックスラッシュ (\) を文字定数として用いる時は、エスケープシーケンスと括弧を併用して

(’\\’)のように書く必要があります。括弧は文脈や記号によっては不要な場合もありますが、記

号を文字定数として使う時は常に括弧で囲んでおくほうが無難です。

文字を画面に表示するには、ioモジュールの述語 io.write_char を使います。

write_char(’A’, !IO)

以下に文字を使ったプログラム例を示します。

1 :- module char_test.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module string.

8 main(!IO) :-

9 Str = "ABCDEF",

10 write_char(Str^elem(3), !IO),

11 nl(!IO).

実行結果は以下のようになります。

$ ./char_test

D

上のプログラムでは文字列から “^elem”演算子を使って 3番目の文字である Dを取り出して表示

しています。最初の文字を 0番目として数えることに注意してください。

Page 24: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

24 第 1章 はじめよう

1.13 コメント

プログラムにはコメントを入れることができます。コメントには %から始まる単一行コメント

% コメント

と、/*から*/までの複数行コメント

/* 複数行コメント */

があります。コメントはコンパイル時には空白として扱われます。コメントはプログラムに関する

注釈を記述する際や、コードの一部を一時的に無効化する際に用いられます。

Page 25: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

25

第 2章

述語

Mercury はプログラムを述語の集合として表現します。述語とは引数に応じて成功であるか失

敗であるかが定まる論理式のことです。Mercury では述語を使って、アルゴリズムの入力と出力

の関係を記述することでプログラムを表現します。本章ではMercuryで述語を定義する方法を説

明します。

2.1 宣言

Mercury では述語は定義する前に宣言する必要があります。述語の宣言は以下のような構文で

記述します。

:- pred 名前 (型 1::モード 1, ..., 型 n::モード n) is 決定性.

宣言には述語の名前、引数の型、引数のモード、述語の決定性の 4つが含まれます。

型は前章で説明したように string型や int型などの、値の種類のことを表しています。モード

とは入力や出力などの引数の使われ方のことで、表 2.1の分類があります。inモードは対応する

表 2.1 引数のモード

モード 読み方

in 入力 (input)

out 出力 (output)

di 破壊的入力 (destructive input)

uo 唯一出力 (unique output)

引数が入力として使われることを意味しています。outモードは対応する引数が出力として使われ

ることを意味しています。diモードは対応する引数が入力として使われており、その値が述語実

行後に無効になることを表しています。diモードの引数は他の述語の入力として使われていては

Page 26: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

26 第 2章 述語

いけません。uoモードは対応する引数が出力として使われており、述語実行後には他の場所で出

てこない新しい値が返ってくることを表しています。diモードや uoモードは、入出力状態の管理

やメモリ管理に使われます。

決定性とは述語が返す解の数のことで、表 2.2のような分類があります。決定性が detの述語、

表 2.2 述語の決定性

決定性 読み方 解の個数

det 決定的 (deterministic) 1個

semidet 準決定的 (semideterministic) 0個~1個

multi 複解 (multisolution) 1個以上

nondet 非決定的 (nondeterministic) 0個以上

つまり決定的な述語は、入力が与えられると必ず成功し 1個の解を返します。決定性が smidetの

述語、つまり準決定的な述語は、入力が与えられると成功する場合と失敗する場合があります。た

だし、成功した場合には 1個の解を返します。表 2.2では失敗した場合の解の個数を 0個と表記し

ています。決定性が multiの述語、つまり複解を返す述語は、入力が与えられると必ず成功し、1

個以上の解を返します。決定性が nondetの述語、つまり非決定的な述語は、入力が与えられると

失敗するか、もしくは成功し 1個以上の解を返します。

標準ライブラリで定義されている述語の宣言をいくつか見てみましょう。intモジュールに定義

されている述語 maxは、入力された 2つの整数のうち値が大きい方を返す述語です。maxは以下

のように宣言されています。

:- pred int.max(int::in, int::in, int::out) is det.

述語 maxには 3つの引数があり、いずれも int型すなわち整数型です。引数のうち最初の 2つは

inモードすなわち入力で、最後のものは outモードすなわち出力です。決定性には detが指定さ

れているので、入力によって失敗することはなく、かならず 1つの解を返します。

write_stringは ioモジュールで以下のように定義されています。

:- pred io.write_string(string::in, io::di, io::uo) is det.

この述語の名前は write_stringで引数は 3個あります。最初の引数は文字列型で入力モード。2

番目と 3番目の引数の io型は入出力状態を表す型です。2番目の引数は破壊的入力モード、3番

目の引数は唯一出力モードになっています。入出力状態は複製されてはならず、逐次的に値を受け

渡す必要があります。そのことが、モードの指定に diと uoを指定することで表現されています。

また Mercury のモードシステムはこの制約が満たされていることを自動的に検査してくれます。

例えば以下のようなプログラムはモードエラーになります。

Page 27: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

2.2 事実 27

main(!IO) :-

write_string("abc", IO0, IO1),

write_string("def", IO0, IO1)

述語の決定性には det が指定されているので、write_string は決定的な述語です。つまり、入

力に対して必ず成功し失敗することは無いことを表しています。ここまでの知識で、以下の述語

mainの宣言も理解できると思います。

:- pred main(io::di, io::uo) is det.

mainは 2つの引数をもっており、共に io型で、最初の引数は diモード、2番目の引数は uoモー

ドです。決定性には detが指定されています。ここでは名前のモジュール名を省略しています。

charモジュールで定義されている is_alphaは決定性に semidetが指定された準決定的な述語

です。

:- pred char.is_alpha(char::in) is semidet.

is_alphaは引数の文字がアルファベットであれば成功し、そうでなければ失敗します。

int モジュールで定義されている nondet_int_in_range は決定性に nondet が指定された非

決定的な述語です。

:- pred nondet_int_in_range(int::in, int::in, int::out) is nondet.

nondet_int_in_range は第 1 引数と第 2 引数で指定した範囲の整数を 3 番目の引数を通じて非

決定的に返します。第 1引数に第 2引数よりも大きな値が指定されていた場合は失敗します。

2.2 事実

述語を定義する簡単な方法は、述語が成功するときの値を列挙するというものです。例えば、引

数が小さな素数の時に成功し、そうでない場合に失敗する述語 small_primeは、

:- pred small_prime(int::in) is semidet.

small_prime(2).

small_prime(3).

small_prime(5).

small_prime(7).

のように書けます。このようなコードがあると、small_prime(5)は述語の定義に含まれているの

で成功し、small_prime(6)は述語の定義には含まれていないので失敗します。上の述語は事実と

よばれる形式で定義されています。事実は以下のような構文の文をいいます。

Page 28: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

28 第 2章 述語

名前 (項 1, ..., 項 n).

事実は、項 1 から項 n までが、名前という関係にあるという事実を表しています。上の例

の small_prime(2) は 2 という値が、small_prime であるという事実を表しています。述語

small_primeの定義は 4つの事実から構成されています。small_primeの宣言は、引数が整数型

で入力、述語は準決定的であることを表しています。

以下に small_primeを使ったプログラム例を示します。

1 :- module small_prime.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7

8 :- pred small_prime(int::in) is semidet.

9 small_prime(2).

10 small_prime(3).

11 small_prime(5).

12 small_prime(7).

13

14 main(!IO) :-

15 (

16 if small_prime(5)

17 then write_string("成功\n", !IO)

18 else write_string("失敗\n", !IO)

19 ),

20 (

21 if small_prime(6)

22 then write_string("成功\n", !IO)

23 else write_string("失敗\n", !IO)

24 ).

実行結果は以下のようになります。

$ ./small_prime

成功

Page 29: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

2.2 事実 29

失敗

上のプログラムでは small_prime(5) と small_prime(6) を呼び出してそれぞれ成功したか

失敗したかを表示しています。small_prime(5) は述語の定義に含まれているので成功し、

small_prime(6) は述語の定義に含まれていないので失敗していることが確認できます。ここ

では small_prime の定義の後に main の定義を配置していますが、main を先に定義して後ろで

small_prime を定義することもできます。

以下は、小さな整数 N に対して、N の階乗 N !を返す述語の定義例です。

:- pred small_fact(int::in, int::out) is semidet.

small_fact(1, 1).

small_fact(2, 2).

small_fact(3, 6).

small_fact(4, 24).

small_fact(5, 120).

事実 small_fact(1, 1).は 1と 1が small_factという関係にあることを表しています。この

述語の宣言は、最初の引数が整数型で入力、2番目の引数が整数型で出力であることを示していま

す。範囲外の値が与えられた場合は失敗するので決定性には semidetを指定しています。このよ

うな述語の定義があるとき、small_fact(4, X) を実行すると、事実の表から条件に合致する事

実を探し出し、small_fact(4, 24).という事実を見つけ出します。その後、24と Xを単一化し

て値 24に Xという名前が付けられます。

以下に small_factを使ったプログラム例を示します。

1 :- module small_fact.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7

8 :- pred small_fact(int::in, int::out) is semidet.

9 small_fact(1, 1).

10 small_fact(2, 2).

11 small_fact(3, 6).

12 small_fact(4, 24).

13 small_fact(5, 120).

Page 30: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

30 第 2章 述語

14

15 main(!IO) :-

16 if small_fact(4, N) then

17 write_int(N, !IO),

18 nl(!IO)

19 else

20 write_string("失敗\n", !IO).

実行結果は以下のようになります。

$ ./small_fact

24

上のプログラムでは small_fact を呼び出して、4 の階乗の値を取り出して N という名前を付け

て、それを画面に表示しています。

2.3 変数を使う

変数を使うと値を列挙することなく述語を定義できます。以下は整数の値を 2倍にして返す述語

の定義です。

:- pred double(int::in, int::out) is det.

double(N, N * 2).

この述語は事実 1つからなり、入力として Nが与えられると、出力として N * 2を返します。こ

れは宣言的には Nと N * 2の間に、doubleという関係があることを表しています。この述語はす

べての入力に対して成功するので、決定性には detが指定されています。以下は doubleを使った

プログラム例です。

1 :- module double.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- pred double(int::in, int::out) is det.

Page 31: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

2.3 変数を使う 31

10 double(N, N * 2).

11

12 main(!IO) :-

13 double(10, X),

14 write_int(X, !IO),

15 nl(!IO).

実行結果は以下のようになります。

$ ./double

20

上のプログラムでは doubleを使って 10を 2倍した値を表示しています。

Mercuryでは同時に複数の出力を返す述語を定義することもできます。以下は 2つの整数を受

け取ってそれらの和と積を返す述語 addmulを定義して使うプログラム例です。

1 :- module addmul.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- pred addmul(int::in, int::in, int::out, int::out) is det.

10 addmul(X, Y, X + Y, X * Y).

11

12 main(!IO) :-

13 addmul(3, 5, Add, Mul),

14 write_string("Add = ", !IO),

15 write_int(Add, !IO),

16 nl(!IO),

17 write_string("Mul = ", !IO),

18 write_int(Mul, !IO),

19 nl(!IO).

Page 32: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

32 第 2章 述語

実行結果は以下のようになります。

./addmul

Add = 8

Mul = 15

上のプログラムでは、3と 5の和である 8と積である 15を表示しています。

2.4 規則を使って述語を定義する

規則を使うと、既存の述語を使って新しい述語を定義できます。規則とは以下のような構文をし

た文のことをいいます。

名前 (項 1, ..., 項 n) :- ゴール.

これはゴールが表す論理式が成り立つならば、項 1から項 nまでに名前という関係が成立すると

いう規則を表しています。ここでゴールをもう少し詳しく見てみたいと思います。まず、述語呼び

出しはゴールです。述語呼び出しは成功する場合と失敗する場合があります。つまり、ゴールも成

功する場合と失敗する場合があります。次に、2つのゴールをコンマ (,)で連結したものもゴール

です。

ゴール 1, ゴール 2

このコンマは論理式の「かつ」の意味で、ゴール 1とゴール 2の両方が成功した場合のみゴール全

体が成功し、どちらか片方が失敗した場合はゴール全体も失敗します。「かつ」に対して「または」

を表すのはセミコロン (;)です。

ゴール 1; ゴール 2

これはゴール 1またはゴール 2が成功した場合にゴール全体も成功し、どちらも失敗した場合に

ゴール全体も失敗します。否定は、

not ゴール

または、

\+ ゴール

で表します。これは、ゴールが失敗した場合に成功し、成功した場合に失敗します。前章で出てき

た if-then-else もゴールです。最後に常に成功するゴールである ture と常に失敗するゴール

である failがあります。

Page 33: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

2.4 規則を使って述語を定義する 33

以下の述語は、年齢を表す整数を引数に受け取り、子供か子供で無いかを判定する述語 is_child

の定義です。この述語は年齢が 0以上 12以下のときに成功し、それ以外の時に失敗します。

:- pred is_child(int::in) is semidet.

is_child(Age) :-

Age >= 0, Age =< 12.

述語 is_child は規則 1 つからなる述語です。これは Age が 0 以上かつ Age が 12 以下ならば

is_child(Age)であると読みます。“>=”や “=<”は値の大小比較をする述語です。値の大小を比

較する述語を表 2.3にまとめます。一般的なプログラム言語と異なり、以下を表す演算子が “<=”

表 2.3 値の大小を比較する述語

演算子 意味

式 1 < 式 2 より小さい

式 1 > 式 2 より大きい

式 1 =< 式 2 以下

式 1 >= 式 2 以上

ではなく “=<” であることに注意が必要です。整数値を比較する場合は、int モジュールをイン

ポートする必要があります。以下は is_childを使ったプログラム例です。

1 :- module is_child.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- pred is_child(int::in) is semidet.

10 is_child(Age) :-

11 Age >= 0, Age =< 12.

12

13 main(!IO) :-

14 if is_child(12) then

15 write_string("子供\n", !IO)

16 else

Page 34: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

34 第 2章 述語

17 write_string("子供ではない\n", !IO).

実行結果は以下のようになります。

$ ./is_child

子供

上のプログラムでは述語 is_child を使って 12 歳が子供であるかそうでないかを判定していま

す。is_childの定義で 12歳は子どもとしているので、結果として子供と表示されます。

以下は doubleを 2回使って引数の値を 4倍する述語 quadrupleの定義例です。

:- pred quadruple(int::in, int::out) is det.

quadruple(X, Z) :-

double(X, Y),

double(Y, Z).

述語 quadruple は規則 1 つからなる述語です。これは宣言的には “double(X, Y) かつ

double(Y, Z) ならば、quadruple(X, Z)である”と読みます。操作的には、変数 Xで受け取っ

た値が、まず doubleに渡され、変数 Yに Xを 2倍した値が入ります。さらに Yが doubleに渡さ

れて最終的に変数 Zに Xを 4倍した値が入ります。以下に quadrupleを使ったプログラム例を示

します。

1 :- module quadruple.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- pred double(int::in, int::out) is det.

10 double(N, N * 2).

11

12 :- pred quadruple(int::in, int::out) is det.

13 quadruple(X, Z) :-

14 double(X, Y),

15 double(Y, Z).

16

Page 35: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

2.4 規則を使って述語を定義する 35

17 main(!IO) :-

18 quadruple(10, X),

19 write_int(X, !IO),

20 nl(!IO).

実行結果は以下のようになります。

$ ./quadruple

40

上のプログラムでは quadrupleを使って、10を 4倍した値を表示しています。

入出力を行う述語の定義を見てみましょう。以下は文字列を表示して改行する述語

write_string_nlを定義して使うプログラム例です。

1 :- module write_string_nl.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7

8 :- pred write_string_nl(string::in, io::di, io::uo) is det.

9 write_string_nl(Str, !IO) :-

10 write_string(Str, !IO),

11 nl(!IO).

12

13 main(!IO) :-

14 write_string_nl("Hello, World!", !IO).

実行結果を以下に示します。

$ ./write_string_nl

Hello, World!

Page 36: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

36 第 2章 述語

2.5 再帰的な述語

述語の定義の中で、今定義しつつある述語を利用することができます。このようにして自分自身

を呼び出す述語のことを再帰的な述語とよびます。再帰的な述語を利用すると、繰り返し処理を実

現できます。以下のプログラムでは、画面に Hello!と 3回表示する述語を定義して使っています。

1 :- module rec1.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- pred loop(int::in, io::di, io::uo) is det.

10 loop(N, !IO) :-

11 if N >= 3 then

12 write_string("終了\n", !IO)

13 else

14 write_string("Hello!\n", !IO),

15 loop(N + 1, !IO).

16

17 main(!IO) :-

18 loop(0, !IO).

実行結果は以下のようになります。

./rec1

Hello!

Hello!

Hello!

終了

述語 loopには 3つの引数がありますが、そのうち最初の変数は現在のループの回数を保持するカ

ウンタの役割をしています。mainでは最初にこのカウンタの値を 0に初期化して loopを呼び出

しています。loop本体では最初にカウンタの値が 3以上であるかどうかを判定し、もしそうであ

Page 37: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

2.5 再帰的な述語 37

れば、画面に終了と表示します。そうでなければ、画面に Helloと表示して、カウンタの値を 1増

やして自分自身を呼び出します。このプログラムでは loopは全部で 4回呼び出されます。最初は

カウンタの値が 0で mainから呼ばれます。残りはカウンタの値が 1、2、3のときで、loop自身

から再帰的によばれます。カウンタの値が 3のときに、if-then-elseゴールの条件を満たすので

再帰が終了します。

次は階乗の計算をする例を示します。階乗とは 1から N までの整数の積のことをいい、以下の

数式で定義されます。

fact(n) =

{1 (n = 0)

n× fact(n− 1) (それ以外)

以下のプログラムでは、再帰を使って階乗の計算をしています。

1 :- module fact.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- pred fact(int::in, int::out) is det.

10 fact(N, R) :-

11 if N = 0 then

12 R = 1

13 else

14 fact(N - 1, M),

15 R = N * M.

16

17 main(!IO) :-

18 write_string("fact(5) = ", !IO),

19 fact(5, N),

20 write_int(N, !IO),

21 nl(!IO).

実行結果は以下のようになります。

Page 38: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

38 第 2章 述語

$ ./fact

fact(5) = 120

factでは最初に if-then-elseゴールで、入力の Nが 0であるかどうかで分岐しています。Nが

0の場合、出力の Rを 1にして終了します。そうでない場合は、N - 1に対して自分自身を再帰的

に呼び出して、N - 1の階乗の値を計算します。N - 1の階乗の値は変数 Mで受け取り、これに N

を掛けることで Nの階乗の値を計算します。計算した値を出力の Rに設定して返しています。

次はフィボナッチ数列の計算をする例を示します。フィボナッチ数列は以下の数式で定義される

数列で、0, 1, 1, 2, 3, 5, 8, 13, · · · のように続きます。

fib(n) =

0 (n = 0)

1 (n = 1)

fib(n− 1) + fib(n− 2) (それ以外)

以下のプログラムでは再帰を使ってフィボナッチ数列の N 番目を計算する述語を定義して使って

います。

1 :- module fib.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- pred fib(int::in, int::out) is det.

10 fib(N, R) :-

11 if N = 0 then

12 R = 0

13 else if N = 1 then

14 R = 1

15 else

16 fib(N - 1, R1),

17 fib(N - 2, R2),

18 R = R1 + R2.

19

20 main(!IO) :-

21 fib(10, N),

Page 39: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

2.5 再帰的な述語 39

22 write_string("fib(10) = ", !IO),

23 write_int(N, !IO),

24 nl(!IO).

実行結果は以下のようになります。

./fib

fib(10) = 55

述語 fib では if-then-else ゴールを 2 回使っています。このように else のあとに次の

if-then-else ゴールを配置することで、条件ごとに異なるゴールに分岐するゴールが作れ

ます。

if 条件 1 then

条件 1が成り立った時に実行するゴール

else if 条件 2 then

条件 2が成り立った時に実行するゴール

...

else if 条件 n then

条件 nが成り立った時に実行するゴール

else

いずれの条件にも当てはまらない時に実行するゴール

述語 fib では、N = 0 のとき R = 0 を返し、N = 1 のとき R = 1 を返しています。それ以外の

時、fibは自分自身を 2度呼び出して、それぞれの答を足しあわせたものを全体の解としています。

以下のプログラムでは、フィボナッチ数列の 0番目から 10番目までの値を計算して表示してい

ます。

1 :- module fib_table.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- pred fib(int::in, int::out) is det.

10 fib(N, R) :-

Page 40: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

40 第 2章 述語

11 if N = 0 then

12 R = 0

13 else if N = 1 then

14 R = 1

15 else

16 fib(N - 1, R1),

17 fib(N - 2, R2),

18 R = R1 + R2.

19

20 :- pred loop(int::in, io::di, io::uo) is det.

21 loop(N, !IO) :-

22 if N > 10 then true

23 else

24 write_string("fib(", !IO),

25 write_int(N, !IO),

26 write_string(") = ", !IO),

27 fib(N, R),

28 write_int(R, !IO),

29 nl(!IO),

30 loop(N + 1, !IO).

31

32 :- pred loop(io::di, io::uo) is det.

33 loop(!IO) :-

34 loop(0, !IO).

35

36 main(!IO) :-

37 loop(!IO).

実行結果は以下のようになります。

./fib_table

fib(0) = 0

fib(1) = 1

fib(2) = 1

fib(3) = 2

fib(4) = 3

Page 41: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

2.6 関数 41

fib(5) = 5

fib(6) = 8

fib(7) = 13

fib(8) = 21

fib(9) = 34

fib(10) = 55

述語 fibは前のプログラムと同じものです。Mercuryでは引数の個数が異なる述語は別の述語と

して扱われます。引数の個数のことを述語のアリティと呼びます。上のプログラムで最初の loop

はアリティ 3、2 番目の loop はアリティ 2 です。アリティ 3 の loop を loop/3、アリティ 2 の

loopを loop/2と表記します。loop/2が実行されると、最初の引数を 0に初期化して loop/3が

実行されます。loop/3は Nの値が 10になるまで再帰的に呼び出されます。

2.6 関数

これまでは述語を定義してきましたが、述語は自然に式と組み合わせることができず不便な場合

があります。Mercuryでは関数を定義することができます。例えば、引数の値を 2倍する関数は

以下のように書けます。

:- func double(int) = int.

func(N) = N * 2.

関数の宣言は、func宣言を使って行います。func宣言は、以下の構文で行います。

:- func 名前 (型 1, ..., 型 n) = 戻り値の型.

上の doubleの例では、引数の型が intで戻り値の型が intです。特に指定をしない限り関数は

引数が inモード、戻り値が outモードで、決定性は detであるとみなされます。関数の定義は、

以下の構文で行います。

関数名 (項 1, ..., 項 n) = 項.

以下に doubleを使ったプログラム例を示します。

1 :- module double.

2

3 :- interface.

4 :- import_module io.

5 :- pred main(io::di, io::uo) is det.

6

Page 42: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

42 第 2章 述語

7 :- implementation.

8 :- import_module int.

9

10 :- func double(int) = int.

11 double(N) = N * 2.

12

13 main(!IO) :-

14 write_int(double(10), !IO),

15 nl(!IO).

実行結果は以下のようになります。

$ ./double

20

上のプログラムでは、述語 write_intの呼び出しの中で関数 doubleを呼び出しています。述語

と関数の違いに注意してください。述語は引数に応じて真であるか偽であるかが定まります、一方

関数は引数に応じて値を返します。

関数はゴールを使って定義することもできます。その場合は以下の構文を使います。

名前 (項 1, ..., 項 n) = 変数 :- ゴール.

関数 doubleをゴールを使って定義すると以下のようになります。

:- func double(int) = int.

double(N) = M :=

M = N * 2.

関数も述語同様、再帰的に呼び出すことができます。以下は再帰を使って階乗を求める関数を定

義した例です。

:- func fact(int) = int.

fact(N) = R :-

if N = 0 then

R = 1

else

R = N * fact(N - 1).

ここでは if-then-elseゴールを使って引数の値に応じて分岐させています。

Page 43: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

2.6 関数 43

Mercuryには if-then-else式と呼ばれる以下の構文が用意されています。

if ゴール then 項 1 else 項 2

if-then-else式は、ゴールを実行して成功すれば項 1の値を返し、失敗すれば項 2の値を返しま

す。以下のプログラムでは、fact関数を if-then-else式を使って定義しています。

1 :- module fact2.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- func fact(int) = int.

10 fact(N) = (if N = 0 then 1 else N * fact(N - 1)).

11

12 main(!IO) :-

13 write_string("fact(5) = ", !IO),

14 write_int(fact(5), !IO),

15 nl(!IO).

実行結果は以下のようになります。

$ ./fact

fact(5) = 120

if-then-else式を使うときは、上のように括弧で囲って式であることを明示する必要があります。

一般に、出力が 1つだけで決定性が detの述語は、関数として定義したほうがプログラムが単

純になります。関数の宣言ではモードを省略すると、入力は inモード、出力は outモードになり

ますが、以下の構文を使うことで、モードを明示することができます。

:- func 名前 (型 1::モード 1, ..., 型 n::モード n) = (戻り値の型::戻り値のモード).

例えば、factの宣言でモードを明示すると、

:- func fact(int::in) = (int::out).

になります。

Page 44: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

44 第 2章 述語

複数の述語や関数で共通に使われる値に、名前を付けておく場合、定数を定義すると便利です。

Mercuryでは引数が 0個の関数を使って、定数を表現します。

まず、定数の宣言は以下のようになります。

:- func 定数名 = 型.

次に、定数の定義は以下のようになります。

定数名 = 値.

以下のプログラムでは、値 123を定数 abcと定義して使っています。

1 :- module const.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7

8 :- func abc = int.

9 abc = 123.

10

11 main(!IO) :-

12 write_int(abc, !IO),

13 nl(!IO).

実行結果は以下のようになります。

./const

123

2引数の関数を中置演算子のように使いたい場合、以下の構文が利用できます。

項 1 ‘関数名‘ 項 2

このように書くと、内部的には、

関数名 (項 1, 項 2)

と同じ意味になります。

以下のプログラムでは関数 addを中置演算子的に使っています。

Page 45: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

2.6 関数 45

1 :- module operator.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- func add(int, int) = int.

10 add(N, M) = N + M.

11

12 main(!IO) :-

13 write_int(10 ‘add‘ 20, !IO),

14 nl(!IO).

実行結果は以下のようになります。

./operator

30

中置演算子を関数として使いたい場合は、

(演算子)

という構文を使います。以下のプログラムでは演算子 “*”を関数として使っています。

1 :- module operator2.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 main(!IO) :-

10 write_int((*)(2,3), !IO),

Page 46: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

46 第 2章 述語

11 nl(!IO).

実行結果は以下のようになります。

./operator2

6

Page 47: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

47

第 3章

リスト

3.1 リストの構築

リストは同じ型の値を並べたデータ構造です。たとえば、整数の 1、2、3 からなるリストは、

[1, 2, 3]、文字列の"hello"と"world"からなるリストは、["hello", "world"] というよう

に、角括弧の間に要素をコンマで区切って指定します。空っぽのリストは []で表します。リスト

の型は Tを要素の型とすると、list(T)というように表します。例えば、整数のリストであれば

list(int)、文字列のリストであれば list(string)といった具合です。リストを要素とするリ

ストも考えることができます。例えば、

[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

は整数のリストのリストで型は list(list(int)) になります。リストに要素を追加するには、

[要素 | リスト]という構文を使います。例えば、リスト [2, 3, 4]の先頭に 1を追加するには、

[1 | [2, 3, 4]] と記述します。追加する要素は [1, 2, 3 | [4, 5, 6]] のようにコンマで

区切って複数指定することができます。以下の 5つはすべてリスト [1, 2, 3]を表しています。

• [1, 2, 3]

• [1 | [2, 3]]

• [1, 2 | [3]]

• [1, 2, 3 | []]

• [1 | [2 | [3 | []]]]

プログラムでリストを利用するときは、標準ライブラリの listライブラリをインポートする必要

があります。

Page 48: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

48 第 3章 リスト

3.2 リストの分解

リストを分解するには、組み込みの述語=を使います。たとえば、リスト [1, 2, 3]をその先頭

の要素と残りのリストに分解するには、

[H | T] = [1, 2, 3]

のように記述します。上のゴールを実行すると、変数 Hに先頭要素の 1が入り、変数 Tに残りのリ

ストの [2, 3]が入ります。一般にリストの先頭要素のことをリストの頭部、残りの要素のことを

リストの尾部とよびます。空リストを以下のように先頭要素と残りの要素に分解しようとすると、

失敗します。

[H | T] = []

また、以下のように 1要素だけからなるリストを分解しようとすると、

[H | T] = [1]

変数 Hには 1が入り、変数 Tには空リスト [] が入ります。これらのことを利用すると、整数リス

トのすべての要素を 2倍する述語は以下のように記述できます。

:- pred double_list(list(int)::in, list(int)::out) is det.

double_list(L1, L2) :-

if

[X | Xs] = L1

then

double_list(Xs, Ys),

L2 = [ X * 2 | Ys ]

else

L2 = [].

このコードでは、最初に入力の L1が先頭要素と残りのリストに分解できるかどうかを試みて、分

解できた場合は、残りのリストを引数にして自分自身を呼び出し、その結果を変数 Ysで受け取り、

受け取ったリストに先頭要素を 2倍した値を付け加えて、変数 L2に渡して返しています。一方、

分解に失敗した場合は入力が空リストであるということなので、変数 L2には空リストを渡して返

しています。以下は上の述語を含むプログラムの例です。

1 :- module double_list.

2 :- interface.

Page 49: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

3.2 リストの分解 49

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, list.

8

9 :- pred double_list(list(int)::in, list(int)::out) is det.

10 double_list(L1, L2) :-

11 if

12 [X | Xs] = L1

13 then

14 double_list(Xs, Ys),

15 L2 = [ X * 2 | Ys ]

16 else

17 L2 = [].

18

19 main(!IO) :-

20 double_list([1, 2, 3], L),

21 print(L, !IO),

22 nl(!IO).

実行結果は以下のようになります。

$ ./double_list

[2, 4, 6]

この例で使っている述語 print は、io モジュールに定義されている任意の値を表示できる述語

です。

上の例からわかるように、あらゆるリストは空リストであるか、頭部と尾部に分解できるかのど

ちらかになります。このことを利用してリストを操作する述語は「または」を表すセミコロンを利

用して以下のように記述することができます。

:- pred double_list(list(int)::in, list(int)::out) is det.

double_list(L1, L2) :-

(

[] = L1,

L2 = []

Page 50: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

50 第 3章 リスト

;

[X | Xs] = L1,

double_list(Xs, Ys),

L2 = [ X * 2 | Ys ]

).

このコードは、“[] = L1 かつ L2 = [] または L1 = [X | Xs] かつ double_list(Xs, Ys) か

つ L2 = [ X * 2 | Ys ]”と読みます。

「または」を使った以下のような述語の定義は、

名前 (引数 1, ..., 引数 n) :- ゴール 1; ...; ゴール n.

以下のように節を分けて記述できます。

名前 (引数 1, ..., 引数 n) :- ゴール 1.

...

名前 (引数 1, ..., 引数 n) :- ゴール n.

このことを利用すると上の述語は、以下のように記述できます。

:- pred double_list(list(int)::in, list(int)::out) is det.

double_list(L1, L2) :-

[] = L1,

L2 = [].

double_list(L1, L2) :-

[X | Xs] = L1,

double_list(Xs, Ys),

L2 = [ X * 2 | Ys ].

さらに、述語の引数の変数を値に置き換えることで、上の述語は以下のようにとても簡潔に記述で

きます。

:- pred double_list(list(int)::in, list(int)::out) is det.

double_list([], []).

double_list([X | Xs], [ X * 2 | Ys ]) :- double_list(Xs, Ys).

最終的な述語を含むプログラム全体を以下に示します。

1 :- module double_list2.

2 :- interface.

Page 51: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

3.3 多相的な述語 51

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, list.

8

9 :- pred double_list(list(int)::in, list(int)::out) is det.

10 double_list([], []).

11 double_list([X | Xs], [ X * 2 | Ys ]) :- double_list(Xs, Ys).

12

13 main(!IO) :-

14 double_list([1, 2, 3], L),

15 print(L, !IO),

16 nl(!IO).

実行結果は以下のようになります。

$ ./double_list2

[2, 4, 6]

3.3 多相的な述語

2つの整数リストを連結する述語は以下のように定義できます。

:- pred app_int(list(int)::in, list(int)::in, list(int)::out) is det.

app_int([], Ys, Ys).

app_int([X | Xs], Ys, [X | Zs]) :- app_int(Xs, Ys, Zs).

この述語は整数のリストだけで利用でき、例えば文字列のリストを連結することはできません。あ

らゆるリストで利用可能なリストを連結する述語を定義するには、型変数を使います。上の述語の

リストの型 list(int)の代わりに、list(T)と書くと、以下のように任意の述語で呼び出し可能

な述語を定義できます。

:- pred app(list(T)::in, list(T)::in, list(T)::out) is det.

app([], Ys, Ys).

app([X | Xs], Ys, [X | Zs]) :- app(Xs, Ys, Zs).

Page 52: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

52 第 3章 リスト

上の述語を含むプログラム例を以下に示します。

1 :- module app.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, list.

8

9 :- pred app(list(T)::in, list(T)::in, list(T)::out) is det.

10 app([], Ys, Ys).

11 app([X | Xs], Ys, [X | Zs]) :- app(Xs, Ys, Zs).

12

13 main(!IO) :-

14 app([1,2,3], [4,5,6], L1),

15 print(L1, !IO),

16 nl(!IO),

17 app(["a", "b"], ["c"], L2),

18 print(L2, !IO),

19 nl(!IO).

実行例は以下のようになります。

$ ./app

[1, 2, 3, 4, 5, 6]

["a", "b", "c"]

上の Tのように型中に現れる変数のことを型変数とよびます。リストを連結する述語は、listモ

ジュールに appendという名前で既に定義されています。

:- pred list.append(list(T)::in, list(T)::in, list(T)::out).

3.4 多相的な関数

関数も多相的にできます。以下はリストの長さを求める関数 len を定義して使うプログラムの

例です。

Page 53: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

3.5 タプル 53

1 :- module len.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, list.

8

9 :- func len(list(T)) = int.

10 len([]) = 0.

11 len([_|Xs]) = 1 + len(Xs).

12

13 main(!IO) :-

14 write_int(len([1, 2, 3]), !IO),

15 nl(!IO),

16 write_int(len(["a", "b"]), !IO),

17 nl(!IO).

実行結果は以下のようになります。

$ ./len

3

2

リストの長さを求めるは、listモジュールに lengthという名前で既に定義されています。

:- func list.length(list(T)) = int.

3.5 タプル

タプルは複数の値の組です。タプルは中括弧の中に要素をコンマで区切って並べます。タプルの

型は中括弧の中に対応する型をコンマで区切って並べます。例えば、文字列"hello"と整数"42"

のタプルは、{"hello", 42} のように表し、その型は{string, int}になります。表 3.1にいく

つかの例を示します。

タプルを分解するには、リストと同じように組み込みの述語 “=”を使います。以下にプログラム

Page 54: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

54 第 3章 リスト

表 3.1 タプルの例

値 型

{12, "abc", 3.14} {int, string, float}

{"abc", {123, 3.14}} {int, {string, float}}

{[1,2,3], [4,5]} {list(int), list(int)}

[{1,"a"}, {2,"b"}] list({int, string})

例を示します。

1 :- module tuple.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 main(!IO) :-

8 {N, S, F} = {12, "abc", 3.14},

9 write_int(N, !IO),

10 nl(!IO),

11 write_string(S, !IO),

12 nl(!IO),

13 write_float(F, !IO),

14 nl(!IO).

実行結果は以下のようになります。

$ ./tuple

12

abc

3.14

3.6 文字のリスト

文字列 (string)は文字のリスト (list(char))と相互変換ができます。文字列を文字のリスト

に変換するには、stringモジュールの to_char_list関数、文字のリストを文字列に変換するに

Page 55: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

3.7 リストの整列 55

は、同じく stringモジュールの from_char_list関数を使います。

:- func string.to_char_list(string) = list(char).

:- func string.from_char_list(list(char)::in) = (string::uo) is det.

上の関数宣言のうち、from_char_list にはモードの指定が付加されています。これは

from_char_list によって新しいメモリ領域に割り当てられた文字列が返ってくることを表して

います。以下のプログラムでは文字列を文字のリストに変換し、その文字列を listモジュールの

reverse関数で反転させ、最後に再び文字列に戻して表示しています。

1 :- module rev_string.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module list, string.

8

9 main(!IO) :-

10 Str0 = "Hello",

11 CList0 = to_char_list(Str0),

12 CList1 = reverse(CList0),

13 Str1 = from_char_list(CList1),

14 write_string(Str1, !IO),

15 nl(!IO).

実行結果は以下のようになります。

$ ./rev_string

olleH

3.7 リストの整列

ここではクイックソートのアルゴリズムを使って整数のリストを昇順に整列するプログラムを

Mercuryで作りたいと思います。

まずはリストをある値よりも小さい要素を集めたリストとある値以上の要素を集めたリストとに

分割する述語 partitionを作ります。述語 partitionの宣言は以下のようになります。

Page 56: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

56 第 3章 リスト

:- partition(int::in, list(int)::in, list(int)::out, list(int)::out)

is det.

まずは空リストが入力として与えられた時は、以下のように空リストを出力として返します。

partition(_, [], [], []).

述語中の “_” は匿名変数とよばれるもので、特に変数名を付ける必要がない場合に使います。次

に、リストが空でない時は、まず、先頭要素を除いたリストに対して再帰的に partitionを呼び

出し、その結果を Less0と Greater0と名づけます。次に、先頭要素と比較用の値を比較して、先

頭要素のほうが小さければ、その値を Less0に付け加え、先頭要素の方が大きいか等しければそ

の値を Greater0に付け加えて返します。これをコードにすると以下のようになります。

partition(Key, [X | Xs], Less, Greater) :-

partition(Key, Xs, Less0, Greater0),

( if X < Key then

Less = [X | Less0],

Greater = Greater0

else

Less = Less0,

Greater = [X | Greater0]).

次に、以下のような宣言の述語 quicksortを作ります。

:- pred quicksort(list(int)::in, list(int)::out) is det.

まずは空リストが入力の場合は空リストを出力として返します。

quicksort([], []).

次に、空でないリストが与えられた場合は、上で作った述語 partitionを使って先頭要素を比較

用の値として、残りのリストを分割します。そして、分割したリストそれぞれに対して quicksort

を再帰的に呼び出して整列させます。最後に、得られたリストを連結することで整列されたリスト

が得られます。以下にコードを示します。

quicksort([X | Xs], Less ++ [X] ++ Greater) :-

partition(X, Xs, Less0, Greater0),

quicksort(Less0, Less),

quicksort(Greater0, Greater).

上のプログラムで使っている “++”はリスト同士を連結する関数で listモジュールに定義されて

Page 57: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

3.7 リストの整列 57

います。

quicksortを使ったプログラム全体を以下に示します。

1 :- module quicksort.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, list.

8

9 :- pred partition(int::in, list(int)::in, list(int)::out,

10 list(int)::out) is det.

11 partition(_, [], [], []).

12 partition(Key, [X | Xs], Less, Greater) :-

13 partition(Key, Xs, Less0, Greater0),

14 ( if X < Key then

15 Less = [X | Less0],

16 Greater = Greater0

17 else

18 Less = Less0,

19 Greater = [X | Greater0]).

20

21 :- pred quicksort(list(int)::in, list(int)::out) is det.

22 quicksort([], []).

23 quicksort([X | Xs], Less ++ [X] ++ Greater) :-

24 partition(X, Xs, Less0, Greater0),

25 quicksort(Less0, Less),

26 quicksort(Greater0, Greater).

27

28 main(!IO) :-

29 quicksort([3, 1, 4, 1, 5, 9, 2], R),

30 print(R, !IO),

31 nl(!IO).

実行結果を以下に示します。

Page 58: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

58 第 3章 リスト

$ ./quicksort

[1, 1, 2, 3, 4, 5, 9]

3.8 複数のモード

述語の宣言はこれまで型とモードをまとめて書いていましたが、これらは分割して以下のように

書くこともできます。

:- pred 名前 (型 1, ..., 型 n).

:- mode 名前 (モード 1, ..., モード n) is 決定性.

例えば、

:- pred app(list(T)::in, list(T)::in, list(T)::out) is det.

を型とモードを分割して宣言すると、

:- pred app(list(T), list(T), list(T)).

:- mode app(in, in, out) is det.

になります。

型は各述語に付き 1つだけですが、モードは複数持たせることができます。2つのリストを連結

する述語 appは以下のように定義していました。

:- pred app(list(T)::in, list(T)::in, list(T)::out) is det.

app([], Ys, Ys).

app([X | Xs], Ys, [X | Zs]) :- app(Xs, Ys, Zs).

この述語はモード宣言を追加することで他の用途にも使うことができます。例えば、

:- mode app(in, in, in) is semidet.

を追加すると、リストが 2つのリストを連結したものになっているかを判定する述語になります。

他にも、

:- mode app(in, out, in) is semidet.

を追加すると、リストから先頭の一部を切り取ったリストを返す述語になります。

以下は appにモードを追加して、複数の用途に使うプログラムの例です。

Page 59: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

3.8 複数のモード 59

1 :- module app2.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module list.

8

9 :- pred app(list(T)::in, list(T)::in, list(T)::out) is det.

10 :- mode app(in, in, in) is semidet.

11 :- mode app(in, out, in) is semidet.

12 app([], Ys, Ys).

13 app([X | Xs], Ys, [X | Zs]) :- app(Xs, Ys, Zs).

14

15 main(!IO) :-

16 % 通常の使い方

17 app([1,2,3], [4,5,6], L1),

18 print(L1, !IO),

19 nl(!IO),

20

21 % リストが連結したものになっているか判定する使い方

22 ( if app([1,2], [3,4], [1,2,3,4])

23 then write_string("true\n", !IO)

24 else write_string("false\n", !IO) ),

25

26 % リストから先頭の一部を切り取る使い方

27 ( if app([1, 2], L2, [1, 2, 3, 4, 5])

28 then print(L2, !IO),

29 nl(!IO)

30 else write_string("failure\n", !IO) ).

実行結果は以下のようになります。

$ ./app2

[1, 2, 3, 4, 5, 6]

Page 60: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

60 第 3章 リスト

true

[3, 4, 5]

Page 61: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

61

第 4章

型を作る

4.1 値を列挙する

type宣言を使うと、ユーザが新しく型を定義できます。ここでは例として、値 red、yellow、

green の 3 つの値を持つ信号を表す型 signal を定義してみましょう。Mercury で型 signal を

定義するには以下のようにします。

:- type signal ---> green; yellow; red.

例として、信号の次の値を返す nextという関数の定義例を以下に示します。

:- func next(signal) = signal.

next(green) = yellow.

next(yellow) = red.

next(red) = green.

ここでは型とその値が int や string などの組み込みの型と全く同じように扱えていることに注

意してください。値 red、yellow、greenのことを、型 signalの構成子とよびます。上の関数を

含むプログラム全体を以下に示します。

1 :- module signal.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7

8 :- type signal ---> green; yellow; red.

Page 62: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

62 第 4章 型を作る

9

10 :- func next(signal) = signal.

11 next(green) = yellow.

12 next(yellow) = red.

13 next(red) = green.

14

15 main(!IO) :-

16 print(next(red), !IO),

17 nl(!IO).

実行結果は以下のようになります。

$ ./signal

green

4.2 引数をもたせる

型の構成子には引数を持たせることもできます。例として図形を表す figure という型を定義

することを考えます。figureは、長方形を表す rectangle、三角形を表す triangle、円を表す

circleからなり、長方形の場合は幅と高さ、三角形の場合も底辺と高さ、円の場合は半径の大き

さを引数として浮動小数点数で持っています。そのような型 figureの定義例を以下に示します。

:- type figure

---> rectangle(float, float)

; triangle(float, float)

; circle(float).

例として図形の面積をもめる述語 areaの定義例を示します。

:- pred area(figure::in, float::out) is det.

area(rectangle(W, H), W * H).

area(triangle(W, H), W * H / 2.0).

area(circle(R), R * R * pi).

pi は math モジュールで定義されている円周率を表す定数です。上の述語を含むプログラム全体

を以下に示します。

Page 63: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

4.3 引数に名前を付ける 63

1 :- module area.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module float, math.

8

9 :- type figure

10 ---> rectangle(float, float)

11 ; triangle(float, float)

12 ; circle(float).

13

14 :- pred area(figure::in, float::out) is det.

15 area(rectangle(W, H), W * H).

16 area(triangle(W, H), W * H / 2.0).

17 area(circle(R), R * R * pi).

18

19 main(!IO) :-

20 Fig = circle(10.0),

21 area(Fig, Area),

22 write_float(Area, !IO),

23 nl(!IO).

実行結果は以下のようになります。

$ ./area

314.1592653589793

4.3 引数に名前を付ける

型の構成子の引数には名前を付けることもできます。以下の例では人物の名前と年齢を表すデー

タ構造を定義しています。

Page 64: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

64 第 4章 型を作る

:- type person

---> person(name::string, age::int).

これにより、以下のようにフィールドの値を取得できるようになります。

...

Taro = person("taro", 16),

...

Name = Taro^name,

...

また、以下のような構文でフィールドを書き換えた新しい値を作ることができます。

...

Taro = person("taro", 16),

...

Hanako = Taro^name := "hanako",

...

上の 2つの概念を含むプログラム例を以下に示します。

1 :- module person.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7

8 :- type person

9 ---> person(name::string, age::int).

10

11 main(!IO) :-

12 Taro = person("taro", 16),

13 Name = Taro^name,

14 write_string(Name, !IO),

15 nl(!IO),

16 Hanako = Taro^name := "hanako",

17 print(Hanako, !IO),

Page 65: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

4.4 自然数 65

18 nl(!IO).

実行結果は以下のようになります。

$ ./person

taro

person("hanako", 16)

4.4 自然数

型定義の中では自分自身を再帰的に使うことができます。以下は自然数 0を表す zeroと後者関

数を表す succを使って、自然数の型を表したデータ構造です。

:- type nat ---> zero; succ(nat).

このようなデータ構造があると、例えば自然数の 3は、succ(succ(succ(zero))) のように表す

ことができます。ここでは nat型を使った関数をいくつか定義してみます。

まずは、nat型の値を int型の値に変換する関数を以下のように定義します。

:- func nat_to_int(nat) = int.

nat_to_int(zero) = 0.

nat_to_int(succ(N)) = 1 + nat_to_int(N).

反対に、int型の値を nat型の値に変換する関数は以下のように定義できます。

:- func int_to_nat(int) = nat.

int_to_nat(I) =

( if I = 0 then zero

else succ(int_to_nat(I - 1)) ).

引数として 0以上の値が与えられた場合は問題ないのですが、負の数が与えられた場合、この定義

では無限ループになってしまいます。それを防止するために、負の数が入力として与えられた場合

はエラーメッセージを表示するようにしましょう。require モジュールの述語 error を使うと、

エラーメッセージを表示してプログラムの実行を止めることができます。

:- pred error(string::in) is erroneous.

この述語の決定性の erroneousは、述語が解を返さず、また、失敗することもないということを

表しています。この述語を使うと、int_to_natは以下のように改善できます。

Page 66: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

66 第 4章 型を作る

:- func int_to_nat(int) = nat.

int_to_nat(I) = R :-

if I < 0 then

error("int_to_nat: 引数が負")

else if I = 0 then

R = zero

else

R = succ(int_to_nat(I - 1)).

引数に負の値が渡された場合、画面に以下のように表示されます。

Uncaught Mercury exception:

Software Error: int_to_nat: 引数が負

Stack dump not available in this grade.

次に、nat同士を加算する関数を定義します。そのような関数は以下のように定義できます。

:- func add(nat, nat) = nat.

add(zero, N) = N.

add(succ(N), M) = succ(add(N, M)).

引数の片方が zeroの場合は、もう片方の値を返して、succ(N)の形の場合は、Nともう片方の値

を足した数に succで 1を足しています。

さらに、addを使って nat同士の掛け算をする関数が以下のように定義できます。

:- func mul(nat, nat) = nat.

mul(zero, _) = zero.

mul(succ(N), M) = add(mul(N, M), M).

引数の片方が zeroの場合は答も zero、succ(N)の形の場合は、Nともう片方の値 Mを掛けた値

に、Mの値を足しています。

以下に natを使ったプログラムの例を示します。

1 :- module nat.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

Page 67: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

4.4 自然数 67

7 :- import_module int, require.

8

9 :- type nat ---> zero; succ(nat).

10

11 :- func nat_to_int(nat) = int.

12 nat_to_int(zero) = 0.

13 nat_to_int(succ(N)) = 1 + nat_to_int(N).

14

15 :- func int_to_nat(int) = nat.

16 int_to_nat(I) = R :-

17 if I < 0 then

18 error("int_to_nat: 引数が負")

19 else if I = 0 then

20 R = zero

21 else

22 R = succ(int_to_nat(I - 1)).

23

24 :- func add(nat, nat) = nat.

25 add(zero, N) = N.

26 add(succ(N), M) = succ(add(N, M)).

27

28 :- func mul(nat, nat) = nat.

29 mul(zero, _) = zero.

30 mul(succ(N), M) = add(mul(N, M), M).

31

32 main(!IO) :-

33 Three = int_to_nat(3),

34 print(Three, !IO), nl(!IO),

35 Five = int_to_nat(5),

36 Result = mul(Three, Five),

37 write_int(nat_to_int(Result), !IO),

38 nl(!IO).

実行結果は以下のようになります。

Page 68: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

68 第 4章 型を作る

succ(succ(succ(zero)))

15

4.5 型パラメータ

型定義には型パラメータをもたせて多相的にすることもできます。以下は多相の二分木を定義し

た例です。

:- type tree(T)

---> leaf(T)

; branch(tree(T), tree(T)).

このような定義があると、

branch(leaf("a"), branch(leaf("b"), leaf("c")))

は、tree(string)型になり、

branch(branch(leaf(1), leaf(2)), branch(leaf(3), leaf(4)))

は、tree(int)型になります。

以下のプログラムでは、木構造の値を構築して、それを表示しています。

1 :- module tree.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7

8 :- type tree(T)

9 ---> leaf(T)

10 ; branch(tree(T), tree(T)).

11

12 main(!IO) :-

13 T1 = branch(leaf("a"), branch(leaf("b"), leaf("c"))),

14 print(T1, !IO),

15 nl(!IO),

16 T2 = branch(branch(leaf(1), leaf(2)), branch(leaf(3), leaf(4))),

Page 69: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

4.5 型パラメータ 69

17 print(T2, !IO),

18 nl(!IO).

実行結果は以下のようになります。

$ ./tree

branch(leaf("a"), branch(leaf("b"), leaf("c")))

branch(branch(leaf(1), leaf(2)), branch(leaf(3), leaf(4)))

木構造に関する述語をいくつか定義してみましょう。以下は木の左右を反転させる述語の定義

です。

:- pred flip(tree(T)::in, tree(T)::out) is det.

flip(leaf(V), leaf(V)).

flip(branch(T1, T2), branch(T4, T3)) :-

flip(T1, T3),

flip(T2, T4).

leafはそのまま返し、branchの時はその子供を左右反転させたものを branchに左右逆にして接

続しています。

以下は木構造をリストに変換する関数の定義です。

:- func tree_to_list(tree(T)) = list(T).

tree_to_list(leaf(V)) = [V].

tree_to_list(branch(T1, T2)) =

tree_to_list(T1) ++ tree_to_list(T2).

leafのときはその値だけからなるリストを返し、branchのときは左右の子供をリストに変換した

ものを “++”で連結しています。

以下は flipと tree_to_listを使ったプログラム例です。

1 :- module flip.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module list.

8

Page 70: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

70 第 4章 型を作る

9 :- type tree(T)

10 ---> leaf(T)

11 ; branch(tree(T), tree(T)).

12

13 :- pred flip(tree(T)::in, tree(T)::out) is det.

14 flip(leaf(V), leaf(V)).

15 flip(branch(T1, T2), branch(T4, T3)) :-

16 flip(T1, T3),

17 flip(T2, T4).

18

19 :- func tree_to_list(tree(T)) = list(T).

20 tree_to_list(leaf(V)) = [V].

21 tree_to_list(branch(T1, T2)) =

22 tree_to_list(T1) ++ tree_to_list(T2).

23

24 main(!IO) :-

25 T1 = branch(leaf("a"), branch(leaf("b"), leaf("c"))),

26 print(T1, !IO),

27 nl(!IO),

28 flip(T1, T2),

29 print(T2, !IO),

30 nl(!IO),

31 print(tree_to_list(T1), !IO),

32 nl(!IO),

33 print(tree_to_list(T2), !IO),

34 nl(!IO).

実行結果は以下のようになります。

$ ./flip

branch(leaf("a"), branch(leaf("b"), leaf("c")))

branch(branch(leaf("c"), leaf("b")), leaf("a"))

["a", "b", "c"]

["c", "b", "a"]

Page 71: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

4.6 数式の計算 71

4.6 数式の計算

今度は数式の抽象構文木を表す型を定義してみます。

:- type expr

---> int(int)

; add(expr, expr)

; sub(expr, expr)

; mul(expr, expr)

; div(expr, expr).

このデータ構造を使うと、例えば 3 + 4× 5は、

add(int(3), mul(int(4), int(5)))

のように表すことができます。

数式を評価して整数値を求める述語は以下のように定義できます。

:- pred eval(expr::in, int::out) is det.

eval(int(N), N).

eval(add(E1, E2), N1 + N2) :-

eval(E1, N1),

eval(E2, N2).

eval(sub(E1, E2), N1 - N2) :-

eval(E1, N1),

eval(E2, N2).

eval(mul(E1, E2), N1 * N2) :-

eval(E1, N1),

eval(E2, N2).

eval(div(E1, E2), N1 / N2) :-

eval(E1, N1),

eval(E2, N2).

intの場合はその値をそのまま返し、それ以外の場合は子供の構文木をそれぞれ再帰的に evalに

通して値を計算しています。

この述語を含むプログラムの例を以下に示します。

Page 72: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

72 第 4章 型を作る

1 :- module expression.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- type expr

10 ---> int(int)

11 ; add(expr, expr)

12 ; sub(expr, expr)

13 ; mul(expr, expr)

14 ; div(expr, expr).

15

16 :- pred eval(expr::in, int::out) is det.

17 eval(int(N), N).

18 eval(add(E1, E2), N1 + N2) :-

19 eval(E1, N1),

20 eval(E2, N2).

21 eval(sub(E1, E2), N1 - N2) :-

22 eval(E1, N1),

23 eval(E2, N2).

24 eval(mul(E1, E2), N1 * N2) :-

25 eval(E1, N1),

26 eval(E2, N2).

27 eval(div(E1, E2), N1 / N2) :-

28 eval(E1, N1),

29 eval(E2, N2).

30

31 main(!IO) :-

32 Expr = add(int(3), mul(int(4), int(5))),

33 eval(Expr, N),

34 write_int(N, !IO),

Page 73: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

4.6 数式の計算 73

35 nl(!IO).

実行結果は以下のようになります。

$ ./expression

23

Page 74: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。
Page 75: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

75

第 5章

値としての述語と関数

Mercury では述語や関数を値として扱うことができます。これは、以下のようなことができる

ことを表しています。

• 述語や関数を変数に代入する• 述語や関数を述語や関数の引数として渡す• 述語や関数から述語や関数を値として返す• 述語や関数をデータ構造に格納する

述語や関数を引数として受け渡す述語や関数のことを高階述語や高階関数とよびます。本章では高

階述語や高階関数の例を、様々なサンプルコードでみていきます。

5.1 値としての述語

Mercuryでは述語を変数に代入して使うことができます。例えば、

P = write_string

とすると、変数 Pに述語 write_stringを代入できます。これを使うには、

call(P, "Hello, World.\n", !IO)

もしくは、

P("Hello, World.\n", !IO)

のようにします。以下にプログラム全体を示します。

1 :- module hello6.

2 :- interface.

Page 76: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

76 第 5章 値としての述語と関数

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 main(!IO) :-

8 P = write_string,

9 P("Hello, World!\n", !IO).

実行結果は以下のようになります。

$ ./hello6

Hello, World!

5.2 値としての述語の型とモード

値としての述語の型は以下のように表します。

pred(引数の型 1, ..., 引数の型 n)

値としての述語のモードは入力として使われる時は、

in(pred(モード 1, ..., モード n) is 決定性)

もしくは、in(...)を省略して、

pred(モード 1, ..., モード n) is 決定性

のように表します。出力として使われる時は、

out(pred(モード 1, ..., モード n) is 決定性)

のように表します。この out(...)は省略できません。

5.3 述語を受け取る述語

例えば、以下のような述語があったとします。

:- pred double(int::in, int::out) is det.

double(X, X * 2).

Page 77: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

5.3 述語を受け取る述語 77

この述語を受け取って、最初の引数に 10を受け渡し結果を返す述語 call10を考えてみます。そ

のような述語の宣言は以下のようになります。

:- pred call10(pred(int,int), int).

:- mode call10(pred(in,out) is det, out) is det.

型とモードをまとめて書くと、

:- pred call10(pred(int,int)::pred(in,out) is det, int::out) is det

になります。値としての述語を受け渡す場合は、型とモードは分けて書いたほうがコードが読みや

すくなります。述語の定義は以下のようになります。

call10(Pred, R) :-

Pred(10, R).

この述語を使ったプログラムの例を以下に示します。

1 :- module call10.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- pred double(int::in, int::out) is det.

10 double(X, X * 2).

11

12 :- pred add3(int::in, int::out) is det.

13 add3(X, X + 3).

14

15 :- pred call10(pred(int,int), int).

16 :- mode call10(pred(in,out) is det, out) is det.

17 call10(Pred, R) :-

18 Pred(10, R).

19

20 main(!IO) :-

21 call10(double, N1),

Page 78: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

78 第 5章 値としての述語と関数

22 write_int(N1, !IO),

23 nl(!IO),

24 call10(add3, N2),

25 write_int(N2, !IO),

26 nl(!IO).

実行結果は以下のようになります。

$ ./call10

20

13

5.4 無名述語

わざわざ名前を付けるまでもない小さな述語を定義するときのために、無名の述語を定義する構

文がMercuryには用意されています。構文は、

pred(引数 1::モード 1, ..., 引数 n::モード n) is 決定性 :- ゴール

のようになります。以下のプログラムでは、無名述語を定義して使うプログラムの例です。

1 :- module anonymous_pred.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 main(!IO) :-

10 P = (pred(X::in, Y::out) is det :- Y = X * 2),

11 P(10, N),

12 write_int(N, !IO),

13 nl(!IO).

実行結果は以下のようになります。

Page 79: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

5.5 述語を返す述語 79

$ ./anonymous_pred

20

5.5 述語を返す述語

今度は述語を返す述語を定義してみましょう。以下は整数引数 Nを受け取って、整数を N倍する

新しい述語を返す述語を定義した例です。

1 :- module make_pred.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- pred make_pred(int, pred(int,int)).

10 :- mode make_pred(in, out(pred(in,out) is det)) is det.

11 make_pred(N, Pred) :-

12 Pred = (pred(X::in, Y::out) is det :- Y = X * N).

13

14 main(!IO) :-

15 make_pred(2, P1),

16 P1(10, X),

17 write_int(X, !IO),

18 nl(!IO),

19 make_pred(3, P2),

20 P2(10, Y),

21 write_int(Y, !IO),

22 nl(!IO).

実行結果は以下のようになります。

$ ./make_pred

20

30

Page 80: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

80 第 5章 値としての述語と関数

5.6 値としての関数

関数も述語と同様に値として扱うことができます。例えば、

F = length

とすると、変数 Fに関数 lengthを代入できます。これを使うには、

apply(F, [1,2,3])

もしくは、

F([1,2,3])

のようにします。以下にプログラム例を示します。

1 :- module functional_value.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module list.

8

9 main(!IO) :-

10 F = length,

11 write_int(F([1,2,3]), !IO),

12 nl(!IO).

実行結果は以下のようになります。

$ ./functional_value

3

値としての関数の型は以下のように表します。

func(型 1, ..., 型 n) = 結果型.

また無名の関数は、

Page 81: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

5.7 述語や関数をデータ構造に格納する 81

func(引数 1, ..., 引数 n) = 結果 :- ゴール

のように書きます。値としての関数 Fを呼び出すには、

以下のプログラムでは関数と値をを引数として受け取って、その関数を値に 2回適用した結果を

返す述語 apply_twiceを定義して使っています。

1 :- module apply_twice.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- pred apply_twice(func(int) = int, int, int).

10 :- mode apply_twice(in, in, out) is det.

11 apply_twice(F, N, R) :-

12 R = F(F(N)).

13

14 main(!IO) :-

15 apply_twice(func(X) = Y :- Y = X * 2, 10, R),

16 write_int(R, !IO),

17 nl(!IO).

実行結果は以下のようになります。

$ ./apply_twice

40

5.7 述語や関数をデータ構造に格納する

述語や関数は整数や文字列など他の値と同様に、リストやタプルに格納することができます。

関数のリストを受け取って、引数の値に順番に関数を適用していく述語は以下のように定義でき

ます。

:- pred apply_list(list(func(int) = int), int, int).

:- mode apply_list(in, in, out) is det.

Page 82: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

82 第 5章 値としての述語と関数

apply_list([], N, N).

apply_list([F|Fs], N, R) :-

apply_list(Fs, F(N), R).

関数リストが空の場合は整数をそのまま返し、空でない場合は先頭の関数を値に適用します。

apply_listを残りのリストに対して再帰的に適用することですべての関数を値に適用します。こ

の述語を使ったプログラムの例を以下に示します。

1 :- module apply_list.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, list.

8

9 :- func twice(int) = int.

10 twice(N) = N * 2.

11

12 :- func incr(int) = int.

13 incr(N) = N + 1.

14

15 :- pred apply_list(list(func(int) = int), int, int).

16 :- mode apply_list(in, in, out) is det.

17 apply_list([], N, N).

18 apply_list([F|Fs], N, R) :-

19 apply_list(Fs, F(N), R).

20

21 main(!IO) :-

22 apply_list([twice, incr], 10, N1),

23 write_int(N1, !IO),

24 nl(!IO),

25 apply_list([incr, twice], 10, N2),

26 write_int(N2, !IO),

27 nl(!IO).

Page 83: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

5.8 部分適用 83

実行結果は以下のようになります。

$ ./apply_list

21

22

5.8 部分適用

関数や述語は引数の一部だけを与えることができます。以下のプログラムでは、2 引数の関数

add を定義して、それに引数を 1 つだけ与えて F と名前を付けて、後から残りの引数を渡してい

ます。

1 :- module partial.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- func add(int, int) = int.

10 add(X, Y) = X + Y.

11

12 main(!IO) :-

13 F = add(3),

14 write_int(F(5), !IO),

15 nl(!IO).

実行結果は以下のようになります。

$ ./partial

8

以下のプログラムは述語の引数を一部だけ与えて使う例です。

1 :- module partial2.

2 :- interface.

Page 84: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

84 第 5章 値としての述語と関数

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- pred add(int::in, int::in, int::out) is det.

10 add(X, Y, X + Y).

11

12 main(!IO) :-

13 P = add(3),

14 P(5, X),

15 write_int(X, !IO),

16 nl(!IO).

実行結果は以下のようになります。

$ ./partial2

8

5.9 リストに関する高階関数

標準ライブラリの listモジュールには、引数として述語や関数を受け取る述語や関数が多数定

義されています。ここではそれらのうちいくつかを紹介します。

5.9.1 map

mapはリストの各要素に述語を適用する述語で以下のように宣言されています。

:- pred list.map(pred(X, Y), list(X), list(Y)).

:- mode list.map(pred(in, out) is det, in, out) is det.

:- mode list.map(pred(in, out) is cc_multi, in, out) is cc_multi.

:- mode list.map(pred(in, out) is semidet, in, out) is semidet.

:- mode list.map(pred(in, out) is multi, in, out) is multi.

:- mode list.map(pred(in, out) is nondet, in, out) is nondet.

:- mode list.map(pred(in, in) is semidet, in, in) is semidet.

Page 85: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

5.9 リストに関する高階関数 85

上のモードで現れる決定性 cc_multiは次章で説明します。以下のプログラムでは、mapを使って

整数リストのすべての要素を 2倍にしています。

1 :- module map.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, list.

8

9 main(!IO) :-

10 map(pred(X::in, Y::out) is det :- Y = X * 2, [1, 2, 3], R),

11 print(R, !IO),

12 nl(!IO).

実行結果は以下のようになります。

$ ./map

[2, 4, 6]

mapには関数バージョンもあり、以下のように宣言されています。

:- func list.map(func(X) = Y, list(X)) = list(Y).

以下のプログラムは map関数を使って整数リストのすべての要素を 2倍しています。

1 :- module map2.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, list.

8

9 main(!IO) :-

10 R = map(func(X) = Y :- Y = X * 2, [1, 2, 3]),

11 print(R, !IO),

Page 86: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

86 第 5章 値としての述語と関数

12 nl(!IO).

実行結果は以下のようになります。

$ ./map2

[2, 4, 6]

5.9.2 filter

filterは指定した条件を満たす要素だけを集めてくる述語で、以下のように宣言されています。

:- pred list.filter(pred(X)::in(pred(in) is semidet), list(X)::in,

list(X)::out) is det.

:- func list.filter(pred(X)::in(pred(in) is semidet), list(X)::in)

= (list(X)::out) is det.

以下のプログラムでは、この述語を使ってリスト中の 5以上の整数を集めてきて表示しています。

1 :- module filter.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, list.

8

9 main(!IO) :-

10 filter(pred(X::in) is semidet :- X >= 5, [2,7,1,8,2,8,1], R),

11 print(R, !IO),

12 nl(!IO).

実行結果は以下のようになります。

$ ./filter

[7, 8, 8]

Page 87: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

5.9 リストに関する高階関数 87

5.9.3 foldl

foldlはリストを左から右に向かって畳み込む述語で以下のように宣言されています。

:- pred list.foldl(pred(L, A, A), list(L), A, A).

:- mode list.foldl(pred(in, in, out) is det, in, in, out) is det.

:- mode list.foldl(pred(in, mdi, muo) is det, in, mdi, muo) is det.

:- mode list.foldl(pred(in, di, uo) is det, in, di, uo) is det.

:- mode list.foldl(pred(in, in, out) is semidet, in, in, out) is semidet.

:- mode list.foldl(pred(in, mdi, muo) is semidet, in, mdi, muo) is semidet.

:- mode list.foldl(pred(in, di, uo) is semidet, in, di, uo) is semidet.

:- mode list.foldl(pred(in, in, out) is multi, in, in, out) is multi.

:- mode list.foldl(pred(in, in, out) is nondet, in, in, out) is nondet.

:- mode list.foldl(pred(in, mdi, muo) is nondet, in, mdi, muo) is nondet.

:- mode list.foldl(pred(in, in, out) is cc_multi, in, in, out) is cc_multi.

:- mode list.foldl(pred(in, di, uo) is cc_multi, in, di, uo) is cc_multi.

foldlは以下のように動作します。述語が foldl(P, [X, Y, Z], S0, S)と呼ばれた時、最初に

P(X, S0, S1)

が実行され、次に、

P(Y, S1, S2)

が実行され、最後に、

P(Z, S2, S)

が実行されます。以下のプログラムでは、foldlを使って、整数リストのすべての要素の和を求め

ています。

1 :- module sum.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, list.

Page 88: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

88 第 5章 値としての述語と関数

8

9 :- pred add(int::in, int::in, int::out) is det.

10 add(X, Y, X + Y).

11

12 main(!IO) :-

13 foldl(add, [1,2,3,4,5], 0, R),

14 write_int(R, !IO),

15 nl(!IO).

実行結果は以下のようになります。

$ ./sum

15

中間状態をして IO状態を受け渡すこともできます。以下のプログラムでは、foldlを使って整

数リストのすべての要素を 1行に 1つずつ表示しています。

1 :- module write_int_list.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module list.

8

9 :- pred write_int_nl(int::in, io::di, io::uo) is det.

10 write_int_nl(N, !IO) :-

11 write_int(N, !IO),

12 nl(!IO).

13

14 :- pred write_int_list(list(int)::in, io::di, io::uo) is det.

15 write_int_list(L, !IO) :-

16 foldl(write_int_nl, L, !.IO, !:IO).

17

18 main(!IO) :-

19 write_int_list([1,2,3,4,5], !IO).

Page 89: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

5.9 リストに関する高階関数 89

実行結果は以下のようになります。

$ ./write_int_list

1

2

3

4

5

上のプログラムで使っている!.IOは状態変数!IOの入力側の値を参照する特殊記法、!:IOは出力

側の値を参照する特殊記法です。

5.9.4 foldr

foldrは foldlとは逆にリストを右から左に畳み込みます。述語は以下のように宣言されてい

ます。

:- pred list.foldr(pred(L, A, A), list(L), A, A).

:- mode list.foldr(pred(in, in, out) is det, in, in, out) is det.

:- mode list.foldr(pred(in, mdi, muo) is det, in, mdi, muo) is det.

:- mode list.foldr(pred(in, di, uo) is det, in, di, uo) is det.

:- mode list.foldr(pred(in, in, out) is semidet, in, in, out) is semidet.

:- mode list.foldr(pred(in, mdi, muo) is semidet, in, mdi, muo) is semidet.

:- mode list.foldr(pred(in, di, uo) is semidet, in, di, uo) is semidet.

:- mode list.foldr(pred(in, in, out) is multi, in, in, out) is multi.

:- mode list.foldr(pred(in, in, out) is nondet, in, in, out) is nondet.

:- mode list.foldr(pred(in, mdi, muo) is nondet, in, mdi, muo) is nondet.

:- mode list.foldr(pred(in, di, uo) is cc_multi, in, di, uo) is cc_multi.

:- mode list.foldr(pred(in, in, out) is cc_multi, in, in, out) is cc_multi.

以下のプログラムでは、リストを末尾の要素から順番に表示しています。

1 :- module write_int_list2.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

Page 90: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

90 第 5章 値としての述語と関数

7 :- import_module list.

8

9 :- pred write_int_nl(int::in, io::di, io::uo) is det.

10 write_int_nl(N, !IO) :-

11 write_int(N, !IO),

12 nl(!IO).

13

14 :- pred write_int_list(list(int)::in, io::di, io::uo) is det.

15 write_int_list(L, !IO) :-

16 foldr(write_int_nl, L, !.IO, !:IO).

17

18 main(!IO) :-

19 write_int_list([1,2,3,4,5], !IO).

実行結果は以下のようになります。

$ ./write_int_list2

5

4

3

2

1

Page 91: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

91

第 6章

非決定性

6.1 基本

以下の述語は、1、2、3を非決定的に返す述語の定義です。

:- pred gen123(int::out) is multi.

gen123(N) :- N = 1; N = 2; N = 3.

この述語は、N = 1 または N = 2 または N = 3 ならば、gen123(N) であると読みます。もしく

は、N = 1または N = 2または N = 3 を生成すると考えることもできます。上の述語は以下のよ

うに書くこともできます。

:- pred gen123(int::out) is multi.

gen123(1).

gen123(2).

gen123(3).

非決定的な述語を決定的な述語の中で使うには、solutionsモジュールの述語 solutionsを使い

ます。solutionsは非決定的な述語が返す値をリストにして返すという役割を果たします。以下

のプログラムでは、gen123を定義して、solutionsで gen123が生成する値をリストとして取得

して表示しています。

1 :- module nondet1.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

Page 92: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

92 第 6章 非決定性

7 :- import_module solutions.

8

9 :- pred gen123(int::out) is multi.

10 gen123(1).

11 gen123(2).

12 gen123(3).

13

14 main(!IO) :-

15 solutions(gen123, R),

16 print(R, !IO),

17 nl(!IO).

実行結果は以下のようになります。

$ ./nondet1

[1, 2, 3]

gen123を使ったプログラム例をいくつか見てみましょう。以下の述語は gen123で値を 2つ生

成して、それらを掛け合わせる述語です。

:- pred p(int::out) is multi.

p(R) :-

gen123(N),

gen123(M),

R = N * M.

以下のプログラムでは、この述語が生成する値を solutions で取得しています。

1 :- module nondet2.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, solutions.

8

9 :- pred gen123(int::out) is multi.

10 gen123(1).

Page 93: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

6.1 基本 93

11 gen123(2).

12 gen123(3).

13

14 :- pred p(int::out) is multi.

15 p(R) :-

16 gen123(N),

17 gen123(M),

18 R = N * M.

19

20 main(!IO) :-

21 solutions(p, R),

22 print(R, !IO),

23 nl(!IO).

$ ./nondet2

[1, 2, 3, 4, 6, 9]

solutionsには結果をソートして、重複する要素を削除する働きがあります。上の述語では、

1 ∗ 1 = 1

1 ∗ 2 = 2

1 ∗ 3 = 3

2 ∗ 1 = 2

2 ∗ 2 = 4

2 ∗ 3 = 6

3 ∗ 1 = 3

3 ∗ 2 = 6

3 ∗ 3 = 9

が計算されて、結果から重複が除かれてソートされた結果が得られています。

以下の述語は、gen123で値を 2つ生成して、それらを掛け合わせ、さらに結果を 5より大きな

値に制限したものです。

:- pred q(int::out) is nondet.

q(R) :-

gen123(N),

gen123(M),

R = N * M,

Page 94: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

94 第 6章 非決定性

R > 5.

以下のプログラムではこの述語が生成する値を solutionsで取得しています。

1 :- module nondet3.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, solutions.

8

9 :- pred gen123(int::out) is multi.

10 gen123(1).

11 gen123(2).

12 gen123(3).

13

14 :- pred q(int::out) is nondet.

15 q(R) :-

16 gen123(N),

17 gen123(M),

18 R = N * M,

19 R > 5.

20

21 main(!IO) :-

22 solutions(q, R),

23 print(R, !IO),

24 nl(!IO).

実行結果は以下のようになります。

$ ./nondet3

[6, 9]

Page 95: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

6.2 ピタゴラス数 95

6.2 ピタゴラス数

ここではピタゴラス数を求めるプログラムを作ってみます。ピタゴラス数とは、X2 + Y 2 = Z2

を満たす自然数の組 {X,Y, Z}のことです。まずは、Nから Mまでの範囲の整数を非決定的に返す述語を以下のように定義します。

:- pred range(int::in, int::in, int::out) is nondet.

range(N, M, R) :-

if N > M then

fail

else if N = M then

R = M

else

(

R = N

;

range(N + 1, M, R)

).

まず Nが Mよりも大きい場合は失敗させています。Nと Mが等しい場合は、その値を答えとしてい

ます。それ以外の場合は、Nを答えとして、「または」を使って、再帰的に自分自身を呼び出した際

の結果も解としています。これにより、指定した範囲の整数を非決定的に得られます。以下にこの

動作を確認するプログラムを示します。

1 :- module range.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, solutions.

8

9 :- pred range(int::in, int::in, int::out) is nondet.

10 range(N, M, R) :-

11 if N > M then

12 fail

Page 96: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

96 第 6章 非決定性

13 else if N = M then

14 R = M

15 else

16 (

17 R = N

18 ;

19 range(N + 1, M, R)

20 ).

21

22 main(!IO) :-

23 solutions(pred(X::out) is nondet :- range(1, 10, X), R),

24 print(R, !IO),

25 nl(!IO).

実行結果は以下のようになります。

$ ./range

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

期待通りの動作をしていることが確認できます。intモジュールには、上の rangeと同じ動作を

する述語 nondet_int_in_rangeが定義されているので、以降ではこの述語を使います。

:- pred nondet_int_in_range(int::in, int::in, int::out) is nondet.

nondet_int_in_rangeを使って、ピタゴラス数を求める述語は以下のように記述できます。

:- pred pythagoras({int,int,int}::out) is nondet.

pythagoras({X,Y,Z}) :-

nondet_int_in_range(1, 10, X),

nondet_int_in_range(1, 10, Y),

nondet_int_in_range(1, 10, Z),

X * X + Y * Y = Z * Z.

X、Y、Zの値を生成して、最後に条件を満たしていることを確認しています。このように値を生成

して条件によりフィルターをかけるプログラミング手法を生成検定法とよびます。

以下に pythagorasを使ったプログラム全体を示します。

1 :- module pythagoras.

2 :- interface.

Page 97: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

6.3 結果を 1つだけ取得する 97

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, solutions.

8

9 :- pred pythagoras({int,int,int}::out) is nondet.

10 pythagoras({X,Y,Z}) :-

11 nondet_int_in_range(1, 10, X),

12 nondet_int_in_range(1, 10, Y),

13 nondet_int_in_range(1, 10, Z),

14 X * X + Y * Y = Z * Z.

15

16 main(!IO) :-

17 solutions(pythagoras, R),

18 print(R, !IO),

19 nl(!IO).

実行結果は以下のようになります。

./pythagoras

[{3, 4, 5}, {4, 3, 5}, {6, 8, 10}, {8, 6, 10}]

6.3 結果を 1つだけ取得する

solutions を使うと、非決定的な述語のすべての結果を取得できますが、解が 1 つ得られれ

ば良いという状況もありえます。そのような場合は、cc_multi や cc_nondet という決定性を

使います。cc_multi は、述語の本体は複数の解を返す可能性があるが、述語自体は決定的なも

のとして扱う、すなわち、最初の解が得られた時点で探索を中止することを指定するものです。

cc_nondetは、非決定的な述語を準決定的な述語として扱うことを指定するものです。cc_multi

や cc_nondet の cc は comitted choice の略です。以下のプログラムではピタゴラス数の最初の

解が得られた時点で探索を中止して、結果を表示しています。

1 :- module pythagoras2.

2 :- interface.

3 :- import_module io.

Page 98: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

98 第 6章 非決定性

4 :- pred main(io::di, io::uo) is cc_multi.

5

6 :- implementation.

7 :- import_module int, solutions.

8

9 :- pred pythagoras({int,int,int}::out) is nondet.

10 pythagoras({X,Y,Z}) :-

11 nondet_int_in_range(1, 10, X),

12 nondet_int_in_range(1, 10, Y),

13 nondet_int_in_range(1, 10, Z),

14 X * X + Y * Y = Z * Z.

15

16 main(!IO) :-

17 if

18 pythagoras(R)

19 then

20 print(R, !IO),

21 nl(!IO)

22 else

23 write_string("no solution\n", !IO).

実行結果は以下のようになります。

$ ./pythagoras2

{3, 4, 5}

6.4 リストに関する非決定的な述語

以下の述語 memは、要素がリスト中に含まれているかどうかを判定する述語です。

:- pred mem(T::in, list(T)::in) is semidet.

mem(X, [X|_]).

mem(X, [_|Ys]) :- mem(X, Ys).

この述語に以下のモードを追加すると、リスト中のそれぞれの要素を非決定的に返す述語が出来上

がります。

Page 99: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

6.4 リストに関する非決定的な述語 99

:- mode mem(T::out, list(T)::in) is nondet.

以下に memを使ったプログラム例を示します。

1 :- module mem.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module list, solutions.

8

9 :- pred mem(T::in, list(T)::in) is semidet.

10 :- mode mem(out, in) is nondet.

11 mem(X, [X|_]).

12 mem(X, [_|Ys]) :- mem(X, Ys).

13

14 main(!IO) :-

15 solutions(pred(X::out) is nondet :- mem(X, [1,2,3]), R),

16 print(R, !IO),

17 nl(!IO).

実行結果を以下に示します。

$ ./mem

[1, 2, 3]

上で定義した述語 memは listモジュールに memberという名前で定義されています。以下に宣言

を示します。

:- pred list.member(T, list(T)).

:- mode list.member(in, in) is semidet.

:- mode list.member(out, in) is nondet.

以下のリストを連結する述語 appは、モードを追加することで、リストを 2つのリストに分割

する述語として使えます。

:- pred app(list(T)::in, list(T)::in, list(T)::out) is det.

:- mode app(out, out, in) is multi.

Page 100: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

100 第 6章 非決定性

app([], Ys, Ys).

app([X | Xs], Ys, [X | Zs]) :- app(Xs, Ys, Zs).

以下のプログラムでは、この述語を使ってリストを分割した結果を表示しています。

1 :- module app3.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module list, solutions.

8

9 :- pred app(list(T)::in, list(T)::in, list(T)::out) is det.

10 :- mode app(out, out, in) is multi.

11 app([], Ys, Ys).

12 app([X | Xs], Ys, [X | Zs]) :- app(Xs, Ys, Zs).

13

14 main(!IO) :-

15 solutions(pred({X,Y}::out) is multi :- app(X, Y, [1,2,3]), R),

16 print(R, !IO),

17 nl(!IO).

実行結果は以下のようになります。

$ ./app3

[{[], [1, 2, 3]}, {[1], [2, 3]}, {[1, 2], [3]}, {[1, 2, 3], []}]

標準ライブラリの述語 appendは以下のように宣言されています。

:- pred list.append(list(T), list(T), list(T)).

:- mode list.append(di, di, uo) is det.

:- mode list.append(in, in, out) is det.

:- mode list.append(in, in, in) is semidet.

:- mode list.append(in, out, in) is semidet.

:- mode list.append(out, out, in) is multi.

Page 101: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

6.4 リストに関する非決定的な述語 101

appendを使うと、様々な述語を表現することができます。例えば、リストのプレフィックスを

求める述語 prefixは、

:- pred prefix(list(T)::out, list(T)::in) is multi.

prefix(Xs, Ys) :- append(Xs, _, Ys).

逆にサフィックスを求める述語 suffixは、

:- pred suffix(list(T)::out, list(T)::in) is multi.

suffix(Xs, Ys) :- append(_, Xs, Ys).

と表すことができます。以下に prefixと suffixを使ったプログラム例を示します。

1 :- module prefix.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module list, solutions.

8

9 :- pred prefix(list(T)::out, list(T)::in) is multi.

10 prefix(Xs, Ys) :- append(Xs, _, Ys).

11

12 :- pred suffix(list(T)::out, list(T)::in) is multi.

13 suffix(Xs, Ys) :- append(_, Xs, Ys).

14

15 main(!IO) :-

16 solutions(pred(X::out) is multi :- prefix(X, [1,2,3]), R1),

17 print(R1, !IO),

18 nl(!IO),

19 solutions(pred(X::out) is multi :- suffix(X, [1,2,3]), R2),

20 print(R2, !IO),

21 nl(!IO).

実行結果は以下のようになります。

Page 102: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

102 第 6章 非決定性

./prefix

[[], [1], [1, 2], [1, 2, 3]]

[[], [1, 2, 3], [2, 3], [3]]

前述の述語 memも appendを使って以下のように定義できます。

:- pred mem(T::in, list(T)::in) is semidet.

:- mode mem(out, in) is nondet.

mem(X, Xs) :- append(_, [X|_], Xs).

memを使ったプログラムを以下に示します。

1 :- module mem2.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module list, solutions.

8

9 :- pred mem(T::in, list(T)::in) is semidet.

10 :- mode mem(out, in) is nondet.

11 mem(X, Xs) :- append(_, [X|_], Xs).

12

13 main(!IO) :-

14 solutions(pred(X::out) is nondet :- mem(X, [1,2,3]), R),

15 print(R, !IO),

16 nl(!IO).

実行結果は以下のようになります。

$ ./mem2

[1, 2, 3]

Page 103: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

6.5 8クイーン問題 103

6.5 8クイーン問題

8クイーン問題とはチェス盤に 8個のクイーンをお互いが攻撃しあえないように配置する問題で

す。つまり、同じクイーンが同一の列、行、対角線上に位置しないように配置するという問題です。

これには、例えば以下のような解があります。

••

••

••

••

ここではこのような解を整数リスト

[3, 5, 0, 4, 1, 7, 2, 6]

で表すことにします。今回はプログラム全体を先に示します。

1 :- module eightqueens.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is cc_multi.

5

6 :- implementation.

7 :- import_module int, list.

8

9 :- pred eightqueens(list(int)::out) is nondet.

10 eightqueens(L) :-

11 perm([0,1,2,3,4,5,6,7], L),

12 safe(L).

13

14 :- pred safe(list(int)::in) is semidet.

15 safe([]).

16 safe([X|Xs]) :-

Page 104: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

104 第 6章 非決定性

17 safe(Xs),

18 noattack(X, Xs, 1).

19

20 :- pred noattack(int::in, list(int)::in, int::in) is semidet.

21 noattack(_, [], _).

22 noattack(Y, [Y1 | Ys], X) :-

23 Y1 - Y \= X,

24 Y - Y1 \= X,

25 noattack(Y, Ys, X + 1).

26

27 main(!IO) :-

28 if

29 eightqueens(R)

30 then

31 print(R, !IO),

32 nl(!IO)

33 else

34 write_string("no solution\n", !IO).

実行結果は以下のようになります。

$ ./eightqueens

[4, 1, 5, 0, 6, 3, 7, 2]

プログラムは生成検定法を使って書かれています。述語 eightqueensでは、まずリストの順列を

求めています。順列を求めるには、listモジュールの述語 perm を使っています。次に述語 safe

でその配置が安全な配置であるかどうかを判定しています。safe では、各クイーンンに対して

noattackを呼び出しています。noattackでは他のクイーンが対角線上に無いことを確認してい

ます。“\=”は右辺と左辺が等しくない時に成功する述語です。

6.6 結果を n個取得する

非決定的な述語で指定した数だけ結果を取得するには、solutionsモジュールに定義されてい

る述語 do_while を使います。do_whileは以下のように宣言されています。

:- pred do_while(pred(T), pred(T, bool, T2, T2), T2, T2).

:- mode do_while(pred(out) is multi, pred(in, out, in, out) is det,

Page 105: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

6.6 結果を n個取得する 105

in, out) is cc_multi.

:- mode do_while(pred(out) is multi, pred(in, out, di, uo) is det,

di, uo) is cc_multi.

:- mode do_while(pred(out) is multi, pred(in, out, di, uo) is cc_multi,

di, uo) is cc_multi.

:- mode do_while(pred(out) is nondet, pred(in, out, in, out) is det,

in, out) is cc_multi.

:- mode do_while(pred(out) is nondet, pred(in, out, di, uo) is det,

di, uo) is cc_multi.

:- mode do_while(pred(out) is nondet, pred(in, out, di, uo) is cc_multi,

di, uo) is cc_multi.

bool型は boolモジュールで以下のように定義されている型です。

:- type bool

---> no

; yes.

do_while は非決定的な値を返す述語と、探索を継続するかをの判断と状態の受け渡しをする述

語、状態の初期値を受け取って状態の最終値を返す述語です。以下のプログラムでは、do_while

を使って、非決定的な述語から 3個の答を取得し、リストとして返しています。

1 :- module do_while1.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is cc_multi.

5

6 :- implementation.

7 :- import_module list, int, bool, solutions.

8

9 :- pred generate(int::out) is nondet.

10 generate(N) :-

11 nondet_int_in_range(1, 10, N).

12

13 :- pred p(int::in, bool::out,

14 {int,list(int)}::in, {int,list(int)}::out) is det.

15 p(N, B, {I,R}, {I+1,[N|R]}) :-

Page 106: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

106 第 6章 非決定性

16 if I >= 3 then B = no

17 else B = yes.

18

19 main(!IO) :-

20 do_while(generate, p, {1,[]}, {_,R}),

21 print(R, !IO),

22 nl(!IO).

実行結果は以下のようになります。

$ ./do_while1

[3, 2, 1]

以下のプログラムでは、do_while中で入出力を行っています。

1 :- module do_while2.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is cc_multi.

5

6 :- implementation.

7 :- import_module list, int, bool, solutions.

8

9 :- pred generate(int::out) is nondet.

10 generate(N) :-

11 nondet_int_in_range(1, 10, N).

12

13 :- pred p(int::in, bool::out,

14 {int,io}::di, {int,io}::uo) is det.

15 p(N, B, {I,!.IO}, {I+1,!:IO}) :-

16 write_int(N, !IO),

17 nl(!IO),

18 ( if I >= 3 then B = no

19 else B = yes).

20

21 main(!IO) :-

Page 107: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

6.7 入力を受け取る 107

22 do_while(generate, p, {1,!.IO}, {_,!:IO}).

実行結果は以下のようになります。

$ ./do_while2

1

2

3

6.7 入力を受け取る

ユーザからの入力を受け取るには、io モジュールの述語 read_line_as_string もしくは

read_lineを使います。read_lineと read_line_as_stringはそれぞれ以下のように宣言され

ています。

:- pred io.read_line(io.result(list(char))::out, io::di, io::uo) is det.

:- pred io.read_line_as_string(io.result(string)::out, io::di, io::uo)

is det.

read_line は入力を文字のリストとして受け取り、read_line_as_string は文字列として受け

取ります。result型は、以下のように定義されている型で、入力が正常に受け取れたか、エラー

が発生したか、終端に到達したかを表しています。

:- type io.result(T)

---> ok(T)

; eof

; error(io.error).

以下のプログラムではユーザの入力を受け取って、それをそのまま画面に表示しています。

1 :- module input.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7

8 main(!IO) :-

Page 108: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

108 第 6章 非決定性

9 read_line_as_string(Result, !IO),

10 (

11 Result = ok(Str),

12 write_string(Str, !IO)

13 ;

14 Result = eof,

15 write_string("eof\n", !IO)

16 ;

17 Result = error(Err),

18 write_string(error_message(Err), !IO),

19 nl(!IO)

20 ).

実行結果は以下のようになります。ここでは最初に"hello"と入力しています。

$ ./input

hello

hello

入力された文字列には改行文字が含まれています。これを取り除くには、string モジュールの

strip関数が便利です。この関数は、文字列の前後の空白文字を取り除いた文字列を返します。以

下のプログラムは、ユーザに名前を入力させて挨拶を表示しています。

1 :- module greeting.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module string.

8

9 main(!IO) :-

10 write_string("名前を入力してください: ", !IO),

11 read_line_as_string(Result, !IO),

12 (

13 Result = ok(Str),

14 Name = strip(Str),

Page 109: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

6.7 入力を受け取る 109

15 write_string(Name, !IO),

16 write_string("さん、こんにちは。\n", !IO)

17 ;

18 Result = eof,

19 write_string("eof\n", !IO)

20 ;

21 Result = error(Err),

22 write_string(error_message(Err), !IO),

23 nl(!IO)

24 ).

実行結果は以下のようになります。

$ ./greeting

名前を入力してください: 太郎

太郎さん、こんにちは。

以下はユーザからインタラクティブに入力を受け取って、入力を整数に変換してその整数の階乗

を表示するプログラムです。このプログラムではユーザが eof (Unixでは Ctrl-D)を入力するま

で繰り返し入力を受け付けます。

1 :- module fact3.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, string.

8

9 :- func fact(int) = int.

10 fact(N) =

11 ( if N = 0 then 1

12 else N * fact(N - 1) ).

13

14 :- pred loop(io::di, io::uo) is det.

15 loop(!IO) :-

16 write_string("> ", !IO),

Page 110: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

110 第 6章 非決定性

17 read_line_as_string(Result, !IO),

18 (

19 Result = ok(Str),

20 (

21 if

22 to_int(strip(Str), N),

23 N > 0

24 then

25 write_string("fact(", !IO),

26 write_int(N, !IO),

27 write_string(") = ", !IO),

28 write_int(fact(N), !IO),

29 nl(!IO),

30 loop(!IO)

31 else

32 write_string("正の整数を入力してください。\n", !IO),

33 loop(!IO)

34 )

35 ;

36 Result = eof

37 ;

38 Result = error(Err),

39 write_string(error_message(Err), !IO),

40 nl(!IO),

41 loop(!IO)

42 ).

43

44 main(!IO) :-

45 loop(!IO).

実行結果は以下のようになります。

$ ./fact3

> 3

fact(3) = 6

> 5

Page 111: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

6.8 インタラクティブに結果を取得する 111

fact(5) = 120

> -3

正の整数を入力してください。

> abc

正の整数を入力してください。

> (Ctrl-D)

6.8 インタラクティブに結果を取得する

ここでは非決定的な述語の結果をインタラクティブに取得する方法を説明します。非決定的な述

語の N 個の結果を取得した時と同じように、solutions モジュールの述語 do_while を使いま

す。以下の get_nextという述語を定義します。

:- pred get_next(int::in, bool::out, io::di, io::uo) is det.

get_next(N, B, !IO) :-

write_int(N, !IO),

nl(!IO),

write_string("More? ", !IO),

read_line(CStr, !IO),

(

if

CStr = ok([Ch|_]),

to_upper(Ch, ’Y’)

then

B = yes

else

B = no

).

to_upperは charモジュールに定義されている述語で、アルファベットの小文字をアルファベッ

トの大文字に変換します。以下のプログラムのようにこの述語を do_while に渡すとインタラク

ティブに結果を取得するプログラムが完成します。

1 :- module interactive.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

Page 112: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

112 第 6章 非決定性

5

6 :- implementation.

7 :- import_module solutions, bool, char, list.

8

9 :- pred generate(int::out) is nondet.

10 generate(N) :-

11 nondet_int_in_range(1, 10, N).

12

13 :- pred get_next(int::in, bool::out, io::di, io::uo) is det.

14 get_next(N, B, !IO) :-

15 write_int(N, !IO),

16 nl(!IO),

17 write_string("More? ", !IO),

18 read_line(CStr, !IO),

19 (

20 if

21 CStr = ok([Ch|_]),

22 to_upper(Ch, ’Y’)

23 then

24 B = yes

25 else

26 B = no

27 ).

28

29 main(!IO) :-

30 do_while(generate, get_next, !IO).

実行結果は以下のようになります。

$ ./interactive

1

More? yes

2

More? y

3

More? no

Page 113: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

113

第 7章

確定節文法

7.1 基本

確定節文法 (DCG)は状態を受け渡す述語を簡単に記述するための構文で、主な用途としては構

文解析器の記述があります。以下のような状態を更新する述語があったとします。

:- pred up(int::in, int::out) is det.

up(N, N + 1).

:- pred down(int::in, int::out) is det.

down(N, N + 1).

このような述語を使って、以下のような状態を順次更新していくコードを書いたとします。

:- pred p(int::in, int::out) is det.

p(ST0, ST4) :-

up(ST0, ST1),

up(ST1, ST2),

down(ST2, ST3),

up(ST3, ST4).

このようなコードを確定節文法を使うと以下のように書けます。

p -->

up,

up,

down,

up.

Page 114: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

114 第 7章 確定節文法

確定節文法を使うと、このように状態を隠蔽して述語を記述できるようになります。上の例では状

態は整数値となっています。以下にプログラム例を示します。

1 :- module dcg.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- pred up(int::in, int::out) is det.

10 up(N, N + 1).

11

12 :- pred down(int::in, int::out) is det.

13 down(N, N - 1).

14

15 :- pred p(int::in, int::out) is det.

16 p -->

17 up,

18 up,

19 down,

20 up.

21

22 main(!IO) :-

23 p(0, X),

24 write_int(X, !IO),

25 nl(!IO).

実行結果は以下のようになります。

$ ./dcg

2

状態受け渡しの自動化は、以下のように状態変数を使っても実現できます。

Page 115: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

7.2 状態の読み取りと更新 115

p(!ST) :-

up(!ST),

up(!ST),

down(!ST),

up(!ST).

逆に、確定節文法を以下のように入出力に使うこともできます。

main -->

write_string("Hello, World!."),

nl,

write_int(42),

nl.

両者は同じような用途に使えますが、入出力や上記の up、downの例のような単純な状態の受け渡

しには状態変数を使い、状態がリストやユーザ定義型などの複雑な型の場合のみ確定節文法を使う

のが良いでしょう。本書ではそのようなスタイルでプログラムを記述しています。

7.2 状態の読み取りと更新

確定節文法のプログラム中に通常の述語呼び出しを記述するには、

{ ゴール }

のように、通常のゴールを中括弧に囲んで指定します。途中で状態を参照する必要がある場合は、

=(項)

という構文を使います。値を書き換えるには、

:=(項)

という構文を使います。以下のプログラムでは整数の状態を受け渡すプログラム例です。途中で、

値の参照と更新を行っています。

1 :- module update.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

Page 116: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

116 第 7章 確定節文法

7 :- import_module int.

8

9 :- pred double(int::in, int::out) is det.

10 double(N, N * 2).

11

12 :- pred incr(int::in, int::out) is det.

13 incr(N, N + 1).

14

15 :- pred p(io::di, io::uo, int::in, int::out) is det.

16 p(!IO) -->

17 incr,

18 double,

19 % 値の参照

20 =(N),

21 { write_int(N, !IO), nl(!IO) },

22 % 値の更新

23 :=(5),

24 double.

25

26 main(!IO) :-

27 p(!IO, 0, N),

28 write_int(N, !IO),

29 nl(!IO).

実行結果は以下のようになります。

$ ./update

2

10

7.3 状態としてリストを受け渡す

状態がリストの時に使える確定節文法のゴールとして、以下のものがあります。

[項 1, 項 2, ...]

Page 117: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

7.4 状態としてユーザ定義型を受け渡す 117

これは、リストの先頭のいくつかの要素とマッチさせて、残りのリストを新たな状態として受け渡

します。以下にプログラム例を示します。

1 :- module gcd2.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module list.

8

9 :- pred p(list(int)::in, list(int)::out) is semidet.

10 p -->

11 [1, 2].

12

13 main(!IO) :-

14 if

15 p([1,2,3,4,5], R)

16 then

17 print(R, !IO),

18 nl(!IO)

19 else

20 write_string("no solution", !IO).

実行結果は以下のようになります。

$ ./gcd2

[3, 4, 5]

7.4 状態としてユーザ定義型を受け渡す

確定節文法でユーザ定義型のフィールドの値を取得する以下の記法が用意されています。

項 =^ フィールド

また、フィールドの値を更新する以下の記法が用意されています。

Page 118: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

118 第 7章 確定節文法

^フィールド := 項

以下のプログラムではユーザ定義型を状態として受け渡しています。

1 :- module gcd3.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- type st ---> st(time::int, score::int).

10

11 :- pred p(st::in, st::out) is det.

12 p -->

13 Score =^ score,

14 ^score := Score + 100,

15 Time =^ time,

16 ^time := Time + 1.

17

18 main(!IO) :-

19 p(st(0,0), R),

20 print(R, !IO),

21 nl(!IO).

実行結果は以下のようになります。

$ ./gcd3

st(1, 100)

フィールドのアップデートは、状態変数でも以下のような構文で実現できます。

!X ^ フィールド := 項

上のプログラムを状態変数で書き換えると以下のようになります。

Page 119: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

7.5 構文解析 119

1 :- module state_variable.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- type st ---> st(time::int, score::int).

10

11 :- pred p(st::in, st::out) is det.

12 p(!ST) :-

13 !ST^score := !.ST^score + 100,

14 !ST^time := !.ST^time + 1.

15

16 main(!IO) :-

17 p(st(0,0), R),

18 print(R, !IO),

19 nl(!IO).

実行結果は以下のようになります。

$ ./state_variable

st(1, 100)

7.5 構文解析

ここでは、"12,34"のようにコンマで区切られた文字列を構文解析するプログラムを作ります。

構文解析では、確定節文法の状態は文字のリストにすると都合が良いので今回はそのようにし

ます。

Mercuryでは以下の構文で型に別名を付けることができます。

:- type 型名 == 型.

今回は文字のリスト list(char)を多用するので、これに以下のように strmと別名を付けたいと

Page 120: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

120 第 7章 確定節文法

思います。

:- type strm == list(char).

最初に、数字を 1文字読み込む構文解析器は以下のように定義できます。

:- pred parse_digit(char::out, strm::in, strm::out) is semidet.

parse_digit(Ch) -->

[Ch],

{ is_digit(Ch) }.

ここでは文字を 1文字読み取って、それが数字であるかを判定しています。is_digitは charモ

ジュールに定義されている述語です。

次に、数字の列を読み取る構文解析器は以下のようになります。

:- pred parse_digits(list(char)::out, strm::in, strm::out) is semidet.

parse_digits([C | Cs]) -->

parse_digit(C),

( if

parse_digits(Xs)

then

{ Cs = Xs }

else

{ Cs = [] } ).

この構文解析器は 1文字以上の数字の列を読み取ります。この構文解析器を使って、整数を読み取

る構文解析器を以下のように定義できます。

:- pred parse_int(int::out, strm::in, strm::out) is semidet.

parse_int(N) -->

parse_digits(Cs),

{ from_char_list(Cs, Str),

to_int(Str, N) }.

parse_digitsが返した文字のリストを文字列に変換し、さらに整数に変換しています。

整数の組の構文解析器は以下のように書けます。

:- pred parse_intpair({int,int}::out, strm::in, strm::out) is semidet.

parse_intpair({N1,N2}) -->

parse_int(N1),

Page 121: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

7.5 構文解析 121

[’,’],

parse_int(N2).

これらを使ったプログラムを以下に示します。

1 :- module parser.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, string, char, list.

8

9 :- type strm == list(char).

10

11 :- pred parse_digit(char::out, strm::in, strm::out) is semidet.

12 parse_digit(Ch) -->

13 [Ch],

14 { is_digit(Ch) }.

15

16 :- pred parse_digits(list(char)::out, strm::in, strm::out) is semidet.

17 parse_digits([C | Cs]) -->

18 parse_digit(C),

19 ( if

20 parse_digits(Xs)

21 then

22 { Cs = Xs }

23 else

24 { Cs = [] } ).

25

26 :- pred parse_int(int::out, strm::in, strm::out) is semidet.

27 parse_int(N) -->

28 parse_digits(Cs),

29 { from_char_list(Cs, Str),

30 to_int(Str, N) }.

31

Page 122: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

122 第 7章 確定節文法

32 :- pred parse_intpair({int,int}::out, strm::in, strm::out) is semidet.

33 parse_intpair({N1,N2}) -->

34 parse_int(N1),

35 [’,’],

36 parse_int(N2).

37

38 main(!IO) :-

39 if

40 to_char_list("12,34", Strm),

41 parse_intpair(Pair, Strm, [])

42 then

43 print(Pair, !IO),

44 nl(!IO)

45 else

46 write_string("parse error\n", !IO).

実行結果は以下のようになります。

$ ./parser

{12, 34}

Page 123: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

123

第 8章

型クラスと存在型

8.1 型クラスの基本

型クラスを使うと、複数の型で共通に利用可能な述語や関数を定義できます。ここでは足し算を

する関数 addと掛け算をする関数 mulを整数と浮動小数点数で共通に利用できるように定義して

みたいと思います。まず typeclass宣言で型クラス addmulを以下のように定義します。

:- typeclass addmul(T) where [

func add(T, T) = T,

func mul(T, T) = T

].

型クラス addmulには関数 addと関数 mulが所属しています。これは、この型クラスのインスタ

ンスになっている型は、この 2つの関数を実装している必要があることを意味しています。型クラ

スは以下の構文で定義します。

:- typeclass 型クラス名 (型変数 1, ..., 型変数 n) where [

述語/関数宣言 1,

...,

述語/関数宣言 n

].

次に instance宣言で、整数と浮動小数点数を型クラス addmulのインスタンスにします。

:- instance addmul(int) where [

add(X, Y) = X + Y,

mul(X, Y) = X * Y

].

Page 124: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

124 第 8章 型クラスと存在型

:- instance addmul(float) where [

add(X, Y) = X + Y,

mul(X, Y) = X * Y

].

これで関数 addと関数 mulは整数と浮動小数点数の両方の型で利用できるようになりました。イ

ンスタンス宣言は以下の構文で行います。

:- instance 型クラス名 (型 1, ..., 型 n) where [

述語/関数定義 1,

...,

述語/関数定義 n

].

インスタンス宣言で使える述語や関数の定義を規則の形式で行う場合は、以下のように定義全体を

括弧で囲む必要があります。

:- instance addmul(int) where [

(add(X, Y) = Z :- Z = X + Y),

(mul(X, Y) = Z :- Z = X * Y)

].

また、述語や関数をインスタンス宣言の内部で定義する代わりに、既存の述語や関数の名前を指定

することもできます。例えば、int_addと int_mulという名前の関数が定義されているとすると、

インスタンス宣言は以下のようになります。

:- instance addmul(int) where [

func(add/2) is int_add,

func(mul/2) is int_mul

].

add/2の 2は関数のアリティを表しています。

以下は addmul型クラスを使ったプログラム例です。

1 :- module addmul.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

Page 125: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

8.1 型クラスの基本 125

6 :- implementation.

7 :- import_module int, float.

8

9 :- typeclass addmul(T) where [

10 func add(T, T) = T,

11 func mul(T, T) = T

12 ].

13

14 :- instance addmul(int) where [

15 add(X, Y) = X + Y,

16 mul(X, Y) = X * Y

17 ].

18

19 :- instance addmul(float) where [

20 add(X, Y) = X + Y,

21 mul(X, Y) = X * Y

22 ].

23

24 main(!IO) :-

25 N = add(mul(2, 3), 4),

26 write_int(N, !IO),

27 nl(!IO),

28 F = add(mul(2.0, 3.0), 4.0),

29 write_float(F, !IO),

30 nl(!IO).

実行結果は以下のようになります。

$ ./addmul

10

10.0

add と mul が整数と浮動小数点数の両方で利用できていることがわかります。型クラスを使うこ

とでこのように型に応じて実装を切り替えることができます。

Page 126: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

126 第 8章 型クラスと存在型

8.2 型クラス制約

関数や述語を宣言するときに、型変数に型クラスで制約を付けることができます。これを利用す

ると、既存の型クラスを利用して複数の型を受け取る述語や関数を定義できます。以下の述語は、

前節で出てきた型クラス addmul の mul を使って値の二乗を計算する関数は以下のように定義で

きます。

:- func pow2(T) = T <= addmul(T).

pow2(V) = mul(V, V).

ここで<= addmul(T)の部分が型クラス制約で、型変数 Tは任意の型ではなく、型クラス addmul

を実装した型に限定するという意味になります。型クラス制約は以下のような構文で記述します。

<= (型クラス 1(型変数, ..., 型変数), ..., 型クラス n(型変数, ..., 型変数))

型クラスを 1つだけ指定する場合は外側の括弧を省略できます。以下に pow2を使ったプログラム

例を示します。

1 :- module addmul2.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, float.

8

9 :- typeclass addmul(T) where [

10 func add(T, T) = T,

11 func mul(T, T) = T

12 ].

13

14 :- instance addmul(int) where [

15 add(X, Y) = X + Y,

16 mul(X, Y) = X * Y

17 ].

18

19 :- instance addmul(float) where [

Page 127: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

8.3 型クラスの継承 127

20 add(X, Y) = X + Y,

21 mul(X, Y) = X * Y

22 ].

23

24 :- func pow2(T) = T <= addmul(T).

25 pow2(V) = mul(V, V).

26

27 main(!IO) :-

28 write_int(pow2(5), !IO),

29 nl(!IO),

30 write_float(pow2(5.0), !IO),

31 nl(!IO).

実行結果は以下のようになります。

$ ./addmul2

25

25.0

8.3 型クラスの継承

ここでは既存の型クラスを拡張した型クラスを定義する方法を説明します。前節ででてきた

addmul を拡張して、引き算をする関数 sub を追加した型クラス addmulsub を定義していみま

しょうみましょう。以下のようになります。

:- typeclass addmulsub(T) <= addmul(T) where [

func sub(T, T) = T

].

このように whereの前に型クラス制約を追加することで、既存の型クラスを拡張できます。拡張

した型クラスの実装は拡張したものだけを記述します。以下にプログラム例を示します。

1 :- module addmul3.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

Page 128: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

128 第 8章 型クラスと存在型

6 :- implementation.

7 :- import_module int, float.

8

9 :- typeclass addmul(T) where [

10 func add(T, T) = T,

11 func mul(T, T) = T

12 ].

13

14 :- instance addmul(int) where [

15 add(X, Y) = X + Y,

16 mul(X, Y) = X * Y

17 ].

18

19 :- instance addmul(float) where [

20 add(X, Y) = X + Y,

21 mul(X, Y) = X * Y

22 ].

23

24 :- typeclass addmulsub(T) <= addmul(T) where [

25 func sub(T, T) = T

26 ].

27

28 :- instance addmulsub(int) where [

29 sub(X, Y) = X - Y

30 ].

31

32 :- instance addmulsub(float) where [

33 sub(X, Y) = X - Y

34 ].

35

36 :- func pow2(T) = T <= addmul(T).

37 pow2(V) = mul(V, V).

38

39 main(!IO) :-

40 write_int(sub(pow2(5), 2), !IO),

Page 129: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

8.4 存在型 129

41 nl(!IO),

42 write_float(sub(pow2(5.0), 2.0), !IO),

43 nl(!IO).

実行結果は以下のようになります。

./addmul3

23

23.0

8.4 存在型

同じ型クラスを実装している異なる型をリストなどの同一の型を要求するデータ構造に格納する

場合、存在型を使ったデータ構造を使うことができます。ここでは以下の numクラスを使います。

:- typeclass num(T) where [

func add(T, T) = T,

pred show(T, io::di, io::uo) is det

].

int 型と float 型、string 型が num クラスのインスタンスとしてそれぞれ適切に宣言されてい

るとします。ここでは、整数と浮動小数点数と文字列からなるリストがあって、そのリストに対し

て適当な操作を行う場合を考えます。これらは型が異なるのでリストに直接格納することはできま

せんが、以下のような値を包み込むデータ構造があると同一の型の値として扱うことが可能になり

ます。

:- type num_wrapper --->

some [T] num_wrapper(T) => num(T).

ここでは新しい型 num_wrapper を定義しています。new_wrapper にはコンストラクタが 1 つあ

り、名前は型名と同じで num_wrapper です。num_wrapper は引数に型 T を取ります。ここで型

Tは some [T]によってこのコンストラクタ内で存在限量されています。さらに型 Tは型クラス制

約=> num(T)によって、型クラス numのインスタンスとなっている型に制限されています。

存在限量されたデータ型のコンストラクタを呼び出すには、名前に’new ’を追加したコンスト

ラクタ名を使います。例えば、

V1 = ’new num_wrapper’(12)

Page 130: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

130 第 8章 型クラスと存在型

のようにします。例えば、整数と浮動小数点数と文字列からなるリストは、

List = [

’new num_wrapper’(12),

’new num_wrapper’(3.14),

’new num_wrapper’("abc")

]

のようにして構築できます。通常は毎回’new num_wrapper’と書くと大変なので、以下のような

コンストラクタ関数を定義します。

:- func new_num(T) = num_wrapper <= num(T).

new_num(V) = ’new num_wrapper’(V).

逆に存在限量されたデータ構造から値を取り出すには、通常のユーザ定義型と同様

num_wrapper(V3) = V1

のようにします。

以下のプログラムでは、まず、整数と浮動小数点数と文字列からなるリストを構築し、map関数

でリストのそれぞれの値を 2倍にし、述語 foldlでリストのそれぞれの値を表示しています。

1 :- module num.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, float, string, list.

8

9 :- typeclass num(T) where [

10 func add(T, T) = T,

11 pred show(T::in, io::di, io::uo) is det

12 ].

13

14 :- instance num(int) where [

15 add(X, Y) = X + Y,

16 (show(V, !IO) :- write_int(V, !IO), nl(!IO))

17 ].

Page 131: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

8.4 存在型 131

18

19 :- instance num(float) where [

20 add(X, Y) = X + Y,

21 (show(V, !IO) :- write_float(V, !IO), nl(!IO))

22 ].

23

24 :- instance num(string) where [

25 add(X, Y) = X ++ Y,

26 (show(V, !IO) :- write_string(V, !IO), nl(!IO))

27 ].

28

29 :- type num_wrapper --->

30 some [T] num_wrapper(T) => num(T).

31

32 :- func new_num(T) = num_wrapper <= num(T).

33 new_num(V) = ’new num_wrapper’(V).

34

35 :- func double(num_wrapper) = num_wrapper.

36 double(W) = new_num(add(V, V)) :-

37 num_wrapper(V) = W.

38

39 :- pred show_num(num_wrapper::in, io::di, io::uo) is det.

40 show_num(W, !IO) :-

41 num_wrapper(V) = W,

42 show(V, !IO).

43

44 main(!IO) :-

45 List1 = [new_num(12), new_num(3.14), new_num("abc")],

46 List2 = map(double, List1),

47 foldl(show_num, List2, !.IO, !:IO).

実行結果は以下のようになります。

$ ./num

24

6.28

Page 132: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

132 第 8章 型クラスと存在型

abcabc

8.5 関数従属性

以下のような numクラスを考えます。

:- typeclass num(T, U, V) where [

func add(T, U) = V

].

:- instance num(int, int, int) where [

add(X, Y) = X + Y

].

:- instance num(int, float, float) where [

add(X, Y) = float(X) + Y

].

:- instance num(float, int, float) where [

add(X, Y) = X + float(Y)

].

:- instance num(float, float, float) where [

add(X, Y) = X + Y

].

この型クラスでは、整数と浮動小数点数のいずれの組み合わせでも対応可能な関数 addを定義して

います。この addを使って、3つの数を足す関数を定義する以下のようになります。

:- all [S] func add3(T, U, V) = W <= (num(T, U, S), num(S, V, W)).

add3(V1, V2, V3) = add(add(V1, V2), V3).

述語宣言の all [S]は型変数 Sが全称限量されていることを示しています。このコードを含むプ

ログラムをコンパイルしようとすると、エラーが発生します。エラーが発生する理由は、型変数 S

の型を決定できないためです。しかし、addの定義上、Tと Uの型が決定すれば、Sの型は決定で

きるはずです。問題は型クラスの定義で、3 つの型変数に何の制約もついていないことにありま

す。この場合は以下のように型クラスの定義を書き換えることで正常に動作するようになります。

Page 133: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

8.5 関数従属性 133

:- typeclass num(T, U, V) <= (T, U -> V) where [

func add(T, U) = V

].

(T, U -> V)という制約は、型 Tと Uが決定すれば、型 Vが決定されるという依存関係を示して

います。このような制約のことを関数従属性とよびます。関数従属性は以下のような構文で指定し

ます。

(型変数, ..., 型変数 -> 型変数, ..., 型変数)

関数従属性はコンマで区切って一度に複数指定することもできます。以下に add3を使ったプログ

ラム例を示します。

1 :- module fundep.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, float.

8

9 :- typeclass num(T, U, V) <= (T, U -> V) where [

10 func add(T, U) = V

11 ].

12

13 :- instance num(int, int, int) where [

14 add(X, Y) = X + Y

15 ].

16

17 :- instance num(int, float, float) where [

18 add(X, Y) = float(X) + Y

19 ].

20

21 :- instance num(float, int, float) where [

22 add(X, Y) = X + float(Y)

23 ].

24

Page 134: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

134 第 8章 型クラスと存在型

25 :- instance num(float, float, float) where [

26 add(X, Y) = X + Y

27 ].

28

29 :- all [S] func add3(T, U, V) = W <= (num(T, U, S), num(S, V, W)).

30 add3(V1, V2, V3) = add(add(V1, V2), V3).

31

32 main(!IO) :-

33 print(add3(3, 4, 5.0), !IO),

34 nl(!IO).

実行結果は以下のようになります。

$ ./fundep

12.0

Page 135: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

135

第 9章

Cインターフェース

9.1 基本

Mercuryの述語や関数の実装を C言語で記述するには、pragma foreign_proc宣言を使いま

す。以下は述語 doubleを C言語で実装したプログラム例です。

1 :- module fli_double.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7

8 :- pred double(int::in, int::out) is det.

9

10 :- pragma foreign_proc("C",

11 double(X::in, Y::out),

12 [will_not_call_mercury, promise_pure],

13 "

14 Y = X * 2;

15 ").

16

17 main(!IO) :-

18 double(10, X),

19 write_int(X, !IO),

20 nl(!IO).

Page 136: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

136 第 9章 Cインターフェース

実行結果は以下のようになります。

$ ./fli_double

20

述語の実装を C言語で記述するには以下の構文を使います。

:- pragma foreign_proc("C",

述語名 (変数 1::モード 1, ..., 変数 n::モード n),

[オプション], "Cのコード").

関数の実装を C言語で記述するには以下の構文を使います。

:- pragma foreign_proc("C",

関数名 (変数 1::モード 1, ..., 変数 n::モード n) = (変数::モード),

[オプション], "Cのコード").

上のプログラムではオプションに、will_not_call_mercuryと promise_pureを指定していま

す。will_not_call_mercuryは CのコードからMercuryのコードを呼び出すことが無いことを

意味しており、promise_pureは述語や関数が純粋である、すなわち、同じ引数を与えたら常に同

じ結果が返ることを表しています。

以下のプログラムでは、C言語の printf関数を利用して、画面に"Hello, World!"と表示し

ています。

1 :- module fli_hello.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7

8 :- pragma foreign_decl("C", "#include <stdio.h>").

9

10 :- pred hello(io::di, io::uo) is det.

11

12 :- pragma foreign_proc("C",

13 hello(IO0::di, IO1::uo),

14 [will_not_call_mercury, promise_pure],

15 "

Page 137: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

9.2 データ受け渡し規約 137

16 printf(\"Hello, World!\\n\");

17 IO1 = IO0;

18 ").

19

20 main(!IO) :-

21 hello(!IO).

実行結果は以下のようになります。

$ ./fli_hello

Hello, World!

ここでは状態変数を使うことで、述語を純粋に保っています。pragma foreign_decl 宣言を使

うと、C言語の宣言をコードに追加できます。ここでは、printf関数が定義されているヘッダー

ファイル stdio.h をインクルードしています。C のコードは文字列で指定する必要があるため、

ダブルクォーテーションやバックスラッシュなどを適切にエスケープする必要がある点に注意が必

要です。

9.2 データ受け渡し規約

Mercuryのデータ型は Cでは表 9.1のように変換されます。bool.boolは yesと noを値とし

表 9.1 データ受け渡し規約

Mercuryの型 Cの型

int MR_Integer

float MR_Float

char MR_Char

string MR_String

bool.bool MR_Bool

builtin.comparison_result MR_Comparison_Result

その他 MR_Word

て持つ型です。これらは Cでは MR_YESと MR_NOに対応します。builtin.comparison_result

は (<) と (=) と (>) を値として持つ型です。これらは C では MR_COMPARE_LESS、

MR_COMPARE_EQUAL、MR_COMPARE_GREATERに対応します。

CでMercuryの値を操作するためのマクロがいくつか用意されています。それらを表 9.2に示

します。以下のプログラムは、C言語で浮動小数点数のリストの先頭要素の値を 2倍にしたリスト

Page 138: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

138 第 9章 Cインターフェース

表 9.2 Mercuryの値を操作するためのマクロ

マクロ 意味

MR_list_is_empty(list) リストが空かどうかの判定

MR_list_head(list) リストの頭部を取り出す

MR_list_tail(list) リストの尾部を取り出す

MR_list_empty() 空リストを作る

MR_list_cons(head,tail) コンスセルを作る

MR_word_to_float MR_Wordを MR_Floatに変換する

MR_float_to_word MR_Floatを MR_Wordに変換する

を返す関数を定義して使う例です。

1 :- module fli_list.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module list.

8

9 :- func double_list(list(float)) = list(float).

10

11 :- pragma foreign_proc("C",

12 double_list(Input::in) = (Result::out),

13 [will_not_call_mercury, promise_pure],

14 "

15 MR_Float f = MR_word_to_float(MR_list_head(Input));

16 MR_Word w = MR_float_to_word(f * 2);

17 MR_Word tail = MR_list_tail(Input);

18 Result = MR_list_cons(w, tail);

19 ").

20

21 main(!IO) :-

22 X = double_list([1.0, 3.0, 5.0]),

23 print(X, !IO),

Page 139: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

9.3 C言語のオブジェクトをMercury上で扱う 139

24 nl(!IO).

実行結果は以下のようになります。

$ ./fli_list

[2.0, 3.0, 5.0]

9.3 C言語のオブジェクトをMercury上で扱う

C言語のオブジェクトをMercury上で扱えるようにするには、pragma foreign_type宣言を

使います。以下のプログラムでは、C言語の構造体 struct fooを、Mercury上で型 fooとして

扱っています。

1 :- module fli_type.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7

8 :- pragma foreign_decl("C",

9 "

10 struct foo {

11 int a;

12 int b;

13 };

14 ").

15

16 :- type record.

17 :- pragma foreign_type("C", record, "struct foo").

18

19 :- pred create_foo(int::in, int::in, record::out) is det.

20 :- pragma foreign_proc("C",

21 create_foo(A::in, B::in, R::out),

22 [will_not_call_mercury, promise_pure],

23 "

Page 140: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

140 第 9章 Cインターフェース

24 R.a = A;

25 R.b = B;

26 ").

27

28 :- pred write_foo(record::in, io::di, io::uo) is det.

29 :- pragma foreign_proc("C",

30 write_foo(R::in, IO0::di, IO1::uo),

31 [will_not_call_mercury, promise_pure],

32 "

33 printf(\"a = %d, b = %d\\n\", R.a, R.b);

34 IO1 = IO0;

35 ").

36

37 main(!IO) :-

38 create_foo(100, 200, R),

39 write_foo(R, !IO).

実行結果は以下のようになります。

$ ./fli_type

a = 100, b = 200

9.4 C言語の定数をMercuryのユーザ定義型として扱う

C言語の定数をMercuryのユーザ定義型として扱うには、pragma foreign_enum宣言を使い

ます。以下のプログラムでは、C 言語の列挙型 enum bar と Mercury の型 bar を対応させてい

ます。

1 :- module fli_enum.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7

8 :- pragma foreign_decl("C",

Page 141: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

9.4 C言語の定数をMercuryのユーザ定義型として扱う 141

9 "

10 enum bar {

11 BAR1, BAR2, BAR3

12 };

13 ").

14

15 :- type bar ---> bar1; bar2; bar3.

16

17 :- pragma foreign_enum("C", bar/0,

18 [

19 bar1 - "BAR1",

20 bar2 - "BAR2",

21 bar3 - "BAR3"

22 ]).

23

24 :- pred write_bar(bar::in, io::di, io::uo) is det.

25 :- pragma foreign_proc("C",

26 write_bar(Bar::in, IO0::di, IO1::uo),

27 [will_not_call_mercury, promise_pure],

28 "

29 switch (Bar) {

30 case BAR1:

31 printf(\"BAR1\\n\");

32 break;

33 case BAR2:

34 printf(\"BAR2\\n\");

35 break;

36 case BAR3:

37 printf(\"BAR3\\n\");

38 break;

39 }

40 IO1 = IO0;

41 ").

42

43 main(!IO) :-

Page 142: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

142 第 9章 Cインターフェース

44 write_bar(bar2, !IO).

実行結果は以下のようになります。

$ ./fli_enum

BAR2

9.5 C言語の関数定義をMercuryのソースに埋め込む

C言語の関数定義をMercuryのソースに埋め込むには、pragma foreign_code 宣言を使いま

す。以下のプログラムでは補助関数を定義してその関数を呼び出しています。

1 :- module fli_sub.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7

8 :- pragma foreign_decl("C",

9 "

10 #include <stdio.h>

11 void hello_sub(void);

12 ").

13

14 :- pragma foreign_code("C",

15 "

16 void hello_sub(void)

17 {

18 printf(\"hello\\n\");

19 }

20 ").

21

22 :- pred hello(io::di, io::uo) is det.

23 :- pragma foreign_proc("C",

24 hello(IO0::di, IO1::uo),

Page 143: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

9.6 C言語からMercuryのコードを呼び出す 143

25 [will_not_call_mercury, promise_pure],

26 "

27 hello_sub();

28 IO1 = IO0;

29 ").

30

31 main(!IO) :-

32 hello(!IO).

実行結果は以下のようになります。

$ ./fli_sub

hello

9.6 C言語からMercuryのコードを呼び出す

C言語からMercuryのコードを呼び出し可能にするには、pragma foreign_export宣言を使

います。以下のプログラムではMercuryの述語 doubleを Cから呼び出しています。

1 :- module fli_export.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- pred double(int::in, int::out) is det.

10 double(X, X * 2).

11

12 :- pragma foreign_export("C",

13 double(in, out),

14 "mercury_double").

15

16 :- pred print_double(int::in, io::di, io::uo) is det.

17 :- pragma foreign_proc("C",

Page 144: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

144 第 9章 Cインターフェース

18 print_double(N::in, IO0::di, IO1::uo),

19 [may_call_mercury, promise_pure],

20 "

21 MR_Integer i;

22 mercury_double(N, &i);

23 printf(\"%d\\n\", i);

24 IO1 = IO0;

25 ").

26

27 main(!IO) :-

28 print_double(10, !IO).

実行結果は以下のようになります。

$ ./fli_export

20

9.7 C言語で準決定的な述語を実装する

準決定的な述語を C 言語で実装する際、失敗を通知するには、変数 SUCCESS_INDICATOR に 0

以外の値を代入します。以下に例を示します。

1 :- module fli_semidet.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int.

8

9 :- pred is_positive(int::in) is semidet.

10 :- pragma foreign_proc("C",

11 is_positive(N::in),

12 [will_not_call_mercury, promise_pure],

13 "

14 SUCCESS_INDICATOR = (N > 0);

Page 145: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

9.8 オブジェクトファイルをリンクする 145

15 ").

16

17 main(!IO) :-

18 (

19 if is_positive(5) then

20 write_string("正の数\n", !IO)

21 else

22 write_string("正の数でない\n", !IO)

23 ),

24 (

25 if is_positive(-5) then

26 write_string("正の数\n", !IO)

27 else

28 write_string("正の数でない\n", !IO)

29 ).

実行結果は以下のようになります。

$ ./fli_semidet

正の数

正の数でない

9.8 オブジェクトファイルをリンクする

C言語のファイルが別ファイルに分かれている場合、オブジェクトファイルを明示的に指定する

必要があります。以下のプログラムを考えます。

1 /* hello_sub.h */

2 #ifndef HELLO_SUB_H_INCLUDE

3 #define HELLO_SUB_H_INCLUDE

4 void hello_sub(void);

5 #endif

1 /* hello_sub.c */

2 #include <stdio.h>

3 #include "hello_sub.h"

Page 146: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

146 第 9章 Cインターフェース

4 void hello_sub(void)

5 {

6 printf("hello\n");

7 }

1 :- module fli_sub2.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7

8 :- pragma foreign_decl("C",

9 "

10 #include \"hello_sub.h\"

11 ").

12

13 :- pred hello(io::di, io::uo) is det.

14 :- pragma foreign_proc("C",

15 hello(IO0::di, IO1::uo),

16 [will_not_call_mercury, promise_pure],

17 "

18 hello_sub();

19 IO1 = IO0;

20 ").

21

22 main(!IO) :-

23 hello(!IO).

これらのプログラムをコンパイルするには以下のようにします。

$ gcc -c hello_sub.c

$ mmc --make fli_sub2 --link-object hello_sub.o

Making Mercury/int3s/fli_sub2.int3

Making Mercury/ints/fli_sub2.int

Page 147: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

9.8 オブジェクトファイルをリンクする 147

Making Mercury/cs/fli_sub2.c

Making Mercury/os/fli_sub2.o

Making fli_sub2

実行すると以下のようになります。

$ ./fli_sub2

hello

Page 148: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。
Page 149: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

149

第 10章

その他の話題

10.1 複数のファイルからなるプログラム

プログラムが大規模になってくると、ソースファイルを複数に分割したほうが見通しが良くなる

ことがあります。Mercury では各ソースファイルはモジュールを構成しています。モジュールの

外部から述語や関数を呼び出し可能にするには、述語や関数の宣言をモジュールのインターフェイ

ス部に配置します。以下のソースファイル sub.mはモジュール subを構成して、外部に公開され

ている述語として doubleが定義されています。

1 :- module sub.

2 :- interface.

3 :- pred double(int::in, int::out) is det.

4

5 :- implementation.

6 :- import_module int.

7 double(X, X * 2).

これで述語 doubleはモジュールの外側、すなわち別のソースファイルから参照できるようになり

ました。

doubleを利用するには、通常のライブラリ同様、import_module宣言でモジュールをインポー

トします。以下のプログラムでは上で定義した doubleを利用しています。

1 :- module main.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

Page 150: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

150 第 10章 その他の話題

5

6 :- implementation.

7 :- import_module sub.

8 main(!IO) :-

9 double(10, X),

10 write_int(X, !IO),

11 nl(!IO).

これらのソースファイルをコンパイルするには、今までどおり mmc --make コマンドを使いま

す。mmc --makeはメインのモジュール名を指定すると必要なファイルを自動的にコンパイルして

くれます。

$ mmc --make main

Making Mercury/int3s/main.int3

Making Mercury/int3s/sub.int3

Making Mercury/ints/main.int

Making Mercury/ints/sub.int

Making Mercury/cs/main.c

Making Mercury/cs/sub.c

Making Mercury/os/main.o

Making Mercury/os/sub.o

Making main

実行すると以下のようになります。

./main

20

10.2 ライブラリの作成

複数のプログラムから共通に利用可能なライブラリを作成する方法を説明します。ここでは前節

で出てきた sub.mをライブラリ化したいと思います。ライブラリを作るには mmc --makeコマン

ドでモジュール名としてコンパイルするモジュール名の先頭に libを付けた名前を指定します。例

えば、subモジュールをライブラリにするには以下のように mmc --make libsubと入力します。

$ mmc --make libsub

Making libsub.a

Page 151: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

10.3 抽象型 151

Making Mercury/os/sub.pic_o

Making libsub.so

ライブラリをシステムにインストールするには、mmc --makeコマンドでモジュール名として、先

頭に lib、末尾に.installを付けた名前を指定します。例えば、上でコンパイルした subライブ

ラリをインストールするには、以下のように、mmc --make libsub.installと入力します。

$ sudo mmc --make libsub.install

...

インストールされたライブラリをプログラムから使うには、mmc --makeコマンドに--mlオプ

ションで利用するライブラリを指定してコンパイルします。以下の例では前節の main.mをインス

トールしたライブラリを利用してコンパイルしています。

$ mmc --make --ml sub main

Making Mercury/int3s/main.int3

Making Mercury/ints/main.int

Making Mercury/cs/main.c

Making Mercury/os/main.o

Making main

$ ./main

20

ライブラリをインストールせずに利用することもできます。その場合、ライブラリ作成時に生成

される initファイルとアーカイブファイルの位置を以下のように mmc --makeのオプションで指

定します。

$ mmc --make main \

--init-file ../sub/sub.init \

--link-object ../sub/libsub.a

10.3 抽象型

整数の集合を表す intset というライブラリを作ることを考えます。ここでは集合を以下のよう

に整数のリストで表現したとします。

:- type intset == list(int).

Page 152: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

152 第 10章 その他の話題

この型はモジュールの外部から使える必要があるので、モジュールのインターフェイス部に配置す

る必要があります。しかし、この宣言をそのまま配置すると、外部に整数のリストで集合を表現し

ていることが公開されてしまうため、モジュールの外部から集合に対して意図しない操作が行えて

しまうという問題があります。この場合、インターフェイス部では、

:- type intset.

のように型の存在だけを宣言して、実装部で、

:- type intset == list(int).

のように型の実装を記述することで問題を解決できます。モジュール外部には型 intsetが存在す

ることだけが宣言されており、型の実装はモジュールの内部に隠されており、たとえモジュールの

ユーザが整数のリストで集合が実装されていることを知っていたとしても、モジュールで公開され

た述語以外を使って集合を操作することはできなくなります。

以下に簡単な集合型を実装したソースコードを示します。

1 :- module intset.

2 :- interface.

3

4 % 集合の抽象型

5 :- type intset.

6

7 % 空集合

8 :- func empty = intset.

9

10 % 集合に要素が含まれるかの判定

11 :- pred member(int::in, intset::in) is semidet.

12

13 % 集合に要素を追加する

14 :- func insert(int, intset) = intset.

15

16 :- implementation.

17 :- import_module list.

18

19 :- type intset == list(int).

20

21 empty = [].

Page 153: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

10.3 抽象型 153

22

23 member(N, S) :- member(N, S).

24

25 insert(N, S0) = S1 :-

26 if intset.member(N, S0) then

27 S1 = S0

28 else

29 S1 = [N | S0].

以下は上のモジュールを使うプログラム例です。

1 :- module intset_test.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module intset.

8

9 main(!IO) :-

10 S = insert(3, insert(5, empty)),

11 (

12 if member(3, S) then

13 write_string("3は集合の要素です\n", !IO)

14 else

15 write_string("3は集合の要素ではありません\n", !IO)

16 ).

実行結果は以下のようになります。

$ ./intset_test

3は集合の要素です

Page 154: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

154 第 10章 その他の話題

10.4 例外処理

ここでは、Mercuryで例外を投げたり、捕捉したりする方法を説明します。まず例外を投げるに

は、exceptionモジュールの述語 throw もしくは関数 throwを使います。

:- func throw(T) = _ is erroneous.

:- pred throw(T::in) is erroneous.

例外の捕捉には以下の構文の tryゴールを使います。

try [] ゴール 1

then ゴール 2

else ゴール 3

catch 項 1 -> 例外ゴール 1

...

catch 項 n -> 例外ゴール n

catch_any 変数 -> 全てに合致する例外ゴール

ゴール 1を実行して成功すればゴール 2を実行し、失敗した場合はゴール 2が実行されます。ゴー

ル 2は失敗する可能性がない場合は省略します。ゴール 1の中で例外が呼ばれた場合は、投げられ

た例外にマッチする項に対応する例外ゴールが実行されます。tryゴールの決定性は cc_multiに

なります。以下に例外を投げる最も簡単な例を示します。

1 :- module ex.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is cc_multi.

5

6 :- implementation.

7 :- import_module exception.

8

9 main(!IO) :-

10 (

11 try []

12 throw("failure")

13 then

14 X = 12

Page 155: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

10.4 例外処理 155

15 catch "failure" ->

16 X = 3

17 ),

18 write_int(X, !IO),

19 nl(!IO).

実行結果は以下のようになります。

$ ./ex

3

tryの中で入出力を使う場合は、tryの後ろの []の中に、[io(!IO)]のように状態変数を指定し

ます。以下にプログラム例を示します。

1 :- module ex_io.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is cc_multi.

5

6 :- implementation.

7 :- import_module exception.

8

9 main(!IO) :-

10 (

11 try [io(!IO)]

12 (

13 write_string("A\n", !IO),

14 throw("failure"),

15 write_string("B\n", !IO)

16 )

17 then

18 write_string("C\n", !IO)

19 catch "failure" ->

20 write_string("D\n", !IO)

21 ).

Page 156: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

156 第 10章 その他の話題

実行結果は以下のようになります。

$ ./ex_io

A

D

10.5 末尾再帰

末尾再帰とは述語や関数で再帰呼び出しをする際に、その呼び出しのあとに処理が残っていない

状態の再帰呼び出しのことをいいます。例として以下の再帰的な関数を考えみます。

:- pred fact(int::in, int::out) is det.

fact(N, R) :-

if N = 0 then

R = 1

else

fact(N - 1, M),

R = N * M.

この述語では、factを呼び出した後に、その結果を使って掛け算を行っています。後の処理が残っ

ているのでこの述語は末尾再帰ではありません。上の述語を以下のように書き換えると末尾再帰に

なります。

:- pred fact(int::in, int::in, int::out) is det.

fact(N, R, R0) :-

if N = 0 then

R0 = R

else

fact(N - 1, N * R, R0).

この述語では、factの呼び出しの後に、処理が残っていません。この述語を使ったプログラム例

を以下に示します。

1 :- module tailcall.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

Page 157: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

10.5 末尾再帰 157

6 :- implementation.

7 :- import_module int.

8

9 :- pred fact(int::in, int::in, int::out) is det.

10 fact(N, R, R0) :-

11 if N = 0 then

12 R0 = R

13 else

14 fact(N - 1, N * R, R0).

15

16 main(!IO) :-

17 fact(5, 1, R),

18 write_int(R, !IO),

19 nl(!IO).

実行結果は以下のようになります。

$ ./tailcall

120

述語や関数を末尾再帰で記述すると、Mercuryの処理系は述語や関数の再帰呼び出しを、ジャン

プ命令を使って、スタックフレームを消費しないようなコードにコンパイルします。逆に末尾再帰

でないプログラムでは、再帰呼び出しのたびにスタックフレームが消費され、再帰を何度も行うプ

ログラムではスタックオーバーフローを起こす可能性があります。

Mercury では末尾再帰になっているかなっていないかの判定が、プログラムを見ただけではす

ぐにはわからない場合があります。以下のプログラムは、一見、末尾再帰になっているように見え

ます。

:- pred fact(int::in, int::out) is det.

fact(N, R) :-

if N = 0 then

R = 1

else

R = N * M,

fact(N - 1, M).

Page 158: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

158 第 10章 その他の話題

しかし、処理の流れを追うと末尾再帰になっていないことがわかります。なぜなら、R = N * Mを

計算するためには、Mの値が必要なため、実際には R = N * Mよりも先に再帰呼び出しが行われ

るためです。

10.6 モードとインスト

インストとは述語や関数の引数の状態のことをいいます。引数の状態は述語や関数の実行と共に

変化します。例えば、値を出力する引数の場合、最初は引数には何も値が代入されていない状態で

ある必要があり、述語の実行と共に代入されている状態に推移します。Mercury では何も代入さ

れていない状態を freeと表現して、値が完全に代入されている状態を groundと表現します。こ

れまで出てきた inとか outとかいった引数のモードは、述語や関数を実行前のインストから実行

後のインストへの写像として表現されています。例えば、outであれば、

:- mode out == free >> ground.

inであれば、

:- mode in == ground >> ground.

と定義されています。以下の宣言のように、inや outの代わりにインストの写像を直接指定する

こともできます。

:- pred fact(int::ground >> ground, int::free >> ground) is det.

インストには表 10.1のものが既定で用意されています。また、上で述べた inと out以外に、以

表 10.1 標準で用意されているインスト

インスト 意味

free 引数は代入されていない変数

ground 引数は完全に代入されている状態である

unique 引数は完全に代入されている状態で、その値を参照している変数が

ひとつしかない

dead 引数は完全に代入されている状態で、その値を参照している変数が

ひとつもない

mostly_unique uniqueと同じだがバックトラックできる

mostly_dead deadと同じだがバックトラックできる

下のモードが定義されています。

Page 159: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

10.6 モードとインスト 159

:- mode uo == free >> unique.

:- mode ui == unique >> unique.

:- mode di == unique >> dead.

:- mode muo == free >> mostly_unique.

:- mode mui == mostly_unique >> mostly_unique.

:- mode mdi == mostly_unique >> mostly_dead.

また、以下の引数付きのモードが定義されています。

:- mode in(Inst) == Inst >> Inst.

:- mode out(Inst) == free >> Inst.

述語や関数を引数として受け渡したり返したりするには、特別なインストを使う必要があります。

述語の場合は、

pred(モード 1, ..., モード n) is 決定性

関数の場合は、

func(モード 1, ..., モード n) = モード is 決定性

となります。例えば、pred(in, out) is detというモードの述語を返す引数のモードは、

free >> pred(in, out) is det

引数付きのモードを使うと、

out(pred(in, out) is det)

になります。

インストはユーザが定義することもできます。その場合以下の構文のインストを使います。

bound(コンストラクタ (引数のインスト, ...), ...)

例として以下の型が定義されていたとします。

:- type test1 ---> a; b; c.

その場合、groundは以下のユーザ定義インストと同じ意味になります。

bound(a; b; c)

bound(...)は、そのモードがとりうる値を列挙します。ここでは、aと bと cをとりうるという

ことを表しています。別の例として以下の型が定義されていたとします。

Page 160: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

160 第 10章 その他の話題

:- type test2 ---> d(int), e(string).

その場合、groundは以下のユーザ定義インストと同じ意味になります。

bound(d(ground); e(ground))

コンストラクタが引数を持つ時はその引数のインストを指定します。ここでは両方共 groundを指

定しています。

現在のMercuryの実装では部分的に代入されたデータ構造を許していないので、以下のコード

は実際には動作させることができませんが、インストの仕組みを理解する上では役に立つと思われ

るので解説します。ここでの例は、2引数のタプルで最初の引数を入力、2番目の引数を出力とし

て利用する例です。以下のようなコードを書くことを想定しています。

:- pred inout({int,int}::???) is det.

inout({X, Y}) :-

Y = X * X.

上の引数はタプルの片方の要素を入力、もう片方の要素を出力として扱っています。上の???の部

分に該当するモードは、Mercuryのインストの構文を利用すると以下のように記述できます。

bound({ground, free}) >> bound({ground, ground})

実行前は、タプルの前半は ground、後半は freeであり、実行後は両方共 groundになっています。

次のコードは現在の Mercuryの実装でも動作させることが可能です。2引数のタプルを入力と

して使うときに、タプルの最初の要素で関数を、2番目の要素で整数を受け渡す例です。

:- pred app({pred(int, int), int}::???, int::out) is det.

app({P, N}, R) :-

P(N, R).

ここで???のモードで inを使うことはできません。ここでは以下のようなモードを指定する必要

があります。

in(bound({pred(in,out) is det, in}))

になります。以下にプログラム全体を示します。

1 :- module inst1.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

Page 161: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

10.6 モードとインスト 161

5

6 :- implementation.

7 :- import_module int.

8

9 :- inst pred_tuple == bound({pred(in,out) is det, ground}).

10

11 :- pred app({pred(int, int), int}::in(pred_tuple), int::out) is det.

12 app({P, N}, R) :-

13 P(N, R).

14

15 main(!IO) :-

16 P = (pred(X::in, Y::out) is det :- Y = X * 2),

17 app({P, 10}, R),

18 write_int(R, !IO),

19 nl(!IO).

実行結果は以下のようになります。

$ ./inst1

20

Page 162: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。
Page 163: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

163

第 11章

標準ライブラリ概覧 (基本的な型)

11.1 整数

整数に関する述語や関数は intモジュールにまとめられています。ここでは intモジュールに

定義されている述語や関数のうち、利用頻度の高いものを示します。

11.1.1 基本演算

:- func int + int = int.

:- mode in + in = uo is det.

:- mode uo + in = in is det.

:- mode in + uo = in is det.

整数同士の加算。逆演算が可能。

:- func int - int = int.

:- mode in - in = uo is det.

:- mode uo - in = in is det.

:- mode in - uo = in is det.

整数同士の減算。逆演算が可能。

:- func (int::in) * (int::in) = (int::uo) is det.

整数同士の乗算。

:- func (int::in) / (int::in) = (int::uo) is det.

整数同士の除算。

Page 164: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

164 第 11章 標準ライブラリ概覧 (基本的な型)

:- func (int::in) rem (int::in) = (int::uo) is det.

整数同士の剰余。

:- func - (int::in) = (int::uo) is det.

整数の符号反転。

11.1.2 ビット演算

:- func \ (int::in) = (int::uo) is det.

ビットの反転。

:- func (int::in) /\ (int::in) = (int::uo) is det.

ビットごとの論理積。

:- func (int::in) \/ (int::in) = (int::uo) is det.

ビットごとの論理和。

:- func int.xor(int, int) = int.

:- mode int.xor(in, in) = uo is det.

:- mode int.xor(in, uo) = in is det.

:- mode int.xor(uo, in) = in is det.

ビットごとの排他的論理和。逆演算が可能。

11.1.3 比較

:- pred (int::in) < (int::in) is semidet.

より小さい。

:- pred (int::in) > (int::in) is semidet.

より大きい。

:- pred (int::in) =< (int::in) is semidet.

以下。

:- pred (int::in) >= (int::in) is semidet.

Page 165: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

11.2 浮動小数点数 165

以上。

11.1.4 その他

:- func int.abs(int) = int.

絶対値を返す。

:- func int.max(int, int) = int.

引数の値が大きい方を返す。

:- func int.min(int, int) = int.

引数の値が小さい方を返す。

:- func int.pow(int, int) = int.

pow(N,M)で Nの M乗を返す。

:- func int.max_int = int.

整数の最大値。

:- func int.min_int = int.

整数の最小値。

11.2 浮動小数点数

浮動小数点数に関する述語や関数は floatモジュールにまとめられています。平方根や三角関

数などの数学関数は後述の math モジュールで定義されています。ここでは float モジュールに

定義されている述語や関数のうち、利用頻度の高いものを示します。

11.2.1 基本演算

:- func (float::in) + (float::in) = (float::uo) is det.

浮動小数点数同士の加算。

:- func (float::in) - (float::in) = (float::uo) is det.

浮動小数点数同士の減算。

Page 166: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

166 第 11章 標準ライブラリ概覧 (基本的な型)

:- func (float::in) * (float::in) = (float::uo) is det.

浮動小数点数同士の乗算。

:- func (float::in) / (float::in) = (float::uo) is det.

浮動小数点数同士の剰余。

:- func - (float::in) = (float::uo) is det.

浮動小数点数同士の符号反転。

11.2.2 比較

:- pred (int::in) < (int::in) is semidet.

より小さい。

:- pred (int::in) > (int::in) is semidet.

より大きい。

:- pred (int::in) =< (int::in) is semidet.

以下。

:- pred (int::in) >= (int::in) is semidet.

以上。

11.2.3 整数との相互変換

:- func float(int) = float.

整数を浮動小数点数に変換。

:- func ceiling_to_int(float) = int.

浮動小数点数をその値を下回らない最小の整数に変換。

:- func floor_to_int(float) = int.

浮動小数点数をその値を超えない最大の整数に変換。

:- func round_to_int(float) = int.

Page 167: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

11.3 数学関数 167

浮動小数点数を少数点第 1位を四捨五入して整数に変換。

:- func truncate_to_int(float) = int.

浮動小数点数を最も近い整数に変換。

11.2.4 その他

:- func abs(float) = float.

絶対値を返す。

:- func max(float, float) = float.

値の大きい方を返す。

:- func min(float, float) = float.

値の小さい方を返す。

:- func hash(float) = int.

ハッシュ値 (正の整数)を返す。

11.3 数学関数

平方根や三角関数などの数学関数は math モジュールに定義されています。ここでは math モ

ジュールに定義されている述語や関数のうち、利用頻度の高いものを示します。

:- func math.pi = float.

円周率。

:- func math.e = float.

自然対数の底。

:- func math.sqrt(float) = float.

平方根。

:- func math.pow(float, float) = float.

累乗。

Page 168: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

168 第 11章 標準ライブラリ概覧 (基本的な型)

:- func math.exp(float) = float.

exp(N)で自然対数の底 eの N乗を返す。

:- func math.ln(float) = float.

自然対数。

:- func math.log10(float) = float.

常用対数。

:- func math.log(float, float) = float.

log(B,X)で底が Bの時の Xの対数。

:- func math.sin(float) = float.

:- func math.cos(float) = float.

:- func math.tan(float) = float.

三角関数。

:- func math.asin(float) = float.

:- func math.acos(float) = float.

:- func math.atan(float) = float.

逆三角関数。

:- func math.atan2(float, float) = float.

atan2(X,Y)で X/Yの逆タンジェント。

:- func math.sinh(float) = float.

:- func math.cosh(float) = float.

:- func math.tanh(float) = float.

双曲線関数。

11.4 文字

文字列に関する述語や関数は charモジュールにまとめられています。ここでは charモジュー

ルに定義されている述語や関数のうち、利用頻度の高いものを示します。

:- func char.to_int(char) = int.

Page 169: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

11.5 文字列 169

文字を文字コードに変換。

:- pred char.from_int(int::in, char::out) is semidet.

:- func char.det_from_int(int) = char.

文字コードを文字に変換。

:- func char.to_upper(char) = char.

アルファベットの小文字を大文字に変換。

:- func char.to_lower(char) = char.

アルファベットの大文字を小文字に変換。

:- pred char.is_whitespace(char::in) is semidet.

文字が空白文字であれば成功。

:- pred char.is_upper(char::in) is semidet.

文字がアルファベット大文字であれば成功。

:- pred char.is_lower(char::in) is semidet.

文字がアルファベット小文字であれば成功。

:- pred char.is_alpha(char::in) is semidet.

文字がアルファベットであれば成功。

:- pred char.is_alnum(char::in) is semidet.

文字がアルファベットか数字であれば成功。

:- pred char.is_digit(char::in) is semidet.

文字が数字であれば成功。

11.5 文字列

文字列に関する述語や関数は string モジュールにまとめられています。ここでは string モ

ジュールに定義されている述語や関数のうち、利用頻度の高いものを示します。

:- func string.length(string::in) = (int::uo) is det.

Page 170: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

170 第 11章 標準ライブラリ概覧 (基本的な型)

文字列の長さ。

:- func string ++ string = string.

:- mode in ++ in = uo is det.

文字列の連結。

:- func string.string(T) = string.

任意の値を文字列に変換。

:- func string.char_to_string(char::in) = (string::uo) is det.

文字を文字列に変換。

:- func string.int_to_string(int::in) = (string::uo) is det.

整数を文字列に変換。

:- func string.float_to_string(float::in) = (string::uo) is det.

浮動小数点数を文字列に変換。

:- func string.replace_all(string::in, string::in, string::in) = (string::uo)

is det.

replace(String0, Search, Replace)で、文字列 String0のすべての Searchを Replaceに

置換した文字列を返す。

:- func string.to_char_list(string) = list(char).

文字列を文字のリストに変換する。

:- func string.from_char_list(list(char)::in) = (string::uo) is det.

文字のリストを文字列に変換する。

:- pred string.to_int(string::in, int::out) is semidet.

:- func string.det_to_int(string) = int.

文字列を整数に変換する。

:- pred string.to_float(string::in, float::out) is semidet.

:- func string.det_to_float(string) = float.

文字列を浮動小数点数に変換する。

Page 171: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

11.5 文字列 171

:- pred string.all_match(pred(char)::in(pred(in) is semidet), string::in)

is semidet.

文字列中の文字を述語に渡して、すべて成功すれば成功、一つでも失敗する文字があれば失敗。

:- pred string.index(string::in, int::in, char::uo) is semidet.

:- func string ^ elem(int) = char.

文字列中の N番目の文字を返す。

:- func string.strip(string) = string.

文字列の前後の空白文字を取り除く。

:- func string.foldl(func(char, A) = A, string, A) = A.

:- pred string.foldl(pred(char, A, A), string, A, A).

:- mode string.foldl(pred(in, di, uo) is det, in, di, uo) is det.

:- mode string.foldl(pred(in, in, out) is det, in, in, out) is det.

:- mode string.foldl(pred(in, in, out) is semidet, in, in, out) is semidet.

:- mode string.foldl(pred(in, in, out) is nondet, in, in, out) is nondet.

:- mode string.foldl(pred(in, in, out) is multi, in, in, out) is multi.

文字に対する畳込み演算。

:- func string.words(string) = list(string).

文字列を空白で区切られた単語に分割。

:- pred string.split(string::in, int::in, string::out, string::out) is det.

文字列を指定した位置で分割。

:- func string.between(string::in, int::in, int::in) = (string::uo) is det.

文字列から指定した範囲を切り出す。

:- func string.append_list(list(string)::in) = (string::uo) is det.

文字列のリストを文字列に変換。

:- func string.join_list(string::in, list(string)::in) = (string::uo) is det.

join_list(Separator, Strings) で、間に Separator を挟んで文字列のリストを文字列に

変換。

Page 172: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

172 第 11章 標準ライブラリ概覧 (基本的な型)

:- func string.hash(string) = int.

文字列のハッシュ値 (正の整数)を計算。

:- pred string.sub_string_search(string::in, string::in, int::out) is semidet.

sub_string_search(String, SubString, Index)で、String中の SubStringのインデック

スを Indexで返す。

11.6 文字列の整形

stringモジュールの関数 formatは、C言語の sprintf 関数のようにフォーマット文字列を受

け取って値を整形します。ここでは format関数の使い方を解説します。format関数は以下のよ

うに宣言されています。

:- func string.format(string, list(string.poly_type)) = string.

上の宣言に現れる poly_typeは以下のように定義されています。

:- type string.poly_type

---> f(float)

; i(int)

; s(string)

; c(char).

format 関数の使い方は基本的には C 言語の sprintf と同じです。文字列中に現れる %d などの

フォーマット指定子が、指定した値の文字列表現に置き換わります。例えば、

format("%s(%d) %c %f\n", [s("sqrt"), i(2), c(’=’), f(1.414)])

は文字列、

"sqrt(2) = 1.414"

を返します。C 言語の printf と異なり、型に応じたタグを値に付ける必要があります。フォー

マット指定子には表 11.1のものが用意されています。また、オプションで’0’、’+’、’-’、’#’、

’ ’、整数、’*’を指定可能です。’0’はゼロ詰め、’+’は符号の表示、’-’は右寄せ、’#’は表

示形式の変更、’ ’は正の数で符号分の空白を出力、整数は出力幅や精度を指定、’*’は出力幅や

精度を引数で受け取る役割があります。フォーマット文字列の詳細は C言語の printf 関数と同

じになっているので、詳細は C言語の解説等が参考になります。

Page 173: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

11.7 多倍長整数 173

表 11.1 フォーマット指定子

フォーマット指定子 説明

d,i 整数を符号付き 10進表示

o 整数を 8進表示

x,X 整数を 16進表示

u 符号なし整数として表示

c 文字を表示

s 文字列を表示

f 浮動小数点数を小数表示

e,E 浮動小数点数を科学技術表示

g,G 浮動小数点数を小数表示もしくは科学技術表示

p 整数を 16進表示 (0x..形式)

11.7 多倍長整数

integer モジュールに多倍長整数を表す型 integer と、関連する述語関数が定義されていま

す。ここでは integerモジュールに定義されている述語や関数のうち、利用頻度の高いものを示

します。

11.7.1 定数

:- func integer.zero = integer.

定数 0。

:- func integer.one = integer.

定数 1。

11.7.2 変換

:- func integer.integer(int) = integer.

整数を多倍長整数に変換。

:- func integer.to_string(integer) = string.

多倍長整数を文字列に変換。

Page 174: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

174 第 11章 標準ライブラリ概覧 (基本的な型)

:- func integer.float(integer) = float.

多倍長整数を浮動小数点数に変換。

:- func integer.int(integer) = int.

多倍長整数を整数に変換。

:- func integer.from_string(string::in) = (integer::out) is semidet.

:- func integer.det_from_string(string) = integer.

11.7.3 基本演算

:- func integer + integer = integer.

加算。

:- func integer - integer = integer.

減算。

:- func integer * integer = integer.

乗算。

:- func integer // integer = integer.

除算。

:- func integer rem integer = integer.

剰余。

:- pred divide_with_rem(integer::in, integer::in,

integer::out, integer::out) is det.

divide_with_rem(X, Y, Q, R)で、Q = X // Y, R = X rem Yの結果を同時に返す。

:- func ’+’(integer) = integer.

符号反転。

Page 175: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

11.7 多倍長整数 175

11.7.4 比較

:- pred ’<’(integer::in, integer::in) is semidet.

より小さい。

:- pred ’>’(integer::in, integer::in) is semidet.

より大きい。

:- pred ’=<’(integer::in, integer::in) is semidet.

以下。

:- pred ’>=’(integer::in, integer::in) is semidet.

以上。

11.7.5 関数

:- func integer.abs(integer) = integer.

絶対値。

:- func integer.pow(integer, integer) = integer.

累乗。

11.7.6 プログラム例

以下のプログラムでは多倍長整数を使って、100の階乗を求めています。

1 :- module integer_fact.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module integer.

8

9 :- func fact(integer) = integer.

Page 176: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

176 第 11章 標準ライブラリ概覧 (基本的な型)

10 fact(N) = (if N = zero then one else N * fact(N - one)).

11

12 main(!IO) :-

13 X = fact(det_from_string("100")),

14 write_string(to_string(X), !IO),

15 nl(!IO).

実行結果は以下のようになります。

$ ./integer_fact

9332621544394415268169923885626670049071596826438162146859296389521759999

3229915608941463976156518286253697920827223758251185210916864000000000000

000000000000

11.8 真偽値

boolモジュールには真偽値を表す以下の型が定義されています。

:- type bool

---> no

; yes.

bool型に関する述語や関数として以下のものが定義されています。

:- func bool.not(bool) = bool.

否定。

:- func bool.or(bool, bool) = bool.

論理和。

:- func bool.and(bool, bool) = bool.

論理積。

:- func bool.xor(bool, bool) = bool.

排他的論理和。

Page 177: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

11.9 maybe型 177

11.9 maybe型

maybeモジュールに以下の maybe(T)型が定義されています。

:- type maybe(T)

---> no

; yes(T).

また、以下の maybe_error(T, E)型も定義されています。

:- type maybe_error(T, E)

---> ok(T)

; error(E).

maybe_error(T)はエラーを文字列で表す場合の型です。

:- type maybe_error(T) == maybe_error(T, string).

以下の述語や関数が maybeモジュールに定義されています。

:- func map_maybe(func(T) = U, maybe(T)) = maybe(U).

maybe型の値を関数を使って書き換えます。

Page 178: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。
Page 179: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

179

第 12章

標準ライブラリ概覧 (コレクション)

12.1 リスト

リストは listモジュールで以下のように定義されています。

:- type list(T)

---> []

; [T | list(T)].

ここでは listモジュールに定義されている述語や関数のうち、利用頻度の高いものを示します。

:- func list(T) ++ list(T) = list(T).

リストの連結。

:- func list.remove_dups(list(T)) = list(T).

リストの重複する要素を削除する。

:- pred list.member(T, list(T)).

:- mode list.member(in, in) is semidet.

:- mode list.member(out, in) is nondet.

(in, in)の場合、リストに要素が含まれていれば成功。(out, in)の場合、リストの各要素を非

決定的に返す。

:- func list.length(list(T)) = int.

リストの長さ。

Page 180: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

180 第 12章 標準ライブラリ概覧 (コレクション)

:- pred list.split_list(int::in, list(T)::in, list(T)::out, list(T)::out)

is semidet.

:- pred list.det_split_list(int::in, list(T)::in, list(T)::out, list(T)::out)

is det.

リストを指定した位置で分割する。

:- pred list.split_upto(int::in, list(T)::in, list(T)::out, list(T)::out)

is det.

リストを指定した位置で分割する。ただし範囲外だった場合、後ろのリストを空にする。

:- pred list.take(int::in, list(T)::in, list(T)::out) is semidet.

:- pred list.det_take(int::in, list(T)::in, list(T)::out) is det.

リストの先頭から n個の要素を取り出す。

:- func list.take_upto(int, list(T)) = list(T).

リストの先頭から n 個の要素を取り出す。リストの要素数より大きい値を指定した場合は全要素

を返す。

:- pred list.drop(int::in, list(T)::in, list(T)::out) is semidet.

:- pred list.det_drop(int::in, list(T)::in, list(T)::out) is det.

リストの先頭から n個取り除いたリストを返す。

:- pred list.insert(T, list(T), list(T)).

:- mode list.insert(in, in, in) is semidet.

:- mode list.insert(in, out, in) is nondet.

:- mode list.insert(out, out, in) is nondet.

:- mode list.insert(in, in, out) is multi.

(in, in, in)の場合、2引数目のリストに 1引数目の値を追加したリストが 3引数目のリストの

場合に成功。(in, out, in)の場合、3引数目のリストから 1引数目の値を 1つ取り除いたリス

トを非決定的に返す。(out, out, in)の場合、3引数目のリストから要素を順番に取り出し、同

時にその要素を取り除いたリストも返す。(in, in, out)の場合、1引数目の要素を 2引数目の

リストに追加したリストを返す。

:- pred list.delete(list(T), T, list(T)).

:- mode list.delete(in, in, in) is semidet.

:- mode list.delete(in, in, out) is nondet.

Page 181: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

12.1 リスト 181

:- mode list.delete(in, out, out) is nondet.

:- mode list.delete(out, in, in) is multi.

(in, in, in) の場合、3 引数目のリストが 1 引数目のリストから 2 引数目の要素を 1 つ取り除

いたリストになっていれば成功。(in, in, out)の場合、1引数目のリストから 2引数目の値を

1つ取り除いたリストを非決定的に返す。(in, out, out)の場合、リストから要素を順番に取り

出し、同時にその要素を取り除いたリストも返す。(out, in, in)の場合、3引数目のリストに

2引数目の値を追加したリストを返す。

:- func list.delete_all(list(T), T) = list(T).

リストから指定した値の要素をすべて取り除く。

:- func list.delete_elems(list(T), list(T)) = list(T).

delete_elems(List, Elems)で Listの要素で、Elemsに含まれるものをすべて取り除く。

:- pred list.replace(list(T), T, T, list(T)).

:- mode list.replace(in, in, in, in) is semidet.

:- mode list.replace(in, in, in, out) is nondet.

(in, in, in, in)の場合、1引数目のリストに含まれる 2引数目の値を 3 引数目の値に置き換

えたリストが 4引数目のリストであれば成功。(in, in, in, out)の場合、1引数目のリストに

含まれる 2引数目の値を 3引数目の値に置き換えたリストを非決定的に返す。

:- func list.replace_all(list(T), T, T) = list(T).

replace_all(List, X, Y)で、Listに含まれる要素 Xを Yにすべて置き換えたリストを返す。

:- pred list.replace_nth(list(T)::in, int::in, T::in, list(T)::out)

is semidet.

:- func list.det_replace_nth(list(T), int, T) = list(T).

リストの n番目の要素を指定した値に置き換えたリストを返す。最初の要素は 1。

:- func list.sort(list(T)) = list(T).

リストをソートする。

:- func list.reverse(list(T)) = list(T).

リストを反転する。

:- pred list.perm(list(T)::in, list(T)::out) is multi.

Page 182: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

182 第 12章 標準ライブラリ概覧 (コレクション)

リストの順列を求める。

:- pred list.index0(list(T)::in, int::in, T::out) is semidet.

:- pred list.index1(list(T)::in, int::in, T::out) is semidet.

:- func list.det_index0(list(T), int) = T.

:- func list.det_index1(list(T), int) = T.

リストの n 番目の要素を求める。末尾に 0 が付くものは、最初の要素が 0。末尾に 1 が付くもの

は、最初の要素が 1。

:- pred list.index0_of_first_occurrence(list(T)::in, T::in, int::out)

is semidet.

:- pred list.index1_of_first_occurrence(list(T)::in, T::in, int::out)

is semidet.

:- func list.det_index0_of_first_occurrence(list(T), T) = int.

:- func list.det_index1_of_first_occurrence(list(T), T) = int.

リストで指定した要素が最初に現れるインデックスを求める。末尾に 0が付くものは、最初の要素

が 0。末尾に 1が付くものは、最初の要素が 1。

:- pred list.last(list(T)::in, T::out) is semidet.

:- func list.det_last(list(T)) = T.

リストの最後の要素を求める。

:- pred list.split_last(list(T)::in, list(T)::out, T::out) is semidet.

:- pred list.det_split_last(list(T)::in, list(T)::out, T::out) is det.

リストを最後以外のリストと最後の要素に分割する。

:- pred list.map(pred(X, Y), list(X), list(Y)).

:- mode list.map(pred(in, out) is det, in, out) is det.

:- mode list.map(pred(in, out) is cc_multi, in, out) is cc_multi.

:- mode list.map(pred(in, out) is semidet, in, out) is semidet.

:- mode list.map(pred(in, out) is multi, in, out) is multi.

:- mode list.map(pred(in, out) is nondet, in, out) is nondet.

:- mode list.map(pred(in, in) is semidet, in, in) is semidet.

リストのすべての要素に述語を適用した結果のリストを返す。

:- func list.map(func(X) = Y, list(X)) = list(Y).

Page 183: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

12.1 リスト 183

リストのすべての要素に関数を適用した結果のリストを返す。

:- pred list.map2(pred(A, B, C), list(A), list(B), list(C)).

:- mode list.map2(pred(in, out, out) is det, in, out, out) is det.

:- mode list.map2(pred(in, out, out) is cc_multi, in, out, out) is cc_multi.

:- mode list.map2(pred(in, out, out) is semidet, in, out, out) is semidet.

:- mode list.map2(pred(in, out, out) is multi, in, out, out) is multi.

:- mode list.map2(pred(in, out, out) is nondet, in, out, out) is nondet.

:- mode list.map2(pred(in, in, in) is semidet, in, in, in) is semidet.

2つのリストのすべての要素に述語を適用した結果のリストを返す。同様に map3~map8まで定義

されている。

:- pred list.foldl(pred(L, A, A), list(L), A, A).

:- mode list.foldl(pred(in, in, out) is det, in, in, out) is det.

:- mode list.foldl(pred(in, mdi, muo) is det, in, mdi, muo) is det.

:- mode list.foldl(pred(in, di, uo) is det, in, di, uo) is det.

:- mode list.foldl(pred(in, in, out) is semidet, in, in, out) is semidet.

:- mode list.foldl(pred(in, mdi, muo) is semidet, in, mdi, muo) is semidet.

:- mode list.foldl(pred(in, di, uo) is semidet, in, di, uo) is semidet.

:- mode list.foldl(pred(in, in, out) is multi, in, in, out) is multi.

:- mode list.foldl(pred(in, in, out) is nondet, in, in, out) is nondet.

:- mode list.foldl(pred(in, mdi, muo) is nondet, in, mdi, muo) is nondet.

:- mode list.foldl(pred(in, in, out) is cc_multi, in, in, out) is cc_multi.

:- mode list.foldl(pred(in, di, uo) is cc_multi, in, di, uo) is cc_multi.

左から右へのリストの畳み込み演算。述語を順番に適用していく。

:- func list.foldl(func(L, A) = A, list(L), A) = A.

左から右へのリストの畳み込み演算。関数を順番に適用していく。

:- pred list.foldr(pred(L, A, A), list(L), A, A).

:- mode list.foldr(pred(in, in, out) is det, in, in, out) is det.

:- mode list.foldr(pred(in, mdi, muo) is det, in, mdi, muo) is det.

:- mode list.foldr(pred(in, di, uo) is det, in, di, uo) is det.

:- mode list.foldr(pred(in, in, out) is semidet, in, in, out) is semidet.

:- mode list.foldr(pred(in, mdi, muo) is semidet, in, mdi, muo) is semidet.

:- mode list.foldr(pred(in, di, uo) is semidet, in, di, uo) is semidet.

Page 184: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

184 第 12章 標準ライブラリ概覧 (コレクション)

:- mode list.foldr(pred(in, in, out) is multi, in, in, out) is multi.

:- mode list.foldr(pred(in, in, out) is nondet, in, in, out) is nondet.

:- mode list.foldr(pred(in, mdi, muo) is nondet, in, mdi, muo) is nondet.

:- mode list.foldr(pred(in, di, uo) is cc_multi, in, di, uo) is cc_multi.

:- mode list.foldr(pred(in, in, out) is cc_multi, in, in, out) is cc_multi.

右から左へのリストの畳み込み演算。述語を順番に適用していく。

:- func list.foldr(func(L, A) = A, list(L), A) = A.

右から左へのリストの畳み込み演算。関数を順番に適用していく。

:- pred list.all_true(pred(X)::in(pred(in) is semidet), list(X)::in)

is semidet.

リストのすべての要素が述語を満たしていれば成功。

:- pred list.all_false(pred(X)::in(pred(in) is semidet), list(X)::in)

is semidet.

リストのすべての要素が述語を満たしていなれば成功。

:- func list.filter(pred(X)::in(pred(in) is semidet), list(X)::in)

= (list(X)::out) is det.

述語を満たす要素だけかき集める。

:- func list.negated_filter(pred(X)::in(pred(in) is semidet), list(X)::in)

= (list(X)::out) is det.

述語を満たさない要素だけかき集める。

:- pred list.filter(pred(X)::in(pred(in) is semidet), list(X)::in,

list(X)::out, list(X)::out) is det.

リストを述語を満たす要素と満たさない要素で分割する。

:- pred list.takewhile(pred(T)::in(pred(in) is semidet), list(T)::in,

list(T)::out, list(T)::out) is det.

述語を満たす間要素を取り出していく。

:- func int ‘..‘ int = list(int).

Page 185: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

12.2 ペア 185

指定した整数の範囲を含むリストを作る。

:- func list.det_head(list(T)) = T.

リストの先頭要素を返す。

:- func list.det_tail(list(T)) = list(T).

リストの先頭要素を除いたリストを返す。

12.2 ペア

pairモジュールには以下の型が定義されています。

:- type pair(T1, T2)

---> (T1 - T2).

pairに関する関数として以下のものが定義されています。

:- func fst(pair(X, Y)) = X.

ペアの最初の要素を返す。

:- func snd(pair(X, Y)) = Y.

ペアの 2番目の要素を返す。

:- func pair(T1, T2) = pair(T1, T2).

ペアを作る。

12.3 連想リスト

assoc_listモジュールには連想リストに関する述語と関数が定義されています。連想リストは

以下のように定義されたデータ構造です。

:- type assoc_list(K, V) == list(pair(K, V)).

連想リストは辞書に似たデータ構造で、キーと値をペアにして格納したり、キーを指定して値を検

索したりできます。

:- func assoc_list(K, V) ^ elem(K) = V is semidet.

:- func assoc_list(K, V) ^ det_elem(K) = V is det.

Page 186: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

186 第 12章 標準ライブラリ概覧 (コレクション)

連想リストから指定したキーの値を探索する。

:- pred assoc_list.remove(assoc_list(K, V)::in, K::in, V::out,

assoc_list(K, V)::out) is semidet.

連想リストから最初に現れる指定したキーの要素を削除する。

:- func assoc_list.map_keys_only(func(K) = L, assoc_list(K, V))

= assoc_list(L, V).

連想リストのキーを指定した関数を利用して書き換える。

:- func assoc_list.map_values_only(func(V) = W, assoc_list(K, V))

= assoc_list(K, W).

連想リストの値を指定した関数を利用して書き換える。

:- func assoc_list.map_values(func(K, V) = W, assoc_list(K, V))

= assoc_list(K, W).

連想リストの値を指定した関数を利用して書き換える。関数はキーと値の両方を受け取る。

:- func assoc_list.filter(pred(K)::in(pred(in) is semidet),

assoc_list(K, V)::in) = (assoc_list(K, V)::out) is det.

連想リストからキーが述語を満たすものだけを取り出す。

:- func assoc_list.negated_filter(pred(K)::in(pred(in) is semidet),

assoc_list(K, V)::in) = (assoc_list(K, V)::out) is det.

連想リストからキーが述語を満たさないものだけを取り出す。

:- pred assoc_list.filter(pred(K)::in(pred(in) is semidet),

assoc_list(K, V)::in, assoc_list(K, V)::out, assoc_list(K, V)::out) is det.

連想リストからキーが述語を満たすものと満たさないものに分ける。

:- func assoc_list.keys(assoc_list(K, V)) = list(K).

連想リストからキーだけを取り出す。

:- func assoc_list.values(assoc_list(K, V)) = list(V).

連想リストから値だけを取り出す。

Page 187: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

12.4 集合 187

12.4 集合

setモジュールには集合を表す型 set(T)と、それを操作する述語と関数が定義されています。

ここでは setモジュールに定義されている述語や関数のうち、利用頻度の高いものを示します。

:- func set.init = set(T).

空集合。

:- func set.set(list(T)) = set(T).

リストを集合に変換。

:- pred set.empty(set(T)::in) is semidet.

リストが空であれば成功。

:- pred set.non_empty(set(T)::in) is semidet.

リストが空でなければ成功。

:- pred set.subset(set(T)::in, set(T)::in) is semidet.

subset(SetA, SetB)で SetAが SetBの部分集合であれば成功。

:- pred set.member(T, set(T)).

:- mode set.member(in, in) is semidet.

:- mode set.member(out, in) is nondet.

(in, in) の場合、値が集合の要素であれば成功。(out, in) の場合、集合の要素を非決定的に

返す。

:- func set.insert(set(T), T) = set(T).

集合に要素を追加。

:- func set.insert_list(set(T), list(T)) = set(T).

集合にリストの要素を追加。

:- func set.delete(set(T), T) = set(T).

集合から指定した要素を削除。

Page 188: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

188 第 12章 標準ライブラリ概覧 (コレクション)

:- func set.delete_list(set(T), list(T)) = set(T).

集合からリストで指定した要素を削除。

:- func set.union(set(T), set(T)) = set(T).

和集合。

:- func set.union_list(list(set(T))) = set(T).

集合のリストの和集合。

:- func set.power_union(set(set(T))) = set(T).

集合の集合の和集合。

:- func set.intersect(set(T), set(T)) = set(T).

集合の共通部分。

:- func set.intersect_list(list(set(T))) = set(T).

集合のリストの共通部分。

:- func set.power_intersect(set(set(T))) = set(T).

集合の集合の共通部分。

:- func set.difference(set(T), set(T)) = set(T).

difference(SetA, SetB)で SetAのうち、SetBに現れる要素を取り除く。

:- func set.count(set(T)) = int.

集合の大きさ (要素数)。

:- func set.map(func(T1) = T2, set(T1)) = set(T2).

集合の各要素に関数を適用した集合を返す。

:- func set.filter(pred(T1), set(T1)) = set(T1).

:- mode set.filter(pred(in) is semidet, in) = out is det.

集合から述語を満たす要素だけを集めた集合を作る。

:- pred set.filter(pred(T1), set(T1), set(T1), set(T1)).

:- mode set.filter(pred(in) is semidet, in, out, out) is det.

Page 189: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

12.5 写像 189

集合から述語を満たす要素を集めた集合と、述語を満たさない要素を集めた集合を作る。

:- func set.fold(func(T, A) = A, set(T), A) = A.

:- pred set.fold(pred(T, A, A), set(T), A, A).

:- mode set.fold(pred(in, in, out) is det, in, in, out) is det.

:- mode set.fold(pred(in, mdi, muo) is det, in, mdi, muo) is det.

:- mode set.fold(pred(in, di, uo) is det, in, di, uo) is det.

:- mode set.fold(pred(in, in, out) is semidet, in, in, out) is semidet.

:- mode set.fold(pred(in, mdi, muo) is semidet, in, mdi, muo) is semidet.

:- mode set.fold(pred(in, di, uo) is semidet, in, di, uo) is semidet.

集合に関する畳み込み演算。

- pred all_true(pred(T)::in(pred(in) is semidet), set(T)::in) is semidet.

集合のすべての要素が述語を満たせば成功。

12.5 写像

mapモジュールには写像を表す型 map(K, V)とそれを操作する述語と関数が定義されています。

ここでは mapモジュールに定義されている述語や関数のうち、利用頻度の高いものを示します。

:- func map.init = (map(K, V)::uo) is det.

空の写像。

:- pred map.is_empty(map(_, _)::in) is semidet.

集合が空の写像であれば成功。

:- pred map.equal(map(K, V)::in, map(K, V)::in) is semidet.

2つの写像が等しければ成功。

:- pred map.contains(map(K, _V)::in, K::in) is semidet.

写像がキーを含めば成功。

:- func map.elem(K, map(K, V)) = V is semidet.

:- func map.det_elem(K, map(K, V)) = V.

写像からキーに対応する値を取り出す。

Page 190: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

190 第 12章 標準ライブラリ概覧 (コレクション)

:- func ’elem :=’(K, map(K, V), V) = map(K, V).

写像にキーが既に存在すれば値を更新し、存在しなければ新しい値を追加する。

:- func map.keys(map(K, _V)) = list(K).

写像のキーをリストで返す。

:- func map.values(map(_K, V)) = list(V).

写像の値をリストで返す。

:- func map.to_assoc_list(map(K, V)) = assoc_list(K, V).

写像を連想リストに変換する。

:- func map.from_assoc_list(assoc_list(K, V)) = map(K, V).

連想リストを写像に変換する。

:- func map.delete(map(K, V), K) = map(K, V).

写像からキーに対応するペアを削除する。

:- func map.delete_list(map(K, V), list(K)) = map(K, V).

写像からキーのリストに対応するペアを削除する。

:- func map.count(map(K, V)) = int.

写像の大きさ (サイズ)を返す。

:- func map.apply_to_list(list(K), map(K, V)) = list(V).

写像からキーのリストに対応する値のリストを返す。

:- func map.foldl(func(K, V, A) = A, map(K, V), A) = A.

:- pred map.foldl(pred(K, V, A, A), map(K, V), A, A).

:- mode map.foldl(pred(in, in, in, out) is det, in, in, out) is det.

:- mode map.foldl(pred(in, in, mdi, muo) is det, in, mdi, muo) is det.

:- mode map.foldl(pred(in, in, di, uo) is det, in, di, uo) is det.

:- mode map.foldl(pred(in, in, in, out) is semidet, in, in, out) is semidet.

:- mode map.foldl(pred(in, in, mdi, muo) is semidet, in, mdi, muo) is semidet.

:- mode map.foldl(pred(in, in, di, uo) is semidet, in, di, uo) is semidet.

:- mode map.foldl(pred(in, in, in, out) is cc_multi, in, in, out) is cc_multi.

Page 191: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

12.6 univ型 191

:- mode map.foldl(pred(in, in, di, uo) is cc_multi, in, di, uo) is cc_multi.

:- mode map.foldl(pred(in, in, mdi, muo) is cc_multi, in, mdi, muo)

is cc_multi.

写像に関する畳込み。

:- func map.union(func(V, V) = V, map(K, V), map(K, V)) = map(K, V).

2つの写像を合成する。同じキーの値があれば、関数を使って新たな値を計算する。

12.6 univ型

univモジュールに定義されている型 univは任意の型のオブジェクトを格納できる型です。こ

こでは univ型を操作する述語と関数を説明します。

:- func univ(T) = univ.

:- mode univ(in) = out is det.

:- mode univ(di) = uo is det.

:- mode univ(out) = in is semidet.

値を univ型に変換したり、逆に univ型からもとの型を復元したりします。

:- pred det_univ_to_type(univ::in, T::out) is det.

univ型からもとの型の値を復元します。復元できない場合はエラーを投げます。

Page 192: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。
Page 193: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

193

第 13章

標準ライブラリ概覧 (入出力)

13.1 コンソール出力

ioモジュールには入出力に関する述語や関数が定義されています。ここでは、ioモジュールに

定義されている述語や関数のうち、コンソール出力に関する利用頻度の高いものを列挙します。

:- pred io.print(T::in, io::di, io::uo) is det.

任意の値を人間が読みやすい形式で画面に表示する。

:- pred io.write(T::in, io::di, io::uo) is det.

任意の値を機械が読みやすい形式で画面に表示する。

:- pred io.nl(io::di, io::uo) is det.

改行文字を画面に表示する。

:- pred io.write_string(string::in, io::di, io::uo) is det.

文字列を画面に表示する。

:- pred io.write_strings(list(string)::in, io::di, io::uo) is det.

文字列のリストのそれぞれの文字列を連結して画面に表示する。

:- pred io.write_char(char::in, io::di, io::uo) is det.

文字を画面に表示する。

:- pred io.write_int(int::in, io::di, io::uo) is det.

整数を画面に表示する。

Page 194: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

194 第 13章 標準ライブラリ概覧 (入出力)

:- pred io.write_float(float::in, io::di, io::uo) is det.

浮動小数点数を画面に表示する。

:- pred io.format(string::in, list(io.poly_type)::in, io::di, io::uo) is det.

フォーマット文字列に従って値を出力する。io.poly_type は string.poly_type の別名です。

フォーマット文字列の詳細は、文字列の整形の章を参照してください。

:- pred io.write_list(list(T), string, pred(T, io, io), io, io).

任意の値を指定したセパレータを挟んで画面に表示する。

:- pred io.flush_output(io::di, io::uo) is det.

バッファにたまった文字列を強制的に画面に出力する。

13.2 コンソール入力

ここでは、ioモジュールに定義されている述語や関数のうち、コンソール入力に関するもので

利用頻度の高いものを列挙します。

:- type io.result(T)

---> ok(T)

; eof

; error(io.error).

入出力の結果を表す型。

:- pred io.read_char(io.result(char)::out, io::di, io::uo) is det.

文字を読み取る。

:- pred io.read_word(io.result(list(char))::out, io::di, io::uo) is det.

単語を読み取る。

:- pred io.read_line(io.result(list(char))::out, io::di, io::uo) is det.

1行読み取る。

:- pred io.read_line_as_string(io.result(string)::out, io::di, io::uo) is det.

1行を文字列として読み込む。

Page 195: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

13.2 コンソール入力 195

:- type io.maybe_partial_res(T)

---> ok(T)

; error(T, io.error).

正しい結果を返すかエラーを返すかを表す型。ただし、エラーを返した場合は、エラーが発生する

までに読み込んだ値も返す。

:- pred io.read_file(io.maybe_partial_res(list(char))::out, io::di, io::uo)

is det.

入力を eofまで全部読み込む。

:- pred io.read_file_as_string(io.maybe_partial_res(string)::out,

io::di, io::uo) is det.

入力を eofまで文字列として全部読み込む。

:- pred io.input_stream_foldl(pred(char, T, T), T, io.maybe_partial_res(T),

io, io).

:- mode io.input_stream_foldl((pred(in, in, out) is det), in, out,

di, uo) is det.

:- mode io.input_stream_foldl((pred(in, in, out) is cc_multi), in, out,

di, uo) is cc_multi.

入力を順番に読み取り述語で処理させる。

:- type io.res

---> ok

; error(io.error).

成功したか ioエラーが発生したかを表す型。

:- pred io.input_stream_foldl_io(pred(char, io, io), io.res, io, io).

:- mode io.input_stream_foldl_io((pred(in, di, uo) is det), out, di, uo)

is det.

:- mode io.input_stream_foldl_io((pred(in, di, uo) is cc_multi), out, di, uo)

is cc_multi.

入力を順番に読み取り述語で処理させる。ただし状態として io状態を受け渡す。

:- pred io.input_stream_foldl2_io(pred(char, T, T, io, io),

T, io.maybe_partial_res(T), io, io).

Page 196: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

196 第 13章 標準ライブラリ概覧 (入出力)

:- mode io.input_stream_foldl2_io((pred(in, in, out, di, uo) is det),

in, out, di, uo) is det.

:- mode io.input_stream_foldl2_io((pred(in, in, out, di, uo) is cc_multi),

in, out, di, uo) is cc_multi.

入力を順番に読み取り述語で処理させる。ただし状態としてユーザ状態と io状態を受け渡す。

:- pred io.input_stream_foldl2_io_maybe_stop(

pred(char, bool, T, T, io, io),

T, io.maybe_partial_res(T), io, io).

:- mode io.input_stream_foldl2_io_maybe_stop(

(pred(in, out, in, out, di, uo) is det),

in, out, di, uo) is det.

:- mode io.input_stream_foldl2_io_maybe_stop(

(pred(in, out, in, out, di, uo) is cc_multi),

in, out, di, uo) is cc_multi.

入力を順番に読み取り述語で処理させる。状態としてはユーザ状態と io状態を受け渡す。述語は

読み込みを続けるか停止するかを bool型の値で返す。

:- pred io.putback_char(char::in, io::di, io::uo) is det.

入力に文字を 1文字戻す。

:- type io.read_result(T)

---> ok(T)

; eof

; error(string, int).

成功かファイル終端かエラー。エラーはエラーメッセージと行番号。

:- pred io.read(io.input_stream::in, io.read_result(T)::out,

io::di, io::uo) is det.

任意の値をMercuryの構文に従って読み取る。型は文脈によって決まる。

13.3 ファイル出力

ここでは、ioモジュールに定義されている述語や関数のうち、ファイル出力に関するものを説

明します。ファイル出力を行うには以下の型のストリームを利用します。

Page 197: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

13.3 ファイル出力 197

:- type io.output_stream.

最初に output_streamを開いて、次に、output_streamを受け取る述語で出力処理を行い、最

後に output_streamを閉じます。ここではファイル出力に関する述語や関数のうち利用頻度の高

いものを列挙します。

13.3.1 ファイルを開く

:- type io.res(T)

---> ok(T)

; error(io.error).

成功かエラーを表す型。

:- pred io.open_output(string::in, io.res(io.output_stream)::out,

io::di, io::uo) is det.

指定したファイル名のファイルから出力ストリームを開きます。既にファイルが存在した場合は

ファイルの中身が消去されます。

:- pred io.open_append(string::in, io.res(io.output_stream)::out,

io::di, io::uo) is det.

指定したファイル名のファイルから出力ストリームを開きます。既にファイルが存在した場合は

ファイルに追記を行います。

:- func io.stdout_stream = io.output_stream.

標準出力を表すストリームです。

:- func io.stderr_stream = io.output_stream.

標準エラー出力を表すストリームです。

13.3.2 ファイルに書き込む

:- pred io.print(io.output_stream::in, T::in, io::di, io::uo) is det.

任意の値を人間が読みやすい形式でファイルに出力する。

:- pred io.write(io.output_stream::in, T::in, io::di, io::uo) is det.

任意の値を機械が読みやすい形式でファイルに出力する。

Page 198: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

198 第 13章 標準ライブラリ概覧 (入出力)

:- pred io.nl(io.output_stream::in, io::di, io::uo) is det.

改行文字をファイルに出力する。

:- pred io.write_string(io.output_stream::in, string::in, io::di, io::uo)

is det.

文字列をファイルに出力する。

:- pred io.write_strings(io.output_stream::in, list(string)::in,

io::di, io::uo) is det.

文字列のリストのそれぞれの文字列を連結してファイルに出力する。

:- pred io.write_char(io.output_stream::in, char::in, io::di, io::uo) is det.

文字をファイルに出力する。

:- pred io.write_int(io.output_stream::in, int::in, io::di, io::uo) is det.

整数をファイルに出力する。

:- pred io.write_float(io.output_stream::in, float::in, io::di, io::uo)

is det.

浮動小数点数をファイルに出力する。

:- pred io.format(io.output_stream::in, string::in, list(io.poly_type)::in,

io::di, io::uo) is det.

フォーマット文字列に従って値を出力する。io.poly_type は string.poly_type の別名です。

フォーマット文字列の詳細は、文字列の整形の章を参照してください。

:- pred io.write_list(io.output_stream, list(T), string,

pred(T, io, io), io, io).

:- mode io.write_list(in, in, in, pred(in, di, uo) is det, di, uo) is det.

:- mode io.write_list(in, in, in, pred(in, di, uo) is cc_multi, di, uo)

is cc_multi.

任意の値を指定したセパレータを挟んでファイルに出力する。

:- pred io.flush_output(io.output_stream::in, io::di, io::uo) is det.

バッファにたまった文字列を強制的にファイルに出力する。

Page 199: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

13.3 ファイル出力 199

13.3.3 ファイルを閉じる

:- pred io.close_output(io.output_stream::in, io::di, io::uo) is det.

ストリームを閉じます。

13.3.4 プログラム例

以下のプログラムでは、output.txtというファイルを作って、文字列、文字、整数、浮動小数

点数をファイルに 1行に 1つずつ出力しています。

1 :- module output_file.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7

8 main(!IO) :-

9 open_output("output.txt", Result, !IO),

10 (

11 Result = ok(Strm),

12 write_string(Strm, "abc", !IO),

13 nl(Strm, !IO),

14 write_char(Strm, ’x’, !IO),

15 nl(Strm, !IO),

16 write_int(Strm, 123, !IO),

17 nl(Strm, !IO),

18 write_float(Strm, 3.14, !IO),

19 nl(Strm, !IO),

20 close_output(Strm, !IO)

21 ;

22 Result = error(Err),

23 write_string(error_message(Err), !IO),

24 nl(!IO)

25 ).

Page 200: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

200 第 13章 標準ライブラリ概覧 (入出力)

実行結果は以下のようになります。

$ ./output_file

$ cat output.txt

abc

x

123

3.14

13.4 ファイル入力

ここでは、ioモジュールに定義されている述語や関数のうち、ファイル入力に関するものを説

明します。ファイル入力を行うには以下の型のストリームを利用します。

:- type io.input_stream.

最初に input_streamを開いて、次に、input_streamを受け取る述語で入力処理を行い、最後に

input_streamを閉じます。ここではファイル入力に関する述語や関数のうち利用頻度の高いもの

を列挙します。

13.4.1 ファイルを開く

:- pred io.open_input(string::in, io.res(io.input_stream)::out,

io::di, io::uo) is det.

指定したファイル名のファイルから入力ストリームを開きます。

:- func io.stdin_stream = io.input_stream.

標準入力を表すストリームです。

13.4.2 入力を読み取る

:- pred io.read(io.input_stream::in, io.read_result(T)::out,

io::di, io::uo) is det.

任意の値をMercuryの構文に従って読み取る。型は文脈によって決まる。

:- pred io.read_char(io.input_stream::in, io.result(char)::out,

io::di, io::uo) is det.

Page 201: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

13.4 ファイル入力 201

文字を読み取る。

:- pred io.read_word(io.input_stream::in, io.result(list(char))::out,

io::di, io::uo) is det.

単語を読み取る。

:- pred io.read_line(io.input_stream::in, io.result(list(char))::out,

io::di, io::uo) is det.

1行読み取る。

:- pred io.read_line_as_string(io.input_stream::in, io.result(string)::out,

io::di, io::uo) is det.

1行を文字列として読み込む。

:- pred io.read_file(io.input_stream::in,

io.maybe_partial_res(list(char))::out, io::di, io::uo) is det.

入力を eofまで全部読み込む。

:- pred io.read_file_as_string(io.input_stream::in,

io.maybe_partial_res(string)::out, io::di, io::uo) is det.

入力を eofまで文字列として全部読み込む。

:- pred io.input_stream_foldl(io.input_stream, pred(char, T, T),

T, io.maybe_partial_res(T), io, io).

:- mode io.input_stream_foldl(in, in(pred(in, in, out) is det),

in, out, di, uo) is det.

:- mode io.input_stream_foldl(in, in(pred(in, in, out) is cc_multi),

in, out, di, uo) is cc_multi.

入力を順番に読み取り述語で処理させる。

:- pred io.input_stream_foldl_io(io.input_stream, pred(char, io, io),

io.res, io, io).

:- mode io.input_stream_foldl_io(in, in(pred(in, di, uo) is det),

out, di, uo) is det.

:- mode io.input_stream_foldl_io(in, in(pred(in, di, uo) is cc_multi),

out, di, uo) is cc_multi.

Page 202: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

202 第 13章 標準ライブラリ概覧 (入出力)

入力を順番に読み取り述語で処理させる。ただし状態として io状態を受け渡す。

:- pred io.input_stream_foldl2_io(io.input_stream,

pred(char, T, T, io, io),

T, io.maybe_partial_res(T), io, io).

:- mode io.input_stream_foldl2_io(in,

in(pred(in, in, out, di, uo) is det),

in, out, di, uo) is det.

:- mode io.input_stream_foldl2_io(in,

in(pred(in, in, out, di, uo) is cc_multi),

in, out, di, uo) is cc_multi.

入力を順番に読み取り述語で処理させる。ただし状態としてユーザ状態と io状態を受け渡す。

:- pred io.input_stream_foldl2_io_maybe_stop(io.input_stream,

pred(char, bool, T, T, io, io),

T, io.maybe_partial_res(T), io, io).

:- mode io.input_stream_foldl2_io_maybe_stop(in,

(pred(in, out, in, out, di, uo) is det),

in, out, di, uo) is det.

:- mode io.input_stream_foldl2_io_maybe_stop(in,

(pred(in, out, in, out, di, uo) is cc_multi),

in, out, di, uo) is cc_multi.

入力を順番に読み取り述語で処理させる。状態としてはユーザ状態と io状態を受け渡す。述語は

読み込みを続けるか停止するかを bool型の値で返す。

:- pred io.putback_char(io.input_stream::in, char::in, io::di, io::uo) is det.

入力に文字を 1文字戻す。

13.4.3 ファイルを閉じる

:- pred io.close_input(io.input_stream::in, io::di, io::uo) is det.

入力ストリームを閉じます。

Page 203: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

13.4 ファイル入力 203

13.4.4 その他

:- pred io.get_line_number(io.input_stream::in, int::out, io::di, io::uo)

is det.

現在の行番号を取得します。

:- pred io.set_line_number(io.input_stream::in, int::in, io::di, io::uo)

is det.

現在の行番号を設定します。

13.4.5 プログラム例

以下のプログラムでは前節のプログラムで作った output.txtを読み込んで、表示しています。

1 :- module input_file.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module string, list, require.

8 :- import_module char.

9

10 :- pred det_read_string(input_stream::in, string::out, io::di, io::uo)

11 is det.

12 det_read_string(Strm, Str, !IO) :-

13 read_line_as_string(Strm, Result, !IO),

14 (

15 Result = ok(Str0),

16 Str = strip(Str0)

17 ;

18 Result = error(Err),

19 error(error_message(Err))

20 ;

21 Result = eof,

Page 204: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

204 第 13章 標準ライブラリ概覧 (入出力)

22 error("file format error\n")

23 ).

24

25 :- pred det_read_char(input_stream::in, char::out, io::di, io::uo) is det.

26 det_read_char(Strm, Char, !IO) :-

27 read_line_as_string(Strm, Result, !IO),

28 (

29 Result = ok(Str),

30 ( if [C|_] = to_char_list(strip(Str))

31 then Char = C

32 else error("file format error\n"))

33 ;

34 Result = error(Err),

35 error(error_message(Err))

36 ;

37 Result = eof,

38 error("file format error\n")

39 ).

40

41 :- pred det_read_int(input_stream::in, int::out, io::di, io::uo) is det.

42 det_read_int(Strm, Int, !IO) :-

43 read_line_as_string(Strm, Result, !IO),

44 (

45 Result = ok(Str),

46 ( if to_int(strip(Str), I)

47 then Int = I

48 else error("file format error\n"))

49 ;

50 Result = error(Err),

51 error(error_message(Err))

52 ;

53 Result = eof,

54 error("file format error\n")

55 ).

56

Page 205: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

13.4 ファイル入力 205

57 :- pred det_read_float(input_stream::in, float::out, io::di, io::uo) is det.

58 det_read_float(Strm, Float, !IO) :-

59 read_line_as_string(Strm, Result, !IO),

60 (

61 Result = ok(Str),

62 ( if to_float(strip(Str), F)

63 then Float = F

64 else error("file format error\n"))

65 ;

66 Result = error(Err),

67 error(error_message(Err))

68 ;

69 Result = eof,

70 error("file format error\n")

71 ).

72

73 main(!IO) :-

74 open_input("output.txt", Result, !IO),

75 (

76 Result = ok(Strm),

77 det_read_string(Strm, Str, !IO),

78 det_read_char(Strm, Char, !IO),

79 det_read_int(Strm, Int, !IO),

80 det_read_float(Strm, Float, !IO),

81 format("%s\n%c\n%d\n%g\n", [s(Str), c(Char), i(Int), f(Float)], !IO)

82 ;

83 Result = error(Err),

84 write_string(error_message(Err), !IO),

85 nl(!IO)

86 ).

実行結果は以下のようになります。

$ ./input_file

abc

x

Page 206: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

206 第 13章 標準ライブラリ概覧 (入出力)

123

3.14

13.5 ファイル操作

ここでは ioモジュールに定義されている述語や関数のうち、ファイル操作に関するものを列挙

します。

:- pred io.make_temp(string::out, io::di, io::uo) is det.

既存のファイルの名前とかぶらない一時ファイルを作って、その名前を返す。

:- pred io.remove_file(string::in, io.res::out, io::di, io::uo) is det.

ファイルを削除する。

:- pred io.rename_file(string::in, string::in, io.res::out, io::di, io::uo)

is det.

ファイルの名前を変える。

13.6 入出力に関するその他の述語・関数

ここでは ioモジュールに定義されている述語や関数のうち、これまで説明していないものを列

挙します。

:- func io.error_message(io.error) = string.

io.error型の値をそれを説明する文字列に変換する。

:- pred io.call_system(string::in, io.res(int)::out, io::di, io::uo) is det.

シェル上で指定したコマンドを実行する。

:- pred io.command_line_arguments(list(string)::out, io::di, io::uo) is det.

コマンドライン引数を取得する。

:- pred io.set_exit_status(int::in, io::di, io::uo) is det.

終了コードを設定する。

Page 207: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

13.6 入出力に関するその他の述語・関数 207

:- pred io.get_environment_var(string::in, maybe(string)::out,

io::di, io::uo) is det.

環境変数を取り出す。

:- pred io.set_environment_var(string::in, string::in, io::di, io::uo) is det.

環境変数を設定する。

Page 208: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。
Page 209: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

209

第 14章

標準ライブラリ概覧 (その他)

14.1 時間

time モジュールには日付と時刻に関する述語や関数が定義されています。ここでは time モ

ジュールに定義されている述語や関数を説明します。

:- type clock_t == int.

クロックを表す型です。

:- pred time.clock(clock_t::out, io::di, io::uo) is det.

システムが起動してからのクロック値を返す。

:- func time.clocks_per_sec = int.

1秒あたりのクロック数。

:- type time_t.

時間を表す抽象的な型です。

:- type tm

---> tm(

tm_year :: int, % 1900年からの経過年

tm_mon :: int, % 月 (0-11)

tm_mday :: int, % 日 (1-31)

tm_hour :: int, % 時 (0-23)

tm_min :: int, % 分 (0-59)

tm_sec :: int, % 秒 (0-61, 60と 61はうるう秒)

Page 210: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

210 第 14章 標準ライブラリ概覧 (その他)

tm_yday :: int, % 1年の何日目か (0-365)

tm_wday :: int, % 曜日 (0-6, 0が日曜日)

tm_dst :: maybe(dst) % 夏時間であるかどうか。

).

:- type dst

---> standard_time % 標準時間

; daylight_time. % 夏時間

時刻を表すデータ型。

:- pred time.time(time_t::out, io::di, io::uo) is det.

現在の時刻を返します。

:- func time.difftime(time_t, time_t) = float.

2つの時刻の差を秒数で返す。

:- func time.localtime(time_t) = tm.

現在の時刻 (ローカルタイム)を返す。

:- func time.localtime(time_t) = tm.

現在の時刻 (世界標準時)を返す。

:- func time.mktime(tm) = time_t.

指定した tmから time_t型の値を作る。

:- func time.asctime(tm) = string.

指定した tmを表す文字列を返す。

:- func time.ctime(time_t) = string.

指定した time_tを表す文字列を返す。

14.2 乱数

random モジュールには乱数に関する述語と関数が定義されています。ここでは random モ

ジュールに定義されている述語や関数のうち利用頻度の高いものを列挙します。

Page 211: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

14.2 乱数 211

:- type random.supply.

乱数生成器を表す型。

:- pred random.init(int::in, random.supply::uo) is det.

乱数生成器を初期化する。

:- pred random.random(int, int, int, random.supply, random.supply).

:- mode random.random(in, in, out, mdi, muo) is det.

:- mode random.random(in, in, out, in, out) is det.

random(Low, Range, Num, RS0, RS) で、Low から (Low + Range - 1) までの間の数をラン

ダムに返す。

:- pred random.permutation(list(T), list(T), random.supply, random.supply).

:- mode random.permutation(in, out, mdi, muo) is det.

:- mode random.permutation(in, out, in, out) is det.

リストをランダムに並べ替える。

14.2.1 プログラム例

以下のプログラムでは 1から 10までの整数を 5個ランダムに表示します。

1 :- module random_test.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module int, random, time.

8

9 :- pred loop(int::in, supply::in, supply::out, io::di, io::uo) is det.

10 loop(N, !RS, !IO) :-

11 if N = 0 then

12 true

13 else

14 random(1, 10, X, !RS),

15 write_int(X, !IO),

Page 212: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

212 第 14章 標準ライブラリ概覧 (その他)

16 nl(!IO),

17 loop(N - 1, !RS, !IO).

18

19 main(!IO) :-

20 clock(Seed, !IO),

21 random.init(Seed, RS),

22 loop(5, RS, _, !IO).

実行結果は以下のようになります。

$ ./random_test

6

8

5

4

7

14.3 ストア

storeモジュールを利用すると、破壊的代入を実現できます。

:- typeclass store(T) where [].

:- instance store(io.state).

:- instance store(store(S)).

ストアの状態として利用可能な型を表す型クラスです。

:- type store(S).

ストアの状態です。

:- some [S] pred store.init(store(S)::uo) is det.

新しいストアの状態を作ります。

:- type generic_mutvar(T, S).

:- type io_mutvar(T) == generic_mutvar(T, io.state).

:- type store_mutvar(T, S) == generic_mutvar(T, store(S)).

Page 213: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

14.3 ストア 213

ミュータブル変数を表す型です。

:- pred store.new_mutvar(T::in, generic_mutvar(T, S)::out, S::di, S::uo)

is det <= store(S).

新しくミュータブル変数を作る。

:- pred store.copy_mutvar(generic_mutvar(T, S)::in, generic_mutvar(T, S)::out,

S::di, S::uo) is det <= store(S).

ミュータブル変数をコピーする。

:- pred store.get_mutvar(generic_mutvar(T, S)::in, T::out,

S::di, S::uo) is det <= store(S).

ミュータブル変数から値を取り出す。

:- pred store.set_mutvar(generic_mutvar(T, S)::in, T::in,

S::di, S::uo) is det <= store(S).

ミュータブル変数に値を設定する。

14.3.1 プログラム例

以下のプログラムはミュータブル変数を作って値を代入し、値を取り出しています。

1 :- module store_test.

2 :- interface.

3 :- import_module io.

4 :- pred main(io::di, io::uo) is det.

5

6 :- implementation.

7 :- import_module store.

8

9 main(!IO) :-

10 new_mutvar(0, MV, !IO),

11 set_mutvar(MV, 10, !IO),

12 get_mutvar(MV, X, !IO),

13 write_int(X, !IO),

14 nl(!IO).

Page 214: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

214 第 14章 標準ライブラリ概覧 (その他)

実行結果は以下のようになります。

$ ./store_test

10

14.4 solutionsモジュール

:- pred solutions(pred(T), list(T)).

:- mode solutions(pred(out) is multi, out(non_empty_list)) is det.

:- mode solutions(pred(out) is nondet, out) is det.

:- func solutions(pred(T)) = list(T).

:- mode solutions(pred(out) is multi) = out(non_empty_list) is det.

:- mode solutions(pred(out) is nondet) = out is det.

複解を返す述語か非決定的な述語を受け取って、結果をリストで返します。結果のリストはソート

されており、重複する要素が削除されています。

:- func solutions_set(pred(T)) = set(T).

:- mode solutions_set(pred(out) is multi) = out is det.

:- mode solutions_set(pred(out) is nondet) = out is det.

:- pred solutions_set(pred(T), set(T)).

:- mode solutions_set(pred(out) is multi, out) is det.

:- mode solutions_set(pred(out) is nondet, out) is det.

複解を返す述語か非決定的な述語を受け取って、結果を集合で返します。

:- pred unsorted_solutions(pred(T), list(T)).

:- mode unsorted_solutions(pred(out) is multi, out(non_empty_list))

is cc_multi.

:- mode unsorted_solutions(pred(out) is nondet, out) is cc_multi.

複解を返す述語か非決定的な述語を受け取って、結果をリストで返します。結果のリストはソート

されておらず、重複も残っています。

:- pred do_while(pred(T), pred(T, bool, T2, T2), T2, T2).

:- mode do_while(pred(out) is multi, pred(in, out, in, out) is det, in, out)

is cc_multi.

:- mode do_while(pred(out) is multi, pred(in, out, di, uo) is det, di, uo)

Page 215: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

14.5 その他 215

is cc_multi.

:- mode do_while(pred(out) is multi, pred(in, out, di, uo) is cc_multi, di, uo)

is cc_multi.

:- mode do_while(pred(out) is nondet, pred(in, out, in, out) is det, in, out)

is cc_multi.

:- mode do_while(pred(out) is nondet, pred(in, out, di, uo) is det, di, uo)

is cc_multi.

:- mode do_while(pred(out) is nondet, pred(in, out, di, uo) is cc_multi, di, uo)

is cc_multi.

述語が yesを返すまでの間、述語の結果を順番に処理していきます。

14.5 その他

ここでは様々なモジュールに定義されている有用な述語や関数を説明します。

14.5.1 builtinモジュール

builtinモジュールはユーザが指定しなくても自動でインポートされます。

:- type comparison_result

---> (=)

; (<)

; (>).

比較結果を表す型です。

:- pred compare(comparison_result, T, T).

:- mode compare(uo, in, in) is det.

:- mode compare(uo, ui, ui) is det.

:- mode compare(uo, ui, in) is det.

:- mode compare(uo, in, ui) is det.

値の大小を比較して、comparison_resultを返します。

14.5.2 exceptionモジュール

:- func throw(T) = _ is erroneous.

例外を投げる (関数形式)。

Page 216: Mercury - 筑波大学logic.cs.tsukuba.ac.jp/~taka/mercury/mercury-intro.pdf · 3 はじめに Mercury はオーストラリアのメルボルン大学で開発されているプログラミング言語です。

216 第 14章 標準ライブラリ概覧 (その他)

:- pred throw(T::in) is erroneous.

例外を投げる (述語形式)。

14.5.3 requireモジュール

:- pred error(string::in) is erroneous.

requireモジュールの述語 errorはメッセージを表示してプログラムを終了させます。

:- func func_error(string) = _ is erroneous.

requireモジュールの関数 func_errorは述語 errorの関数版です。