如何在没有“printf”的情况下调用Seq.whatever中的函数?

时间:2013-02-19 19:24:17

标签: f# sequences seq

我是f#的新手,我尝试编写一个应该遍历给定目录中所有文件的程序,以及每个“.txt”类型的文件,为文件添加一个ID号+“DONE”。 / p>

我的节目:

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

我的问题:

如果我不写最后一行(printfn“%A”a)文件不会改变。(如果我写这行,它可以工作并更改文件) 当我使用调试器时,我发现当它到达线路时,如果“让a = ......”它继续到printfn线并且它“看到”时,它并没有真正计算出'a'的值。它会回到'a'并计算'a'的答案。 它为什么以及如何在不打印的情况下“启动”该功能?

也 - 有人可以告诉我为什么我要将文件添加为函数“addNumber”的返回类型? (我添加了这个,因为它是如何工作的,但我真的不明白为什么......)

最后一个问题 - 如果我在[]定义的行后面写COUNT变量 它给出了一个错误,并说一个常量不能是“可变的”,但是如果一个添加(这就是为什么我这样做)之前的另一行(比如字符串)它“忘记”错误并起作用。 为什么?如果你真的不能拥有一个可变的const我怎么能做一个静态变量?

5 个答案:

答案 0 :(得分:9)

  

如果我不写最后一行(printfn“%A”a),文件将不会改变。

F#序列是懒惰的。因此,为了强制评估,您可以执行一些不返回序列的操作。例如,您可以调用Seq.iter(有副作用,返回()),Seq.length(返回int这是序列的长度)或{{3} }(返回一个列表,一个急切的数据结构)等等。

  

有人可以告诉我为什么我必须添加file : string作为函数“addNumber”的返回类型?

方法和属性访问不适合F#类型推断。类型检查器从左到右,从上到下工作。当您说file.Contains时,它不知道Contains成员应该使用哪种类型。因此,您的类型注释是F#类型检查器的一个很好的提示。

  

如果我在[<Literal>]定义的行之后写COUNT变量   它给出了一个错误,并说常量不能是“可变的”

引自Seq.toList

  

可以使用Literal属性标记要作为常量的值。此属性具有将值编译为常量的效果。

可变值可以在程序中的某个位置更改其值;编译器抱怨有充分的理由。您只需删除[<Literal>]属性。

答案 1 :(得分:3)

Seq.map旨在将一个值映射到另一个值,通常不会改变值。 seq<_>表示一个延迟生成的序列,因此,正如Alex指出的那样,在枚举序列之前不会发生任何事情。这可能更适合codereview,但这是我写这个的方式:

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需要返回类型,F#中的所有表达式都需要。如果函数执行操作而不是计算值,则它可以返回unit()。关于COUNT,值不能是mutable[<Literal>](C#中的const)。这些都是精确的对立面。对于静态变量,请使用模块范围的let 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()

答案 2 :(得分:3)

详细说明Alex的答案 - F#序列被懒惰地评估。这意味着序列中的每个元素都是“按需”生成的。

这样做的好处是,您不会将计算时间和内存浪费在您不需要的元素上。懒惰的评估确实需要一点点习惯 - 特别是因为你不能假设执行顺序(或者执行甚至会发生)。

您的问题有一个简单的解决方法:只需使用Seq.iter强制执行/评估序列,并将'ignore'函数传递给它,因为我们不关心序列返回的值。 / p>

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

答案 3 :(得分:1)

f#从上到下进行评估,但在创建printfn之前,您只创建了惰性值。因此,printfn实际上是第一个被执行的东西,它反过来执行其余的代码。我认为你可以做同样的事情,如果你在Seq.map(addNumber)之后添加println并对它做toList也会强制进行评估。

答案 4 :(得分:1)

这是懒惰序列的一般行为。你有相同的,比如C#使用IEnumerable,其中seq是别名。 在伪代码中:

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

ToArray触发序列的评估:

这说明一个序列只是一个 description 这个事实,并没有告诉你什么时候会被枚举:这是对序列的消费者的控制


要进一步了解这个主题,您可能需要查看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)

最值得注意的是,序列未缓存(尽管你可以这样做)。你可以看到描述和运行时行为之间的dintinguo可能会产生重要的结果,因为重新计算序列本身会产生非常高的成本,并且如果每个值本身是线性的,则会引入二次运算数!