@pad が彼のコードで示しているように、適用すべきロジックは、関数の現在のスコープの問題を解決する前にサブ問題を再帰的に解決する再帰呼び出しを行うことです。
の場合largest
、コードを「手動で」実行すると明らかになるスタック領域を単に使用するため、逆方向に解決しても意味がありません。ただし、設計パターンは他の状況でも役立ちます。という関数を想像してくださいzip
:
(* zip ([1,2,3,4], [a,b,c]) = [(1,a),(2,b),(3,c)] *)
fun zip (x::xs, y::ys) = (x,y) :: zip (xs, ys)
| zip _ = []
この関数は、2 つのリストのタプルを多数のタプルのリストに変換し、残りの値を破棄します。状況によっては役立つかもしれませんし、それを定義することもそれほど悪くはありません。ただし、対応する を作成するのunzip
は少しトリッキーです。
(* unzip ([(1,a),(2,b),(3,c)] = ([1,2,3],[a,b,c]) *)
fun unzip [] = ([], [])
| unzip ((x,y)::xys) =
let
val (xs, ys) = unzip xys
in
(x::xs, y::ys)
end
通常の "最初から"-largest を手動で実行すると、次のようになります。
largest [1,4,2,3,6,5,4,6,7]
~> largest [4,2,3,6,5,4,6,7]
~> largest [4,3,6,5,4,6,7]
~> largest [4,6,5,4,6,7]
~> largest [6,5,4,6,7]
~> largest [6,4,6,7]
~> largest [6,6,7]
~> largest [6,7]
~> largest [7]
~> 7
すべてのインデント レベルで関数呼び出しの現在のコンテキストをスタック メモリに保存し、新しい関数を呼び出して矢印の後に結果を返す必要があると想像して、「最後から」最大のものを手動で実行すると、次の~>
ようになります。
largest [1,4,2,3,6,5,4,6,7] ~> 7
\_
largest [4,2,3,6,5,4,6,7] ~> 7
\_
largest [2,3,6,5,4,6,7] ~> 7
\_
largest [3,6,5,4,6,7] ~> 7
\_
largest [6,5,4,6,7] ~> 7
\_
largest [5,4,6,7] ~> 7
\_
largest [4,6,7] ~> 7
\_
largest [6,7] ~> 7
\_
largest [7] ~> 7
では、単純により多くのメモリを使用する場合、初期の再帰呼び出しを有用にするこれらの非末尾再帰関数がなぜ役立つのでしょうか? に戻ってunzip
、この厄介な「逆の考え方」をせずに解決したい場合、x と y を配置する場所がないため、タプルである新しい結果を構築する際に問題が発生します。
(* Attempt 1 to solve unzip without abusing the call stack *)
fun unzip [] = ([], [])
| unzip ((x,y)::xys) = ...something with x, y and unzip xys...
そのような場所を作成するための 1 つのアイデアは、ビルドxs
とys
. xys リストの最後に到達すると、それらの値が返されます。
(* Attempt 2 to solve unzip without abusing the call stack *)
local
fun unzipAux ([], xs, ys) = (xs, ys)
| unzipAux ((x,y)::xys, xs, ys) = unzipAux (xys, x::xs, y::ys)
in
fun unzip xys = unzipAux (xys, [], [])
end
(xs, ys)
しかし、結果が逆になることに気付いたかもしれません。これを修正する簡単な方法はrev
、一度だけ使用することです。これは、基本ケースで最もよく達成されます。
(* Attempt 3 to solve unzip without abusing the call stack *)
local
fun unzipAux ([], xs, ys) = (rev xs, rev ys)
| unzipAux ((x,y)::xys, xs, ys) = unzipAux (xys, x::xs, y::ys)
in
fun unzip xys = unzipAux (xys, [], [])
end
これは興味深い質問をもたらします:どのようにrev
実装されていますか?