Как вы создаете рабочий процесс F #, который позволяет что-то вроде пошагового? - PullRequest
3 голосов
/ 08 января 2010

Я хотел бы создать конструктор, который создает выражения, которые возвращают что-то вроде продолжения после каждого шага.

Примерно так:

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 - F # на iPhone. Передача переключателей objc приводит меня в бешенство.)

Ответы [ 2 ]

2 голосов
/ 08 января 2010

проблема с вашей реализацией в том, что она возвращает «unit -> 'a» для каждого вызова Bind, поэтому вы получите другой тип результата для разного количества шагов (в общем, это подозрительное определение монада / вычислительное выражение).

Правильным решением должно быть использование некоторого другого типа, который может представлять вычисление с произвольным числом шагов. Вам также нужно будет различать два типа шагов - некоторые шаги просто оценивают следующий шаг вычисления, а некоторые возвращают результат (через ключевое слово return). Я буду использовать тип seq<option<'a>>. Это ленивая последовательность, поэтому чтение следующего элемента оценит следующий шаг вычисления. Последовательность будет содержать None значений, за исключением последнего значения, которое будет Some(value), представляющего результат, возвращаемый с использованием return.

Еще одна подозрительная вещь в вашей реализации - нестандартный тип члена 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)? Спасибо!

1 голос
/ 08 января 2010

Монады и создатели вычислений приводят меня в замешательство, но я адаптировал кое-что, что я сделал в более ранней публикации 1002 *. Может быть, некоторые кусочки могут быть полезны.

Приведенный ниже код содержит очередь действий и форму, в которой событие Click прослушивает следующее действие, доступное в очереди действий. Код ниже - пример с 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)
...