我是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我怎么能做一个静态变量?
答案 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可能会产生重要的结果,因为重新计算序列本身会产生非常高的成本,并且如果每个值本身是线性的,则会引入二次运算数!