0

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?

Soldalma
  • 4,183
  • 2
  • 18
  • 34

1 Answers1

4

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.

TheInnerLight
  • 11,536
  • 1
  • 28
  • 51
  • I followed your suggestion regarding interfaces and of course it worked. Two questions. First, after I constructed my objects using the `create` function some upcasts became unnecessary and the compiler started giving warnings about them. Are the objects I am now dealing with different from the objects I had created before using the regular class constructors? If that is the case, am I losing some functionality? Second: what happens if my classes inherit from several interfaces and `create` only upcasts to one of them? Can I simultaneously upcast to several interfaces? – Soldalma May 07 '17 at 16:27