Glisp文法
原則
- 要素の間は必ず区切り文字を入れなくてはならない
(++ [][])→(++ [] [])(+ 1 2)3→(+ 1 2) 3
- 英語を排する
fn→=>Top→_Bottom→!Unit→()pow→**concat→++
- シンボルにはUnicodeの文字と記号が使える:
πℝ∞°半径 - キーワードリテラル(
:foo)は無い - ネストを浅く書くことが出来る
- 可変長引数
- 演算子は引数がなくても意味を持つ
(+)は0、(*)は1、(-)は0、(/)は1(<)や(==)はtrue(比べる組がないので必ず成り立つ)
- メタデータは
^{...}で付与する。型の指定はx: Tと(: v T)で行う - 評価は何も書き換えない。書き換えは、アクションの実行で新しい構文木を作ること
- 型について
- 型は必ずデフォルト値を持つ
- 型も値であり、名前空間を値と共有する
- 型は値の集合であり、サブタイプを認める
0→number、[number number]→[...number]は値が変わらないので、そのまま渡せるnumberからintのように値が変わる変換は、明示的に書くcast: (value: T, targetType: G.Type) => UnumberからintはMath.floor、その逆は_.ident
- すべての値は型である
0は0のみからなるリテラル型numberは、値としてのnumberそれ自身のみからなる型を意味することは出来ない
- パスで参照できる式は限られる。すべての式がパスを持つわけではない
- 関数適用の引数は、番号ではなく仮引数の名前で指す:
x = (first [1 2])の./x/xs - 可変長引数は名前で指すとベクタになる:
c = (+ 1 2 3)の./c/xs/1は2 - let式の戻り値はパスで指さない。参照したければ名前を付ける:
{a = 10 r = (* a 2) r}のr - 関数の仮引数、ジェネリクス、本体はパスで指さない。関数のシグネチャの中にパスは書けない
- 関数適用の引数は、番号ではなく仮引数の名前で指す:
環境
環境は隠れた状態ではなく、常に見えている1つのlet式。REPLもプロジェクトファイルも、そのlet式を編集しながら計算していく。
;; prelude ← session ← project というlet式の入れ子
{x = 100
y = (+ x 1)}x = 20 や ./x/b = 30 のような中置記法は、REPLやアプリのシェルでのみ使える。実体は「環境を更新するアクションを返す式」。まだ無い名前は追加される。言語自体に def や undef は無い。
型注釈
interface Type<T> {
id: string;
default: T;
module?: Record<string, any>;
meta?: Record<string, any>;
}型変換(: v T) は v: T を前置にした形。合えば v をそのまま返し、合わなければ T のデフォルト値に置き換えて診断を出す。
(: 42 number) ;; → 42
(: "hi" number) ;; → 0(デフォルト値。診断あり)
(: () number) ;; → 0(診断なし)
(map xs (: % number)) ;; 各要素を number に変換型から型を作る
未定: 型の呼び出しを、派生型を生み出す用途に使えないか。オプショナルな第一引数と、メタデータを追記するためのキーワード付き引数を受け取るイメージ。
module(型に紐づいたstatic constやmethodなどをまとめる場所)は拡張できない(したい)
{x: 10 y: 20}
{x: number y: number}
[...number]
(vector number) ;; [...number] と同じ
(IO number)コンストラクタはmodule内に収める
(Mat2d.new 2 0 0 2 0 0)関数宣言
(=> (str: string n: number): string (++ ...(repeat str n)))
(=> (arg0: Required arg1?: Optional): ReturnType (ƒ arg0 arg1))
(=> (T) (test: boolean then: T else: T): T)
(=> (F T) (x: (F T)): T ...) ;; F は IO や vector のような型構築子になれる
(=> (T: Show) (v: T): string (show v)) ;; 制約付きジェネリクスは将来
(Mat2d.trs [0 0] (rad π)
;; 関数型
(=> (a: number b: number): number)
(=> (number number): number)抽象化のはしご
関数の本体が、そのまま次の段になる。「この関数は1段下から見ると何なのか」を定義側で表す。
circle = (=> (c: vec2 r: number): Path (ellipse c [r r]))
ellipse = (=> (c: vec2 r: vec2): Path (path ...~(ellipse-segments c r)))
(circle [0 0] 10)
;; → (ellipse [0 0] [10 10])
;; → (path ...) ellipse-segments の中身は見えない
;; → Pathの値- どの部分式を進めるかは、関数の定義ではなく、ツールや使う人が選ぶ
(sq (+ 1 1))→(* (+ 1 1) (+ 1 1))- 引数を先に進めれば
(sq (+ 1 1))→(sq 2)→(* 2 2)
~exprは「今ここで計算する。途中は見せない」という印。再帰やヘルパーを段に出さないために使う- 計算するのは値が必要なものだけ。受け渡すだけの引数は書いたままの式で残る
~xのように引数そのものに付けると、その引数は計算される...~exprは...と~exprの組み合わせ
~が最終的な値を変えることはない
repeat = (=> (T) (v: T n: number): [...T]
(? (== n 0) true [] _ [v ...(repeat v (- n 1))]))
pow = (=> (x: number a: number): number (* ...~(repeat x a)))
(pow (+ 1 1) (+ 1 2))
;; → (* (+ 1 1) (+ 1 1) (+ 1 1))
;; → 8Let式
{a = 10
b = 20
c = (* a b)
(+ a b c)}レコード
{x: 10 key1: 20}
{x: 10 x: 20}キーワード付き引数
アクション(未実装)
プロジェクトファイル自身に「自らの構文木を操作するアクション」を値として持てるようにする。
- 評価しても何も変わらない。「編集の説明」という値になるだけ
- 実行すると、説明どおりに編集した新しい構文木を作って差し替える。undoは古い構文木に戻すだけ
- 引数は常に評価される。式や位置を渡したいときは、呼び出し側で
`を付けて明示する`exprは「という式を表す値」。ast型の値になる- 作る時点では中の名前を解決しない。挿入先で参照エラーになるとしても、お構いなしにそのまま挿入し、そこで普通に診断が出る
~はここでも「今計算する」という意味
{radius = 10
grow = (Action.set `./radius 20) ;; 名前は仮
(circle [0 0] radius)}
(Action.insert . `(+ π 2)) ;; πはそのまま挿入される
(Action.insert . `(+ ~π 2)) ;; πの値が埋め込まれる関数適用式 ()
(+ 1 2 3)
(fn arg0 arg1 key0=value0 key1=value1)アイディア
1/2 ;; 有理数リテラル
20rad ;; 角度
(* [1 2] Vec2.I) ;; アドホック多相
(++ "a" "b")
(++ (Path.circle [0 0] 20) (Path.line [20 20] [120 240]))
(IO string) ;; stringアクション
;; 入力 Bndr.jsインスタンス
(Input number)
;; 表示。UIの他に、MIDIのボタンのLEDにフィードバックしたりなんだり出来る
(Display boolean)
;; Pipe
(|> x (=> (x: number): number (* x 10)) --)