表題

カリー化を伴わないパラメータ特殊化の記法

著者

Sebastian Egner

状態

この SRFI は現在「確定」の状態である。 SRFI の各状態の説明については ここ を参照せよ。 この SRFI に関する議論については メーリングリストのアーカイブ を参照せよ。

概要

関数型プログラミングでは、 複数のパラメータをとる手続きのいくつかのパラメータを特殊化する必要がしばしばある。 たとえば、二項演算である cons を例に挙げると、 この手続きから一項演算である (lambda (x) (cons 1 x)) を生成したいことがあるかもしれない。 このようなパラメータの特殊化は、 「部分適用」(partial application)、「演算子切断」(operator section)、「射影」(projection) などと呼ばれることもある。

この SRFI で提案する仕組みを使えば、 このような特殊化を簡単に記述することができるようになる。 この仕組みを理解するために、いくつかの例を挙げよう。

(cut cons (+ a 1) <>) は右の式と同じ (lambda (x2) (cons (+ a 1) x2))
(cut list 1 <> 3 <> 5) は右の式と同じ (lambda (x2 x4) (list 1 x2 3 x4 5))
(cut list) は右の式と同じ (lambda () (list))
(cut list 1 <> 3 <...>) は右の式と同じ (lambda (x2 . xs) (apply list 1 x2 3 xs))
(cut <> a b) は右の式と同じ (lambda (f) (f a b))

上記のように、 cut マクロは第一引数に指定された手続きのパラメータを特殊化する。 結果として生成される手続きの仮引数となるのは、 <> というシンボル (「スロット」と読む) で表される。 また、<...> というシンボル (「残余スロット」と読む) は、手続きの残余引数を渡すことを表す。 最後の例を見ると分かるように、 第一引数にスロットを指定することもできる。

cut の他に、その変種である cute というマクロもある (この名前は "cut with evaluated non-slots" に因む)。 このマクロは、手続きが呼び出される時点ではなく、手続きが特殊化される時点で、スロット以外の式を評価する。 例を挙げよう。

(cute cons (+ a 1) <>) は右の式と同じ (let ((a1 (+ a 1))) (lambda (x2) (cons a1 x2)))

この例と前述の最初の例とを比べると分かるように、 cute では (+ a 1) が1度だけ評価されるのに対し、 cut では生成される手続きが呼び出されるたびに評価される。

この SRFI で提案する仕組みは、手続きの引数の任意のサブセットを特殊化することができる。 結果として生成される手続きは、固定個引数にも可変個引数にもできる。 しかしこの仕組みは、引数の順序を入れ替えたり、省略したり、重複させたり、 あるいは、引数を処理したりはできない。 そのような必要があれば、lambda のような別の仕組みを使わなければならない。

論拠

特殊化を扱うための特別にエレガントな方法は、 カリー化 (currying) として知られている (Schönfinkel 1924, Curry 1958)。 カリー化の考え方は、 n 項関数を、 1つめの引数を (n-1) 項関数に変換する高階関数であると見なすことで (そして (n-1) 項関数をさらにカリー化することで)、 多項関数を1項関数に落とすというものである。 この観点は、理論的なエレガンスさに加えて、 関数の第一引数を特殊化するための非常にコンパクトな記法を与える。 最初の例で言えば、(cons 1) と書くことができる。

しかし、Scheme はカリー化が可能な言語ではない。 手続きに渡す引数の個数は、仮引数の個数と常に一致しなければならない。 これにより、引数なしの手続きや、可変個引数の手続きを使うことはできるが、 引数を特殊化するためには、lambda-式を書き、 仮引数を指定するための不適切な識別子を発明しなければならない (上述の例の x がそれである)。 このような理由から、 この SRFI では、 手続きの引数の任意のサブセットを特殊化するための シンプルでコンパクトな記法を提案する。

ノート: ここで提案している仕組み自体はカリー化ではない!

ここで提案する仕組みの目的は、 Scheme プログラミング言語において、 カリー化の恩恵を享受できるようにすることである。 実際面において、カリー化には主に2つの利点がある。 高階型を単純化することができるということと、 引数を特殊化するための記法が簡単になるということである。 Scheme は動的型付け言語なので、型に対する利点はないに等しい。 この SRFI では、特殊化に関して言及する。

