mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
347 lines
9.8 KiB
ReStructuredText
347 lines
9.8 KiB
ReStructuredText
:orphan:
|
|
|
|
Swift supports what type theory calls "algebraic data types", or ADTs, which
|
|
are an amalgam of two familiar C-family language features, enums and unions.
|
|
They are similar to enums in that they allow collections of independent symbolic
|
|
values to be collected into a type and switched over::
|
|
|
|
enum Color {
|
|
case Red, Green, Blue, Black, White
|
|
}
|
|
|
|
var c : Color = .Red
|
|
switch c {
|
|
case .Red:
|
|
...
|
|
case .Green:
|
|
...
|
|
case .Blue:
|
|
...
|
|
}
|
|
|
|
They are also similar to C unions in that they allow a single type to
|
|
contain a value of two or more other types. Unlike C unions, however, ADTs
|
|
remember which type they contain, and can be switched over, guaranteeing that
|
|
only the currently inhabited type is ever used::
|
|
|
|
enum Pattern {
|
|
case Solid(Color)
|
|
case Outline(Color)
|
|
case Checkers(Color, Color)
|
|
}
|
|
|
|
var p : Pattern = .Checkers(.Black, .White)
|
|
switch p {
|
|
case .Solid(var c):
|
|
print("solid \(c)")
|
|
case .Outline(var c):
|
|
print("outlined \(c)")
|
|
case .Checkers(var a, var b):
|
|
print("checkered \(a) and \(b)")
|
|
}
|
|
|
|
Given the choice between two familiar keywords, we decided to use 'enum' to
|
|
name these types. Here are some of the reasons why:
|
|
|
|
Why 'enum'?
|
|
===========
|
|
|
|
The common case works like C
|
|
----------------------------
|
|
|
|
C programmers with no interest in learning about ADTs can use 'enum' like they
|
|
always have.
|
|
|
|
"Union" doesn't exist to Cocoa programmers
|
|
------------------------------------------
|
|
|
|
Cocoa programmers really don't think about unions at all. The frameworks vend
|
|
no public unions. If a Cocoa programmer knows what a union is, it's as a
|
|
broken C bit-bangy thing. Cocoa programmers are used to more safely
|
|
and idiomatically modeling ADTs in Objective-C as class hierarchies. The
|
|
concept of closed-hierarchy variant value types is new territory for them, so
|
|
we have some freedom in choosing how to present the feature. Trying to relate
|
|
it to C's 'union', a feature with negative connotations, is a disservice if
|
|
anything that will dissuade users from wanting to learn and take advantage of
|
|
it.
|
|
|
|
It parallels our extension of 'switch'
|
|
--------------------------------------
|
|
|
|
The idiomatic relationship between 'enum' and 'switch' in C is
|
|
well-established--If you have an enum, the best practice for consuming it is to
|
|
switch over it so the compiler can check exhaustiveness for you. We've extended
|
|
'switch' with pattern matching, another new concept for our target audience,
|
|
and one that happens to be dual to the concept of enums with payload. In the
|
|
whitepaper, we introduce pattern matching by starting from the familiar C case
|
|
of switching over an integer and gradually introduce the new capabilities of
|
|
Swift's switch. If all ADTs are 'enums', this lets us introduce both features
|
|
to C programmers organically, starting from the familiar case that looks like
|
|
C::
|
|
|
|
enum Foo { case A, B, C, D }
|
|
|
|
func use(x:Foo) {
|
|
switch x {
|
|
case .A:
|
|
case .B:
|
|
case .C:
|
|
case .D:
|
|
}
|
|
}
|
|
|
|
and then introducing the parallel new concepts of payloads and patterns
|
|
together::
|
|
|
|
enum Foo { case A, B, C, D, Other(String) }
|
|
|
|
func use(x:Foo) {
|
|
switch x {
|
|
case .A:
|
|
case .B:
|
|
case .C:
|
|
case .D:
|
|
case .Other(var s):
|
|
}
|
|
}
|
|
|
|
People already use 'enum' to define ADTs, badly
|
|
-----------------------------------------------
|
|
|
|
Enums are already used and abused in C in various ways as a building block for
|
|
ADT-like types. An enum is of course the obvious choice to represented the
|
|
discriminator in a tagged-union structure. Instead of saying 'you write union
|
|
and get the enum for free', we can switch the message around: 'you write enum
|
|
and get the union for free'. Aside from that case, though, there are many uses
|
|
in C of enums as ordered integer-convertible values that are really trying to
|
|
express more complex symbolic ADTs. For example, there's the pervasive LLVM
|
|
convention of 'First_*' and 'Last_*' sigils::
|
|
|
|
/* C */
|
|
enum Pet {
|
|
First_Reptile,
|
|
Lizard = First_Reptile,
|
|
Snake,
|
|
Last_Reptile = Snake,
|
|
|
|
First_Mammal,
|
|
Cat = First_Mammal,
|
|
Dog,
|
|
Last_Mammal = Dog,
|
|
};
|
|
|
|
which is really crying out for a nested ADT representation::
|
|
|
|
// Swift
|
|
enum Reptile { case Lizard, Snake }
|
|
enum Mammal { case Cat, Dog }
|
|
enum Pet {
|
|
case Reptile(Reptile)
|
|
case Mammal(Mammal)
|
|
}
|
|
|
|
Or there's the common case of an identifier with standardized symbolic values
|
|
and a 'user-defined' range::
|
|
|
|
/* C */
|
|
enum Language : uint16_t {
|
|
C89,
|
|
C99,
|
|
Cplusplus98,
|
|
Cplusplus11,
|
|
First_UserDefined = 0x8000,
|
|
Last_UserDefined = 0xFFFF
|
|
};
|
|
|
|
which again is better represented as an ADT::
|
|
|
|
// Swift
|
|
enum Language {
|
|
case C89, C99, Cplusplus98, Cplusplus11
|
|
case UserDefined(UInt16)
|
|
}
|
|
|
|
Rust does it
|
|
------------
|
|
|
|
Rust also labels their ADTs 'enum', so there is some alignment with the
|
|
"extended family" of C-influenced modern systems programming languages in making
|
|
the same choice
|
|
|
|
Design
|
|
======
|
|
|
|
Syntax
|
|
------
|
|
|
|
The 'enum' keyword introduces an ADT (hereon called an "enum"). Within an enum,
|
|
the 'case' keyword introduces a value of the enum. This can either be a purely
|
|
symbolic case or can declare a payload type that is stored with the value::
|
|
|
|
enum Color {
|
|
case Red
|
|
case Green
|
|
case Blue
|
|
}
|
|
|
|
enum Optional<T> {
|
|
case Some(T)
|
|
case None
|
|
}
|
|
|
|
enum IntOrInfinity {
|
|
case Int(Int)
|
|
case NegInfinity
|
|
case PosInfinity
|
|
}
|
|
|
|
Multiple 'case' declarations may be specified in a single declaration, separated
|
|
by commas::
|
|
|
|
enum IntOrInfinity {
|
|
case NegInfinity, Int(Int), PosInfinity
|
|
}
|
|
|
|
Enum declarations may also contain the same sorts of nested declarations as
|
|
structs, including nested types, methods, constructors, and properties::
|
|
|
|
enum IntOrInfinity {
|
|
case NegInfinity, Int(Int), PosInfinity
|
|
|
|
constructor() {
|
|
this = .Int(0)
|
|
}
|
|
|
|
func min(x:IntOrInfinity) -> IntOrInfinity {
|
|
switch (self, x) {
|
|
case (.NegInfinity, _):
|
|
case (_, .NegInfinity):
|
|
return .NegInfinity
|
|
case (.Int(var a), .Int(var b)):
|
|
return min(a, b)
|
|
case (.Int(var a), .PosInfinity):
|
|
return a
|
|
case (.PosInfinity, .Int(var b)):
|
|
return b
|
|
}
|
|
}
|
|
}
|
|
|
|
They may not however contain physical properties.
|
|
|
|
Enums do not have default constructors (unless one is explicitly declared).
|
|
Enum values are constructed by referencing one of its cases, which are scoped
|
|
as if static values inside the enum type::
|
|
|
|
var red = Color.Red
|
|
var zero = IntOrInfinity.Int(0)
|
|
var inf = IntOrInfinity.PosInfinity
|
|
|
|
If the enum type can be deduced from context, it can be elided and the case
|
|
can be referenced using leading dot syntax::
|
|
|
|
var inf : IntOrInfinity = .PosInfinity
|
|
return inf.min(.NegInfinity)
|
|
|
|
The 'RawRepresentable' protocol
|
|
-------------------------------
|
|
|
|
In the library, we define a compiler-blessed 'RawRepresentable' protocol that
|
|
models the traditional relationship between a C enum and its raw type::
|
|
|
|
protocol RawRepresentable {
|
|
/// The raw representation type.
|
|
typealias RawType
|
|
|
|
/// Convert the conforming type to its raw type.
|
|
/// Every valid value of the conforming type should map to a unique
|
|
/// raw value.
|
|
func toRaw() -> RawType
|
|
|
|
/// Convert a value of raw type to the corresponding value of the
|
|
/// conforming type.
|
|
/// Returns None if the raw value has no corresponding conforming type
|
|
/// value.
|
|
class func fromRaw(_:RawType) -> Self?
|
|
}
|
|
|
|
Any type may manually conform to the RawRepresentable protocol following the above
|
|
invariants, regardless of whether it supports compiler derivation as underlined
|
|
below.
|
|
|
|
Deriving the 'RawRepresentable' protocol for enums
|
|
--------------------------------------------------
|
|
|
|
An enum can obtain a compiler-derived 'RawRepresentable' conformance by
|
|
declaring "inheritance" from its raw type in the following
|
|
circumstances:
|
|
|
|
- The inherited raw type must be IntegerLiteralConvertible,
|
|
FloatLiteralConvertible, CharLiteralConvertible, and/or
|
|
StringLiteralConvertible.
|
|
- None of the cases of the enum may have non-void payloads.
|
|
|
|
If an enum declares a raw type, then its cases may declare raw
|
|
values. raw values must be integer, float, character, or string
|
|
literals, and must be unique within the enum. If the raw type is
|
|
IntegerLiteralConvertible, then the raw values default to
|
|
auto-incrementing integer literal values starting from '0', as in C. If the
|
|
raw type is not IntegerLiteralConvertible, the raw values must
|
|
all be explicitly declared::
|
|
|
|
enum Color : Int {
|
|
case Black // = 0
|
|
case Cyan // = 1
|
|
case Magenta // = 2
|
|
case White // = 3
|
|
}
|
|
|
|
enum Signal : Int32 {
|
|
case SIGKILL = 9, SIGSEGV = 11
|
|
}
|
|
|
|
enum NSChangeDictionaryKey : String {
|
|
// All raw values are required because String is not
|
|
// IntegerLiteralConvertible
|
|
case NSKeyValueChangeKindKey = "NSKeyValueChangeKindKey"
|
|
case NSKeyValueChangeNewKey = "NSKeyValueChangeNewKey"
|
|
case NSKeyValueChangeOldKey = "NSKeyValueChangeOldKey"
|
|
}
|
|
|
|
The compiler, on seeing a valid raw type for an enum, derives a RawRepresentable
|
|
conformance, using 'switch' to implement the fromRaw and toRaw
|
|
methods. The NSChangeDictionaryKey definition behaves as if defined::
|
|
|
|
enum NSChangeDictionaryKey : RawRepresentable {
|
|
typealias RawType = String
|
|
|
|
case NSKeyValueChangeKindKey
|
|
case NSKeyValueChangeNewKey
|
|
case NSKeyValueChangeOldKey
|
|
|
|
func toRaw() -> String {
|
|
switch self {
|
|
case .NSKeyValueChangeKindKey:
|
|
return "NSKeyValueChangeKindKey"
|
|
case .NSKeyValueChangeNewKey:
|
|
return "NSKeyValueChangeNewKey"
|
|
case .NSKeyValueChangeOldKey:
|
|
return "NSKeyValueChangeOldKey"
|
|
}
|
|
}
|
|
|
|
static func fromRaw(s:String) -> NSChangeDictionaryKey? {
|
|
switch s {
|
|
case "NSKeyValueChangeKindKey":
|
|
return .NSKeyValueChangeKindKey
|
|
case "NSKeyValueChangeNewKey":
|
|
return .NSKeyValueChangeNewKey
|
|
case "NSKeyValueChangeOldKey":
|
|
return .NSKeyValueChangeOldKey
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|