首页 文章

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

提问于
浏览
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

我的问题:

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

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

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

5 回答

  • 3

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

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

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

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

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

  • 1

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

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

    ToArray触发序列的评估:

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


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

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

  • 1

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

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

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

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

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

    引自MSDN

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

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

  • 9

    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()
    

相关问题