f# - 如何创建支持单步执行之类的 ​​F# 工作流?

标签 f# workflow computation-expression

我想创建一个构建器来构建表达式,在每个步骤后返回类似延续的东西。

像这样:

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()
}

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

最佳答案

你的实现的问题是它为每次调用 Bind 返回“unit -> 'a”,所以你会为不同数量的步骤得到不同类型的结果(一般来说,这是一个可疑的定义monad/计算表达式)。

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

您实现中的另一个可疑之处是 Bind 的非标准类型成员。您的绑定(bind)将一个值作为第一个参数这一事实意味着您的代码看起来更简单一些(您可以编写 let! a = 1 )但是,您不能组成逐步计算。你可能希望能够写:

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

我上面描述的类型也可以让你这样写。一旦你有了类型,你只需要遵循 Bind 的类型签名和 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

希望这对您有所帮助!

PS:我发现这个问题很有趣!如果我将我的答案添加到我的博客 (http://tomasp.net/blog) 的博文中,您介意吗?谢谢!

关于f# - 如何创建支持单步执行之类的 ​​F# 工作流?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2026556/

相关文章:

haskell - 有没有办法强制 Haskell 代码排序像 F# 一样工作?

Git 工作流 : Share code between computers without pushing to public repo

workflow - 失明如何影响你的编码风格?

exception - 对于 vs2012 中使用 mstest 用 F# 编写的单元测试,如何断言引发异常?

f# - 在 F# 中获取非静态成员的名称

F# 搜索具有多个值/输入的记录

version-control - Mercurial 功能工作 - 保持定制工作干净的最佳实践?

.net - 何时在计算表达式中实现 `Zero` 成员?

f# - (如何)我可以让这个单子(monad)绑定(bind)尾递归吗?

f# - 我的记录器计算表达式有什么问题?