【发布时间】:2016-06-28 03:57:40
【问题描述】:
我试图编写一个通用的mapFoldWhile 函数,它只是mapFold,但要求state 是option,并在遇到None 状态时立即停止。
我不想使用mapFold,因为它会转换整个列表,但我希望它在发现无效状态(即None)后立即停止。
这是我的第一次尝试:
let mapFoldWhile (f : 'State option -> 'T -> 'Result * 'State option) (state : 'State option) (list : 'T list) =
let rec mapRec f state list results =
match list with
| [] -> (List.rev results, state)
| item :: tail ->
let (result, newState) = f state item
match newState with
| Some x -> mapRec f newState tail (result :: results)
| None -> ([], None)
mapRec f state list []
List.rev 惹恼了我,因为练习的目的是提前退出,构建新列表应该更慢。
所以我查了一下 F# 自己的 map 做了什么,是:
let map f list = Microsoft.FSharp.Primitives.Basics.List.map f list
不祥的Microsoft.FSharp.Primitives.Basics.List.map可以找到here,看起来像这样:
let map f x =
match x with
| [] -> []
| [h] -> [f h]
| (h::t) ->
let cons = freshConsNoTail (f h)
mapToFreshConsTail cons f t
cons
consNoTail 的内容也在这个文件中:
// optimized mutation-based implementation. This code is only valid in fslib, where mutation of private
// tail cons cells is permitted in carefully written library code.
let inline setFreshConsTail cons t = cons.(::).1 <- t
let inline freshConsNoTail h = h :: (# "ldnull" : 'T list #)
所以我猜 F# 的不可变列表实际上是可变的,因为性能?我有点担心这一点,因为我认为这是 F# 中的“必经之路”,因此使用了 prepend-then-reverse list 方法。
我一般对 F# 或函数式编程不是很有经验,所以也许(可能)创建一个新的 mapFoldWhile 函数的整个想法是错误的,但是我该怎么做呢?
我经常发现自己需要“提前退出”,因为某个收藏项目“无效”,而我知道我不必查看其他内容。在某些情况下我使用List.pick 或Seq.takeWhile,但在其他情况下我需要做更多的事情(mapFold)。
对于此类问题是否有有效的解决方案(特别是mapFoldWhile,通常是“提前退出”)与函数式编程概念,还是我必须切换到命令式解决方案/使用Collections.Generics.List?
【问题讨论】:
-
如果您不需要使用
List<T>,您可以在mapFoldWhile内部使用ResizeArray<T>并返回T[]。因此,您正在使用可变性构建结果,但函数的 API 是不可变的(这是 F# 在内部所做的)。或者您使用高效的流媒体库,例如 NessosStreams -
定义高效。如果您的文件夹功能足够昂贵以保证提前退出,那么您可能没有理由关心
List.rev。你量过吗? -
@scrwtp 是的,这似乎是一个比昨天更大的问题(一个漫长的周末之后的一个漫长的星期一......)。当我意识到
List.rev没有被其他类似map的函数使用时,我感到措手不及,担心它可能会很慢,然后找不到替代方法。我确实相信提前退出应该很容易实现,为此 Tomas 指出了一个很好的解决方案。
标签: f#