4

I have a use case for treating a constructor for a derived type as a delegate and I can't figure out if it's impossible or I'm just incapable of working it out.

type SomeJobEvent(jobId : int, otherThing : string) =
    member this.JobId = jobId
    member this.OtherThing = otherThing

type SomeJobStarted(jobId : int, otherThing : string) =
    inherit SomeJobEvent(jobId, otherThing)

type SomeJobComplete(jobId : int, otherThing : string) =
    inherit SomeJobEvent(jobId, otherThing)

type SomeJobError(jobId : int, otherThing : string) =
    inherit SomeJobEvent(jobId, otherThing)

Lets imagine that this is the model, in real life the model happens to be in C# and my code is in F#, but for brevity it's much easier to type out in F#.

And what I want to do is...

let raise eventObject = 
    // This is just a helper, the raise event takes obj.
    eventRaiser.Raise(eventObject)

let raise jobId otherThing eventConstructor = 
    // Lets treat the 'event' constructor as a function
    raise(eventConstructor(jobId, otherThing))

[<EntryPoint>]
let main args = 

    // Lets curry this function up so I don't have to pass around 1234
    let raiseEventForJob1234 = raise 1234 "other thing"

    raiseEventForJob1234 SomeJobStarted

    raiseEventForJob1234 SomeJobComplete

Now as the constructors passed into the raiseEventForJob1234 have the same signature and are part of the same inheritance chain, it feels possible. It's just I am not sure how to make it work, it's not even that they all quack, they are actually ducks!

Edit: There is a great answer here from @tomas, but also a really useful extension from Piaste in the comments make sure you check out both.

Community
  • 1
  • 1
tigerswithguitars
  • 2,497
  • 1
  • 31
  • 53
  • Not that new at all and I know C# very well. But I don't have any knowledge base around me other than the internet. I know how I could do this in C# using some pretty knarley reflection, but it seems like F# should be able to do it more elegantly. – tigerswithguitars May 25 '16 at 16:37

1 Answers1

8

The problem you are getting here is called value restriction - basically, if you want to use function as generic, it has to be defined as an explicit function.

In your case raiseEventForJob1234 is generic (it can create and trigger different types of events), but it is defined as a value. Adding an argument solves this:

let raiseEventForJob1234 f = raise 1234 "other thing" f

raiseEventForJob1234 SomeJobStarted
raiseEventForJob1234 SomeJobComplete

Also note that this only works in F# 4.0 (in Visual Studio 2015). Previous version of F# did not support treating constructors as functions.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • That's amazing! I'll have to read up more on value restriction then. I thought the currying would just handle that for me and the value would be the right function. I've hit this with 'void' parameter functions before, wasn't expecting it here. – tigerswithguitars May 25 '16 at 16:45
  • Sub question, as we're here is then. Is there any way I can pass a curried version as an argument without having to `let` define each version with the parameter? I guess not. – tigerswithguitars May 25 '16 at 17:00
  • 2
    @tigerswithguitars Easiest approach is to use a "currifier" function. Many libraries provide them, or you can write it yourself if it's a one-off: `let curry2 f = fun a -> fun b -> f(a, b)`. LMK if I understood you correctly. – piaste May 25 '16 at 17:20
  • This is amazing stuff. I'll link this in the question. – tigerswithguitars May 26 '16 at 08:08