20 — 落とし穴 (実測)
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 で溜めること。
次: 付録。