在F#中使用Seq.map和Array.map作为相同代码时的不同输出

时间:2017-08-31 08:21:50

标签: f#

我有一个简单的基于事件的FizzBu​​zz实现,如下所示

import os

def file_size(filename):
    st = os.stat(filename)
    return st.st_size

当我使用// Events in F# type FizzBuzz() = let _event = Event<int>() member this.Event = _event.Publish member this.Check n = _event.Trigger n // Instantiate let fizzBuzzer = FizzBuzz() // Add an event handler fizzBuzzer.Event.Add (function | x when x%5=0 && x%3=0 -> printfn "FizzBuzz" | x when x%3=0 -> printfn "Fizz" | x when x%5=0 -> printfn "Buzz" | x -> printfn "%d" x) 测试时,我得到了预期的输出:

[|1..15|] |> Array.map fizzBuzzer.Check

但是,当我用1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz val it : unit [] = [|(); (); (); (); (); (); (); (); (); (); (); (); (); (); ()|] 测试时,我有

[1..15] |> Seq.map fizzBuzzer.Check

我不明白为什么两个输出应该是不同的。

2 个答案:

答案 0 :(得分:8)

F#序列 lazy ,因此它们只能根据消费代码的需要进行迭代。当您使用Seq.map函数时,它会生成一个尚未迭代的延迟序列。当F#Interactive获取序列作为表达式的结果时,它故意不打印整个序列,因为序列可能是无限的,或者可能需要很长时间才能计算。相反,它抓取序列的第一个四个元素,并打印它们。然后它检查序列是否还有更多值(即,序列&#39}的枚举器返回.MoveNext()函数true),如果是,则在末尾打印... 。所以序列实际上会计算出五个值,尽管你只能看到其中的四个。

如果你真的想要强制执行整个序列(因为你想要它的副作用,比如打印到屏幕上,要运行),那么你应该使用Seq.iter而不是Seq.map。请注意,Seq.iter仅接受返回()的函数(即unit类型),这清楚地表明它正在寻找具有副作用而非函数的函数有意义的回报值。

答案 1 :(得分:5)

Seq.map创建一个惰性序列,在使用之前不会对其元素进行求值。如您所见,toString方法仅显示序列的前四个元素,后跟...。这样做是因为序列可以是无限的(并且没有办法告诉),所以如果它总是试图显示所有元素,那么可能会导致无限循环。因为它只显示了四个第一个元素,所以它只需要评估那四个加上第五个元素(为了决定是否显示...,我想)。所以这些是你的函数执行的唯一次,直到你遍历整个序列。

另一方面,

Array.map创建一个数组,这是一个严格的数据结构。因此,在创建数组的那一刻,数组的所有元素都存在。

一般来说,赋予map的函数通常不会产生副作用(或者至少不是外部可见的副作用),因此在评估函数的每次调用时,都无关紧要。当然,这种情况并非如此。

此外,在使用阵列或一系列单元时几乎没有用处。显然,你对调用函数的副作用比对返回值更感兴趣。因此,不应使用map来创建您不感兴趣的数组或值序列,而应使用Seq.iter在原始数组的所有元素上调用函数,而不创建任何新数据结构

或者您也可以使用map创建数组或字符串序列而不是单位。