首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >F#交互中的时态递归

F#交互中的时态递归
EN

Stack Overflow用户
提问于 2017-01-16 15:40:01
回答 1查看 180关注 0票数 2

上下文:我一直在使用即席和Opusmodus在现场情况下进行计算机辅助创作(在观众面前编程古典音乐)。由于我是一个专业的.Net开发人员,我开始为.Net ( F#和C#组合)编写自己的软件,深受即席和Opusmodus的影响。我现在谈到了实现时态递归的问题,因为它在即席上工作,无法在.Net平台上找到这样的方法。一些方向和灵感将是非常有用的。

定义:时态递归最简单地定义为任何代码块(函数、方法等)计划在未来某个精确的时间点被召回。

方案中的示例:理论上,标准递归函数是一个临时递归函数,它会立即调用自身--即没有任何时间延迟。例如(在计划中):

代码语言:javascript
运行
复制
;; A standard recursive function

(define my-func
  (lambda (i)
    (println 'i: i)
    (if (< i 5) 
        (my-func (+ i 1)))))

使用时态递归的相同功能如下所示:

代码语言:javascript
运行
复制
;; 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#交互中做的事情是:

代码语言:javascript
运行
复制
let playMeAgain time<ms>
    instrument.Play "c4 e4 g4"
    callback (time<ms> playMeAgain)

playMeAgain 1000<ms>

然后,一旦我在F#互动中将此函数更改为以下内容,就不会再次调用以前的playMeAgain,但是函数的新版本(到playMeAgain的新绑定)将被调用:

代码语言:javascript
运行
复制
 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 )

代码语言:javascript
运行
复制
    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)

带参数的时间递归()我想要做的事情如下:

代码语言:javascript
运行
复制
let pp (notation:string) = 
    defOstinato "prepPiano" (fun (notation) -> 
        piano.Play notation
        let nextNotation = markovChainOfChords notation
        repOstinato ("prepPiano", 8. , nextNotation) 
    )

pp("c4e4g4)
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-01-16 16:41:50

下面是我所写的最初的答案,它提供了一个“通用”解决方案,一个la Lisp。但后来我想,也许你不需要“将军”,也许你只是有一个-两个功能,像这样(即“和弦”和“打击乐”,就这样?)如果是这样的话,您可能对将函数引用保持为可变变量感到满意:

代码语言:javascript
运行
复制
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())

不过,这也带来了一些不便:

  1. 您不能同时声明变量并初始化它并使其递归:可变值不能是递归的。因此,您必须使用以Unchecked.defaultOt<_>作为初始化值的笨拙技巧,然后分别分配它。
  2. 由于不能内联初始化值,所以必须显式地指定其类型。
  3. let f <- fun x -> ...的语法很笨拙。它看起来有点像fun x在尖括号里。
  4. 当将此类函数(如参数)传递给callback时,需要始终将其包装在lambda表达式中,如我的示例:(fun() -> temporal()),否则callback将得到对函数的引用,就像调用callback时一样,而不是按计划的时间那样。

原始答案

要实现Lisp的目标,您需要执行Lisp所做的事情--即按照Lisp定义函数的方式定义函数,而不是将函数定义为程序执行之外的静态构造,而是作为将名称映射到代码(即Map<string, obj -> obj> )的字典。

显然,要做到这一点,您必须放弃let构造并自行设计。我们叫它defun吧。它将把名称和代码作为参数,将它们放入字典(当然,字典必须是可变的--当然,这是它在Lisp中的实际工作方式),并返回一个“引用”类型--它本身就是一个具有相同签名的函数,但当调用时,它将首先从字典中获取实际代码并执行它。

因为您的函数可能都是不同的类型,所以整个过程都需要擦除类型(因此是obj -> obj)。这有点让人不舒服,但是嘿,这并不比Lisp更糟糕!

代码语言:javascript
运行
复制
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>

用法:

代码语言:javascript
运行
复制
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

请注意,从技术上讲,您可以通过犯错误来打破这一错误:

代码语言:javascript
运行
复制
defun "x" (fun a -> "Hello " + a)
x 5 6  // InvalidCastException

但是,这也是Lisp想要做的!

但我们还没有脱离困境:仍然不能进行递归。

代码语言:javascript
运行
复制
 let x = defun "x" (fun a b -> a + (x b a))  // `x` is not defined

嗯..。从技术上讲,你只需说let rec x,它就会起作用:

代码语言:javascript
运行
复制
 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

代码语言:javascript
运行
复制
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
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41679938

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档