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)文件不会改变。(如果我写了这一行,它可以工作并更改文件)当我使用调试器时,我发现它并没有真正计算'a' 的值当它到达该行时,如果“let a =......”它继续到 printfn 行,而不是当它“看到”那里的 'a' 时,它会返回并计算 ' 的答案一种'。为什么会这样?如何在不打印的情况下“启动”该功能?

还有-有人能告诉我为什么我必须添加文件作为函数“addNumber”的返回类型吗?(我添加这个是因为它是如何工作的,但我真的不明白为什么......)

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

4

5 回答 5

9

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

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

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

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

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

引用MSDN

打算作为常量的值可以用 Literal 属性进行标记。此属性具有将值编译为常量的效果。

可变值可以在程序中的某个时刻改变它的值;编译器抱怨是有充分理由的。您可以简单地删除[<Literal>]属性。

于 2013-02-19T19:49:42.583 回答
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,值不能是mutableand [<Literal>](const在 C# 中)。这些是完全相反的。对于静态变量,使用模块范围的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()
于 2013-02-19T19:36:29.177 回答
3

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

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

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

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

这是惰性序列的一般行为。你也有同样的情况,比如使用 IEnumerable 的 C#,其中 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没有被缓存(尽管您可以这样做)。然后您会看到,描述和运行时行为之间的差异可能会产生重要的后果,因为重新计算序列本身会产生非常高的成本,并且如果每个值本身都是线性的,则会引入二次运算!

于 2013-02-19T19:37:04.050 回答