本文へ移動
Hikari リファレンス

構文リファレンス

リテラル・演算子・束縛・ブロックなど、Hikari の構文要素ごとの形式と実行例を列挙する。

enum 定義 enum

enum Name := OneOf(Tag1(T...), Tag2(T...), ...)

タグ付きデータ型を宣言する。各タグは Name.Tag (型値の下の slot) として住み、フラット束縛はしない。複数タグは OneOf(...) でまとめる。

>>> enum Shape := OneOf(Circle(Float), Rect(Float, Float))
>>> Shape.Circle(2.0) matches Shape
true

refinement 型 refinement

refinement { v: T | 述語 }

基底型を述語で絞った型を作る。中身はちょうど 1 スロットで、型注釈と body の両方が要る。境界 (束縛・パラメーター・結果型) で契約として強制され、満たせない値は静的に報告される。

>>> type Pos := refinement { v: Int | v > 0 }
>>> 3 matches Pos
true

タプル (a, b)

(a, b, ...)

2 要素以上の不変・固定長タプル。複数引数の適用・多値返却・分解代入に使う。

>>> (1, 2).length()
2

ブロック/関数/オブジェクト統一リテラル { slot-list | body }

{ slot-list | body }

関数・ブロック・データオブジェクトはすべて同じリテラルで作る。body があれば呼び出しで body を評価し (関数)、body が空なら呼び出しで自身を返す (データ)。

>>> {x | x + 1}(2)
3
>>> {x := 1, y := 2 |}
{ x := 1, y := 2 |}

メソッド呼び出し x.f / x.f(a)

recv.method(args)

ドットでレシーバーのスロット/型メソッドを取り出し、並置または括弧で引数を渡す中置糖衣。

>>> [1, 2, 3].length()
3
>>> 5.to_float()
5.0

モジュール取り込み import <対象> := "path.hika"

import <対象> := "path.hika"

ファイル / std: 標準ライブラリ / pkg: 依存を取り込み、対象名へ束縛する宣言形。import m := "x" はモジュール名前空間全体を、import { a, b } := "x" は列挙 member を分解束縛する ({…} の有無で分岐)。path は := 右辺の文字列リテラル 1 個のみ (動的パス・変数・補間は構文エラー)。プレフィックス無しは呼び出し元ファイル基準の相対パス、std:name は標準ライブラリ、pkg:alias はマニフェスト経由の依存解決。
import は呼び出し元ファイルの実体パスに依存するため、本リファレンス生成の評価環境 (ファイルを持たない孤立式) では実行できない。以下は構文のみ検証する例。

>>> import m := "std:math"
>>> print m.sqrt(4.0)

ユーザー定義ループ loop

loop { slot-list | body }

その関数を break / continue の捕捉境界 (ループ) として宣言する。return だけを素通しする点が conduit と違う。渡したブロック内の break! / continue! は正当と判定され、転帰は captureControl 値として観測する。conduit との併用は構文エラー。

>>> twice := loop { b | capture(b); capture(b); () }
>>> mutable n := 0
>>> twice { n = n + 1 }
>>> n
2

中置二項演算子 + - * / %

a op b

算術演算はレシーバーのスロット呼び出しへの中置糖衣。優先順位は乗除 > 加減。

>>> 1 + 2 * 3
7

代入 =

name = expr / recv.slot = expr

既存の可変な束縛・スロットを更新する。名前もスロットも導入しないので、束縛が無ければ静的エラー、未定義スロットへの代入は実行時エラーになる。

>>> mutable s := "a"
>>> s = s + "b"
>>> s
"ab"

公開限定 export { name, … }

export { foo, bar }

ファイルトップレベルで公開する member を列挙して限定する (無ければ全公開)。中身は識別子のみ。

>>> export { foo, bar }

分岐 dispatch match

match subject { pattern [| guard] => body … }

subject を各 arm のパターンに構造照合し、最初に一致した arm の body を返す。
subject を省くと arm 頭が述語 (Bool 式) の短絡連鎖になる。

>>> match 2 { 1 => "a"; 2 => "b"; _ => "c" }
"b"

分配束縛 a, b := expr / { name, ... } := value

a, b := expr / { name1, name2 } := value

タプルから位置ごとに複数ローカルを一括束縛する (位置 destructure)、またはオブジェクトから同名スロットを一括束縛する (名前 destructure)。

>>> a, b := (1, 2)
>>> a + b
3
>>> { x, y } := {x := 1, y := 2 |}
>>> x + y
3

制御透過関数 conduit

conduit { slot-list | body }

