上下文:我一直在使用即席和Opusmodus在现场情况下进行计算机辅助创作(在观众面前编程古典音乐)。由于我是一个专业的.Net开发人员,我开始为.Net ( F#和C#组合)编写自己的软件,深受即席和Opusmodus的影响。我现在谈到了实现时态递归的问题,因为它在即席上工作,无法在.Net平台上找到这样的方法。一些方向和灵感将是非常有用的。
定义:时态递归最简单地定义为任何代码块(函数、方法等)计划在未来某个精确的时间点被召回。
方案中的示例:理论上,标准递归函数是一个临时递归函数,它会立即调用自身--即没有任何时间延迟。例如(在计划中):
;; 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)))))
在前面的示例中(回调(现在) my-func (+ i 1))提供与(my-func (+ i 1))类似的函数--两者都负责立即调用my-func,为i传递一个递增的值。然而,这两个递归调用的操作方式有很大的不同。由递归调用(回调(现在) my-func (+ I1))形成的时间递归是作为与当前控制状态不同的事件实现的。换句话说,当调用(my-func (+ i 1))维护控制流,并且可能(假设没有尾部优化)调用堆栈时,(回调(现在) my-func (+ i 1))调度我的-func,然后将控制流返回给实时调度程序。
我的问题,考虑到我已经在C#中安装并运行了调度程序,并且运行良好。我还可以调度一个F#函数,这个函数将由C#调度程序调用。但是,如何使用F#交互实现一个调度函数调用,其中函数本身可以进行实时更改。
所以,我想在F#交互中做的事情是:
let playMeAgain time<ms>
instrument.Play "c4 e4 g4"
callback (time<ms> playMeAgain)
playMeAgain 1000<ms>
然后,一旦我在F#互动中将此函数更改为以下内容,就不会再次调用以前的playMeAgain,但是函数的新版本(到playMeAgain的新绑定)将被调用:
let playMeAgain time<ms>
instrument.Play "d4 f4 a4"
callback (500<ms> playMeAgain)
在.NET下,这是可能的吗?如何在F#下完成这种“热可换”的代码技术,因为这不是递归,因为F#定义了递归(因此需要let rec playMeAgain语法来正确编译)。
有关时间递归的详细信息,请参阅此链接
添加了代码,以便于讨论参数为模块的时间递归( TemporalRecursion = open LcmMidi.MidiDotNet open LCM.Instrument open LCM.Sound.Sounds open 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)
发布于 2017-01-16 16:41:50
下面是我所写的最初的答案,它提供了一个“通用”解决方案,一个la Lisp。但后来我想,也许你不需要“将军”,也许你只是有一个-两个功能,像这样(即“和弦”和“打击乐”,就这样?)如果是这样的话,您可能对将函数引用保持为可变变量感到满意:
let mutable f : int -> int -> int = Unchecked.defaultof<_>
f <- fun a b -> a + b
f 5 6 // it = 11
f <- fun a b -> a - b
f 5 6 // it = -1
f <- fun a b -> if a % 2 = 0 then a + b else f b (a-1) // recursion
f 5 6 // it = 10
let mutable temporal : unit -> unit
temporal <- fun () -> doStuff; callback (time + 10) (fun () -> temporal())
不过,这也带来了一些不便:
Unchecked.defaultOt<_>
作为初始化值的笨拙技巧,然后分别分配它。let f <- fun x -> ...
的语法很笨拙。它看起来有点像fun x
在尖括号里。callback
时,需要始终将其包装在lambda表达式中,如我的示例:(fun() -> temporal())
,否则callback
将得到对函数的引用,就像调用callback
时一样,而不是按计划的时间那样。原始答案
要实现Lisp的目标,您需要执行Lisp所做的事情--即按照Lisp定义函数的方式定义函数,而不是将函数定义为程序执行之外的静态构造,而是作为将名称映射到代码(即Map<string, obj -> obj>
)的字典。
显然,要做到这一点,您必须放弃let
构造并自行设计。我们叫它defun
吧。它将把名称和代码作为参数,将它们放入字典(当然,字典必须是可变的--当然,这是它在Lisp中的实际工作方式),并返回一个“引用”类型--它本身就是一个具有相同签名的函数,但当调用时,它将首先从字典中获取实际代码并执行它。
因为您的函数可能都是不同的类型,所以整个过程都需要擦除类型(因此是obj -> obj
)。这有点让人不舒服,但是嘿,这并不比Lisp更糟糕!
let mutable definitions : Map<string, obj -> obj> = Map.empty
let call name arg =
let fn = Map.find name definitions // NOTE: will throw when name is not defined
fn arg
let defun name (f: 'a -> 'b) =
definitions <- Map.add name (fun arg -> box <| f (unbox arg)) definitions
fun (a: 'a) -> call name (box a) |> unbox<'b>
用法:
let x = defun "x" (fun a b -> a + b)
x 5 6 // it = 11
defun "x" (fun a b -> a - b)
x 5 6 // it = -1
请注意,从技术上讲,您可以通过犯错误来打破这一错误:
defun "x" (fun a -> "Hello " + a)
x 5 6 // InvalidCastException
但是,这也是Lisp想要做的!
但我们还没有脱离困境:仍然不能进行递归。
let x = defun "x" (fun a b -> a + (x b a)) // `x` is not defined
嗯..。从技术上讲,你只需说let rec x
,它就会起作用:
let rec x = defun "x" (fun a b -> if a % 2 = 0 then a + b else x b (a-1))
x 5 6 // it = 10
但是这将生成编译器警告,因为从技术上讲,取决于x
的主体,编译器不能保证整个定义不会导致无限递归。在这种情况下,它不会使用,因为x
是在没有立即调用的lambda表达式中使用的。但编译器不知道这一点,因此出现了警告。
所以你可以在这里停下来。但是这种不稳定感让我有点不舒服,所以我将提供另一种递归方法:向函数传递一个额外的参数,这将是对其本身的引用。然后函数可以调用它或传递给callback
。
let mutable definitions : Map<string, obj -> obj> = Map.empty
let call name arg =
let fn = Map.find name definitions
fn arg // NOTE: will throw when name is not defined
let defun name (f: ('a -> 'b) -> 'a -> 'b) =
let self = fun (a: 'a) -> call name (box a) |> unbox<'b>
definitions <- Map.add name (fun arg -> box <| f self (unbox arg)) definitions
self
// Note: no `rec` keyword
let x = defun "x" (fun recur a b -> if a % 2 = 0 then a + b else recur b (a-1))
x 5 6 // it = 10
defun "x" (fun recur a b -> a - b)
x 5 6 // it = -1
defun "x" (fun recur a -> "Hello " + a)
x 5 6 // InvalidCastException
https://stackoverflow.com/questions/41679938
复制相似问题