動的型付けの関数型プログラミング言語でカリー関数と可変長関数の両方を使用できるようにすることを考えていますが、それが可能かどうかは疑問です。
ここにいくつかの擬似コードがあります:
sum = if @args.empty then 0 else @args.head + sum @args.tail
これはおそらくそのすべての引数を合計することです。次に、sum
自体が数値として扱われる場合、結果は0
になります。例えば、
sum + 1
+
が数値でのみ機能すると想定すると、1になります。ただし、sum == 0
がtrueの場合でも、sum
は、指定された引数の数に関係なく、その値と機能プロパティを維持します(したがって、「部分的に適用される」と「可変」が同時に)。 、私が宣言した場合
g = sum 1 2 3
その場合、g
は6
と等しくなりますが、g
をさらに適用することもできます。たとえば、g 4 5 == 15
はtrueです。この場合、オブジェクトg
をリテラル6
で置き換えることはできません。整数として扱うと同じ値が得られますが、内部に異なるコードが含まれているためです。
この設計を実際のプログラミング言語で使用すると、混乱や曖昧さが生じますか?
可変引数はどのように実装できますか?引数リストの終わりを知らせる何らかのメカニズムが必要です。これは
これらのメカニズムはどちらも、カリー化のコンテキストで可変引数を実装するために使用できますが、適切なタイピングが大きな問題になります。この関数がカリー化を使用していることを除いて、関数_sum: ...int -> int
_を扱っていると仮定しましょう(したがって、実際には、引数の数がわからないことを除いて、_sum: int -> ... -> int -> int
_のような型があります)。
ケース:ターミネーター値:end
を特別なターミネーターとし、T
をsum
のタイプとします。これで、end
に適用すると、関数は_sum: end -> int
_を返し、intに適用すると、別の合計のような関数__sum: int -> T
_が得られます。したがって、T
は次のタイプの結合です:T = (end -> int) | (int -> T)
。 T
に置き換えると、_end -> int
_、_int -> end -> int
_、_int -> int -> end -> int
_などのさまざまな型が得られます。ただし、ほとんどの型システムはそのような型に対応していません。
ケース:明示的な長さ:可変引数関数の最初の引数は可変引数の数です。したがって、_sum 0 : int
_、_sum 1 : int -> int
_、_sum 3 : int -> int -> int -> int
_などです。これは、一部の型システムでサポートされており、依存型の例です。実際には、引数の数は型パラメーターであり、通常のパラメーターではありません–関数のアリティがランタイム値に依存することは意味がありません。s = ((sum (floor (Rand 3))) 1) 2
は明らかに型が間違っています:これは評価されますs = ((sum 0) 1) 2 = (0 1) 2
、s = ((sum 1) 1) 2 = 1 2
、またはs = ((sum 2) 1) 2 = 3
のいずれかに。
実際には、エラーが発生しやすく、一般的な型システムに(意味のある)型がないため、これらの手法は使用しないでください。代わりに、値のリストを1つのパラメーターとして渡すだけです:_sum: [int] -> int
_。
はい、オブジェクトが関数と値の両方として表示される可能性があります。強制型システムで。 sum
をSumObj
とします。これには2つの強制があります。
coerce: SumObj -> int -> SumObj
_は、sum
を関数として使用できるようにします。coerce: SumObj -> int
_を使用すると、結果を抽出できます。技術的には、これは上記のターミネーター値のケースのバリエーションであり、_T = SumObj
_、およびcoerce
はタイプのアンラッパーです。多くのオブジェクト指向言語では、これは演算子のオーバーロードで簡単に実装できます。 C++:
_#include <iostream>
using namespace std;
class sum {
int value;
public:
explicit sum() : sum(0) {}
explicit sum(int x) : value(x) {}
sum operator()(int x) const { return sum(value + x); } // function call overload
operator int() const { return value; } // integer cast overload
};
int main() {
int zero = sum();
cout << "zero sum as int: " << zero << '\n';
int someSum = sum(1)(2)(4);
cout << "some sum as int: " << someSum << '\n';
}
_
Haskellでのprintfのこの実装 を、 この動作の説明 とともに確認することをお勧めします。後者のページには、このようなことを行うことに関するOleg Kiselyovの論文へのリンクがあり、これも一読の価値があります。実際、関数型言語を設計している場合は、 OlegのWebサイト を強制的に読む必要があります。
私の意見では、これらのアプローチは少しハックですが、それが可能であることを示しています。ただし、言語が完全に依存する型付けを備えている場合は、はるかに簡単です。その整数引数を合計する可変関数は次のようになります。
type SumType = (t : union{Int,Null}) -> {SumType, if t is Int|
Int, if t is Null}
sum :: SumType
sum (v : Int) = v + sum
sum (v : Null) = 0
明示的な名前を付ける必要なしに再帰型を定義するための抽象化により、そのような関数の記述が容易になる場合があります。
編集:もちろん、私はもう一度質問を読んだだけで、動的に型付けされた言語を言った、その時点で明らかに型力学は本当に関連していない、したがって、@ amonの回答にはおそらく必要なものがすべて含まれています。ええと、静的言語でどうしたらいいのか疑問に思っている人がいるので、ここに置いておきます...
以下は、Pythonのオプションの引数を利用して、@ amonの「ターミネーター」アプローチを使用するPython3の可変個関数のカリー化バージョンです。
def curry_vargs(g):
actual_args = []
def f(a, force=False):
nonlocal actual_args
actual_args.append(a)
if force:
res = g(*actual_args)
actual_args = []
return res
else:
return f
return f
def g(*args): return sum(args)
f = curry_vargs(g)
f(1)(2)(3)(4,True) # => 10
返される関数f
は、外部スコープにバインドされている配列内の連続した呼び出しで渡された引数を収集します。 force
引数がtrueの場合のみ、これまでに収集されたすべての引数を使用して元の関数が呼び出されます。
この実装の注意点は、常に最初の引数をf
に渡す必要があるため、すべての引数がバインドされ、空の引数リストでのみ呼び出すことができる「サンク」関数を作成できないことです(ただし、私はこれはカレーの典型的な実装と一致していると思います)。
もう1つの注意点は、間違った引数(たとえば、間違った型)を渡すと、元の関数を再度カリー化する必要があることです。内部配列をリセットする他の方法はありません。これは、カリー化された関数が正常に実行された後にのみ行われます。
「オブジェクトを関数と非関数の値にすることはできますか?」という簡単な質問は、かっこなしの関数への参照が内部の関数オブジェクトとして評価されるため、Pythonで実装できますか? 。これを曲げて任意の値を返すことができるかどうかはわかりません。
LISPシンボルは値と関数値を同時に持つことができるため、LISPではおそらく簡単です。シンボルが関数の位置に(リストの最初の要素として)出現すると、関数の値が選択されます。