Following advice in SO and other places I tried to implement polymorphism using interfaces, as in the following simplified example.
type IFace =
// abstract methods
abstract member foo: int -> int
type X(i: int) =
// some code
interface IFace with
member this.foo j = i + j
type Y(s: string) =
// some code
interface IFace with
member this.foo j = (int s) + j
type Z(x: float) =
// some code
interface IFace with
member this.foo j = (int x) + j
These values can be used by the constructors of X, Y, and Z:
let zX = 1
let zY = "2"
let zZ = 3.0
I would like to build a polymorphic factory like
let create (str: string) =
match str with
| "X" -> X(zX)
| "Y" -> Y(zY)
| "Z" -> Z(zZ)
| _ -> failwith "Something went wrong"
let XObj = create "X"
let YObj = create "Y"
let ZObj = create "Z"
However, create
will not compile since it would return objects of different types.
One alternative would be to upcast all cases in create
to System.Object:
let create1 (str: string) =
match str with
| "X" -> X(zX) :> System.Object
| "Y" -> Y(zY) :> System.Object
| "Z" -> Z(zZ) :> System.Object
| _ -> failwith "Something went wrong"
Then create1
would compile but its return value would (I believe) be useless unless downcast to X, Y, or Z. But this raises the polymorphism issue again: to downcast generically I would need a function that returns values of different types.
Another alternative would be to use a Discriminated Union
type ClassDU =
| XClass of X
| YClass of Y
| ZClass of Z
and use the function create
defined above. But that does not work. Without upcasting the compiler still sees the different cases as having different types. And upcasting to ClassDU does not work since ClassDU is not a class.
In this code snippet
http://fssnip.net/k6/title/-F-Factory-Pattern
the problem is solved using an abstract class XYZ from which X, Y, and Z would inherit the abstract method foo. The function analogous to create
would upcast all cases to the abstract class:
let create2 (str: string) =
match str with
| "X" -> X(zX) :> XYZ
| "Y" -> Y(zY) :> XYZ
| "Z" -> Z(zZ) :> XYZ
| _ -> failwith "Something went wrong"
Is it possible to create a polymorphic factory for types inheriting from an interface or does one have to create an abstract class as in the code snippet?
Interfaces
To do this with interfaces, you just need to upcast to the type of the interface, e.g.:
let create (str: string) =
match str with
| "X" -> X(zX) :> IFace
| "Y" -> Y(zY) :> IFace
| "Z" -> Z(zZ) :> IFace
| _ -> failwith "Something went wrong"
This has type signature:
val create : str:string -> IFace
Disciminated Unions
To do this with DUs, you need to add the appriopriate case constructor in the create
function.
type ClassDU =
| XClass of X
| YClass of Y
| ZClass of Z
let createDU (str: string) =
match str with
| "X" -> XClass <| X(zX)
| "Y" -> YClass <| Y(zY)
| "Z" -> ZClass <| Z(zZ)
| _ -> failwith "Something went wrong"
This has type signature:
val createDU : str:string -> ClassDU
I think your misunderstanding around the behaviour of discriminated unions stems from the fact that you're thinking of them as being like abstract classes with multiple subclasses, it's better and more accurate to think of them as a single type with multiple constructors.