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 with 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 with 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

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

代わりに名前呼び出しの結果を push すると、その束縛には要素型が 付かない。両側とも非空になった状態で比較すると、アドレスや長さで答えるのでは なく実行時に失敗する。注釈がその解決策で、上の xs / ys が注釈を 持っているのはそのためである。

型引数を取る struct が「自分で型を語る」かどうかは、型引数によって決まる。 Box::equals は struct 全体に対して 1 つだけ生成され、T 由来のフィールドを T が何だったか知らないまま比較する。それが内容比較になるのは Int / Bool / Unit / String のとき。Double / Bytes と、配列や struct のときは アドレス比較になる。したがって Box[Int]::{ value: 1 } は解決し、 Box[Double]::{ value: x } は解決しない。後者は注釈を付けても直らない — 別途修正中の既知の不具合で、実行時の失敗はそれに偶然出会わないための ガードである。

コンパイル時の縁: 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 with 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 with Console {
  println("\{eq2([1], [1])}")
}
no impl `Eq` for `Array[Int]`

以上が全部。2つの縁のどちらでも必ず知らされる — witness が無い場合は コンパイル時に、注釈の無い空配列はトラップで。どちらもアドレスで黙って 答えることはない。

次: 並行処理