つまり、引数が 1 つしかないアリティが 1 を超える関数を呼び出す場合、エラーを表示する代わりに、その引数をカリー化し、アリティを減らした結果の関数を返す必要があります。Lispのマクロを使ってこれを行うことは可能ですか?
6 に答える
可能ですが、有用な結果が必要な場合は簡単ではありません。
常に単純なカリー化を行う言語が必要な場合、実装は簡単です。複数の入力のすべてのアプリケーションをネストされたアプリケーションに変換するだけで、複数の引数の関数についても同じです。Racket の言語機能を使用すると、これは非常に簡単な演習になります。(他の Lisp では、使用したいコードの周りにマクロを配置することで、同様の効果を得ることができます。)
(ちなみに、私は Racket の上にこれを行う言語を持っています。これは自動カリー化された言語の魅力を十分に備えていますが、実用的なものではありません。)
ただし、引数が 1 つの関数でしか機能しないため、あまり便利ではありません。たとえば、あなたの言語の周りのLispシステムの残りを外国語として扱い、それを使用するためのフォームを提供するなど、いくつかのハッキングでそれを便利にすることができます。もう 1 つの方法は、周囲の Lisp の関数に関するアリティ情報を言語に提供することです。これらのいずれも、より多くの作業が必要です。
別のオプションは、すべてのアプリケーションをチェックすることです。言い換えれば、あなたは毎回回します
(f x y z)
のアリティをチェックするコードに変換し、
f
十分な引数がない場合はクロージャーを作成します。これ自体はそれほど難しいことではありませんが、かなりの諸経費が発生します。マクロレベルで使用する関数のアリティに関するいくつかの情報の同様のトリックを使用して、そのようなクロージャーを作成する必要がある場所を知ることができますが、それは本質的に同じ方法では困難です。
しかし、あなたがやりたいことの高レベルには、もっと深刻な問題があります。問題は、可変アリティ関数が自動カリー化でうまく機能しないことです。たとえば、次のような式を取ります。
(+ 1 2 3)
これをそのまま呼び出すか、または に変換するかをどのように決定し((+ 1 2) 3)
ますか? ここに簡単な答えがあるように思えますが、これはどうですか?(お気に入りの Lisp 方言に翻訳してください)
(define foo (lambda xs (lambda ys (list xs ys))))
この場合(foo 1 2 3)
、さまざまな方法で a を分割できます。さらに別の問題は、次のようなものをどうするかです。
(list +)
ここでは+
式として持っていますが、これは s アリティに適合するゼロ入力に適用するのと同じであると判断できますが+
、加算関数に評価される式をどのように書くのでしょうか? (補足: ML と Haskell は nullary 関数を持たないことでこれを「解決」します...)
これらの問題のいくつかは、「実際の」アプリケーションごとに括弧を持たなければならないことを決定することで解決できるため、+
単独では適用されません。しかし、それは自動カリー化された言語を持つことの魅力の多くを失い、解決すべき問題がまだ残っています...
curry
Scheme では、次の手順を使用して関数をカリー化できます。
(define (add x y)
(+ x y))
(add 1 2) ; non-curried procedure call
(curry add) ; curried procedure, expects two arguments
((curry add) 1) ; curried procedure, expects one argument
(((curry add) 1) 2) ; curried procedure call
ラケットのドキュメントから:
[curry] は、proc のカリー化バージョンであるプロシージャを返します。結果のプロシージャが最初に適用されるとき、受け入れることができる引数の最大数が指定されていない限り、結果は追加の引数を受け入れるプロシージャになります。
curry
次のように、新しいプロシージャを定義するときに自動的に使用するマクロを簡単に実装できます。
(define-syntax define-curried
(syntax-rules ()
((_ (f . a) body ...)
(define f (curry (lambda a (begin body ...)))))))
の次の定義add
がカリー化されます。
(define-curried (add a b)
(+ a b))
add
> #<procedure:curried>
(add 1)
> #<procedure:curried>
((add 1) 2)
> 3
(add 1 2)
> 3
簡単ではありませんが、短い答えはイエスです。
限られたコンテキストでのみですが、すべての呼び出しを でラップpartial
するマクロとしてこれを実装できます。Clojure には、可変アリティ関数やダイナミット呼び出しなど、これをかなり難しくする機能がいくつかあります。Clojure には、呼び出しがいつ引数を持てなくなり、実際に呼び出す必要があるかを具体的に決定するための正式な型システムがありません。
Alex W が指摘したように、Common Lisp Cookbookは、Common Lispの「カレー」関数の例を示しています。特定の例は、そのページのさらに下にあります。
(declaim (ftype (function (function &rest t) function) curry)
(inline curry)) ;; optional
(defun curry (function &rest args)
(lambda (&rest more-args)
(apply function (append args more-args))))
自動カリー化の実装はそれほど難しくないはずなので、試してみました。以下は広範囲にテストされておらず、引数が多すぎないことを確認していないことに注意してください (その数以上の引数がある場合、関数は単に完了します)。
(defun auto-curry (function num-args)
(lambda (&rest args)
(if (>= (length args) num-args)
(apply function args)
(auto-curry (apply (curry #'curry function) args)
(- num-args (length args))))))
ただし、動作するようです:
* (auto-curry #'+ 3)
#<CLOSURE (LAMBDA (&REST ARGS)) {1002F78EB9}>
* (funcall (auto-curry #'+ 3) 1)
#<CLOSURE (LAMBDA (&REST ARGS)) {1002F7A689}>
* (funcall (funcall (funcall (auto-curry #'+ 3) 1) 2) 5)
8
* (funcall (funcall (auto-curry #'+ 3) 3 4) 7)
14
上記のいくつかのマクロ構文シュガーのプリミティブ (完全なラムダ リストを適切に処理せず、単純なパラメーター リストのみを処理する) バージョン:
(defmacro defun-auto-curry (fn-name (&rest args) &body body)
(let ((currying-args (gensym)))
`(defun ,fn-name (&rest ,currying-args)
(apply (auto-curry (lambda (,@args) ,@body)
,(length args))
,currying-args))))
funcall の必要性はまだ面倒ですが、うまくいくようです:
* (defun-auto-curry auto-curry-+ (x y z)
(+ x y z))
AUTO-CURRY-+
* (funcall (auto-curry-+ 1) 2 3)
6
* (auto-curry-+ 1)
#<CLOSURE (LAMBDA (&REST ARGS)) {1002B0DE29}>
もちろん、言語の正確なセマンティクスを決定し、ソース ファイルを実装言語に変換する独自のローダーを実装するだけで済みます。
たとえば、すべてのユーザー関数呼び出し(f a b c ... z)
を に変換し、Scheme の上に(...(((f a) b) c)... z)
すべて(define (f a b c ... z) ...)
を変換(define f (lambda(a) (lambda(b) (lambda(c) (... (lambda(z) ...) ...)))))
して、自動カリー化スキームを持たせることができます (もちろん、varargs 関数を禁止します)。
また、独自のプリミティブを定義し、varargs 関数など(+)
をバイナリに変換し、そのアプリケーションを==>fold
などを使用するように変更する必要があります。自分でフォームを書く。(+ 1 2 3 4)
(fold (+) (list 1 2 3 4) 0)
(+ 1 2 3 4)
fold
それが、「言語のセマンティクスを決定する」という意味です。
ローダーは、ファイルの内容をマクロの呼び出しにラップするのと同じくらい簡単にすることができます-質問に従って、実装する必要があります。
Lisp にはすでに関数型カリー化があります。
* (defun adder (n)
(lambda (x) (+ x n)))
ADDER
http://cl-cookbook.sourceforge.net/functions.html
私が Lisp マクロについて読んでいるものは次のとおりです。
これを純粋な Lisp で実装することは可能です。マクロを使用して実装することもできますが、マクロを使用すると、非常に基本的なものをより混乱させるようです。