在 F# 中创建接受通用委托的函数

问题描述 投票:0回答:2

我为 GLFW 创建了一些 F# 绑定。为此,有几个回调。现在,除了回调定义(这是一种委托类型)、更惯用的 F# 回调函数、这些函数类型的参数以及将回调函数编组到 GLFW 的实际绑定之外,我设置的所有回调包装器基本上都是相同的.

这里有两个绑定和回调委托:

module Windowing.GLFW.Native.Window
open System.Runtime.InteropServices

[<Struct>]
type GLFWwindow = struct end

type GLFWwindowsizefun = delegate of window: nativeptr<GLFWwindow> * width_height: (int * int) -> unit

type GLFWwindowfocusfun = delegate of window: nativeptr<GLFWwindow> * focused: int -> unit

[<DllImport(glfwDLLPath, CallingConvention = CallingConvention.Cdecl)>]
extern GLFWwindowsizefun glfwSetWindowSizeCallback(GLFWwindow* window, GLFWwindowsizefun callback)

/// Sets the focus callback for the specified window.
[<DllImport(glfwDLLPath, CallingConvention = CallingConvention.Cdecl)>]
extern GLFWwindowfocusfun glfwSetWindowFocusCallback(GLFWwindow* window, GLFWwindowfocusfun callback

然后我有高级包装器,旨在隐藏互操作细节:

module Windowing.GLFW.FSharp
open Windowing.GLFW.Native
open FSharp.NativeInterop
open System.Runtime.InteropServices

type GLFWWindow = GLFWWindow of nativeptr<Window.GLFWwindow>

let setWindowSizeCallback (callbackRef: outref<Window.GLFWwindowsizefun>)
                          (callback: GLFWWindow * int * int -> unit)
                          (GLFWWindow window) =
    let callback = Window.GLFWwindowsizefun(fun window (width, height) -> callback(GLFWWindow window, width, height))
    callbackRef <- callback
    Window.glfwSetWindowSizeCallback(window, callback) |> ignore

let setWindowFocusCallback (callbackRef: outref<Window.GLFWwindowfocusfun>)
                           (callback: GLFWWindow * int -> unit)
                           (GLFWWindow window) =
    let callback = Window.GLFWwindowfocusfun(fun window focused -> callback(GLFWWindow window, focused))
    callbackRef <- callback
    Window.glfwSetWindowFocusCallback(window, callback) |> ignore

如您所见,这些包装函数实际上是相同的。要创建一个新的,我只需复制一个,更改函数委托类型、回调类型定义和 GLFW 绑定。

我想要的是创建一个处理所有这些机制的通用函数,然后定义实际的包装器,我只需传入相关的类型值。但是,我无法让它与通用委托约束一起工作。

例如,我尝试过这个:

let setGenericCallback (callbackDef: 'GLFWcallback when 'GLFWcallback: delegate<nativeptr<Window.GLFWwindow> * 'Args, unit>)
                       (setCallbackFun: nativeptr<Window.GLFWwindow> * 'GLFWcallback -> 'GLFWcallback)
                       (callback: GLFWWindow * 'Args -> unit)
                       (callbackRef: outref<'GLFWcallback>)
                       (GLFWWindow window) =
    let callback = callbackDef(fun window args -> callback(GLFWWindow window, args))
    callbackRef <- callback
    setCallbackFun(window, callback) |> ignore

但是在

let callback = callbackDef ...
行上,我收到错误“该值不是函数,无法应用”。如果我尝试
callbackDef.Invoke
,我会收到错误“查找不确定类型的对象”,即使它应该是委托。同样奇怪的是,我无法用
'GLFWcallback
静态约束
^GLFWcallback
。看来
outref<type>
不能采用
type
的静态约束类型。

我提到的两个资源是:

如果我有这样一个通用函数,那么我可以将其重新定义为:

let setWindowSizeCallback callbackRef callback window =
    setGenericCallback Window.GLFWwindowposfun
                       Window.glfwSetWindowPosCallback
                       callback
                       callbackRef
                       window

所以,我被困住了。我收到的错误对我来说没有意义,也没有告诉我真正的问题是什么。委托约束似乎没有正常工作或者我没有正确使用它。

如何让它发挥作用?如何正确定义这样的函数以允许我使用通用委托?请注意,我的代表并不那么通用。他们都是

delegate of nativeptr<GLFWwindow> * 'T -> unit
类型。

.net f# delegates
2个回答
1
投票

您可以将类型化委托转换为非类型化委托,然后动态调用它。我也不确定对性能的影响是什么 - 我怀疑它可能会更慢,但我还没有尝试过。

我无法运行它,但进行了以下类型检查(但我做了一些编辑,因为我对

GLFWindow
GLFwindow
之间的差异感到困惑):

let inline setGenericCallback 
  (callbackDef: 'GLFWcallback when 'GLFWcallback: delegate<nativeptr<GLFWwindow> * 'Args, unit>) 
      (callback: GLFWWindow * (int * int) -> unit) (GLFWWindow window) =
    let callback = 
      (callbackDef :> System.Delegate).DynamicInvoke
        [| fun window args -> callback(GLFWWindow window, args) |]
    ()

也就是说,我同意其他评论者的说法——接受一些代码重复只是更容易。这对于基本上是手动自动生成的绑定的代码尤其有意义。


0
投票

遇到同样的问题后,进一步的研究最终发现了这个问题以及以下规范:

5.2.8 委托约束 显式委托约束具有以下形式:

typar : delegate<tupled-arg-type, return-type>

在约束求解期间(第 14.5 节),如果

constraint type : delegate<tupled-arg-type, return-types>
是具有声明
type
D
的委托类型
type D = delegate of object * arg1 * ... * argN
,则满足
tupled-arg-type = arg1 * ... * argN
。也就是说,委托必须匹配 CLI 设计模式,其中
sender
对象是事件的第一个参数。

注意:此约束形式的存在主要是为了允许定义与事件编程相关的某些 F# 库函数。它很少直接在 F# 编程中使用。

delegate
约束并不暗示任何有关子类型的信息。特别是,一个 “委托”约束并不意味着该类型是
System.Delegate
的子类型。

delegate
约束仅适用于遵循 CLI 事件处理程序通常形式的委托类型,其中第一个参数是“发送者”对象。原因是该约束的目的是简化向 F# 程序员呈现 CLI 事件处理程序。

所以基本上,

delegate

© www.soinside.com 2019 - 2024. All rights reserved.