Swiftの列挙型はなぜ強いのか

「Swiftの列挙型が強い」という言葉をよく耳にするようになりました。わたしもSwiftの列挙型は好きです。

しかし列挙型はSwift以外にも存在します。そのため、「Swiftの列挙型が強い」という言葉が
「__列挙型自体が強い(Swiftはどうでもいい)__」という意味なのか
「__Swiftの列挙型が特別強い(ガンガンいこうぜ！)__」という意味なのか
はっきりさせなければなりません。

# Swiftにおける列挙型とは

[The Swift Programming Language (Swift 4.1): Enumerations](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html)

Swiftには2種類の列挙型がある。

* __Raw Values__
  * 一般的に「列挙型」「enumeration」と呼ばれるものの使い方をする
* __Associated Values__
  * 一般的に「algebraic data type(代数的データ型)」「discriminated unions」「tagged unions」「variants」などと呼ばれるものの使い方をする

Raw Valuesの例:

```swift
enum CompassPoint: String {
    case north, south, east, west
}
```

Associated Valuesの例:

```swift
enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}
```

各列挙子に対し、
事前にデフォルト値(= "raw values")を与えるのがRaw Valuesなやり方で、
後で関連する値(= "associated values"、関連値)を与えるのがAssociated Valuesなやり方。


「Swiftの列挙型が強い」という話題でどちらの種類が挙げられているのかはわからないが、せっかくなので両方とも考えてみる。

# Raw Values(いわゆる列挙型)の特徴

Raw Valuesでは各列挙子へ同じ型のデフォルト値を事前に与えなければならない。

Swift Raw Valuesの特徴:

* protocol `RawRepresentable` を満たすように自動でメソッドが追加される
  * むしろ `RawRepresentable` に合わないような宣言をするとエラー
  * `-dump-ast` または `-print-ast` でその様子を見ることができる
* ( `RawRepresentable` に反しない範囲で)様々な型の値を持つことができる
* `Int` や `String` の値を持つ場合、明示的に与えなくても勝手に振ってくれる(「Implicitly Assigned Raw Values」)
* switchでの列挙子網羅を強制する

C/C++/C#にとっての列挙型は「名前が付いた一連の整数定数」でしかない。

```c
enum Day {
    Sun, Mon, Tue, Wed, Thu, Fri, Sat
};

printf("%d %d %d\n", Sun, Mon, Tue);  // 0 1 2
```

Kotlinでは各列挙子のことをオブジェクトとみなしている。また、列挙子は列挙型のインスタンスなので以下のように初期化する。

```kotlin
enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

println(Color.RED)  // RED
println(Color.RED.rgb)  //  16711680
```

Scalaでは各列挙子が `Val` クラスのインスタンスという扱いになっている。これの派生を作ってしまえば列挙子に様々な型の値を持たせることができる。(継承しなくてもOK)

```scala
object Planet extends Enumeration {
  protected case class Val(mass: Double, radius: Double) extends super.Val

  val Mercury = Val(3.303e+23, 2.4397e6)
  val Venus   = Val(4.869e+24, 6.0518e6)
  val Earth   = Val(5.976e+24, 6.37814e6)
  val Mars    = Val(6.421e+23, 3.3972e6)
  val Jupiter = Val(1.9e+27, 7.1492e7)
  val Saturn  = Val(5.688e+26, 6.0268e7)
  val Uranus  = Val(8.686e+25, 2.5559e7)
  val Neptune = Val(1.024e+26, 2.4746e7)
}

println(Planet.Mercury) // Mercury
println(Planet.Mercury.mass)  // 3.303E23
```

TypeScript 列挙型にはnumeric enums、string enumsの2種類がある。このうち、numeric enumsのみ自動で初期値を用意してくれる。

```typescript
enum Direction {
    Up,
    Down,
    Left,
    Right,
}
```

関数を用いて列挙子の値を初期化することができたり、コンパイル時にインライン化されて消えてしまうconst enumがあったり、両方の型を持つheterogeneous enumsがあったり、上で紹介した言語ではできないことがいくつかある。

```typescript
enum BooleanLikeHeterogeneousEnum {
    No = 0,
    Yes = "YES",
}

console.log(BooleanLikeHeterogeneousEnum.No); // 0
```

# Associated Values(いわゆる代数的データ型)の特徴

