関数型プログラミング::再帰関数

そこで、関数型言語のコンパイラ/インタプリタを書くことにしました。 最初に、彼は各ニューロンチームが個別のオブジェクトであり、パラメータが付加されたコンピューティングツリーの形で教会を作りました。 get()関数が呼び出されると、必要な入力にアクセスしました。 なんらかのstrchrを作成したこともありましたが、うまくいきました。

しかし、このバイクを見ると、少なくとも2つの欠点があることに気付きました。すべての機能が組み込まれているため、Haskell / Erlangのサーバーのようにエンドレスコンピューティングは不可能です。

server state = server renew(state, iosystem)
server EXIT = EXIT

そのため、ベンチを折りたたみ、スタックされた仮想マシンを作成する必要がありました。 さらに、それは単なる車ではなく、Fort車です。2番目の枠を修正できます-Fortは呼び出しスタックにアクセスできます:)。サーバー関数を再帰的に再呼び出しする代わりに、パラメーターを変更するだけです。

ただし、そのような最適化を行うことができなかった次の形式の関数がまだありました。

g(xs) = f(xs) . g(k(xs))
g(EOSEQ) = INIT


ここで: f(xs)は部分的に指定された関数で、「次の状態」型を「結果」型に変換する関数を返します。短くて理解できない場合は、

f :: ('state -> ('param -> 'result))

k(xs)は、計算されたデータの次の状態を返す関数です。たとえば、 (head | tail -> tail)または(x -> 1 + x)

EOSEQ-シーケンス終了文字

INIT-結果の最初の近似

「。」 -合成演算子; 私の場合- (.) :: (('a -> 'b) -> ('b -> 'c) -> ('a -> 'c))
(いまいましい、私は言葉でそれを書きたくない...ヘニー)

そのような関数の例を考えてみましょう。

fac N -> N * fac (N-1)
fac 0 -> 1


ここでxs = N, f(N) = (N *), k(x) = x - 1, EOSEQ = 0, INIT = 1

実行の順序(一般的に最初):

g(xs) = f(xs) . f(k(xs)) . f(k^2(xs)) . ... . f(k^N(xs)) . INIT

例:

fac N = N * (N-1) * ((N-1) - 1) * ... * (N - (N - 2)) * 1

このような関数は、コンパイラーが繰り返すことができるように変換および作成されている必要があります。 したがって、関数gをgとg1のペアで置き換える方法を開発しました。

g(xs) = f(xs) . g(k(xs))
g(EOSEQ) = INIT


=>

g(xs) = g1(xs, INIT)

g1(xs, ACC) = g1(k(xs), f(xs) (ACC))
g1(EOSEQ, ACC) = ACC


ここで、ACCは結果を保存するバッテリーです。

そのような関数の計算手順:

g1 = INIT . f(xs) . f(k(xs)) . ... . f(k^N(xs))

最初に目を引くのは、INITが式の先頭に移動したライラック象です。 f(xs)は単純ですが、それに乗ることができます...しかし、あなたはそれに頼るべきではありません。

例で変換を検討してください。

summ(head|tail) = head + summ(tail)
summ([]) = 0


=>

summ(xs) = summ1(xs, 0)

summ1(head|tail, accum) = summ1(tail, head + accum)
summ1([], accum) = accum


それはうまくいくようです-リストを要約し、最後に到達して完了します。

この例はより薄くなりました。

isSort (x|y|tail) = if x >= y then isSort(y|tail) else false
isSort (x|[]) = true


ここで: f(x|y|tail) = (value -> if x >= y then value else false)

=>

isSort(list) = isSort1(list, true)

isSort1(x|y|tail, flag) = isSort1(y|tail, if x >= y then flag else false)
isSort1(x|[], flag) = flag
-- :
-- isSort1(_, false) = false


問題は、最初の要素に到達すると、元の関数が停止し、結果の関数が踏みにじるということです。彼女は、スタックの最上部の「フラグ」の値を気にしませんでした。

追加のパラメーター、つまり計算可能アキュムレーターを導入するというアイデアがありました。 しかし、その後、最初にもう1つの例を確認します。

attach(pstart、pfin、head | tail)= [pstart | 頭| pfin] | 接続(pstart、pfin、tail)
attach(pstart、pfin、[])= []

attach( "Dorough、"、 "!"、["World"] | ["Wall"] | ["green devil"])= ["Dow、Peace!"] | [「ドロウ、壁!」] | [「ダロー、緑の悪魔!」]。

=>

attach(pstart、pfin、head | tail)= attach1(pstart、pfin、head | tail、[])
attach1(pstart、pfin、head | tail、[])= attach1(pstart、pfin、tail、[pstart | head | pfin] | ACC)
attach(pstart、pfin、[]、ACC)= ACC

attach( "Hello、"、 "!"、["World"] | ["Wall"] | ["green devil"])= ["Dowr、green devil!"] ["Dorow、Wall!"] ["ドーロー、平和!」]。

ご覧のとおり、これは元の関数が返した魔法の値ではありません。

これが私のマナの終わりなので、要約します。

オプション1:正式なメタ処理変換を複雑にします。

オプション2:バッテリーの再帰関数をすぐに記述します。

正直に言うと、私は2番目の選択肢です。脳を発達させるからです-(アクションの明確な説明がない場合)アルゴリズムの実装方法を考える必要があります。

コメントや提案を聞いてうれしいですが、今のところは青いボトルから飲みます。

Source: https://habr.com/ru/post/J51412/


All Articles