純粋関数型プログラミングでは、実行の順序は重要ではないため、未定です(つまり、コンパイラー次第です)。副作用がある場合は、実行の順序が重要です。では、F#でどのように定義できますか?
指定されたパスのすべての空のサブフォルダーを再帰的に削除する関数があります。さらに、それらの名前が特定のリストにある場合、それらに含まれるいくつかのファイルを削除します。
アルゴリズムは簡単です。
- リスト内のすべてのファイルを削除します。
- サブフォルダーの再帰呼び出しを実行します。
- フォルダが空の場合は削除します。これが最後のステップでなければなりません。
さらに、この関数は、削除された要素の数をタプル(削除されたフォルダーの数、削除されたファイルの数)として返します。
これが私のコードです:
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#に伝えたい場合、最後の行をどのように書く必要がありますか?関数を副作用のないものとしてマークするためのキーワードまたは特別な構文はありますか?