私は他の投稿を編集するつもりでしたが、これはそれ自体で十分な大きさです。
これは「タイプマジック」でそれを行う1つの方法ですが、特定の数の引数の関数に固有のリフティング関数が必要なため、やや最適ではないように感じます(以下で詳しく説明します)。
再帰データ型を定義することから始めましょう
data RecT a = RecR a
| RecC (a -> RecT a)
したがって、タイプRecTの変数は、ラップされた結果(RecR)にすることも、継続的な再帰(RecC)にすることもできます。
では、どのようにして何かを取得し、それをタイプRecT aにキャストしますか?
値は簡単です:
valRecT x = RecR x
RecRxは明らかにタイプRecTaです。
idのように1つの引数を取る関数はどうですか?
idRecT x = RecC $ \x -> RecR x
RecCは、変数を受け取り、タイプRecTaを返す関数をラップします。表現
\x -> RecR x
以前に観察したように、RecRxはタイプRecTaであるため、これはまさにそのような関数です。
より一般的には、任意の1つの引数関数を解除できます。
lift1RecT :: (a -> a) -> RecT a
lift1RecT fn = RecC $ \a -> RecR $ fn a
これを一般化するには、RecC内でより深くネストされた関数呼び出しを繰り返しラップします。
lift2RecT :: (a -> a -> a) -> RecT a
lift2RecT fn = RecC $ \b -> RecC $ \a -> RecR $ fn b a
lift3RecT :: (a -> a -> a -> a) -> RecT a
lift3RecT fn = RecC $ \c -> RecC $ \b -> RecC $ \a -> RecR $ fn c b a
さて、これですべての作業を行って、任意の数の引数の関数を単一の型RecTaに変換しました。これをどのように使用しますか?
関数適用の1つのレベルを簡単に書き留めることができます。
reduceRecT :: RecT a -> a -> RecT a
reduceRecT (RecC fn) = fn
reduceRecT _ = undefined
つまり、reduceRecTは、タイプRecT aとタイプaの引数を取り、1レベル削減された新しいRecTaを返します。
RecT内で終了した計算を結果に展開することもできます。
unrollRecT :: RecT a -> a
unrollRecT (RecR fn) = fn
unrollRecT _ = undefined
これで、引数のリストを関数に適用する準備が整いました。
lApply :: [a] -> RecT a -> a
lApply [] fn = unrollRecT fn
lApply (l:ls) fn = lApply ls $ (reduceRecT fn) l
最初に基本ケースを考えてみましょう。計算が終了したら、結果をアンラップして返します。再帰的な場合は、引数リストを1つ減らしてから、リストの先頭を減らしたfnに適用してfnを変換し、新しいRecTaを作成します。
これを試してみましょう:
lApply [2,5] $ lift2RecT (**)
> 32.0
では、このアプローチの長所と短所は?ええと、テンプレートHaskellバージョンは部分的なリストアプリケーションを行うことができます。これは、ここに示されている等再帰型のソリューションには当てはまりません(ただし、原則として、これをいくつかの醜い方法で修正できます)。タイプソリューションには、より多くのボイラープレートコードが関連付けられているという欠点もあります。使用するすべてのNに対してlistNRecTが必要です。最後に、混合変数型の関数にlApplyを適用する場合、これを類似のタプルソリューションに一般化するのははるかに簡単ではありません。
もちろん、もう1つの興味深い可能性は、TemplateHaskellを使用してlistNRecT関数を生成することで簡潔さを高めることです。これにより、定型文がなくなりますが、ある意味では、両方の実装の欠点があります。