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

nih*_*il0 1 f#

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

// 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) 
Run Code Online (Sandbox Code Playgroud)

当我使用我测试它[|1..15|] |> Array.map fizzBuzzer.Check得到预期的输出:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
val it : unit [] =
  [|(); (); (); (); (); (); (); (); (); (); (); (); (); (); ()|]
Run Code Online (Sandbox Code Playgroud)

但是,当我测试时[1..15] |> Seq.map fizzBuzzer.Check,我有

1
2
Fizz
4
Buzz
val it : seq<unit> = seq [(); (); (); (); ...]
Run Code Online (Sandbox Code Playgroud)

我不明白为什么两个输出应该不同.

rmu*_*unn 8

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

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


sep*_*p2k 5

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

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

一般来说,赋予的函数map通常不具有副作用(或者至少不是外部可见的副作用),因此当评估函数的每个调用时,无关紧要.当然,这并非如此.

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

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