好みと一般的なプログラミングスタイルの順に、次のようにコードを記述します。
利用可能な場合はマップ/フォールド
let x = [1 .. 10] |> List.map ((*) 2)
そのちょうど便利で使いやすいです。
非末尾再帰関数
> let rec map f = function
| x::xs -> f x::map f xs
| [] -> [];;
val map : ('a -> 'b) -> 'a list -> 'b list
> [1 .. 10] |> map ((*) 2);;
val it : int list = [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]
ほとんどのアルゴリズムは、末尾再帰なしで読み、表現するのが最も簡単です。これは、あまり深く再帰する必要がない場合に特に効果的であり、多くの並べ替えアルゴリズムやバランスの取れたデータ構造でのほとんどの操作に適しています。
log 2 (1,000,000,000,000,000)〜= 50であることを忘れないでください。したがって、末尾再帰のないlog(n)操作はまったく怖いものではありません。
アキュムレータを使用した末尾再帰
> let rev l =
let rec loop acc = function
| [] -> acc
| x::xs -> loop (x::acc) xs
loop [] l
let map f l =
let rec loop acc = function
| [] -> rev acc
| x::xs -> loop (f x::acc) xs
loop [] l;;
val rev : 'a list -> 'a list
val map : ('a -> 'b) -> 'a list -> 'b list
> [1 .. 10] |> map ((*) 2);;
val it : int list = [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]
それは機能しますが、コードは不器用であり、アルゴリズムの優雅さはわずかに曖昧です。上記の例は読むのにそれほど悪くはありませんが、ツリーのようなデータ構造に入ると、それは本当に悪夢になり始めます。
継続渡しで末尾再帰
> let rec map cont f = function
| [] -> cont []
| x::xs -> map (fun l -> cont <| f x::l) f xs;;
val map : ('a list -> 'b) -> ('c -> 'a) -> 'c list -> 'b
> [1 .. 10] |> map id ((*) 2);;
val it : int list = [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]
このようなコードを見るたびに、「これは巧妙なトリックです!」と自分に言い聞かせます。読みやすさを犠牲にして、非再帰関数の形状を維持し、バイナリツリーへの末尾再帰挿入にとって非常に興味深いことがわかりました。
おそらくここで話す私のモナド恐怖症、あるいはLispのcall / ccに精通していないという私の本質的な欠如ですが、CSPが実際にアルゴリズムを単純化する機会はほとんどないと思います。反例はコメントで歓迎されています。
whileループ/forループ
シーケンスの理解を除けば、F#コードでwhileループやforループを使用したことは一度もないと思います。とにかく...
> let map f l =
let l' = ref l
let acc = ref []
while not <| List.isEmpty !l' do
acc := (!l' |> List.hd |> f)::!acc
l' := !l' |> List.tl
!acc |> List.rev;;
val map : ('a -> 'b) -> 'a list -> 'b list
> [1 .. 10] |> map ((*) 2);;
val it : int list = [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]
その事実上、命令型プログラミングのパロディーです。代わりに宣言することで少し健全性を維持できるかもしれませんがlet mutable l' = l
、重要な関数では。を使用する必要がありますref
。