あなたが言及したように、それは命令型プログラミング言語のネスト/ダブルループになります。実際に欠けているのは、2 番目のループです。
コードの次の行からわかるように、「内側」ループは のすべての要素を通過しd、これが完了すると、「外側」ループは の一番上の要素をポップして最初からやり直そうとします。m
else if null d then number_in_months(d, tl m)
ただし、ご覧のとおり、リストdが空であることをテストしたばかりで、これ (まったく同じリスト) を の末尾の再帰呼び出しに指定すると、が空になるmまで、連続する呼び出しごとに同じケースになります。mあなたは0を返します。
したがって、あなたが見逃しているのは、元の入力リストの「コピーを保持する」ことmです。これはさまざまな方法で実行できますが、内部 (ヘルパー) 関数が適切に最も使用される関数であり、ネストされたループのように「見える」ことさえあります。
fun number_in_months (d, m) =
    let
      fun nim' ([], y::ys) = nim (d, ys)                 (* 1 *)
        | nim' (_, []) = 0                               (* 2 *)
        | nim' ((_, x2, _) :: xs, yss as (y::ys)) = ...  (* 3 *)
    in
      nim'(d, m)
    end
上記のコードに一致するパターンを使用すると、はるかに単純になり、エラーが発生しにくくなります。ケース 1 では、「内側」のループが のすべての要素を通過したため、いつでも変更されない外側の関数をd使用して再帰呼び出しが行われます。dケース 2 では、「外側の」ループが のすべての要素を通過し、m0 (加算の中立要素) を返します。ケース 3 では、実際の作業を行います。ここでは、引数の型を強制する必要がなく、トリプルの 2 番目の要素を引き出す必要がないように、パターン マッチングが使用されていますx2。必要なのは、計算を行い、 and を使用して再帰呼び出しを行うことだけxsですyss。
このようにする場合、内部 (ヘルパー) 関数は元の入力リストの「コピー」を使用し、dその要素をステップ実行します (変更する可能性があります) が、使用できる元の入力リストへの参照を常に取得します。必要な場合。