Creating functions that take in generic delegates in F#

Viewed 76

I have created some F# bindings for GLFW. For this, there are several callbacks. Right now, all of my set callback wrappers are basically identical aside from the callback definition (which is a delegate type), a more idiomatic F# callback function, the arguments of these function types, and the actual binding that marshals the callback function to GLFW.

Here are two bindings and callback delegates:

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

Then I have high-level wrappers that are meant to hide the interop details:

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

As you can see, these wrapper functions are practically identical. To create a new one, I just copy one, change the function delegate types, the callback type definition, and the GLFW binding.

What I would like is to make a generic function that handles all of these mechanisms, and then to define the actual wrappers, I just pass in the relevant type values. However, I have been unable to get this to work with generic delegate constraints.

For example, I have tried this:

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

But on the line let callback = callbackDef ..., I get the error "This value is not a function and cannot be applied". If I try callbackDef.Invoke, I get the error "Lookup on object of indeterminate type", even though it's supposed to be a delegate. What is also weird is that I am not able to statically constraint 'GLFWcallback with ^GLFWcallback. It appears that outref<type> cannot take a statically constrainted type for type.

The two resources I have been referring to are:

If I had such a generic function, then I could redefine things as:

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

So, I am stuck. The errors I am getting don't make sense to me or tell me what's really wrong. It doesn't seem like the delegate constraint is working properly or that I'm using it properly.

How do I get this working? How do I properly define a function like this that allows me to use generic delegates? Note that my delegates are not all that generic. They are all of the type delegate of nativeptr<GLFWwindow> * 'T -> unit.

1 Answers

You may be able to cast the typed delegate into an untyped delegate and then invoke it dynamically. I'm also not sure what the performance implications are - I suspect it may be slower, but I have not tried this.

I was not able to run this, but the following type-checks (but I made some edits as I was confused by the difference between GLFWindow and 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) |]
    ()

That said, I agree with what other commenters said - it is just easier to live with some code duplication. This makes especially sense in code that is basically manually auto-generated binding.

Related