F# seq 行为

F# seq behavior

提问人:kam 提问时间:12/4/2020 最后编辑:kam 更新时间:12/5/2020 访问量:137

问:

我对 F# 中序列表达式的内部工作有点困惑。

通常,如果我们使用 seq 制作一个顺序文件读取器,而没有故意缓存数据

 seq { 
       let mutable current = file.Read()
       while current <> -1 do
           yield current
     }

如果我们尝试进行一些重复或回溯,我们最终会得到一些奇怪的行为,我的想法是,由于 Read() 是一个调用一些可变值的函数,如果我们重新迭代,我们不能期望输出是正确的。但是,即使在边界读取上,这也表现得很好?

let Read path =
    seq {
        use fp = System.IO.File.OpenRead path
        let buf = [| for _ in 0 .. 1024 -> 0uy |]
        let mutable pos = 1
        let mutable current = 0
        while pos <> 0 do
            if current = 0 then
                pos <- fp.Read(buf, 0, 1024)
            if pos > 0 && current < pos then
                yield buf.[current]
                current <- (current + 1) % 1024 
   } 

 let content = Read "some path" 

我们显然使用相同的缓冲区来增强性能,但假设我们读取 1025 字节,如果我们在仍然获得正确输出后尝试读取位置< 1025 的任何字节,它将触发对缓冲区的更新。这怎么可能,有什么区别?

F# 序列 行为

评论

1赞 Sebastian Redl 12/4/2020
你如何重申或回溯一个?seq
0赞 dbc 12/5/2020
FileStream已缓冲。来自文档FileStream 缓冲输入和输出以获得更好的性能。如果有一个未缓冲的流,则可以将其包装在 BufferedStream 中,它将被缓冲。但是你能解释一下你在这里想做什么吗?如果我们尝试进行一些重复或回溯,我们最终会得到一些奇怪的行为,因为序列不能被随机倒带或访问。
0赞 kam 12/7/2020
@Sebastian,example let s = 读取“某个路径”;设 s_1024 = Seq.skip 1024 s;let s_1025 = Seq.tail s_1024 上面的这适用于同一序列的不同实例,它们也将属于不同的缓冲区。如下所述,我在获取相同值时看到一些“奇怪”的东西的原因是因为文件流的范围。我正在实现一个“持久”文件读取器,为了方便起见,它可以作为序列传递。它的使用会读到某个点,然后如上所示回溯,但会有一个更大的嘎嘎声。
0赞 kam 12/7/2020
具有持久含义,假设没有其他线程同时写入文件,我们将在序列的同一位置获得相同的输出

答:

2赞 Arshia001 12/5/2020 #1

你的问题有点不清楚,所以我试着猜测。

当你创建一个 时,你实际上是在创建一个状态机,它只会在它需要的时候运行。当您从它请求第一个元素时,它将从顶部开始并运行,直到您的第一条指令。然后,当您请求另一个值时,它将从该点运行到下一个点,依此类推。seq { }yieldyield

请记住,a 会产生一个 ,这就像一个“执行计划”。每次开始迭代序列(例如,通过调用 ),都会在后台调用序列,这会导致创建一个新序列。它是实际提供价值的。你可以用更经典的术语来理解它,即有一个可以迭代的数组(可迭代或枚举)和该数组上的许多指针,每个指针都位于数组中的不同点(许多迭代器或枚举器)。seq { }IEnumerable<'T>Seq.headGetEnumeratorIEnumerator<'T>IEnumerator

在第一个代码中,很可能是块的外部。这意味着您正在读取的文件已烘焙到执行计划中;无论您开始迭代序列多少次,您都将始终从同一个文件中读取。这显然会导致不可预测的行为。fileseq

但是,在第二个代码中,该文件将作为块定义的一部分打开。这意味着,每次迭代序列时,你都会获得一个新的文件句柄,或者实质上,每个枚举器都会获得一个新的文件句柄。此代码起作用的原因是,您不能反转枚举器或多次迭代它,至少不能使用单个线程。seq

(现在,如果您要手动获取枚举器并将其推进到多个线程上,您可能会很快遇到问题。但这是一个不同的话题。

评论

0赞 kam 12/7/2020
谢谢,是的,可能就是这样。我没有考虑创建文件流的范围。我问这个问题的全部原因只是为了确保我在测试时涵盖了所有可能的极端情况,我可以看到我没有。对于多线程部分,这不是问题,因为它不太可能以对其有害的方式从多个胎面中受益。