Reputation: 4758
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
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
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