変数の有効範囲を表す用語に「スコープ (scope)」があります。この用語を使うと、厳密ではありませんが、変数の有効範囲は「レキシカルスコープ (lexical scope)」と「ダイナミックスコープ (dynamic scope)」の 2 つに分けることができます。伝統的な Lisp はダイナミックスコープですが、現在のプログラミング言語、たとえば、Scheme, Common Lisp, OCaml, F# はレキシカルスコープを採用しています。
それでは、レキシカルスコープについて詳しく見てみましょう。変数 x を表示する関数 foo を定義します。
> let x = "global";; val x: string = "global" > let foo () = printfn "%s" x;; val foo: unit -> unit > foo();; global val it: unit = ()
関数 foo には局所変数 x を定義していないので、foo を実行した場合は大域変数の値を参照します。その結果 global が表示されます。それでは、foo1 という関数から foo を呼び出す場合を考えてみましょう。foo1 には let で局所変数 x を定義します。この場合、foo はどちらの値を表示するのでしょうか。実際に試してみましょう。
> let foo1 () = let x = "local\n" in foo ();; val foo1: unit -> unit > foo1();; global val it: unit = ()
foo1 を実行すると大域変数の値 global を表示しました。このように、foo1 で定義した局所変数 x は、foo から参照することはできません。次の図を見てください。
上図では変数の有効範囲を枠で表しています。foo1 の let で定義した局所変数 x は、let の枠の中でのみ有効です。もしも、この枠で変数が見つからない場合は、ひとつ外側の枠を調べます。順番に外側の枠を調べていくと、最後には関数定義の枠に行き着きます。ここで変数(引数)が見つからない場合は大域変数を調べます。
関数 foo は関数定義の枠しかありません。そこに変数 x が定義されていないので、大域変数を調べることになるのです。このように、関数 foo から foo1 の枠と let の枠を超えて変数 x にアクセスすることはできないのです。これを「レキシカルスコープ」といいます。レキシカルには文脈上いう意味があり、変数が定義されている構造の範囲内 (枠内) でないと、その変数にアクセスすることはできません。
ところが伝統的な Lisp の場合、foo1 で定義した変数 x は呼び出された関数 foo からアクセスすることができます。これを「ダイナミックスコープ」といいます。foo1 で定義された変数 x は、foo1 の実行が終了するまで存在します。そして、foo1 から呼ばれた関数ならば、どこからでも参照することができるのです。もしも、foo1 をダイナミックスコープの処理系、たとえば Emacs Lisp で実行するならば、foo で表示される x の値は local になります。
それでは、関数の中で定義された局所関数やラムダ式の場合はどうなるのでしょうか。次の例を見てください。
リスト 1 : リストの要素を n 倍する let times_element n xs = List.map (fun x -> x * n) xs
ラムダ式の引数は x だけなので、変数 n は大域変数を参照するように思われるかもしれません。ところが、変数 n は関数 times_element の引数 n を参照するのです。これを図に示すと、次のようになります。
ポイントは、ラムダ式が times_element 内で定義されているところです。変数 n は関数の引数として定義されていて、その有効範囲は関数の終わりまでです。ラムダ式はその範囲内に定義されているため、変数 n にアクセスすることができるのです。つまり、関数内で定義されたラムダ式は、そのとき有効な局所変数にアクセスすることができるのです。
これは let で定義された局所的な関数も同じです。times_element は次のように書き換えることができます。
リスト 2 : リストの要素を n 倍する let times_element n xs = let timesN x = x * n in List.map timesN xs
局所関数 timesN は times_element 内で定義されているので、timesN から times_element の引数 n を参照することができます。
Lisp などの関数型言語では、関数を生成する関数を簡単に作ることができます。このとき使われる機能が「クロージャ (closure)」です。クロージャは評価する関数と参照可能な局所変数をまとめたものです。クロージャは関数のように実行することができますが、クロージャを生成するときに参照可能な局所変数を保持するところが異なります。参照可能な局所変数の集合を「環境」と呼ぶことがあります。
F# の関数はカリー化できるので、関数を返す関数はとても簡単に作成することができます。また、F# は関数型言語なので、当然ですがクロージャも使うことができます。F# でクロージャを生成するには「ラムダ式」を使うか、局所的な関数を定義して、その関数を返します。たとえば、「引数を n 倍する関数」を生成する関数は、ラムダ式を使うと次のようになります。
> let foo n = fun x -> n * x;; val foo: n: int -> x: int -> int > let foo10 = foo 10;; val foo10: (int -> int) > foo10 100;; val it: int = 1000 > let foo5 = foo 5;; val foo5: (int -> int) > foo5 11;; val it: int = 55
関数 foo は引数を n 倍する関数を生成します。関数 foo の型 int -> int -> int は int -> (int -> int) を意味しています。これは引数 int を受け取り int -> int という関数を返すことを表しています。変数 foo10 に foo 10 の返り値をセットします。すると、foo10 は引数を 10 倍する関数として使うことができます。同様に、変数 foo5 に foo 5 の返り値をセットすると、foo5 は引数を 5 倍する関数になります。
ラムダ式を生成するとき、評価する関数のほかに、そのとき参照可能な局所変数、つまり「環境」もいっしょに保存されます。この場合、参照可能な局所変数は foo の引数 n です。そして、クロージャを実行するときは、保存されている局所変数を参照することができるのです。
foo 10 を実行して無名関数を生成するとき、定義されている局所変数は n で、その値は 10 ですね。この値がクロージャに保存されているので、foo10 の関数は引数を 10 倍した結果を返します。foo 5 を評価すると n の値は 5 で、それがクロージャに保存されているので、foo5 の関数は引数を 5 倍した結果を返すのです。
また、let で局所的な関数を定義して、その関数を返すとクロージャを生成することができます。let を使った例を示します。
> let foo n = let bar x = n * x in bar;; val foo: n: int -> (int -> int) > let foo100 = foo 100;; val foo100: (int -> int) > foo100 11;; val it: int = 1100
let で関数 bar を定義して、bar を返します。すると、foo は「引数を n 倍する関数」を生成する関数になります。
もっとも、F# では関数をカリー化するだけで同様の関数を作ることができます。
> let foo n x = n * x;; val foo: n: int -> x: int -> int > let foo10 = foo 10;; val foo10: (int -> int) > foo10 5;; val it: int = 50 > let foo100 = foo 100;; val foo100: (int -> int) > foo100 5;; val it: int = 500
このように、F# は関数をカリー化できるので、部分適用により目的の関数を簡単に生成することができます。
クロージャを理解する場合、環境を「連想リスト (association list : a-list)」で考えるとわかりやすいと思います。ここで簡単に連想リストについて説明します。
連想リストは Lisp でよく用いられるデータ構造で、F# ではキーとデータのタプルを要素とするリストで実現することができます。型で記述すると ('a * 'b) list になり、'a がキーで 'b がデータに対応します。
上図の場合、文字列 "a", "b", "c", "d" がキーで、整数 1, 2, 3, 4 がデータとなります。
一般に、関数を呼び出す場合、関数を評価するための環境は空リストです。最初に、引数がこの環境に追加されます。let で定義される局所変数もこの環境に追加されます。もしも、環境に該当する変数が存在しない場合は大域変数を参照します。
たとえば、foo 5 と呼び出すと環境は次のようになります。
foo 5 ==> 環境 : [(n, 5)]
連想リストのキー n が変数名で、その値が 5 です。クロージャを生成するとき、この連想リストを保持すると考えてください。そして、クロージャを評価するときは、保存していた環境を使います。したがって、foo5 11 を評価すると、環境 [(n, 5)] に引数 x の値が追加され、[(x, 11), (n, 5)] になります。この環境で式 n * x を評価するので、5 * 11 = 55 を返すわけです。
関数の評価が終了すると、環境に追加された変数は削除されます。foo5 11 の評価で追加された変数は x なので、(x, 11) が削除され [(n, 5)] になります。このように、クロージャに保存された環境は変化しません。
ただし、Common Lisp や Scheme のように、変数の値を書き換えることができる処理系では、クロージャに保存された変数の値を変更することが可能です。興味のある方は お気楽 Common Lisp プログラミング入門 レキシカルスコープとクロージャ を読んでみてください。
ところで、伝統的な Lisp はレキシカルスコープではなくダイナミックスコープです。ここで簡単にダイナミックスコープについて説明します。もしも、F# がダイナミックスコープであれば、最初の例題で示した関数 foo は関数 foo1 から呼び出されると、foo1 の局所変数にアクセスすることができます。これを図に示すと次のようになります。
ダイナミックスコープの場合、局所変数はひとつの連想リストで管理されていると考えてください。この場合、キーが局所変数を表し、データがその値に対応します。局所変数にアクセスするときは、この連想リストから該当する変数を探すのです。見つからない場合は大域変数を検索します。
関数呼び出しや let などで局所変数が定義されたときに、変数とその値が連想リストの先頭へ追加されます。そして、関数や let の評価が終了したときに、連想リストから変数が削除されます。関数が呼び出されるたびに、新しい変数が連想リストに追加されますが、呼び出した側で定義した局所変数も、この連想リストの中に残っています。
たとえば、(1) のように関数 foo を呼び出した場合、関数の引数がなくて局所変数の定義もないので連想リストは空です。ところが、(2) のように関数 foo1 を呼び出した場合、局所変数 x が定義されているので、連想リストに (x, "local") がセットされます。この状態で関数 foo が呼び出されると、連想リストには foo1 で定義した (x, "local") が残っているので、foo では x の値が "local" となるのです。
このように、ダイナミックスコープでは、変数のアクセスは関数を評価する順番に左右されます。したがって、関数の中で定義されていない変数があっても、それがグローバル変数として扱われるとは限らないのです。
ダイナミックスコープで便利なところは、ほかの関数から他の関数の局所変数にアクセスできるところです。そのかわり、プログラムを見ただけでは、どの変数にアクセスしているのかわからないという欠点があります。プログラムの可読性の点でいえば、レキシカルスコープの方が優れていると思います。
最後に、関数 foo(x) と bar(x) を合成して新しい関数 baz(x) を作ることを考えてみましょう。関数 baz(x) を次のように定義します。
baz(x) = bar( foo(x) )
たとえば、foo(x) = 2 * x + 1, bar(y) = y * y + 3 * y とすると、baz(x) は次のようになります。
baz(x) = bar( foo(x) ) = (2 * x + 1) * (2 * x + 1) + 3 * (2 * x + 1) = 4 * x * x + 4 * x + 1 + 6 * x + 3 = 4 * x * x + 10 * x + 4
実際のプログラムは数式を展開するのではなく、foo(x) の評価結果を bar(x) に渡すだけなので簡単です。F# にはパイプ演算子 |> という便利な機能があり、bar( foo(x) ) という関数呼び出しを次のように表すことができます。
x |> foo |> bar
簡単な例を示しましょう。
> let foo x = 2 * x + 1;; val foo: x: int -> int > let bar y = y * y + 3 * y;; val bar: y: int -> int > bar (foo 4);; val it: int = 108 > 4 |> foo |> bar;; val it: int = 108
F# の場合、関数の合成は演算子 >> を使うと簡単です。
> (>>);; val it: (('a -> 'b) -> ('b -> 'c) -> 'a -> 'c)
左辺式の返り値が右辺式の引数になるので、右辺式が受け付ける値でなければいけません。そうでないと関数を合成することはできません。この条件を演算子 >> の型が表しています。左辺式の返り値が型 'b ならば、右辺式の引数も型は 'b でなければいけません。
簡単な例を示しましょう。
> let baz = foo >> bar;; val baz: (int -> int) > baz 4;; val it: int = 108
関数 foo と bar の合成は bar( foo( x ) ) と表すことができます。実際に 4 を計算すると 108 になります。この関数は演算子 >> で合成することができます。foo >> bar の返り値を変数 baz に束縛すると、baz を合成関数として使うことができます。
次の関数を定義してください。
リスト : 解答例 let rec remove_string c xs = List.filter (fun (s: string) -> s[0] <> c) xs let rec remove_number n xs = List.filter (fun x -> x % n <> 0) xs let rec any pred = function [] -> false | x::xs when not (pred x) -> any pred xs | _ -> true let rec every pred = function [] -> true | x::xs when pred x -> every pred xs | _ -> false let rec rev_append xs ys = match xs with [] -> ys | x::xs1 -> rev_append xs1 (x::ys)
val remove_string: c: char -> xs: string list -> string list val remove_number: n: int -> xs: int list -> int list val any: pred: ('a -> bool) -> _arg1: 'a list -> bool val every: pred: ('a -> bool) -> _arg1: 'a list -> bool val rev_append: xs: 'a list -> ys: 'a list -> 'a list
> remove_string 'A' ["abc"; "ABC"; "def"];; val it: string list = ["abc"; "def"] > remove_string 'A' ["Abc"; "ABC"; "def"];; val it: string list = ["def"] > remove_string 'A' ["abc"; "aBC"; "def"];; val it: string list = ["abc"; "aBC"; "def"] > remove_number 2 [1; 2; 3; 4; 5; 6; 7];; val it: int list = [1; 3; 5; 7] > remove_number 3 [1; 2; 3; 4; 5; 6; 7];; val it: int list = [1; 2; 4; 5; 7] > remove_number 8 [1; 2; 3; 4; 5; 6; 7];; val it: int list = [1; 2; 3; 4; 5; 6; 7] > any (fun x -> x % 2 = 0) [1; 2; 3; 5; 7; 9];; val it: bool = true > any (fun x -> x % 2 = 0) [1; 3; 5; 7; 9];; val it: bool = false > every (fun x -> x % 2 = 0) [2; 4; 6; 8; 10];; val it: bool = true > every (fun x -> x % 2 = 0) [2; 4; 5; 8; 10];; val it: bool = false > rev_append [1; 2; 3] [4; 5; 6];; val it: int list = [3; 2; 1; 4; 5; 6] > rev_append [] [4; 5; 6];; val it: int list = [4; 5; 6]
今回は簡単な例題として、「順列 (permutation)」と「組み合わせ (combination)」を取り上げます。
異なる n 個の順列の総数は、n の階乗 (n!) だけあります。たとえば、3 つの整数 1, 2, 3 の順列は次に示すように 6 通りあります。
1 2 3, 1 3 2, 2 1 3, 2 3 1, 3 1 2, 3 2 1
順列を生成するプログラムは再帰定義で簡単に作ることができます。[1; 2; 3] の順列を生成する場合、最初に 1 で始まる順列を生成します。これは 1 を取り除いた数字 [2; 3] の順列を生成することで実現できます。次は 2 で始まる順列を生成します。同様に、2 を取り除いた数字 [1; 3] の順列を生成すればいいわけです。[2; 3] や [1; 3] の順列を生成する場合も同じように考えることができます。
ここでは、リストの中から n 個の要素を取り出す順列を求めます。プログラムは次のようになります。
リスト 3 : 順列の生成 (1) // リストの表示 let print_list xs = printfn "%A" xs // リストから要素を削除する let rec remove x = function [] -> [] | y :: ys -> if x = y then remove x ys else y :: remove x ys // 順列の生成 let permutation n xs f = let rec perm n xs a = if n = 0 then f (List.rev a) else List.iter (fun x -> perm (n - 1) (remove x xs) (x :: a)) xs perm n xs []
関数 permutation は高階関数で、引数 n が選ぶ要素の個数、引数 xs がリスト、引数 f が生成した順列に適用する関数です。たとえば、順列が数字のリストであれば、リストを表示する関数 print_list を渡すことで、生成した順列を表示することができます。%A は任意の値を表示するための書式指定子です。実際の処理は局所関数 perm で行います。
perm の引数 n が選ぶ要素の個数、引数 xs がリストです。引数 a は累積変数で、選んだ数字を格納するリストです。n が 0 の場合、順列がひとつ完成したので List.rev で a を逆順にして関数 f に渡します。選んだ数字は a の先頭に追加していくので、逆順になることに注意してください。
n が 0 でなければ、数字を一つ選んで perm を再帰呼び出しします。数字の選択はリストの先頭から順番に行えばいいので、高階関数 List.iter を使っています。iter の定義をリスト 4 に示します
リスト 4 : 高階関数 iter の定義 let rec iter f = function [] -> () | x :: xs -> f x; iter f xs
iter はリストの要素に関数 f を適用します。その結果は捨てられることに注意してください。つまり、副作用を目的とした関数なのです。iter の返り値は unit になります。
ラムダ式でリストの要素 x を受け取り、この中で prem を再帰呼び出しします。perm の第 1 引数は n - 1、第 2 引数は xs から x を削除したリスト、第 3 引数は選択した数字のリスト a に x を追加したものです。これで数字 x を選択したことになります。x の削除は関数 remove で行います。
関数 remove はリストから x を探し、見つけた x をすべて削除します。List.filter を使う場合は、次のようになります。
リスト 5 : 要素の削除 let remove x xs = List.filter (fun y -> x <> y) xs
それでは、実際に試してみましょう。
> permutation 3 [1; 2; 3] print_list;; [1; 2; 3] [1; 3; 2] [2; 1; 3] [2; 3; 1] [3; 1; 2] [3; 2; 1] val it: unit = ()
正常に動作していますね。
生成した順列をリストに格納して返す場合は、List.foldBack を使うと簡単です。プログラムは次のようになります。
リスト 6 : 順列の生成 (2) let permutations n xs = let rec perm n xs a b = if n = 0 then a::b else List.foldBack (fun x y -> perm (n-1) (remove x xs) (x::a) y) xs b perm n xs [] []
局所関数 perm は permutation の局所関数を改造したもので、生成した順列を第 4 引数 b のリストに格納します。perm は順列を格納したリスト (第 4 引数) をそのまま返します。perm を呼び出す場合、この返り値を第 4 引数に渡すことで、生成した順列を格納していくことができます。
List.foldBack の初期値 (第 3 引数) に引数 b を渡すことで、ラムダ式の第 2 引数 y に順列を格納するリストを渡します。あとは perm を再帰呼び出しすると、その返り値は次にラムダ式を呼び出すときの引数 y に渡されるので、順列を格納したリストを perm に渡していくことができます。
それでは実行結果を示します。
val permutations: n: int -> xs: 'a list -> 'a list list when 'a: equality
> permutations 3 [1; 2; 3];; val it: int list list = [[3; 2; 1]; [2; 3; 1]; [3; 1; 2]; [1; 3; 2]; [2; 1; 3]; [1; 2; 3]]
正常に動作していますね。
次は「組み合わせ (combination)」を生成するプログラムを作ってみましょう。たとえば、リスト [1; 2; 3; 4; 5] の中から 3 個を選ぶ組み合わせは次のようになります。
1 2 3, 1 2 4, 1 2 5, 1 3 4, 1 3 5, 1 4 5 2 3 4, 2 3 5, 2 4 5, 3 4 5
最初に 1 を選択した場合、次は [2; 3; 4; 5] の中から 2 個を選べばいいですね。2 番目に 2 を選択したら、次は [3; 4; 5] の中から 1 個を選べばいいわけです。これで、[1; 2; 3], [1; 2; 4], [1; 2; 5] が生成されます。[2; 3; 4; 5] の中から 2 個選ぶとき、2 を選ばない場合があります。この場合は [3; 4; 5] の中から 2 個を選べばいいわけです。ここで 3 を選ぶと [1; 3; 4], [1; 3; 5] が生成できます。同様に、3 を除いた [4; 5] の中から 2 個をえらぶと [1; 4; 5] を生成することができます。
これで 1 を含む組み合わせを生成したので、次は 1 を含まない組み合わせ、つまり [2; 3; 4; 5] から 3 個を選ぶ組み合わせを生成すればいいわけです。けっきょく、この処理の考え方は次に示す組み合わせの公式と同じです。
nC0 = nCn = 1 nCr = n-1Cr-1 + n-1Cr
これをプログラムすると次のようになります。
リスト 7 : 組み合わせの生成 let combination r xs f = let rec comb r xs a = match (r, xs) with (0, _) -> f (List.rev a) | (_, []) -> () | (_, y::ys) -> comb (r - 1) ys (y::a); comb r ys a comb r xs []
val combination: r: int -> xs: 'a list -> f: ('a list -> unit) -> unit
局所関数 comb は引数 xs のリストから r 個を選ぶ組み合わせを生成します。選んだ数字は引数 a に格納します。r が 0 になったら組み合わせを一つ生成できたので、a を List.rev で逆順にして関数 f を呼び出します。xs が空リストならば何もしないで unit を返します。この 2 つの条件が再帰呼び出しの停止条件になります。
最後の節は xs が空リストでない場合です。ここで comb を再帰呼び出しします。最初の呼び出しは先頭の要素を選択する場合です。先頭要素を a に追加して、リスト ys の中から r - 1 個を選びます。最後の呼び出しが先頭の要素を選ばない場合です。リスト ys の中から r 個を選びます。
簡単な実行例を示します。
> combination 3 [1; 2; 3; 4; 5] print_list;; [1; 2; 3] [1; 2; 4] [1; 2; 5] [1; 3; 4] [1; 3; 5] [1; 4; 5] [2; 3; 4] [2; 3; 5] [2; 4; 5] [3; 4; 5] val it: unit = ()
正常に動作していますね。
生成した組み合わせをリストに格納して返す場合も簡単です。プログラムは次のようになります。
リスト 8 : 組み合わせの生成 (2) let combinations r xs = let rec comb r xs a b = match (r, xs) with (0, _) -> (List.rev a) :: b | (_, []) -> b | (_, y::ys) -> comb (r - 1) ys (y::a) (comb r ys a b) comb r xs [] []
val combinations: r: int -> xs: 'a list -> 'a list list
局所関数 comb は生成した組み合わせ (引数 a) を引数 b のリストに格納し、それをそのまま返します。comb を呼び出す場合、この返り値を引数 b に渡すことで、生成した組み合わせを格納していくことができます。
具体的には、comb を再帰呼び出しするところで、1 回目の呼び出しの返り値を 2 回目の呼び出しの第 4 引数に渡します。それから、引数 xs が空リストのときは引数 b をそのまま返します。これで生成した組み合わせをリストに格納することができます。
それでは実行結果を示します。
> combinations 3 [1; 2; 3; 4; 5];; val it: int list list = [[1; 2; 3]; [1; 2; 4]; [1; 2; 5]; [1; 3; 4]; [1; 3; 5]; [1; 4; 5]; [2; 3; 4]; [2; 3; 5]; [2; 4; 5]; [3; 4; 5]]
正常に動作していますね。
次の関数を定義してください。
リスト : 解答例 let rec flatten = function [] -> [] | x::xs -> x @ flatten xs let flatmap f xs = flatten (List.map f xs) (* 別解 *) let rec flatmap1 f = function [] -> [] | x::xs -> f x @ flatmap1 f xs let rec select = function [] -> [] | [x] -> [(x, [])] | x::xs -> (x, xs) :: List.map (fun (y, ys) -> (y, x::ys)) (select xs) let print_list xs = printfn "%A" xs let rec permutation_list n xs = if n = 0 then [[]] else flatmap (fun (y, ys) -> List.map (fun zs -> y::zs) (permutation_list (n - 1) ys)) (select xs) let rec remove x xs = List.filter (fun y -> x <> y) xs let rec iota n m = if n > m then [] else n :: iota (n + 1) m let derangement fn m = let rec perm n xs a = if xs = [] then fn (List.rev a) else List.iter (fun x -> if x = n then () else perm (n + 1) (remove x xs) (x::a)) xs perm 0 (iota 0 (m - 1)) []
> flatten [[1; 2; 3]; [4; 5; 6]; [7; 8; 9]];; val it: int list = [1; 2; 3; 4; 5; 6; 7; 8; 9] > List.map (fun x -> [x;x;x]) [1; 2; 3; 4];; val it: int list list = [[1; 1; 1]; [2; 2; 2]; [3; 3; 3]; [4; 4; 4]] > flatmap (fun x -> [x;x;x]) [1;2;3;4];; val it: int list = [1; 1; 1; 2; 2; 2; 3; 3; 3; 4; 4; 4] > select [1; 2; 3; 4];; val it: (int * int list) list = [(1, [2; 3; 4]); (2, [1; 3; 4]); (3, [1; 2; 4]); (4, [1; 2; 3])] > permutations 4 [1; 2; 3; 4];; val it: int list list = [[1; 2; 3; 4]; [1; 2; 4; 3]; [1; 3; 2; 4]; [1; 3; 4; 2]; [1; 4; 2; 3]; [1; 4; 3; 2]; [2; 1; 3; 4]; [2; 1; 4; 3]; [2; 3; 1; 4]; [2; 3; 4; 1]; [2; 4; 1; 3]; [2; 4; 3; 1]; [3; 1; 2; 4]; [3; 1; 4; 2]; [3; 2; 1; 4]; [3; 2; 4; 1]; [3; 4; 1; 2]; [3; 4; 2; 1]; [4; 1; 2; 3]; [4; 1; 3; 2]; [4; 2; 1; 3]; [4; 2; 3; 1]; [4; 3; 1; 2]; [4; 3; 2; 1]] > derangement print_list 3;; [1; 2; 0] [2; 0; 1] val it: unit = () > derangement print_list 4;; [1; 0; 3; 2] [1; 2; 3; 0] [1; 3; 0; 2] [2; 0; 3; 1] [2; 3; 0; 1] [2; 3; 1; 0] [3; 0; 1; 2] [3; 2; 0; 1] [3; 2; 1; 0] val it: unit = ()