20 — 落とし穴 (実測)

前: wasm をターゲットにする

English version: 20_pitfalls.vibe.md (canonical)

ここに挙げるのは、人の午後を 1 つ潰す規則である。どれも現行のコンパイラで 実測した。全一覧は docs/user/reference/cheatsheet.md にある。 この章は最初に噛みつくものだけ。

handle の適格性は型システムではない

handle が、それが覆う perform をすべて見られない場合、プログラムは 型検査を通ってもコンパイルに失敗しうる。呼び出しが「見える」条件は 2 つ。 callee の型が effect row を持っているか、handle がもともと見通せる callee であるか — トップレベル fn、それを別名で束縛したもの、あるいは handled body の内側で宣言されたクロージャ。

したがって local 束縛や引数を経由した呼び出しも、その型が row を持って いれば通る。local 束縛であること自体が問題なのではない。 handled body の外で宣言された row を持たないクロージャも、本体が 純粋な計算だけなら通る。handle は引数を見ている。失敗するのは見える本体が 無い呼び出し — 即座に適用するクロージャや、本体が perform する、あるいは この判定が名前を追えない呼び出しをするラムダ。診断は直し方を述べ、ADR 番号は 出さない。実測した全一覧は docs/user/reference/cheatsheet.md にある。

// skip: 適格性による拒否 — callee が名前ではなく式
effect Ask {
  Get() -> Int
}

fn ask() -> Int with Ask {
  perform Ask::Get()
}

fn main() -> Int {
  handle { ((n: Int) -> Int { n })(ask()) } with {
    Ask::Get() => resume(0)
  }
}
handle of effect 'Ask' cannot be compiled here: the handled body calls through an expression rather than a name, and this handle cannot see what that call performs. Give the callee a name it can see -- a top-level `fn`, or a `let` bound to the closure literal inside the handled body -- or move the `handle` into the function that performs.

Int の幅はタグビットに従う

Int は 63-bit (タグビット 1 本、ADR-0105)。リテラルの最大は 4611686018427387903。max + 1 はどのバックエンドでも -4611686018427387904 に wrap する。まだ 2^61-1 / 62-bit と書いてある 文章は古い。

文字列補間にはレンダラが要る

\{x} で補間するユーザー struct には derive(Show) か fn T::to_string(v) -> String が要る。スカラー・Option・タプル・配列は 既にレンダリングされる。Show が無いとかつてはポインタが表示されたが、 今はエラー (#1445)。

s[i] は String ではなくバイト

String は byte string。s[i] はそのオフセットのバイトで、Int。 'A' == 65。1 バイトの String が欲しければ String::from_char_code(s[i]) — これはバイト書き込みで、別名 String::from_byte (#2203) — かスライス。 そのバイトが文字そのものなのは ASCII のときだけ。

トップレベルは宣言だけ

トップレベルの裸の式は拒否される (ADR-0069)。fn main か test ブロックに 入れること。

// skip: ADR-0069 — ファイルは宣言の並びである
1 + 2
top-level expressions are not allowed; move it into fn main

test / bench は文字列を取るか何も取らないか

test { } と test "name" { } は通る。test foo { } (裸の識別子) は 通らない。

Error と Exception

effect は Exception。effect の綴りとしての Error は廃止された (#1461): with Error は拒否され、メッセージが Exception を示す。 古いコードでは Error::Throw が操作の修飾子として今も見え、受理は されるが、新しく増やさないこと。

配列の ==

== は値で比較する。要素がスカラーでない配列も、関数の戻り値として来た 配列も、空リテラルも — 注釈の有無によらず — そうなる。注釈の無い let xs = [] は、それを埋める Array::push から要素型を受け取る (#2157)。 ただし push する値が自分で型を語る場合に限る — リテラル、リテラルだけ からなる配列・tuple・struct、両分岐が一致する if がそれにあたる。

名前や呼び出しの結果を push した束縛は、構文だけでは要素型を語らない。 vibe run と vibe test は、その要素が == で比較できるなら内容で答える。 Double も Bytes も配列もそうである。let xs: Array[Int] = [] は、 それに頼らない書き方である。generic な struct は具体的な型引数ごとに 比較されるので、Box[Double] も Box[Array[Int]] も内容で比較される。 失敗するのは構造的な比較子を持たない要素で、実行時ではなくビルドエラーに なる。vibe check はそれを報告しない。

知っておく価値のある境界は Eq の witness を持たない generic な T で、 こちらは不意打ちではなくコンパイルエラーになる — no impl Eq for Array[Int]`。等価性 を参照。

行頭の - は被演算子に接しているときだけ前置

次の行が演算子で始まるとき、改行は式を終わらせない。ただし例外が一つある: 行頭の - / ! / ~ は、被演算子との間に空白が無ければ前置演算子に なる (#3041)。だから単独の行の -1 は新しい式を始め、- 1 (- の後に 空白) は上の行の続きとして引き算になる:

fn main allows Console {
  let a = 10
  let fresh = {
    println("computing")
    -1
  }
  let continued = a
    - 1
  let trailing = a -
    1
  println("\{fresh} \{continued} \{trailing}")
}
computing
-1 9 9

決め手は - の後の空白なので、折り返す引き算は 1 行で a - 1 と書くか、 次の行頭に - 1 と書くか、- を前の行の末尾に置く。それ以外の行頭の演算子 (+, *, |>, && など) は常に前の行の続きになる。Unit を返す文の後に 空白付きの - 1 を書くと型エラーになり、メッセージは二つの直し方 — 空白を消す、または (-1) と書く — を名指しする。

fn はキーワード

let fn = 1 は parse error。r#fn という逃げ道は無い。束縛の名前を 変えること。

for は常に集めるわけではない

let xs = for x in arr { x * 2 } は Array なら通る。同じ位置に pull イテレータを置くと位置付きのエラーになる。ArrayBuilder で溜めること。

次: 付録。