Reputation: 8293
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).
:?
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:
Does this mean that "sealed" types are "simple" F# types (i.e., not defined as "class types")?
Found the issue #1320: Allow types to be Sealed by default, but I think that it only proposes "class" types to be "sealed" by default (as the "simple" types already are). Then again, this may be a misinterpretation.
I thought I understood what the box
operator does (even mentioned the same scenario there), but just realized that I don't know what "box
a returns value a wrapped in a .NET System.Object
instance" means.
Does "wrapping" mean that a box
ed "simple" F# type is redefined the same way as the original input, but as a "class type", inheriting everything from System.Object
?
Yes, there are two questions in the title, but please consider my reasoning: I tried to separate and link the questions bidirectionally, but
an abstract answer to the first one (similar to the guide's) without examples would probably just as unhelpful without context, and
asking the second question would inevitable duplicate efforts to define what a "sealed" type is.
Upvotes: 1
Views: 170
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
Reputation: 11372
Sealed types are a .NET concept. They are types that cannot be inherited from. This includes:
[<Sealed>]
attributesealed
keywordNow, 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