2

私はf#が初めてで、特定のディレクトリ内のすべてのファイルを調べ、タイプ「.txt」の各ファイルに対してid番号+「DONE」をファイルに追加するプログラムを作成しようとしました。

私のプログラム:

//const:
[<Literal>]
let notImportantString= "blahBlah"
let mutable COUNT = 1.0

//funcs:
//addNumber --> add the sequence number COUNT to each file.
let addNumber (file : string)  =
 let mutable str = File.ReadAllText(file)
 printfn "%s" str//just for check
 let num = COUNT.ToString()
 let str4 = str + " " + num + "\n\n\n DONE"
 COUNT <- COUNT + 1.0
 let str2 =  File.WriteAllText(file,str4)
 file

//matchFunc --> check if is ".txt"
let matchFunc (file : string) =
 file.Contains(".txt") 

//allFiles --> go through all files of a given dir
let allFiles dir =

seq
    { for file in Directory.GetFiles(dir) do
        yield file  
           }

////////////////////////////

let dir = "D:\FSharpTesting"
let a = allFiles dir 
         |> Seq.filter(matchFunc) 
         |> Seq.map(addNumber)
printfn "%A" a

私の質問:

Tf 最後の行を書きません (printfn "%A" a) ファイルは変更されません (この行を書き込めば動作し、ファイルが変更されます)。 「let a =......」の場合、行に到着したときの「a」の値は、printfn行に続き、そこで「a」を「見た」ときよりも戻って、「の答えを計算します。 a'. それはなぜですか、印刷せずに機能を「開始」するにはどうすればよいですか??

また、関数「addNumber」の戻り値の型としてファイルを追加する必要がある理由を誰か教えてもらえますか? (これがどのように機能するかという理由でこれを追加しましたが、理由がよくわかりません....)

最後の質問- [] 定義の行の直後に COUNT 変数を書き込むと、エラーが発生し、定数を「変更可能」にすることはできないと言われますが、(これが私がそうした理由です) 前に別の行を追加すると (文字列) 間違いを「忘れ」、機能します。どうして?そして、本当に変更可能な const を持つことができない場合、どうすれば静的変数を作成できますか?

4

5 に答える 5

9

最後の行 (printfn "%A" a) を書き込まないと、ファイルは変更されません。

F# シーケンスは遅延です。したがって、評価を強制するには、シーケンスを返さない操作を実行できます。たとえば、Seq.iter(副作用があり、return ())、 (シーケンスの長さであるSeq.lengthanを返す)、または(リスト、熱心なデータ構造を返す) などを呼び出すことができます。intSeq.toList

file : string関数「addNumber」の戻り値の型として追加する必要がある理由を誰か教えてもらえますか?

メソッドとプロパティへのアクセスは、F# の型推論ではうまく機能しません。型チェッカーは、左から右、上から下に機能します。あなたが言うときfile.Contains、それはこれがメンバーでどのタイプであるべきかわかりませんContains。したがって、型注釈は F# 型チェッカーの良いヒントになります。

定義の行の直後に COUNT 変数を記述する[<Literal>]と、エラーが発生し、定数を「可変」にすることはできないと表示されます。

MSDNからの引用:

定数であることを意図した値は、Literal 属性でマークできます。この属性には、値を定数としてコンパイルする効果があります。

変更可能な値は、プログラムのある時点でその値を変更できます。コンパイラは正当な理由で不平を言います。属性を簡単に削除でき[<Literal>]ます。

于 2013-02-19T19:49:42.583 に答える
3

Seq.map通常、値を変更するのではなく、ある値を別の値にマップすることを目的としています。seq<_>遅延生成されたシーケンスを表すため、Alexが指摘したように、シーケンスが列挙されるまで何も起こりません。これはおそらくコードレビューに適していますが、これを書く方法は次のとおりです。

Directory.EnumerateFiles(dir, "*.txt")
  |> Seq.iteri (fun i path -> 
    let text = File.ReadAllText(path)
    printfn "%s" text
    let text = sprintf "%s %d\n\n\n DONE" text (i + 1)
    File.WriteAllText(path, text))

Seq.mapF#のすべての式と同様に、戻り型が必要です。関数が値を計算するのではなく、アクションを実行する場合、関数は:を返すことができunitます()。に関しては、値をand (C#の場合)にCOUNTすることはできません。それらは正反対です。静的変数の場合は、モジュールスコープのバインディングを使用します。mutable[<Literal>]constlet mutable

module Counter =
  let mutable count = 1

open Counter
count <- count + 1

countただし、プライベート実装の一部としてカウンター変数を使用して関数を作成することにより、グローバルな可変データを回避できます。あなたはクロージャーでこれを行うことができます:

let count =
  let i = ref 0
  fun () ->
    incr i
    !i

let one = count()
let two = count()
于 2013-02-19T19:36:29.177 に答える
3

Alexの答えを詳しく説明すると、F#シーケンスは遅延評価されます。これは、シーケンス内の各要素が「オンデマンド」で生成されることを意味します。

これの利点は、不要な要素の計算時間とメモリを無駄にしないことです。ただし、遅延評価には少し慣れが必要です。具体的には、実行の順序を想定できないためです(または、実行がまったく行われないためです)。

あなたの問題には簡単な修正があります:Seq.iterシーケンスによって返される値を気にしないので、シーケンスの実行/評価を強制するために使用し、'ignore'関数をそれに渡します。

let a = allFiles dir 
     |> Seq.filter(matchFunc) 
     |> Seq.map(addNumber)
     |> Seq.iter ignore   // Forces the sequence to execute
于 2013-02-19T19:36:44.137 に答える
1

f#は上から下に評価されますが、printfnを実行するまでは遅延値のみを作成します。したがって、printfnは実際に最初に実行され、コードの残りの部分を実行します。Seq.map(addNumber)の後にprintlnを追加し、それにtoListを実行すると、評価も強制されるので、同じことができると思います。

于 2013-02-19T19:28:01.937 に答える
1

これは、レイジーシーケンスの一般的な動作です。たとえば、C#でIEnumerableを使用している場合、seqはエイリアスです。擬似コードの場合:

    var lazyseq = "abcdef".Select(a => print a); //does not do anything
    var b = lazyseq.ToArray(); //will evaluate the sequence

ToArrayはシーケンスの評価をトリガーします:

これは、シーケンスが単なる説明であり、いつ列挙されるかを示していないという事実を示しています。これは、シーケンスのコンシューマーを制御します。


このテーマについてもう少し詳しく説明するには、F#wikibookのこのページをご覧ください

let isNebraskaCity_bad city =
    let cities =
        printfn "Creating cities Set"
        ["Bellevue"; "Omaha"; "Lincoln"; "Papillion"]
        |> Set.ofList

    cities.Contains(city)

let isNebraskaCity_good =
    let cities =
        printfn "Creating cities Set"
        ["Bellevue"; "Omaha"; "Lincoln"; "Papillion"]
        |> Set.ofList

    fun city -> cities.Contains(city)

最も注目すべきは、Sequenceがキャッシュされないことです(ただし、キャッシュすることはできます)。次に、シーケンス自体が再計算されるため、説明と実行時の動作の違いが重要な結果をもたらす可能性があります。これにより、非常に高いコストが発生し、各値自体が線形である場合、2次数の操作が発生する可能性があります。

于 2013-02-19T19:37:04.050 に答える