我是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
我的问题:
如果我不写最后一行(printfn“%A”a)文件将不会改变 . (如果我写这行,它可以工作并更改文件)当我使用调试器时我发现它并没有真正计算当'a'到达线时,如果“让a = ......”,它继续到printfn线,而当它“看到”'a'时,它返回并计算答案''一个' . 它为什么以及如何在不打印的情况下“启动”功能?
还有 - 有人告诉我为什么要添加文件作为函数“addNumber”的返回类型? (我添加了这个,因为它是如何工作但我真的不明白为什么....)
最后一个问题 - 如果我在[]定义的行之后写COUNT变量它会给出一个错误并且说一个常量不能是“可变的”但是如果一个add(这就是为什么我这样做了)之前的另一行(比如字符串)它“忘记”错误和工作 . 为什么?如果你真的不能有一个可变const我怎么能做一个静态变量?
5 回答
详细说明Alex的答案 - F#序列被懒惰地评估 . 这意味着序列中的每个元素都是“按需”生成的 .
这样做的好处是,您不会将计算时间和内存浪费在您不需要的元素上 . 懒惰的评估确实需要一点点习惯 - 特别是因为你不能假设执行顺序(或者执行甚至会发生) .
您的问题有一个简单的修复:只需使用
Seq.iter
强制执行/评估序列,并将'ignore'函数传递给它,因为我们不关心序列返回的值 .f#从上到下进行评估,但在创建printfn之前,您只创建了惰性值 . 因此,printfn实际上是第一个被执行的东西,它反过来执行其余的代码 . 我认为你可以做同样的事情,如果你在Seq.map(addNumber)之后添加println并对它做toList也会强制进行评估 .
这是懒惰序列的一般行为 . 你有相同的,比如C#使用IEnumerable,其中seq是别名 . 在伪代码中:
ToArray触发序列的评估:
这说明了一个序列只是一个描述的事实,并没有告诉你什么时候会被枚举:这是对序列消费者的控制 .
要进一步了解该主题,您可能需要查看this page from F# wikibook:
最值得注意的是,序列是 not cached (尽管你可以这样做) . 然后你会看到描述和运行时行为之间的dintinguo可能会产生重要的结果,因为重新计算序列本身会产生非常高的成本,并且如果每个值本身是线性的,则会引入二次运算数!
F#序列是懒惰的 . 因此,为了强制评估,您可以执行一些不返回序列的操作 . 例如,您可以调用Seq.iter(有副作用,返回
()
),Seq.length(返回int
这是序列的长度)或Seq.toList(返回列表,急切的数据结构)等 .方法和属性访问与F#类型推断不相称 . 类型检查器从左到右,从上到下工作 . 当你说
file.Contains
时,它不知道Contains
成员应该使用哪种类型 . 因此,您的类型注释是F#类型检查器的一个很好的提示 .引自MSDN:
可变值可以在程序中的某个点更改其值;编译器抱怨有充分的理由 . 您只需删除
[<Literal>]
属性即可 .Seq.map
旨在将一个值映射到另一个值,通常不会改变值 .seq<_>
表示一个延迟生成的序列,因此,正如Alex指出的那样,在枚举序列之前不会发生任何事情 . 这可能更适合codereview,但这是我写这个的方式:Seq.map
需要返回类型,F#中的所有表达式都需要 . 如果函数执行操作,而不是计算值,则它可以返回unit
:()
. 关于COUNT
,值不能是mutable
和[<Literal>]
(C#中的const
) . 这些是精确的对立面 . 对于静态变量,请使用模块范围的let mutable
捆绑:但是,您可以通过将
count
函数与计数器变量作为其私有实现的一部分来避免全局可变数据 . 你可以用一个闭包来做到这一点: