[Crystal] 「配列」っぽい型

複数の要素からなるリストを扱う型としては**Array**（配列）がその代表格ですが，それ以外にも「配列」**っぽい**型がいくつか存在しています。ここでは似ているようで異なるこれらの型を簡単にご紹介します。

# **Slice(T)**
**Slice**にリテラル表現はありませんが，**Array**リテラルとよく似た`Slice[...]`という特殊な構文（マクロ）で**Slice**オブジェクトを生成することができます。

```crystal
slice = Slice[1, 2, 3]
p slice[0]
#=> 1
```

**Array**と同様，**Slice**もジェネリクス型で，型引数によって要素として受け入れられるオブジェクトが制限されますが，上記の構文を使用した場合は，要素の型から自動的に**Slice**の型引数が設定されます。

```crystal
slice2 = Slice[1, "2", '3']
p typeof(slice2)
#=> Slice(Char | Int32 | String)
```

使い勝手の面からすると**Slice**型は「生成後に要素の数を変更できない固定長の**Array**」であるかのように見えます。基本的に可変のオブジェクトで`#[]=`メソッドによる要素の変更は可能ですが，`#<<`や`#push`，`#shift`のような実行後に要素数が変化するメソッドは使用できません。

```crystal
slice[0] = 0

p slice
#=> Slice[0, 2, 3]

slice << 4
#=> Error in line 7: undefined method '<<' for Slice(Int32)
#
#   Rerun with --error-trace to show a complete error trace.
```

ただし，生成時のオブションによってはリードオンリーな（不変の）**Slice**を作ることも可能です。

```crystal
slice3 = Slice["1", "2", "3", read_only: true]

slice3[0] = "0"
#=> Can't write to read-only Slice (Exception)
#   0x5580d04f9b98: check_writable at /usr/lib/crystal/slice.cr 496:5
#   0x5580d04f9af4: []= at /usr/lib/crystal/slice.cr 174:5
#   0x5580d04a780e: __crystal_main at /eval 3:1
#                      :
#                      :
```

特に注意が必要なのは**Array**と大きく挙動が異なる`#+`メソッドです。Arrayの`#+`は**Array**オブジェクトを引数に取り，自身と引数を結合した**Array**を返しますが，**Slice**の場合は整数値を引数に取り，引数の数だけ先頭から要素を取り除いた**Slice**を返します。

```crystal
array = [1, 2, 3]
p array + [4]
#=> [1, 2, 3, 4]

p slice + 1
#=> Slice[2, 3]
```

実際のところ，**Slice**が内部で保持しているのはポインタ（**Pointer**）です。ポインタはデータが始まるメモリ番地でしかなく，どこまでが有効なデータなのかのチェックが行われません。そのため，不用意にポインタを用いると，本来アクセスすべきでないメモリ番地の情報を読み書きできてしまいます。そこで，予めデータが始まるメモリ番地（ポインタ）に加えてデータの大きさを指定しておき，バッファオーバーフローなどを回避するための仕組みが**Slice**というわけです。

Crystalだけでコードを書いているとポインタを扱う様な機会はそうありませんが，例えば，C言語で書かれた関数にポインタを渡す様な場合などでは**Slice**がよく用いられます。また，特に8bit単位のバイトデータを扱う**Slice(UInt8)**には，**Bytes**という別名が用意されており，I/Oや文字列処理などでバイト列の操作が必要な場合（`IO#read`, `IO#write`や`String#to_slice`など）にはこの**Bytes**型を使用することになります。

# **Tuple(*T)**

**Tuple**オブジェクトは，`Tuple.new`に要素のリスト渡すことで生成できますが，それ以外にも`Tuple.from`メソッドに**Array**を渡したり，専用のリテラル構文`{...}`を用いて生成することができます。

```crystal
# 全て同じ結果
p tuple = Tuple.new(1, "2", '3')
p tuple = Tuple(Int32, String, Char).from([1, "2", '3'])
p tuple = {1, "2", '3'}
#=> {1, "2", '3'}
```

**Tuple**は，ぱっと見は「一旦生成されたら一切の変更がきかない不変な**Array**」のように見えます。

不変なオブジェクトであるため，要素の追加/削除を伴う`#<<`や`#push`，`#shift`などが利用できないだけでなく，すでに存在する要素を別のオブジェクトに更新する`#[]=`なども使用できません。

**Array**や**Slice**と異なる**Tuple**の一番大きな特徴は，格納した要素の型の取り扱いです。**Tupple**では何番目の要素がどの型の値であるかを内部に保持しているため，`#[]`などで値を取り出した際にその型固有のメソッドをそのまま使用することができます。

```crystal
t1 = tuple[1]
p typeof(t1)
#=> String
p t1.size
#=> 1
```

一方，**Array**や**Slice**でも内部に複数の型を格納することは可能ですが，そうした場合，各要素の型は格納した要素の型全てのユニオン型になり，個々の要素の実際の型は隠蔽されてしまいます。そのため，同様のことを**Array**で行おうとすると，`if`文などで実際の値の型を特定する必要があります。

```crystal
array = [1, "2", '3']
a1 = array[1]
p typeof(a1)
#=> (Char | Int32 | String)

# この状態ではString型に固有のメソッドは利用不可
p a1.size
#=>Error in line 5: undefined method 'size' for Char (compile-time type is (Char | Int32 | String))

if a1.is_a?(String)
  #　ここではa1は確実にString型
  p a1.size
  #=> 1
end
```

_（**Tuple**生成のサンプルコードで，`Tuple.new`では型引数を指定しなくても自動的に判定してくれるのに`Tuple.from`では必ず型引数を指定する必要があるのもこのことが影響しています。コンパイラからは配列`[1, "2", '3']`の要素は全て**(Char|Int32|String)**型の値に見えており，何番目の要素が実際にはどの型なのかを判別できません。そのため各要素をどの型の値として扱うかを型引数の形で明示する必要があります。）_

便利ではあるものの，値の更新ができないため，微妙に使いどころが限られる**Tuple**ですが，おそらく一番よく目にするのはメソッドの引数周りではないでしょうか。Crystalではスプラット（`*`）記号を使用してメソッドに可変長引数を指定することができます。この際，メソッド側では**Tuple**型の変数として引数を受け取ることになります。

```crystal
def foo(*args)
  p typeof(args)
end

foo(1,"2", '3')
#=> Tuple(Int32, String, Char)
```

逆に，複数の引数が定義されているメソッドに対して，**Tuple**を利用して引数を一括して指定することもできます。

```crystal
def bar(x : Int32, y : String)
  p x
  p y
end

args = {1, "2"}
bar(*args)
#=> 1
#   "2"
```

# **Set(T)**

**Set**（集合）はユニークな（重複のない）オブジェクトのコレクションです。

**Set**オブジェクトは`Set.new`にArrayを渡したり，`Array#to_set`などの形で**Array**から生成することができます。また，専用のリテラル表現はありませんが，`Set{...}`という特殊な構文で**Set**オブジェクトを生成することができます。

```crystal
# 全て同じ結果
p set = Set.new([1, 2, 3])
p [1, 2, 3].to_set
p Set{1, 2, 3}
#=> Set{1, 2, 3}
```

他の「配列っぽい」型と異なり，**Set**の要素には順番がありません。そのオブジェクトが**Set**に含まれているかどうかのみが考慮されます。そのため，これまでの型では標準的に使えた`#[]`にインデックスを指定して要素を取得するという操作が**Set**できません。また，ほかの型にはない「別の**Set**との間で数学的な集合演算を行う」ためのメソッドが用意されています。

```crystal
set1 = Set{0, 1, 2, 3, 4, 5}
set2 = Set{0, 2, 4, 6, 8, 10}

p set1 | set2 # 和集合
#=> Set{0, 1, 2, 3, 4, 5, 6, 8, 10}

p set1 - set2 # 差集合
#=> Set{1, 3, 5}

p set1 & set2 # 積集合
#=> Set{0, 2, 4}

p set1 ^ set2 # 対象差
#=> Set{1, 3, 5, 6, 8, 10}
```

**Set**は要素の同一性を判断するため，内部データの保持に**Hash**を使用しています。つまり，既存の要素と新しいオブジェクトとの重複判定は，ハッシュキーとして同一とみなせるか（`#==`と`#hash`）によって行われます。

また，仮に格納するオブジェクトの状態を変更可能であったとしても，**Set**に格納している間は状態変更をすべきではありません。**Set**における重複判定はオブジェクトの格納時にのみ行われ，一旦格納されたあとで要素の状態が変更されないことを想定しています。もし**Set**に格納した可変なオブジェクトの状態が変更された場合，他の要素と値が重複してしまう可能性があります。

```crystal
class Person
  property name
  
  def_equals_and_hash @name
  
  def initialize(@name : String)
  end
  
  def inspect(io)
    io << '"' << @name << '"' 
  end
end

persons = Set(Person).new 
john = Person.new("John")
mike = Person.new("Mike")

persons << john << mike
# この時点では各要素はユニーク
p persons
#=> Set{"John", "Mike"}

persons << Person.new("John")
# 同じ値のデータは重複しない
p persons
#=> Set{"John", "Mike"}

# その後に要素の状態を変更すると
mike.name = "John"
# 重複した値が含まれ得る
p persons
#=> Set{"John", "John"}
```

# 配列**っぽい**型のまとめ

| 型        |インデックス利用|要素の追加削除|要素の入れ替え|
|-----------|----------------|--------------|----------|
| **Array** |◯              |◯            |◯        |
| **Slice** |◯              |×            |◯        |
| **Tuple** |◯              |×            |×        |
| **Set**   |×              |◯            |×        |

[^2]: 更新自体は可能だがすべきではない

# 【おまけ】配列**っぽい**型のリテラル**っぽい**生成法

**Set**を生成する際の`Set{...}`のようなリテラル**っぽい**動きをする構文は，標準ライブラリだけでなく自作の型でも使用することができます。そのために必要なのは，その型に引数を取らないコンストラクタと，引数を1つ受け取れるインスタンスメソッド`#<<`が用意されていることだけ。**Array**のように内部に複数のオブジェクトを格納できるような型にとって便利な機能ではありますが，実際はその型が必ずしも**Array**のようなコレクションである必要はありません。

例えば，数値をどんどん加算していく**Adder**クラスを考えてみましょう。`Adder#<<`に整数値（**Int32**型）を与えていくと`Adder#total`で合計値を取得できる，というだけのもので，実装はこんな感じ。

```crystal
class Adder
  getter total
  @total = 0

  def <<(num : Int32)
    @total += num
  end
end
```

実装から想定される基本的な使い方はこう。

```crystal
add = Adder.new
p add.total
#=> 0

add << 10
p add.total
#=> 10

add << -1
p add.total
#=> 9
```

このような型でも，引数のないコンストラクタと`#<<`メソッドをもっているため，先のリテラル**っぽい**構文が利用できます。また，そのために実装に手を加える必要はまったくありません。

```crystal
add = Adder{1, 2, 3}
p add.total
#=> 6
```