Soldalma
Soldalma

Reputation: 4758

Can I polymorphically access static methods?

I need to call a static member of a class before calling the class constructor. The class implements an interface and I will need to call (polymorphically) that same static member later on after the object is constructed.

I believe some languages allow one to access a static method with an instance name before the dot, something like

myClassInstance.staticMethod

F# does not seem to allow that, especially if the class inherits from an interface, since interfaces cannot contain static methods.

The following code exemplifies the problem:

module Types

type IMult =
    abstract member Something : float
    abstract member MultBy : float -> float

open Types

type Point(x: float) =
    interface Types.IMult with
        member this.Something with get() = x
        member this.MultBy (x: float) =
            (this :> IMult).Something * x

    static member public foo x = x + 3.0

let myPointConstructorArgument = Point.foo 5.0
let pt = Point(myPointConstructorArgument)

printfn "%f" <| (pt :> IMult).MultBy 10.0 // OK: 80.0

let bar (instnc: IMult) = instnc.foo // Error: foo not defined 
let pt0 = Point(bar pt 6.0) // No good: error above

My question is: is it possible to somehow retrieve the object's class and then call the abstract method?

I tried the following:

let ptType = pt0.GetType()
let mtd = ptType.foo // Error: foo not defined
let mtd = ptType.GetMethod("foo") // Ok, but not what I need, see below
let i2 = mtd 3.0 // Error: mtd is not a function

Any suggestions for alternative ways of doing this?

Upvotes: 2

Views: 474

Answers (2)

Nathan Wilson
Nathan Wilson

Reputation: 639

It sounds like what you're really looking for is "type classes" or "witnesses," which is a language feature that isn't supported yet, but this issue, which tracks the implementation of this feature, is quite active.
Currently in F# there are a few ways of getting the polymorphic access that you want.

1) Instance members through an interface. It seems that you can't put the polymorphic member directly on your IMult type, since you need to access it before constructing an instance, but you could create an interface for "types", and pass that along as an additional argument (this is very similar to how type classes would work "under the covers"):

//your "trait"
type IFoo<'a> =
  abstract member Foo : float -> float

//your "witness", defines what "Foo" means for the "Point" type.  Other witnesses could define what "Foo" means for other types.
let fooablePoint = { new IFoo<Point> with member this.Foo x = Point.foo x }

let bar (fooable:IFoo<'a>) = fooable.Foo //this isn't really necessary anymore, but illustrates that a function can use a polymorphic "static" function, given a witness
let pt0 = Point(bar fooablePoint 6.0)

2) static member constraints (as pointed out in the comments above):

let inline bar (pt:^a) x = 
  (^a : (static member foo : float -> float) x)

on the one hand, this gives you the ability to polymorphically access a static member on a type, though it shouldn't be overused according to fsharp.org. It's syntax is pretty opaque and can be difficult to decipher. Also, it only works for inline functions, so if used ubiquitously, could significantly disrupt the performance of your application. Note that this also only works if you already have an instance of type ^a, so this solution also may not work for you.

3) Just pass the foo function into whatever other function needs it:

let bar (foo:float -> float) x = foo x // type annotations wouldn't be necessary, and just provided for clarity
let pt0 = Point (bar Point.foo 6.0)

4) (discussed in the comments below): inside IMult you can still define

abstract member foo: float -> float 

Then within Point, just have

interface Types.IMult with 
  member this.Foo x = Point.foo x

this might give you everything you want. That member would then exist both as a static member on the Point type, and an instance member on IMult, so it isn't really "static polymorphism" anymore, but it might achieve your goals.

Upvotes: 3

MrD at KookerellaLtd
MrD at KookerellaLtd

Reputation: 2795

I'd go with "1" above, don't get hung up on type classes - they don't exist, and in some ways, they're not ideal anyway.

for me you're missing an abstraction, think of it as some sort of factory or domain entity, that captures your static methods, and simply explicitly pass it around.

type MultDomain<'a when 'a :> IMult> =
    abstract member foo :  float -> float

type PointDomain =
    interface MultDomain<Point> with
        member o.foo x = x + 3.0

(the constraint "'a :> IMult" isn't required in your context, but shows that you can use the type of the object you are yet to create in this interface)

so the answer is "yes, as long as you map them to instance methods of some type that you can invoke polymorphically"...or maybe "no, but you can map them to instance methods of some type that you can invoke polymorphically".

(personally I never have static methods, and even avoid constructors!)

Upvotes: 0

Related Questions