An implementation problem of F# Seq

前端 未结 2 1146
自闭症患者
自闭症患者 2021-02-14 02:09

I am digging into F# source code recently.

in Seq.fs:

// Binding. 
//
// We use a type defintion to apply a local dynamic optimization. 
// We automatic         


        
2条回答
  •  不要未来只要你来
    2021-02-14 03:09

    I'm not sure what sort of answer you're looking for. As you have noticed, the comment does not match the behavior of the compiler. I can't say whether this is an instance of a comment getting out of sync with the implementation or whether it's actually a performance bug (for example, the spec doesn't seem to call out any specific performance requirements).

    However, it should be possible in theory for the compiler's machinery to generate an implementation which operates on your example in linear time. In fact, it's even possible to build such an implementation in a library using computation expressions. Here's a rough example, based largely on the paper Tomas cited:

    open System.Collections
    open System.Collections.Generic
    
    type 'a nestedState = 
    /// Nothing to yield
    | Done 
    /// Yield a single value before proceeding
    | Val of 'a
    /// Yield the results from a nested iterator before proceeding
    | Enum of (unit -> 'a nestedState)
    /// Yield just the results from a nested iterator
    | Tail of (unit -> 'a nestedState)
    
    type nestedSeq<'a>(ntor) =
      let getEnumerator() : IEnumerator<'a> =
        let stack = ref [ntor]
        let curr = ref Unchecked.defaultof<'a>
        let rec moveNext() =
          match !stack with
          | [] -> false
          | e::es as l -> 
              match e() with
              | Done -> stack := es; moveNext()  
              | Val(a) -> curr := a; true
              | Enum(e) -> stack := e :: l; moveNext()
              | Tail(e) -> stack := e :: es; moveNext()
        { new IEnumerator<'a> with
            member x.Current = !curr
          interface System.IDisposable with
            member x.Dispose() = () 
          interface IEnumerator with
            member x.MoveNext() = moveNext()
            member x.Current = box !curr
            member x.Reset() = failwith "Reset not supported" }
      member x.NestedEnumerator = ntor
      interface IEnumerable<'a> with
        member x.GetEnumerator() = getEnumerator()
      interface IEnumerable with
        member x.GetEnumerator() = upcast getEnumerator()
    
    let getNestedEnumerator : 'a seq -> _ = function
    | :? ('a nestedSeq) as n -> n.NestedEnumerator
    | s -> 
        let e = s.GetEnumerator()
        fun () ->
          if e.MoveNext() then
            Val e.Current
          else
            Done
    
    let states (arr : Lazy<_[]>) = 
      let state = ref -1 
      nestedSeq (fun () -> incr state; arr.Value.[!state]) :> seq<_>
    
    type SeqBuilder() = 
      member s.Yield(x) =  
        states (lazy [| Val x; Done |])
      member s.Combine(x:'a seq, y:'a seq) = 
        states (lazy [| Enum (getNestedEnumerator x); Tail (getNestedEnumerator y) |])
      member s.Zero() =  
        states (lazy [| Done |])
      member s.Delay(f) = 
        states (lazy [| Tail (f() |> getNestedEnumerator) |])
      member s.YieldFrom(x) = x 
      member s.Bind(x:'a seq, f) = 
        let e = x.GetEnumerator() 
        nestedSeq (fun () -> 
                     if e.MoveNext() then  
                       Enum (f e.Current |> getNestedEnumerator) 
                     else  
                       Done) :> seq<_>
    
    let seq = SeqBuilder()
    
    let rec walkr n = seq { 
      if n > 0 then
        return! walkr (n-1)
        return n
    }
    
    let rec walkl n = seq {
      if n > 0 then
        return n
        return! walkl (n-1)
    }
    
    let time = 
      let watch = System.Diagnostics.Stopwatch.StartNew()
      walkr 10000 |> Seq.iter ignore
      watch.Stop()
      watch.Elapsed
    

    Note that my SeqBuilder is not robust; it's missing several workflow members and it doesn't do anything regarding object disposal or error handling. However, it does demonstrate that SequenceBuilders don't need to exhibit quadratic running time on examples like yours.

    Also note that there's a time-space tradeoff here - the nested iterator for walkr n will iterate through the sequence in O(n) time, but it requires O(n) space to do so.

提交回复
热议问题