Вопрос

I'm new to f# and I tried to write a program supposed to go through all files in a given dir and for each file of type ".txt" to add an id number + "DONE" to the file.

my program:

//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

My question:

Tf I do not write the last line (printfn "%A" a) the files will not change.(if I DO write this line it works and change the files) when I use debugger I see that it doesn't really computes the value of 'a' when it arrives to the line if "let a =......" it continues to the printfn line and than when it "sees" the 'a' there it goes back and computes the answer of 'a'. why is it and how can i "start" the function without printing??

also- Can some one tells me why do I have to add file as a return type of the function "addNumber"? (I added this because that how it works but I don't really understand why....)

last question- if I write the COUNT variable right after the line of the [] definition it gives an error and says that a constant cannot be "mutable" but if a add (and this is why I did so) another line before (like the string) it "forgets" the mistakes and works. why that? and if you really cannot have a mutable const how can I do a static variable?

Это было полезно?

Решение

if I do not write the last line (printfn "%A" a) the files will not change.

F# sequences are lazy. So to force evaluation, you can execute some operation not returning a sequence. For example, you can call Seq.iter (have side effects, return ()), Seq.length (return an int which is the length of the sequence) or Seq.toList (return a list, an eager data structure), etc.

Can some one tells me why do I have to add file : string as a return type of the function "addNumber"?

Method and property access don't play nice with F# type inference. The type checker works from left to right, from top to bottom. When you say file.Contains, it doesn't know which type this should be with Contains member. Therefore, your type annotation is a good hint to F# type checker.

if I write the COUNT variable right after the line of the [<Literal>] definition it gives an error and says that a constant cannot be "mutable"

Quoting from MSDN:

Values that are intended to be constants can be marked with the Literal attribute. This attribute has the effect of causing a value to be compiled as a constant.

A mutable value can change its value at some point in your program; the compiler complains for a good reason. You can simply delete [<Literal>] attribute.

Другие советы

To elaborate on Alex's answer -- F# sequences are lazily evaluated. This means that each element in the sequence is generated "on demand".

The benefit of this is that you don't waste computation time and memory on elements you don't ever need. Lazy evaluation does take a little getting used to though -- specifically because you can't assume order of execution (or that execution will even happen at all).

Your problem has a simple fix: just use Seq.iter to force execution/evaluation of the sequence, and pass the 'ignore' function to it since we don't care about the values returned by the sequence.

let a = allFiles dir 
     |> Seq.filter(matchFunc) 
     |> Seq.map(addNumber)
     |> Seq.iter ignore   // Forces the sequence to execute

Seq.map is intended to map one value to another, not generally to mutate a value. seq<_> represents a lazily generated sequence so, as Alex pointed out, nothing will happen until the sequence is enumerated. This is probably a better fit for codereview, but here's how I would write this:

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.map requires a return type, as do all expressions in F#. If a function performs an action, as opposed to computing a value, it can return unit: (). Regarding COUNT, a value cannot be mutable and [<Literal>] (const in C#). Those are precise opposites. For a static variable, use a module-scoped let mutable binding:

module Counter =
  let mutable count = 1

open Counter
count <- count + 1

But you can avoid global mutable data by making count a function with a counter variable as a part of its private implementation. You can do this with a closure:

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

let one = count()
let two = count()

f# is evaluated from top to bottom, but you are creating only lazy values until you do printfn. So, printfn is actually the first thing that gets executed which in turn executes the rest of your code. I think you can do the same thing if you tack on a println after Seq.map(addNumber) and do toList on it which will force evaluation as well.

This is a general behaviour of lazy sequence. you have the same in, say C# using IEnumerable, for which seq is an alias. In pseudo code :

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

ToArray triggers the evaluation of a sequence :

This illustrate the fact that a sequence is just a description, and does not tell you when it will be enumerated : this is in control of the consumer of the sequence.


To go a bit further on the subject, you might want to look at this page from 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)

Most notably, Sequence are not cached (although you can make them so). You see then that the dintinguo between the description and the runtime behaviour can have important consequence as the sequence itself is recomputed which can incur a very high cost and introduce quadratic number of operations if each value is itself linear to get !

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top