Wikipediaに代数的データ型を扱える言語の一覧がある。
[Algebraic data type - Wikipedia](https://en.wikipedia.org/wiki/Algebraic_data_type#Programming_languages_with_algebraic_data_types)
これを列挙型(enum)と呼んでいるのは、Swift以外ではRust先輩くらいのものと思われる。他にあったら教えてください。

Swift Associated Valuesの特徴:

* 再帰的データ構造でもある
  * 再帰も代数的データ型に含むことがある
  * リストなどを作るのに再帰させる必要があるのできっと大事
* 各列挙子に自動的に静的メソッドが用意される
  * enum `HogeEnum` に `swiftc -dump-ast` すると__(HogeEnum.Type) -> (String) -> HogeEnum__というメソッドが増えているのがわかる
* 例外を除いて非変

静的メソッドとシグネチャが競合するようなメソッドがあるとエラーになってしまう。

```swift
enum Barcode {
    case qrCode(String)

    // エラー
    static func qrCode(_ str: String) -> Barcode {
        return Barcode.qrCode(str)
    }
}
```

Kotlinで同じようなことをするとエラーにならず、 `data class` の方が呼ばれる。

```swift
sealed class Barcode {
    data class QRCode(val str: String): Barcode()

    companion object {
        fun QRCode(str: String): Barcode {
            return Barcode.QRCode(str)
        }
    }
}
```

Swiftは共変を指定できないし、Swift Associated Valuesも基本的に非変なのだが、 `Optional` だけ共変になっている。このような特別扱いはstruct `Array` でも起きている。

```swift
class Animal {}
class Cat: Animal {}

let cat0: Optional<Cat> = .some(Cat())
let animal0: Optional<Animal> = cat0

enum MyOptional<T> {
    case some(T)
    case none
}

let cat1: MyOptional<Cat> = .some(Cat())
// エラー
let animal1: MyOptional<Animal> = cat1
```

共変非変を指定できるScalaではOptionとimmutable.Listに共変( `Option[+A]` 、 `List[+A]` )を指定している。mutableのほうは非変。

TypeScriptで代数的データ型を表現するには、「singleton types(要はリテラル)」「union types」「type guards」「type aliases」を使う必要がある。

```typescript
interface Square {
    kind: "square";
    size: number;
}
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
interface Circle {
    kind: "circle";
    radius: number;
}

type Shape = Square | Rectangle | Circle;
```

変数や戻り値がnullableであることを示したいだけなら `--strictNullChecks` オプションとunion types( `|` )で足りる。

# 結論？

* Raw Values(列挙型的使い方)もAssociated Values(代数的データ型的使い方)も既に他言語にあるものなので目新しいものではない
* どちらの種も言語によって宣言方法やできることが異なる

個人的には機能性と学習コストと自分の目的が釣り合っているSwiftとKotlinくらいがちょうどいいです。できることの多さだけで強さを判断するならScala先輩がトップに見える。

# 参考文献とか

[The Swift Programming Language (Swift 4.1): Enumerations](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html)
[RawRepresentable - Swift Standard Library | Apple Developer Documentation](https://developer.apple.com/documentation/swift/rawrepresentable)
[SwiftのOptionalはただのenum? - Qiita](https://qiita.com/koher/items/5dd6ce7427e7ecff424)
[SwiftのArrayがミュータブルでもCovariantな理由 - Qiita](https://qiita.com/koher/items/81767a345d5ed53c04b7)

[Chapter 6. Variants / Real World OCaml](https://realworldocaml.org/v1/en/html/variants.html)
[ソフトウェア技法: No.6 (直積型と代数的データ型)](http://www.cs.tsukuba.ac.jp/~kam/lecture/fp2016/6.pdf)

[C 列挙体の宣言](https://msdn.microsoft.com/ja-jp/library/whbyts4t.aspx)
[列挙型 [C++]](https://msdn.microsoft.com/ja-jp/library/2dzy4k6e.aspx)
[列挙型 (C# プログラミング ガイド) | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/enumeration-types)

[Enum Classes - Kotlin Programming Language](https://kotlinlang.org/docs/reference/enum-classes.html)

[Scala Standard Library 2.12.4 - scala.Enumeration](https://www.scala-lang.org/api/current/scala/Enumeration.html)
[列挙型 (enum) が欲しいときの Enumeration と case object... - tnoda-scala](http://tnoda-scala.tumblr.com/post/106430183326/%E5%88%97%E6%8C%99%E5%9E%8B-enum-%E3%81%8C%E6%AC%B2%E3%81%97%E3%81%84%E3%81%A8%E3%81%8D%E3%81%AE-enumeration-%E3%81%A8-case-object)
[型パラメータと変位指定 · Scala研修テキスト](https://dwango.github.io/scala_text/type-parameter.html)
[Scala列挙型メモ(Hishidama's Scala Enumeration Memo)](http://www.ne.jp/asahi/hishidama/home/tech/scala/enumeration.html)
[Effective Scala](http://twitter.github.io/effectivescala/index-ja.html)
[ScalaでListが共変でなければいけない理由 - kmizuの日記](http://kmizu.hatenablog.com/entry/20120810/1344601464)
[scala-hackathon/option.rst at master · yuroyoro/scala-hackathon](https://github.com/yuroyoro/scala-hackathon/blob/master/doc/source/practice/option.rst)

[Enums &middot; TypeScript](https://www.typescriptlang.org/docs/handbook/enums.html)
[Advanced Types &middot; TypeScript](http://www.typescriptlang.org/docs/handbook/advanced-types.html)
[Union Typesは直和型ではない | 雑記帳](https://blog.miz-ar.info/2015/01/union-types/)
[TypeScript、お前もか： nullやundefinedの扱いがイイカゲン過ぎ【事実誤認あり】 - 檜山正幸のキマイラ飼育記](http://d.hatena.ne.jp/m-hiyama/20180112/1515717324)
[TypeScript、僕が悪かった、ゴメン： nullやundefinedの扱いはマトモだった - 檜山正幸のキマイラ飼育記](http://d.hatena.ne.jp/m-hiyama/20180112/1515750208)