3

純粋関数型プログラミングでは、実行の順序は重要ではないため、未定です(つまり、コンパイラー次第です)。副作用がある場合は、実行の順序が重要です。では、F#でどのように定義できますか?

指定されたパスのすべての空のサブフォルダーを再帰的に削除する関数があります。さらに、それらの名前が特定のリストにある場合、それらに含まれるいくつかのファイルを削除します。

アルゴリズムは簡単です。

  1. リスト内のすべてのファイルを削除します。
  2. サブフォルダーの再帰呼び出しを実行します。
  3. フォルダが空の場合は削除します。これが最後のステップでなければなりません。

さらに、この関数は、削除された要素の数をタプル(削除されたフォルダーの数、削除されたファイルの数)として返します。

これが私のコードです:

let rec DeleteEmptyFolders path filenames =
    // Deletes a file or folder using the given function.
    // Returns 1 if the file could be deleted, otherwise 0.
    let Delete delete name =
        try
            delete name;
            1
        with
            | _ -> 0

    // The function result (number of deleted folders and files).
    let deletedFolders (a, _) = a
    let deletedFiles   (_, a) = a

    let accumulator a b = 
        ( deletedFolders a + deletedFolders b,
          deletedFiles a + deletedFiles b )

    // Deletes the given files and returns the number of deleted elements.
    let DeleteFiles folder names =
        names
        |> Seq.map (fun n -> Path.Combine (folder, n))
        |> Seq.map (fun n -> Delete File.Delete n)
        |> Seq.reduce (+)

    // Deletes the folder if it is empty
    // (Directory.Delete will fail if it is not empty).
    let DeleteFolder folder = Delete Directory.Delete folder

    // The recursive call
    let DeleteEmptySubFolders folder files =
        Directory.EnumerateDirectories folder
        |> Seq.map (fun p -> DeleteEmptyFolders p files)
        |> Seq.reduce (accumulator)

    // Three functions are executed: DeleteEmptySubFolders, DeleteFolder and DeleteFiles
    // But it has to be done in the correct order: DeleteFolder must be executed last.
    accumulator (DeleteEmptySubFolders path filenames) (DeleteFolder path, DeleteFiles path filenames)

DeleteEmptySubFoldersが最初に実行され、次にDeleteFolderとDeleteFilesが実行されることがわかりました。これは、関数がコードに表示される順序です。しかし、これはF#のルールではないと思います。それは、コンパイラーが決定したものです。それは他の注文である可能性があります。

もちろん、コードの最後の行の要素を交換することもできます。コンパイラはそれに応じて実行順序を変更します。しかし、これは言語の規則ではないので、それはただ運がいいでしょう。

このトピックに関する別の質問で、値(つまり、引数のない関数)は、宣言された順序で初期化されることを読みました。

let firstCall  = DeleteFiles path filenames
let secondCall = DeleteEmptySubFolders path filenames
let thirdCall  = DeleteFolder path

accumulator (secondCall) (thirdCall, firstCall)

これで、呼び出しは正しい順序で行われます。しかし、繰り返しになりますが、これはF#のルールですか、それともコンパイラの動作方法ですか?(関数が使用されていない場合、コンパイラーは値をまったく初期化しないことを決定する可能性があります)

実行の順序が重要であり、各呼び出しをいつ実行する必要があるかをF#に伝えたい場合、最後の行をどのように書く必要がありますか?関数を副作用のないものとしてマークするためのキーワードまたは特別な構文はありますか?

4

2 に答える 2

7

F#は純粋な関数型プログラミング言語ではありません。関数と値は上から下、左から右に計算されます。

于 2012-08-05T00:34:09.970 に答える
1

コードを次のように書くのが最善だと思います

let firstCall  = DeleteFiles path filenames
let secondCall = DeleteEmptySubFolders path filenames
let thirdCall  = DeleteFolder path

accumulator (secondCall) (thirdCall, firstCall)
于 2012-08-05T00:43:49.833 に答える