説明のために、もう少し例を挙げよう。
(map (cut * 2 <>) '(1 2 3 4))
(map (cut vector-set! x <> 0) indices)
(for-each (cut write <> port) exprs)
(map (cut <> x y z) (list min max))
(for-each (cut <>) thunks)

仕様

特殊化を行う式の形式的構文を以下に示す。これは Revised^5 Report on the Algorithmic Language Scheme のスタイルに従っている。

<cut-expression> --> (cut <slot-or-expr> <slot-or-expr>*)
|(cut <slot-or-expr> <slot-or-expr>* <...>) ; 残余スロットを伴う場合
|(cute <slot-or-expr> <slot-or-expr>*) ; 特殊化時に非スロットを評価する
|(cute <slot-or-expr> <slot-or-expr>* <...>) ; 残余スロットを伴う場合
<slot-or-expr> --> <>; スロット
| <expression>; 非スロット式

cut マクロは <cut-expression><lambda expression> に変換する。 この lambda-式は <slot-or-expr>* リストの中のスロットの個数だけ仮引数をとる。 結果として生成される <lambda expression> の本体は、最初の <slot-or-expr> を呼び出し、 <slot-or-expr>* を引数として渡す。 引数の順序はマクロに指定された順序そのままである。 残余スロットが存在する場合、 生成される手続きも可変個引数をとる。 その本体は最初の <slot-or-expr> を呼び出し、特殊化された手続きに引数を渡す。

cute マクロは cut マクロと同様だが、 最初に非スロット式を評価して (評価の順序は不定) その値を新しい変数に束縛し、 非スロット式をその変数に置換する。 要するに、 cut は、生成された手続きを呼び出した時点で 非スロット式を評価するが、 cute は、手続きを生成する時点で 非スロット式を評価する。

実装

参照実装では R5RS のマクロを使用して cutcute の2つのマクロを定義する。 他の SRFI やライブラリは一切使用していない。 この実装では <slot-or-expr;> リストを列挙するために、内部的なマクロを使用している。 R5RS のマクロはハイジニックで参照透過であるため、 このマクロで新しく導入される仮引数の名前は一意であり、 名前の衝突が起きることはない。 テンプレート (param ... slot) ( R5RS 7.1.5 節 を参照) は、引数の順序を保つ。 この参照実装は Al Petrofsky により実装された。 ここから入手できる。

最後に、小さな信頼性テストを用意した。 この SRFI で定義された仕組みに対して、 いくつかの特殊ケースを検証し、 何か間違いがあればエラーを出すようにしている。 ただし、このテストに合格するからと言って、 正しい実装であるとは限らない。

設計上の論拠

なぜ、本当のカリー化/非カリー化をしないのですか?

Scheme では、複数引数の手続きを1引数の手続きの入れ子に変換したり、 その逆の変換を行うマクロを実装することは可能だ。 このような変換は、別のプログラミング言語では「カリー化」(curry) および「非カリー化」(uncurry) と呼ばれる。 しかし、Scheme は本来的に非カリー化された言語であり、 カリー化された手続きを簡単に扱う方法がない。 そのため、カリー化の「定石通りの」実装は、 「カリー化し、いくつかの引数を特殊化し、非カリー化する」 という順序で適用した場合にのみ、有益なものとなるだろう。 これがまさに、この SRFI で定義する cut マクロの目的である。 Scheme においてカリー化/非カリー化を持ち出すことが妥当なのは、 主にコンビネータ論理 (combinatory logic) の概念を教える場合である。

なぜ、引数の入れ替え、省略、重複を許すような汎用的な仕組みにしないのですか?

その理由は、この SRFI の著者は、 ここで提案した仕組みと、より汎用的な仕組みとを統合してしまうのは、 危険過ぎると判断したからである。 特に、引数の入れ替えが可能であれば、 引数の意味を覚えておく必要があるが、 名前のついていない変数はとても有害だからである。 この SRFI で提案している仕組みは、この危険性を回避するように設計されている。 詳細は、議論スレッド "OK, how about...," (Alan Bawden), "is that useful?" (Walter C. Pelissero), "l, the ultimate curry that is not curry" (Al Petrofsky) を参照してください。

なぜ、このマクロの名前は cut/cute なのですか?

うーん、この SRFI のために最初に提案された名前は curry だったが、よく知られているカリー化とは別物だという事実に、 すぐに具合いの悪さを感じ始めた。 他の案も提案された。たとえば、 section, specialise, specialize, partial-apply, partial-call, partial-lambda, _j, _i, $, &, srfi-26, foobar, xyz, schoenfinkelize, curry-which-isnt-curry, tandoori などである。また、ランダムに5文字を選んでそれを名前にするという提案もなされた。 とは言うものの、これらの名前がすべてまじめに提案されたわけではない。 これらのうちのいくつかは、単に論点を説明するためのものだ。 さらに、私はこれらの名前と戯れながらかなりの時間を過ごし、 ここに載せてはいない他の候補についても考えあぐねた。 議論メーリングリストに出てくる意見は、 世の人々の意見に対するかなり偏った無作為標本ではあるが、 違った名前を提案してくれることは この SRFI にとって有益なことであると思った。 しかし、満場一致の同意を得るのは現実的ではないだろう。 cut という名前は「演算子切断」(operator section) を参考にしている。他のプログラミング言語では、この概念がよく使われるからである。 しかし、私はどちらかと言えば "Curry Upon This" の頭文字語として覚えている ;-)。 cut の評価バージョンの名前としては cut!, cutlet, cut*, cute が提案された。

