【发布时间】:2017-01-16 15:40:01
【问题描述】:
背景: 我一直在使用 Extempore 和 Opusmodus 在现场环境中进行计算机辅助作曲(在观众面前编写古典音乐)。由于我是一名专业的 .Net 开发人员,因此我开始编写自己的 .Net 软件(F# 和 C# 的组合),深受 Extempore 和 Opusmodus 的影响。我现在要实现时间递归,因为它可以在 Extempore 中工作,并且无法在 .Net 平台上找到方法。一些方向和灵感会很有帮助。
定义: 时间递归最简单地定义为任何代码块(函数、方法等),它安排自己在未来某个精确的时间点被回调。
Scheme 中的示例: 理论上,标准递归函数是一个时间递归函数,它会立即回调自身——即没有任何时间延迟。例如(在方案中):
;; A standard recursive function
(define my-func
(lambda (i)
(println 'i: i)
(if (< i 5)
(my-func (+ i 1)))))
相同的功能,但使用时间递归将是这样的:
;; A temporally recursive function with 0 delay
;; (callback (now) my-func (+ i 1)) ~= (my-func (+ i 1))
;; (now) here means immediately - straight away
(define my-func
(lambda (i)
(println 'i: i)
(if (< i 5)
(callback (now) my-func (+ i 1)))))
在前面的示例中 (callback (now) my-func (+ i 1)) 提供与 (my-func (+ i 1)) 类似的功能 - 两者都负责立即回调 my-func,传递i 的递增值。然而,这两个递归调用的操作方式却大不相同。由递归调用 (callback (now) my-func (+ i 1)) 形成的时间递归被实现为与当前控制状态不同的事件。换句话说,虽然调用 (my-func (+ i 1)) 维护控制流,并且可能(假设没有尾部优化)调用堆栈,但 (callback (now) my-func (+ i 1)) 调度my-func 然后将控制流返回给实时调度器。
我的问题 鉴于我已经在 C# 中启动并运行了调度程序部分并且运行良好。我还可以安排将由 C# 调度程序调用的 F# 函数。但是如何完成一个预定的函数调用,其中函数本身可以使用 F# Interactive 实时更改。
所以我希望能够在 F# 交互中做的事情是这样的:
let playMeAgain time<ms>
instrument.Play "c4 e4 g4"
callback (time<ms> playMeAgain)
playMeAgain 1000<ms>
然后,只要我在 F# Interactive 中将此函数更改为以下内容,之前的 playMeAgain 就不会再次被调用,而是会调用新版本的函数(与 playMeAgain 的新绑定):
let playMeAgain time<ms>
instrument.Play "d4 f4 a4"
callback (500<ms> playMeAgain)
在 .NET 下这可能吗?如何在 F# 下完成这种“热插拔”代码技术,因为这不是递归,因为 F# 定义了递归(因此需要 let rec playMeAgain 语法才能正确编译)。
有关时间递归的详细信息,请参阅this link
优秀的example of why Temporal Recursion is a crucial technique in this domain:
添加代码以方便讨论带参数的时间递归 模块 TemporalRecursion = 打开 LcmMidi.MidiDotNet 打开 LCM.Instrument 打开 LCM.Sound.Sounds 打开 LcmMidi.MidiDotNet
let mutable private temporalRecursives : Map<string, obj -> obj> = Map.empty
let private call name args =
let fn = temporalRecursives |> Map.find name
fn args
let private againHandler (args:System.EventArgs) =
let newArgs = args :?> LcmMidi.TemporalRecursionEventArgs
let name = newArgs.FunctionName
call name ()
|> ignore
let defOstinato name (f: 'a -> 'b) =
if (temporalRecursives.ContainsKey name) then
temporalRecursives <- Map.remove name temporalRecursives
temporalRecursives <- Map.add name (fun arg -> box <| f (unbox arg)) temporalRecursives
fun (a: 'a) -> call name (box a) |> unbox<'b>
let repOstinato (name:string) (time:float) =
let message = new LcmMidi.TemporalRecursionMessage(name, float32 time)
message.Again.Add againHandler
LcmMidi.MidiDotNet.TimedScheduler.Instance.Schedule(message)
带参数的时间递归 我希望能够做的是:
let pp (notation:string) =
defOstinato "prepPiano" (fun (notation) ->
piano.Play notation
let nextNotation = markovChainOfChords notation
repOstinato ("prepPiano", 8. , nextNotation)
)
pp("c4e4g4)
【问题讨论】:
标签: recursion f#-interactive temporal