16 — 等価性

前: ジェネリクス・trait・derive

English version: 16_equality.vibe.md (canonical)

== は値で比較する。内容の等しい配列どうしは等しく、フィールドの等しい struct どうしは等しい。黙ってアドレスを比べる経路は無い。

知っておく価値のある縁が1つある。こちらも誤った答えは返さない。Eq の witness を持たない generic な T はコンパイルエラーになる。以下で扱う。

ふつうの場合

スカラー、タプル、derive(Eq) の付いた struct と enum、内容で比較される Bytes、そして配列。

struct Point {
  x: Int; y: Int
} derive (Eq)

fn same_ints(a: Array[Int], b: Array[Int]) -> Bool {
  a == b
}

fn main allows Console {
  println("lits = \{[1, 2] == [1, 2]}")
  let a = [
    1,
    2
  ]
  let b = [
    1,
    2
  ]
  println("lets = \{a == b}")
  println("fn   = \{same_ints(a, b)}")
  println("tuple = \{([1, 2], 0) == ([1, 2], 0)}")
  println("struct = \{Point::{ x: 1, y: 2 } == Point::{ x: 1, y: 2 }}")
}
lits = true
lets = true
fn   = true
tuple = true
struct = true

Bytes も内容比較で、タプルの要素や derive(Eq) のフィールドとして 使ったときも同じ。

例外だと思われがちなもの

要素がスカラーでない配列も、関数の戻り値として来た配列も、空から始めた 配列も、値で比較される。最後のものは実際に走らせる価値がある — 空の配列 2つの片方に push しても、古い答えではなく正しい答えが返る。

fn mk() -> Array[Int] {
  [
    1,
    2
  ]
}

fn main allows Console {
  let pairs: Array[(Int, Int)] = [(1, 2)]
  let same: Array[(Int, Int)] = [(1, 2)]
  let other: Array[(Int, Int)] = [(1, 3)]
  println("non-scalar elements = \{pairs == same}, differ = \{pairs == other}")
  println("function returns    = \{mk() == mk()}")
  let xs: Array[Int] = []
  let ys: Array[Int] = []
  println("empty and empty     = \{xs == ys}")
  Array::push(xs, 1)
  println("after one push      = \{xs == ys}")
  Array::push(ys, 1)
  println("after both          = \{xs == ys}")
  let us = []
  let vs = []
  Array::push(us, 1)
  Array::push(vs, 2)
  println("no annotation       = \{us == vs}")
}
non-scalar elements = true, differ = false
function returns    = true
empty and empty     = true
after one push      = false
after both          = true
no annotation       = false

us と vs には注釈が無いが、それでも内容で比較される。注釈の無い let xs = [] は、それを埋める Array::push から要素型を受け取る (#2157) — ただし push する値が自分で型を語る場合に限る。リテラルはそうで、 リテラルだけからなる配列・tuple・struct や、両分岐が一致する if も同じ。

代わりに名前や呼び出しの結果を push すると、その束縛の構文だけでは 要素型が決まらない。vibe run と vibe test はそれでも型を付け、要素が == で比較できるもの — Int / String / Double / Bytes、配列、 option、tuple、宣言された struct — なら内容で答える。比較できないのは クロージャのフィールドのように、構造的な比較子を持たない要素である。その 比較は実行時のトラップではなく、直し方を名指しするビルドエラーになる。 vibe check はこの検査を走らせない。上の xs / ys の注釈は、束縛の 時点で要素型を書いておく書き方である。

generic な struct は、具体的な型引数ごとに比較される。Box[Double] も Box[Bytes] も Box[Array[Int]] も、Box[Int] と同じく内容で比較される。 比較子がまだ無いのは二つで、外側の関数の型パラメータを引数にしたものと、 Nest[T] が Nest[Array[T]] を持つような閉じない再帰である。この二つは コンパイルでき、比較を実行すると trap する。下のビルドエラーではない。

コンパイル時の縁: witness を持たない generic な T

fn f[T: Eq](a: T, b: T) の内側では、コード生成の時点で要素型が消えて いるので、== には呼び出し側が渡す Eq の witness が答える。witness を 持つ型なら、そのまま構造的な答えになる:

fn eq2[T: Eq](a: T, b: T) -> Bool {
  a == b
}

fn main allows Console {
  println("Int    same = \{eq2(1, 1)}, differ = \{eq2(1, 2)}")
  println("String same = \{eq2("x", "x")}, differ = \{eq2("x", "y")}")
}
Int    same = true, differ = false
String same = true, differ = false

Eq の witness を持たない型なら渡すものが無いので、その呼び出しは 拒否される:

// skip: これはコンパイルエラー。出るメッセージを見せるための例
fn eq2[T: Eq](a: T, b: T) -> Bool { a == b }

fn main allows Console {
  println("\{eq2([1], [1])}")
}
no impl `Eq` for `Array[Int]`

witness が無い呼び出しについては、以上が全部で、コンパイルエラーになる。 構造的な比較子が無い要素は、直し方を名指しするビルドエラーになる。どちらも アドレスでは答えない。上の、外側の型パラメータと閉じない再帰は例外で、 コンパイルは通り、比較を実行すると trap する。

次: 並行処理。