この SRFI をマクロを使わずに実装することは可能ですか?

実装できるとは言えない。 Stephan Houben が議論の中で指摘したように ("Implementing it as a procedure" を参照)、 cute の仕組みを手続きとして実装することは可能だ。 詳細については Al Petrofsky の投稿 "Problems with "curry"'s formal specification" も参照せよ。 しかし、手続きによる実装では、少し性能が落ちることになる。 それから、cut の仕組みを手続きとして実装することは不可能である。 両方の仕組みが必要であるため、この SRFI ではマクロによって実装している。

lambda-式は可変個引数リストにドット記法を使うのに、なぜ残余スロットに別のシンボルを使うのですか?

理由は2つある。 1つめの理由は手続きによる実装を考慮しているからである (前述の質問を参照せよ)。 手続きによる実装ではドット記法を使うことはできない。 2つめの理由は、 Felix Winkelmann が指摘しているように、 R5RS のハイジニック マクロがドット記法を処理する方法が原因である。 議論スレッド "Improper lists in macros [WAS: none]" を参照せよ。

非スロット式を評価するタイミングを非スロット式ごとに指定することはできないのですか?

cut は、特殊化された手続きが呼び出されるときに、すべての非スロット式を評価する。 cute は、手続きが特殊化されるときに、すべての非スロット式を評価する。 これらは両極端であり、非スロット式ごとに評価のタイミングを選択できる構文を定義することは可能だ。 しかし、そのような柔軟性を与えても、混乱が起きるだけで益がないと思う。 もし、そのような細かい指定が必要になるなら、 letlambda を使って明示的に手続きを構築するほうがよいだろう。

なぜ (cut if <> 0 1) のような式は正しくないのですか?

<slot-or-expr> はスロット シンボルであるか、 あるいは、 R5RS, Section 7.1.3 の意味の <expression> でなければならないという仕様である。 if<expression> ではないので、 上記の式は間違っている。 cutcute がこのように制限されている理由は、 そのような一般化された式の意味を定義することが困難だからである。 詳細については議論のアーカイブを参照してください。

謝辞

この SRFI の重要な部分は、他の人々による寄与に基づいており、 議論アーカイブに依るところが大きい。 特に、意味論と設計上の論拠は、この議論を通じて大きく改善された。 貢献してくれたすべての人々に謝辞を送りたい。

著作権

Copyright (C) Sebastian Egner (2002). All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


編集者: Mike Sperber
著者: Sebastian Egner
最終更新日時: Sun Jan 28 13:40:30 MET 2007