toraritte
toraritte

Reputation: 8293

What is a "sealed" type in F# and why doesn't the type test pattern operator (:?) work on it out of the `box`?

The F# language guide (see Signatures article) has a very sparse definition of what a "sealed" type is:

Attribute Description
[<Sealed>] For a type that has no abstract members, or that should not be extended.
[<Interface>] For a type that is an interface.

I searched the entire PDF version of the language guide using the term seal, and this was the most informative (out of all 8 results).

Context: :? fails on "simple types" with error FS0016

For example, I was playing with type abbreviations when both matches failed

type Lofa = | A | B
type Miez = Lofa

let (a: Miez) = A

match a with | :? Miez -> a
match a with | :? Lofa -> a

with

error FS0016: The type 'Miez' does not  have any
proper subtypes and cannot be used as the source
of a type test or runtime coercion.

error FS0016: The type 'Lofa' does not  have any
proper subtypes and cannot be used as the source
of a type test or runtime coercion.

The SO thread F# Why can't I use the :? operator in F# interactive? provides an answer (i.e., to use box, hence the mnemonic pun in the title),

match (box a) with | :? Miez -> a
match (box a) with | :? Lofa -> a

but it doesn't explain why it works or what "sealed" types are, and also raises questions:


Note on possible duplicates

Yes, there are two questions in the title, but please consider my reasoning: I tried to separate and link the questions bidirectionally, but

Upvotes: 1

Views: 170

Answers (2)

Brian Berns
Brian Berns

Reputation: 17038

A sealed type is one that may not be extended via inheritance. In other words, other types can't inherit from a sealed type. Since it's not possible to inherit from a discriminated union, Lofa and Miez are both sealed. If you want to seal an F# class type, however, you must explicitly mark it with the [<Sealed>] attribute.

The reason that :? can't be used on a sealed type is that its runtime type is guaranteed to be the same as its compile-time type:

let strStrong : string = "abc"       // strongly-typed value
printfn "%A" (strStrong :? string)   // not allowed, since the compiler knows it must be a string

let strWeak : obj = "abc"            // weakly-typed value
printfn "%A" (strWeak :? string)     // allowed, because it could be a non-string

Note that box (i.e. casting to obj) is just one way to create a weakly typed value. It also happens commonly with class hierarchies:

[<AbstractClass>]
type Animal =
    abstract member Sound : string

[<Sealed>]
type Dog =
    inherit Animal
    override _.Sound = "Woof"

type Cat =
    inherit Animal
    override _.Sound = "Meow"

let testAnimal (animal : Animal) =
    match animal with
        | :? Dog -> printfn "This is a dog"
        | _ -> printfn "Some other animal"

let testDog (dog : Dog) =
    match dog with
        | :? Dog -> printfn "This is a dog"   // not allowed, since we already know this is a Dog, and Dog is sealed
        | _ -> printfn "Impossible"

let testCat (cat : Cat) =
    match cat with
        | :? Cat -> printfn "This is a cat"   // allowed (with a warning), since Cat isn't sealed
        | _ -> printfn "Impossible"

Upvotes: 2

Tarmil
Tarmil

Reputation: 11372

Sealed types are a .NET concept. They are types that cannot be inherited from. This includes:

  • F# records
  • F# unions
  • F# classes declared with the [<Sealed>] attribute
  • C# classes declared with the sealed keyword

Now, the :? operator tests whether a value is an instance of a given type. For example, if a variable a has statically known type T, and U is a subtype of T, then a :? U checks whether a is type U. It's false if a is a concrete T or another subtype of T.

But if T is sealed, then it has no subtype. So the expression a :? U is trivially false. Unless U is T or a supertype of T, in which case it's trivially true. Either way, it's a useless test. So the F# compiler assumes you made a mistake.

The expression box a, on the other hand, upcasts a to type obj. This type is not sealed (obviously, since all other types inherit from it). So there's nothing wrong with using :? on it.

Upvotes: 1

Related Questions