カレー機能と可変機能を同時に使用することはできますか?


13

動的に型付けされた関数型プログラミング言語でカリー化関数と可変関数の両方を使用可能にすることを考えていますが、可能かどうかは疑問です。

擬似コードは次のとおりです。

sum = if @args.empty then 0 else @args.head + sum @args.tail

これはすべての引数を合計することになっています。次に、それsum自体が数値として扱われる場合、結果はになり0ます。例えば、

sum + 1

+数値でのみ機能すると仮定して、1に等しくなります。ただし、sum == 0true であっても、sum引数がいくつ与えられても(したがって、「部分的に適用」と「可変」が同時に)、たとえば

g = sum 1 2 3

その後gに等しく6、しかし、我々はまだ、さらに適用することができますg。たとえば、g 4 5 == 15trueです。この場合、オブジェクトgをリテラル6で置き換えることはできません。整数として扱われた場合は同じ値を生成しますが、内部には異なるコードが含まれているためです。

この設計を実際のプログラミング言語で使用すると、混乱やあいまいさが生じますか?


1
厳密に言えば、カリー化を言語の基礎として使用するということは、すべての関数が単項であることを意味します。ただし、その言語のプログラムは、複数の引数をとったかのように見えます。これは、通常の関数と同じように可変個の関数にも当てはまります。
キリアンフォス

それから私の質問は、「オブジェクトを関数と非関数の値に同時にすることができますか?」に単純化されます。上記の例でsumは、は0引数なしであり、引数を使用して再帰的に自分自身を呼び出します。
マイケルツァン

それは仕事ではありませんreduceか?
ラチェットフリーク

1
あなたが上で使用している機能を見てみましょうargsemptyhead、とtail。これらはすべてリスト関数であり、おそらくより簡単で簡単なことは、可変要素が存在するリストを使用することであることを示唆しています。(したがって、sum [1, 2, 3]代わりにsum 1 2 3
マイケルショー

回答:


6

可変引数はどのように実装できますか?引数リストの終わりを知らせる何らかのメカニズムが必要です。これは

  • 特別なターミネーター値、または
  • 追加パラメーターとして渡される可変引数リストの長さ。

これらのメカニズムは両方とも、可変引数を実装するためにカリー化のコンテキストで使用できますが、適切なタイピングが大きな問題になります。関数を扱っていると仮定しましょうsum: ...int -> intこの関数がカリーを使用除いて(したがってsum: int -> ... -> int -> int、引数の数が分からないことを除いて、実際にはのような型があります)。

ケース:ターミネーター値:をend特別なターミネーターTとし、のタイプにしsumます。end関数への適用がreturn:sum: end -> intに適用され、intに適用されると、別のsum-like関数:が得られることがわかりましたsum: int -> T。したがってT、これらの型の結合は次のとおりT = (end -> int) | (int -> T)です。代入することによりT、我々は、以下のような様々な可能性のあるタイプを取得しend -> intint -> end -> intint -> int -> end -> intなどを、しかし、ほとんどのタイプのシステムは、このようなタイプに対応していません。

ケース:明示的な長さ:vararg関数の最初の引数は、可変引数の数です。ですからsum 0 : intsum 1 : int -> intsum 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 2s = ((sum 2) 1) 2 = 3

実際には、これらの手法はエラーが発生しやすく、一般的な型システムでは(意味のある)型を持たないため、使用しないでください。代わりに、値のリストを1つのパラメーターとして渡しますsum: [int] -> int

はい。オブジェクトが関数と値の両方として表示される可能性があります。たとえば、強制のある型システムなどです。ましょうsuma 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';
}

素晴らしい答え!可変引数をリストにまとめることの欠点は、カリー化の部分的な適用を失うことです。キーワード引数..., force=False)を使用して初期関数の適用を強制する、ターミネーターアプローチのPythonバージョンをいじっていました。
ThomasH

リストを取る関数を部分的に適用する独自の高次関数を作成できますcurryList : ([a] -> b) -> [a] -> [a] -> b, curryList f xs ys = f (xs ++ ys)
ジャック

2

あなたは見てみたいことは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の答えにはおそらく必要なものがすべて含まれています。まあ、静的言語でどのようにそれをするのか疑問に思っている人がこれに遭遇した場合に備えて、ここに残しておきます...


0

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では簡単でしょう。関数値は、シンボルが関数位置に表示されるときに(リストの最初の要素として)単に選択されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.