Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# casting an object to an interface

Tags:

interface

f#

I have a class called 'Pane' (think glass pane) that implements IPane:

type IPane =
    abstract PaneNumber : int with get, set
    abstract Thickness : float<m> with get, set
    abstract ComponentNumber : int with get, set
    abstract Spectra : IGlassDataValues with get, set
    ...

type Pane(paneNumber, componentNumber, spectra, ...) =
    let mutable p = paneNumber
    let mutable n = componentNumber
    let mutable s = spectra
    ...

    interface IPane with
        member this.PaneNumber
            with get() = p
            and set(value) = p <- value
        member this.ComponentNumber
            with get() = n
            and set(value) = n <- value
        member this.Spectra
            with get() = s
            and set(value) = s <- value
            ...

I create a list of panes (Pane list):

let p = [ p1; p2 ]

however I need to cast this to an IPane list as this is a parameter type in another function. The following code produces an error:

let p = [ p1; p2 ] :> IPane list
'Type constraint mismatch. The type
  Pane list
is not compatible with type
  IPane list
The type 'IPane' does not match the type 'Pane'

This is confusing as Pane implements IPane. Simply passing the Pane list object as a parameter into the required function also produces an error.

How do I cast Pane list to IPane list?

like image 827
Nick Avatar asked Jun 05 '14 08:06

Nick


3 Answers

F# doesn't allow inheritence in quite the way you want.

A better way would be to use:

[p1 ;p2] |> List.map (fun x -> x:> IPane)

Alternatively, you can change your function to use something like this

let f (t:#IPane list) = ()

and here you can do f [p1;p2] as the # tells the compiler that any type that inherits from IPane is fine.

like image 82
John Palmer Avatar answered Oct 29 '22 21:10

John Palmer


Another way would have been like this:

let p = [p1; p2] : IPane list
// or equivalently
let p : IPane list = [p1; p2]

In your original code, what happened is that first it inferred the type of [p1; p2] without any constraints, which it found to be Pane list, and then it tried to upcast it to IPane list, which it couldn't do because F# has strict variance rules: even if Pane is a subtype of IPane, Pane list is not a subtype of IPane list.

In my code above, on the other hand, instead of trying to infer the type of [p1; p2], it verifies that it can match the type IPane list that it was given; and in doing so, it implicitly upcast the individual list elements p1 and p2 to IPane.

like image 34
Tarmil Avatar answered Oct 29 '22 21:10

Tarmil


The solution was easier than I thought.

I was defining p1 and p2 as:

let p1 = new Pane(1, 2, blah, blah)
let p2 = new Pane(2, 2, blah, blah)

However if I cast them as IPanes at this point, everything works:

let p1 = new Pane(1, 2, blah, blah) :> IPane
let p2 = new Pane(2, 2, blah, blah) :> IPane

John Palmer's solution above is probably more rigorous as the original pane objects are maintained as the concrete types.

like image 25
Nick Avatar answered Oct 29 '22 21:10

Nick