我尝试创建一个自定义展开函数,返回其最后一个累加器值,如:
val unfold' : generator:('State -> ('T * 'State) option) -> state:'State -> 'T list * 'State
我设法做了以下事情:
let unfold' generator state =
let rec loop resultList state =
match generator state with
| Some (value, state) -> loop (value :: resultList) state
| None -> (List.rev resultList), state
loop [] state
但是我想避免List.rev
结果列表并使用正确的顺序生成它。我想有必要使用continuation来构建列表,但我对函数式编程很陌生,并且还没有设法将我的思想包围在延续中;我能想象的所有替代方案都会将累加器放在结果列表中,或者不允许函数返回它。
有没有办法做到这一点?
由于这是一项个人学习练习,我更倾向于回答如何解释,而不是简单地提供完整的代码。
答案 0 :(得分:5)
没有List.rev
的方法是传递函数而不是resultList
参数。让我们调用该函数buildResultList
。在每个步骤中,此函数将获取列表的已构建尾部,在当前项目前添加,然后将其传递给上一个步骤中的函数,该步骤将附加 previous < / em> item,将其传递给 previous-previous 步骤中的函数,依此类推。此链中的最后一个函数将在列表中添加第一个项目。整个递归循环的结果将是链的 last 函数(它调用所有先前的函数),然后您将使用空列表作为参数调用。我担心,只要编写代码就可以清楚地看到这一点。
然而,问题是,对于&#34;更好的&#34;的任何定义,这都不会更好。由于计算正在进行&#34;转发&#34;,并且生成了结果列表&#34;向后&#34; (head :: tail,Lisp-style),你有来累积结果某处。在您的代码中,您将其累积在临时列表中,但如果您将其修改为使用continuation,那么您将在堆上将其作为一系列在链中相互引用的闭包累积。有人可能会争辩说,它本质上是同一个列表,只是模糊不清。
您可以尝试的另一种方法是使用延迟序列:构建递归seq
计算,它将yield
当前项目,然后yield!
本身。然后你可以枚举这个序列,它不会需要一个临时的&#34;存储。 但,如果您仍希望在最后获得列表,则必须通过List.ofSeq
将序列转换为列表,然后猜猜这是怎么回事将要执行?从理论上讲,从纯粹的数学角度来看,List.ofSeq
将以完全相同的方式实现:首先构建临时列表然后反转它。但是F#库在这里作弊:它以可变的方式构建列表,因此它不必反转。
最后,由于这是一个学习练习,你也可以自己实现相当于懒惰的序列。现在,标准的.NET序列(又名IEnumerable<_>
,这是Seq<_>
的别名)本质上是可变的:你正在改变内部状态每次移动到下一个项目时都会使用迭代器。你可以做到这一点,或者,本着学习的精神,你可以做一个不可变的等价物。这将是几乎像列表(即head :: tail),除了它,因为它 lazy ,&#34; tail&#34;必须是承诺而不是实际的顺序,所以:
type LazySeq<'t> = LazySeq of (unit -> LazySeqStep<'t>)
and LazySeqStep<'t> = Empty | Cons of head: 't * tail: LazySeq<'t>
枚举的方法是调用函数,它将返回下一个项目加上序列的尾部。然后你可以将你的展开编写为一个函数,它将当前项作为head
返回,然后只返回自己包含在tail
的闭包中。事实证明,非常简单。
答案 1 :(得分:2)
感谢Fyodor Soikin的回答,这是最终的功能:
let unfold' generator state =
let rec loop build state =
match generator state with
| Some (value, state) -> loop (fun newValue -> build (value :: newValue)) state
| None -> (build []), state
loop id state