在F#中修改了map2(没有截断列表) - 如何用惯用法做到这一点?

时间:2010-05-15 08:31:29

标签: haskell f# refactoring

我想将这样的功能重写为F#:

zipWith' :: (a -> b -> c) -> (a -> c) -> (b -> c) -> [a] -> [b] -> [c]
zipWith' _ _ h []     bs     = h `map` bs
zipWith' _ g _ as     []     = g `map` as
zipWith' f g h (a:as) (b:bs) = f a b:zipWith f g h as bs

我的第一次尝试是:

let inline private map2' (xs : seq<'T>) (ys : seq<'U>) (f : 'T -> 'U -> 'S) (g : 'T -> 'S) (h : 'U -> 'S) =
    let xenum = xs.GetEnumerator()
    let yenum = ys.GetEnumerator()
    seq {
        let rec rest (zenum : IEnumerator<'A>) (i : 'A -> 'S) =
            seq {
                yield i(zenum.Current)
                if zenum.MoveNext() then yield! (rest zenum i) else zenum.Dispose()
            }
        let rec merge () =
            seq {
                if xenum.MoveNext()
                then
                    if yenum.MoveNext()
                    then yield (f xenum.Current yenum.Current); yield! (merge ())
                    else yenum.Dispose(); yield! (rest xenum g)
                else
                    xenum.Dispose()
                    if yenum.MoveNext()
                    then yield! (rest yenum h)
                    else yenum.Dispose()
            }
        yield! (merge ())
    }

然而,它很难被认为是惯用的。我听说过LazyList,但我无法在任何地方找到它。

3 个答案:

答案 0 :(得分:3)

正如Brian所说,F#在PowerPack中提供了一个常见的Haskell风格的惰性列表,因此您可以使用它。不幸的是,使用标准F#序列表达式来表达这种事情是没有好办法的,因为它们只能表达使用for从单个序列读取数据的计算(在您的情况下,您需要从中读取)多个序列)。

然而,可以编写一个计算(类似于seq { .. })来处理IEnumerator<T> - 这是一个必要的计算,修改后面的枚举器,但它可以用于编码seq不够好时的模式。我正在计划博客,但与此同时,您可以get it here(代码还包括您问题的解决方案)。

然后你可以这样写:

// Zip using specified functions for sequences 
let zipWithFun f g h (a:seq<_>) (b:seq<_>) = 
  // Local helper function that works with iterators (xs and ys)
  let rec zipWithFunE xs ys = iter {
    // Try to get first element from both iterators (mutates the iterators!)
    let! x = xs
    let! y = ys
    match x, y with 
    | Some(x), Some(y) -> 
        // If both produced value, then combine values using 'f' & continue
        yield f (x, y)
        yield! zipWithFunE xs ys 
    // If only one produced value, yield the value and then return the
    // remaining values projected using one of the functions
    | Some(rest), _ ->
        yield g rest
        yield! ys |> Enumerator.map g
    | _, Some(rest) ->
        yield g rest
        yield! ys |> Enumerator.map g
    | _ -> () }

  // Construct a 'seq' value from a function that processes enumerators
  Enumerator.toSeq (fun () -> 
    zipE (a.GetEnumerator()) (b.GetEnumerator()))

代码的核心部分几乎复制了原始Haskell解决方案的结构,这使得这种方法非常有吸引力,但您仍然可以直接使用序列,而无需将数据复制到其他数据结构。

答案 1 :(得分:2)

LazyList位于F#PowerPack中。你可能需要它来更优雅地写这个。鉴于您的首次尝试代码看起来有多好,我希望您在编写LazyList版本时可以毫无困难。

答案 2 :(得分:1)

我建议:

let forever xs =
    Seq.append (Seq.map Some xs) (Seq.initInfinite (fun _ -> None))

let zipWith f g h xs ys =
    Seq.zip (forever xs) (forever ys)
    |> Seq.takeWhile (fun (x, y) -> Option.isSome x || Option.isSome y)
    |> Seq.map ( function
        | (Some x, Some y) -> f x y
        | (Some x, None  ) -> g x
        | (None  , Some y) -> h y
        | _ -> failwith "quite unexpected !" )