代码之家  ›  专栏  ›  技术社区  ›  James Moore

你如何创建一个F#工作流来支持单步操作?

  •  3
  • James Moore  · 技术社区  · 16 年前

    我想创建一个构建器,它构建的表达式在每个步骤后都会返回类似于continuation的内容。

    module TwoSteps = 
      let x = stepwise {
        let! y = "foo"
        printfn "got: %A" y
        let! z = y + "bar"
        printfn "got: %A" z
        return z
      }
    
      printfn "two steps"
      let a = x()
      printfn "something inbetween"
      let b = a()
    

    其中“let a”行返回包含后面要计算的其余表达式的内容。

    对于每个步骤数,使用单独的类型执行此操作非常简单,但当然不是特别有用:

    type Stepwise() =
      let bnd (v: 'a) rest = fun () -> rest v
      let rtn v = fun () -> Some v
      member x.Bind(v, rest) = 
        bnd v rest
      member x.Return v = rtn v
    
    let stepwise = Stepwise()
    
    module TwoSteps = 
      let x = stepwise {
        let! y = "foo"
        printfn "got: %A" y
        let! z = y + "bar"
        printfn "got: %A" z
        return z
      }
    
      printfn "two steps"
      let a = x()
      printfn "something inbetween"
      let b = a()
    
    module ThreeSteps = 
      let x = stepwise {
        let! y = "foo"
        printfn "got: %A" y
        let! z = y + "bar"
        printfn "got: %A" z
        let! z' = z + "third"
        printfn "got: %A" z'
        return z
      }
    
      printfn "three steps"
      let a = x()
      printfn "something inbetween"
      let b = a()
      printfn "something inbetween"
      let c = b()
    

    结果就是我想要的:

    two steps
    got: "foo"
    something inbetween
    got: "foobar"
    three steps
    got: "foo"
    something inbetween
    got: "foobar"
    something inbetween
    got: "foobarthird"
    

    let someHandler = Stepwise<someMergedEventStream>() {
      let! touchLocation = swallowEverythingUntilYouGetATouch()
      startSomeSound()
      let! nextTouchLocation = swallowEverythingUntilYouGetATouch()
      stopSomeSound()
    }
    

    并让事件触发工作流中的下一步(特别是,我想在iPhone上用MonoTouch-F#玩这种东西。绕过objc选择器让我发疯。)

    2 回复  |  直到 16 年前
        1
  •  2
  •   Tomas Petricek    16 年前

    实现的问题在于它返回“unit->”对于绑定的每个调用,都是一个“0”,因此对于不同的步骤数,您将得到不同类型的结果(通常,这是monad/计算表达式的可疑定义)。

    正确的解决方案应该是使用其他类型,它可以表示具有任意步数的计算。您还需要区分两种类型的步骤——有些步骤只计算计算的下一步,有些步骤返回结果(通过 return 关键字)。我要用打字机 seq<option<'a>> . 这是一个延迟序列,因此读取下一个元素将计算下一步的计算。序列将包含 None 值,但最后一个值除外,该值将为 Some(value) ,表示使用返回的结果 返回 .

    实现中的另一个可疑之处是非标准类型的 Bind let! a = 1 )但是,您不能编写逐步计算。您可能希望能够编写:

    let foo() = stepwise { 
      return 1; }
    let bar() = stepwise { 
      let! a = foo()
      return a + 10 }
    

    绑定 Return 在实现中,您将看到:

    type Stepwise() = 
      member x.Bind(v:seq<option<_>>, rest:(_ -> seq<option<_>>)) = seq {
        let en = v.GetEnumerator()
        let nextVal() = 
          if en.MoveNext() then en.Current
          else failwith "Unexpected end!" 
        let last = ref (nextVal())
        while Option.isNone !last do
          // yield None for each step of the source 'stepwise' computation
          yield None
          last := next()
        // yield one more None for this step
        yield None      
        // run the rest of the computation
        yield! rest (Option.get !last) }
      member x.Return v = seq { 
        // single-step computation that yields the result
        yield Some(v) }
    
    let stepwise = Stepwise() 
    // simple function for creating single-step computations
    let one v = stepwise.Return(v)
    

    let oneStep = stepwise {
      // NOTE: we need to explicitly create single-step 
      // computations when we call the let! binder
      let! y = one( "foo" ) 
      printfn "got: %A" y 
      return y + "bar" } 
    
    let threeSteps = stepwise { 
      let! x = oneStep // compose computations :-)
      printfn "got: %A" x 
      let! y = one( x + "third" )
      printfn "got: %A" y
      return "returning " + y } 
    

    如果您想一步一步地运行计算,您可以简单地迭代返回的序列,例如使用F# for 关键词。下面还打印了步骤的索引:

    for step, idx in Seq.zip threeSteps [ 1 .. 10] do
      printf "STEP %d: " idx
      match step with
      | None _ -> ()
      | Some(v) -> printfn "Final result: %s" v
    

    我发现这个问题很有趣!你介意我把我的答案添加到我的博客上吗( http://tomasp.net/blog )? 谢谢

        2
  •  1
  •   Community Mohan Dere    9 年前

    单子和计算构建器让我很困惑,但我已经改编了我在 earlier SO post

    下面的代码包含一个操作队列和一个表单,单击事件在其中侦听操作队列中可用的下一个操作。下面的代码是一个连续执行4个操作的示例。在FSI中执行它并开始单击表单。

    open System.Collections.Generic
    open System.Windows.Forms
    
    type ActionQueue(actions: (System.EventArgs -> unit) list) =
        let actions = new Queue<System.EventArgs -> unit>(actions) //'a contains event properties
        with
            member hq.Add(action: System.EventArgs -> unit) = 
               actions.Enqueue(action)
            member hq.NextAction = 
                if actions.Count=0 
                    then fun _ -> ()
                    else actions.Dequeue()
    
    //test code
    let frm = new System.Windows.Forms.Form()
    
    let myActions = [
        fun (e:System.EventArgs) -> printfn "You clicked with %A" (e :?> MouseEventArgs).Button
        fun _ -> printfn "Stop clicking me!!"
        fun _ -> printfn "I mean it!"
        fun _ -> printfn "I'll stop talking to you now."
        ]
    
    let aq = new ActionQueue(myActions)
    
    frm.Click.Add(fun e -> aq.NextAction e)
    
    //frm.Click now executes the 4 actions in myActions in order and then does nothing on further clicks
    frm.Show()
    

    您可以单击表单4次,然后再单击就不会发生任何事情。

    let moreActions = [
        fun _ -> printfn "Ok, I'll talk to you again. Just don't click anymore, ever!"
        fun _ -> printfn "That's it. I'm done with you."
        ]
    
    moreActions |> List.iter (aq.Add)