16

手続き型コードを関数型コードに変換しようとして、いまだに行き詰まってしまうことがあります。

手続き型イディオム/スニペットにマッピングされた機能イディオム/スニペットのリストはありますか?

編集

これらのスニペットの集中管理された Web サイトがないように思われるので、これをコミュニティ wiki に変えています。プロシージャル -> 機能スニペットをここに貼り付けてください。

4

5 に答える 5

9

fshubのこの投稿から編集)

初めて OCaml/F# でブレーク/コンティニューに手を伸ばしたとき、いわば (無限の) ループに陥りました。そのようなものは存在しないからです! OCaml では例外を使用してループから抜けることができますが、これは非常に安価ですが、F# (.NET) ではオーバーヘッドが非常に高く、「通常の」フロー制御には役に立ちません。

これは、repeat/until と break を多用するソート アルゴリズムでしばらく前に (時間をつぶすために) 遊んでいたときに発生しました。再帰的な末尾呼び出し関数でもまったく同じ結果が得られますが、読みやすさはわずかに低下するだけです。そこで、「変更可能な bDone」と「while not bDone」を捨てて、これらの命令型構造を使用せずにコードを記述しようとしました。以下は、ループ部分だけを抜き出し、末尾呼び出しを使用して、repeat/until、do/while、while/do、break/continue、test-in-the-middle スタイルのコードを記述する方法を示しています。これらはすべて、従来の F# 'while' ステートメントとまったく同じ速度で実行されるように見えますが、マイレージは異なる場合があります (一部のプラットフォームでは、tailcall が適切に実装されていないため、パッチが適用されるまでスタック フォールトが発生する可能性があります)。最後に、両方のスタイルで書かれた (悪い) ソート アルゴリズムがあります。

従来の F# 命令型スタイルで記述された 'do/while' ループから始めて、同じタイプのループと、while/do、repeat/until、途中でのテストなどの異なるセマンティクスの両方を提供する機能のバリエーションを見てみましょう。 、さらにブレーク/コンティニュー (モナドなし..ええと、ワークフロー!)。

#light
(* something to work on... *)
let v = ref 0
let f() = incr v;
let g() = !v;
let N = 100000000

let imperDoWhile() =
    let mutable x = 0
    let mutable bDone = false
    while not bDone do
        f()
        x <- x + 1
        if x >= N then bDone <- true

わかりました、それは簡単です。f() は常に少なくとも 1 回 (do/while) 呼び出されることに注意してください。

これは同じコードですが、機能的なスタイルです。ここでミュータブルを宣言する必要はないことに注意してください。

let funDoWhile() =
    let rec loop x =
        f()             (*Do*)
        if x < N then   (*While*)
            loop (x+1)
    loop 0

関数呼び出しを if ブロック内に置くことで、それを従来の do/while にスピンさせることができます。

let funWhileDo() =
    let rec loop x =
        if x < N then   (*While*)
            f()         (*Do*)
            loop (x+1)
    loop 0

ある条件が真になるまでブロックを繰り返すのはどうですか (繰り返し/まで)? 簡単に...

let funRepeatUntil() =
    let rec loop x =
        f()                 (*Repeat*)
        if x >= N then ()   (*Until*)
        else loop (x+1)
    loop 0

モナドなしのブレークについてはどうでしたか? 次のように、'unit' を返す条件式を導入するだけです。

let funBreak() =
    let rec loop() =
        let x = g()
        if x > N then ()    (*break*)
        else
            f()
            loop()
    loop()

続けてみてはどうですか?ええと、それはループのもう 1 つの呼び出しです。まず、構文松葉杖を使用します。

let funBreakContinue() =
    let break' () = ()
    let rec continue' () =
        let x = g()
        if   x > N then break'()
        elif x % 2 = 0 then
            f(); f(); f();
            continue'()
        else
            f()
            continue'()
    continue'()

そして、(醜い)構文松葉杖なしで再び:

let funBreakContinue'() =
    let rec loop () =
        let x = g()
        if   x > N then ()
        elif x % 2 = 0 then
            f(); f(); f();
            loop()
        else
            f()
            loop ()
    loop()

やさしい!

これらのループ形式の良い結果の 1 つは、ループ内の状態を簡単に見つけて実装できることです。たとえば、バブル ソートは配列全体を継続的にループし、場違いな値を見つけると交換します。配列を介したパスが交換を生成したかどうかを追跡します。そうでない場合は、すべての値が適切な場所にある必要があるため、並べ替えを終了できます。最適化として、配列を通過するたびに、配列の最後の値が正しい場所にソートされます。したがって、ループは毎回 1 ずつ短縮できます。ほとんどのアルゴリズムはスワップをチェックし、スワップがあるたびに「bModified」フラグを更新します。ただし、最初のスワップが完了すると、その割り当ては不要になります。すでに true に設定されています。

バブル ソートを実装する F# コードを次に示します (そうです、バブル ソートはひどいアルゴリズムです。クイック ソートは素晴らしいです)。最後に、状態を変更しない命令型の実装があります。交換ごとに bModified フラグを更新します。興味深いことに、命令型のソリューションは小さな配列では高速ですが、大きな配列ではわずか 1 ~ 2% 遅くなります。(しかし、良い例のために作られました)。

let inline sort2 f i j (a:'a array) =
    let i' = a.[ i ]
    let j' = a.[ j ]
    if f i' j' > 0 then
        a.[ i ] <- j'
        a.[ j ] <- i'

let bubble f (xs:'a array) =
    if xs.Length = 0
    then ()

    let rec modified i endix =
        if i = endix then
            unmodified 0 (endix-1)
        else
            let j = i+1
            sort2 f i j xs
            modified j endix
    and unmodified i endix =
        if i = endix then
            ()
        else
            let j = i+1
            let i' = xs.[ i ]
            let j' = xs.[ j ]
            if f i' j' > 0 then
                xs.[ i ] <- j'
                xs.[ j ] <- i'
                modified j endix
            else
                unmodified j endix
    in unmodified 0 (xs.Length-1)

let bubble_imperitive f (xs:'a array) =
    let mutable bModified = true
    let mutable endix = xs.Length - 1
    while bModified do
        bModified <- false
        endix <- endix - 1
        for i in 0..endix do
            let j = i+1
            let i' = xs.[ i ]
            let j' = xs.[ j ]
            if f i' j' > 0 then
                xs.[ i ] <- j'
                xs.[ j ] <- i'
                bModified <- true
        done
    done
于 2009-05-08T15:08:35.057 に答える
4

ああ、これは気の利いた質問です。ここにいくつかのコードスニップがあり、pythonまたは何か cloe で:

  • for ループは反復子に置き換えることができます

    stripped_list = [line.strip() for line in line_list]

  • for ループは、apply または map または filter に置き換えることができます

    map(upper, ['文', '断片']) ['文', '断片']

  • 関数の合成によるネストされた for ループ

  • ループの代わりに末尾再帰

  • for ループの代わりのジェネレータ式

    sum(x*x for x in range(10))

于 2009-05-07T02:24:50.433 に答える