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) => U
        • number から 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))
;; → 8

Let式

{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)) --)