0

I am resubmitting a question asked almost a decade ago on this site link - but which is not as generic as I would like.

What I am hoping for is a way to construct a function from a list of types, where the final output type can have an arbitrary/default value (such as 0.0 for a float, or "" for a string). So, from

[float; int; float;]

I would get something that amounts to

fun(f: float) ->
     fun(i: int) ->
          0.0

I am hopeful of achieving this, but am so far unable to. It would be helping me out a lot if I could see a sample that does the above.

The answer in the above link goes some of the way, but the example seems to know its function signature at compile time, which I won't, and also generates a compiler warning.

The scenario I have, for those that find context helpful, is that I want to be able to open a dll and one way or another identify a method which will have a given signature with argument-types limited to a known set of types (i.e. float, int). For each input parameter in this function signature I will run code to generate a 'buffer' object, which will have

  • a buffer of data items of the given type, i.e. [1.2; 3.2; 4.5]
  • a supplier of that data type (supplies may be intermittent so the receiving buffer may be empty at any one time)
  • a generator function that transforms data items before being dispatched. This function can be updated at any time.
  • a dispatch function. The dispatch target of bufferA will be bufferB, and for bufferB it will be a pub-sub thing where subscribers can subscribe to the end result of the calculation, in this case a stream of floats. Data accumulates in applicative style down the chain of buffers, until the final result is published as a new stream.
  • a regulator that turns the stream of data heading out to the consumer on or off. This ensures orderly function application.

The function from the dll will eventually be given to BufferA to apply to a float and pass the result on to buffer B (to pick up an int). However, while setting up the buffer infrastructure I only need a function with the correct signature, so a dummy value, such as 0.0, is fine.

For a function of a known signature I can handcraft the code that creates the necessary infrastructure, but I would like to be able to automate this, and ideally register dlls and have new calculated streams available plugin-style without rebuilding the application.

tweega
  • 43
  • 5

2 Answers2

1

If you're willing to throw type safety out the window, you could do this:

let rec makeFunction = function
    | ["int"] -> box 0
    | ["float"] -> box 0.0
    | ["string"] -> box ""
    | "int" :: types ->
        box (fun (_ : int) -> makeFunction types)
    | "float" :: types ->
        box (fun (_ : float) -> makeFunction types)
    | "string" :: types ->
        box (fun (_ : string) -> makeFunction types)
    | _ -> failwith "Unexpected"

Here's a helper function for invoking one of these monstrosities:

let rec invokeFunction types (values : List<obj>) (f : obj) =
    match types, values with
        | [_], [] -> f
        | ("int" :: types'), (value :: values') ->
            let f' = f :?> (int -> obj)
            let value' = value :?> int
            invokeFunction types' values' (f' value')
        | ("float" :: types'), (value :: values') ->
            let f' = f :?> (float -> obj)
            let value' = value :?> float
            invokeFunction types' values' (f' value')
        | ("string" :: types'), (value :: values') ->
            let f' = f :?> (string -> obj)
            let value' = value :?> string
            invokeFunction types' values' (f' value')
        | _ -> failwith "Unexpected"

And here it is in action:

let types = ["int"; "float"; "string"]   // int -> float -> string
let f = makeFunction types
let values = [box 1; box 2.0]
let result = invokeFunction types values f
printfn "%A" result   // output: ""

Caveat: This is not something I would ever recommend in a million years, but it works.

Brian Berns
  • 15,499
  • 2
  • 30
  • 40
  • Brian - many thanks for your reply here. What I end up with here, though, is an object, whereas what I need is a function with a specific signature, albeit not one that my code knows ahead of time. I can then give that function to another component and 'say' to it, "expect more functions like this one in the future. The future functions will do 'real' calculations. – tweega Apr 07 '22 at 07:57
  • The object you end up does have the runtime type you want, which I think is the best you can possibly hope for. There's obviously no way to create a function that has the correct compile-time signature, since you don't even know what that signature is until runtime. So, if I understand you correctly, what you're asking for seems self-contradictory, and thus impossible. – Brian Berns Apr 07 '22 at 13:27
0

I got 90% of what I needed from this blog by James Randall, entitled compiling and executing fsharp dynamically at runtime. I was unable to avoid concretely specifying the top level function signature, but a work-around was to generate an fsx script file containing that signature (determined from the relevant MethodInfo contained in the inspected dll), then load and run that script. James' blog/ github repository also describes loading and running functions contained in script files. Having obtained the curried function from the dll, I then apply it to default arguments to get representative functions of n-1 arity using

let p1: 'p1 =  Activator.CreateInstance(typeof<'p1>) :?> 'p1              
let fArity2  = fArity3 p1

Creating and running a script file is slow, of course, but I only need to perform this once when setting up the calculation stream

tweega
  • 43
  • 5