その関数が自分の呼び出し境界で return / break / continue を捕捉せず上位へ素通しする。制御構造を Hikari で書くための注釈で、通常の戻り値・適用規則・スコープは普通の関数と変わらない。後が { 以外なら構文エラー。

>>> unless := conduit { cond: Bool, then: {| Unit } | if cond { () } then }
>>> unless(false, { 42 })
42

前置演算子 -

-x

単項マイナスのみが前置演算子として存在する (論理否定に前置 ! は無く、.not() を使う)。

>>> x := 5
>>> -x
-5

動的添字アクセス x.[i]

recv.[expr]

[] 内の式を評価した Int を添字として Sequence の要素を引く。

>>> [10, 20, 30].[1]
20

可変の宣言 mutable

mutable name := expr

名前に値を mutable に束縛する新規ローカル宣言。重複・シャドウの規則は := と同じで、slot-list では可変スロットの宣言になる。以後の書き換えは代入 = で行う。

>>> mutable n := 1
>>> n = n + 1
>>> n
2

型アスクリプション expr: Type

expr: Type

任意の式に型を後置表明し、評価地点で照合する。: は最低優先 (二項演算子・関数適用より弱く非結合)。

>>> 1 + 2: Int
3

型テスト matches

expr matches Type

値が型に一致するかを試す中置演算子。優先順位は比較と同じ段で非結合。予約語ではなく、中置位置に現れたときだけ演算子として読むので、スロット名や普通の名前としても使える。

>>> 1 matches Int
true

型定義 type

type Name := <型式>

型式を値化して名前に束縛する。以後その名前を matches / 型注釈 / match の型パターンで使える。

>>> type Meters := Int
>>> 5: Meters
5

失敗伝播 expr?

expr?

Result/Option の失敗 variant なら関数から即座に脱出し (Result はそのまま返す)、成功 variant なら中身を取り出す。return の構文糖。

>>> f := {r | r?}
>>> f(Ok(5))
5
>>> g := {r | r?}
>>> g(Err({kind := "boom", message := "x" |}))
Err({ kind := "boom", message := "x" |})

整数リテラル 42

42

10進のほか 0x (16進) / 0b (2進) / 0o (8進) prefix、桁区切り _ を書ける。

>>> 42
42

文字列リテラル "..."

"text"

ダブルクォートのみ。要素は Unicode code point (rune) 単位。

>>> "hi"
"hi"

文字列補間 ${...}

"text${expr}text"

文字列リテラル中の ${expr} を評価し、文字列化して埋め込む。

>>> name := "hikari"
>>> "hello, ${name}"
"hello, hikari"

明示的 kick 後置 !

f!

引数無し呼び出し f!f() の短縮。全名前スロットが埋まらない値適用は部分適用になるが、明示的 kick (() / !) だけが残りのスロットへデフォルト値を補完して本体評価に進む。

>>> v := {x := 0, y := 0 | x + y}
>>> v!
0

束縛 :=

name := expr

名前に値を immutable に束縛する新規ローカル宣言。同じスコープでの再宣言はエラー (シャドウ不可)。

>>> x := 2
>>> x + 1
3

束縛を写した自分 inner_bound

inner_bound name

slot-list に書いて、束縛を写した自分に届く名前を宣言する。inner が派生元を指すのに対しこちらは派生のたびに再束縛される。型注釈は持てない (inner_bound name: T は構文エラー)。同じリテラルで inner と同じ識別子は宣言できない。

>>> p := { inner_bound b, x, y | b }
>>> q := p(1, 2)
>>> q.x
1

比較・論理演算子 == != < <= > >= && ||

a == b / a && b

比較は Ordering 経由の全順序、&&/|| は Bool 専用の短絡評価 (糖衣に脱糖しない)。

>>> 1 < 2
true
>>> true && false
false

浮動小数リテラル 3.14

3.14

小数点の両側に数字が必要 (3..5 は Float にならない)。指数表記 1e10 も Float。

>>> 3.14
3.14

真偽値リテラル true / false

true / false

Bool の 2 値。制御構造・&&/|| の被演算子は Bool 専用 (§9.3 厳格判定)。

>>> true
true

自己参照名 inner

inner name

slot-list に書いて内部名を宣言する。本体からその名前で値自身 (リテラルが生んだ値そのもの) に届く。名前は書き手が選ぶ。データスロットではないので obj.name では引けず、等価・shape にも影響しない。1 リテラルに高々 1 つで、slot-list 領域限定。

>>> fact := { inner self, n: Int | if(n <= 1, { 1 }, { n * self(n - 1) }) }
>>> fact(5)
120

複数行文字列 """..."""

"""..."""

""" で開き """ で閉じる複数行文字列。開き """ の直後は改行必須、閉じ """ は行頭空白のみの独立行に置く。閉じ行のインデントを共通接頭辞として各行から剥がす (dedent)。本文中の単独の " はエスケープ不要。エスケープ (\n \t ほか) と ${...} 補間は通常の文字列と同じ。

>>> """
>>> a "quoted" line
>>> """
"a \"quoted\" line"

証明対象外の refinement 型 refinement_opaque

refinement_opaque { v: T | 述語 }

refinement と同じ型を作るが、述語が決定可能断片の外にあることを明示して静的検査の証明対象から外す。実行時の振る舞いは refinement と同一である。

>>> type Even := refinement_opaque { v: Int | v % 2 == 0 }
>>> 4 matches Even
true