WIP:Crossable
動機
直交性を限りなく高めた言語を作ってみたい
実現方針
なし
仕様
Crossable 文法
S式が直交性高い。
あとで良い感じの文法を作ってもよし
コンパイル
重くていい。変更のあったファイル?関数?だけをコンパイルできるようにすれば問題ない。
リリースビルドに時間がかかるのは問題としない。
動的型付け or 静的プロトコル決め or 漸進
変数のメタ情報
デフォルトを不変にする
可変変数は作れるが、読み書きできるのは作ったスレッドのみ
スレッドで共有するにはSTM?チャネル?
nil をどうするか
許したくないが、動的型付けなら許すしかなさそう
(def x 42)
(def x 42 {:mutable true :thread_safe false :doc "全ての答え" :nilable true})
シンボルの再束縛は可能
関数などに渡した変数が、知らぬ間に変わっていることが恐怖
シンボルはスコープによって絞られているので、再束縛するのは怖くない、はず
スコープ
現在のスコープと親のスコープを参照できる、かな
スコープが切り替わる時に引き継ぐデータを決めて、それ以外は破棄する。GCでも活用する?
切り替えの方法
データ構造
データ型
スカラー型
Int
Float
Str
Bool
Keyword
複合型
Map
(Map key value key value)
{key value key value ...}
Vector
(Vector value value)
[value value ...]
Deque(両端キュー、リスト)
(Deque value value ...)
#[value value]
Set
(Set value value ...)
#{value value}
Record
(def User (Record name age))
(defrecord User name age)
(User :name "kekemoto" :age 16)
User 型は Record 型で Record 型は Type 型
Fn
Macro
Type
Uneval のような型をユーザー でも定義出来るようにしたい。
Record とか Vector を組み合わせて独自の型を作れる
データの入れ方があれば良い。結果としてデータ構造が出来てれば良い
(deftype Tag (Fn [name attr content] {@name @attr @content}))
データが Tag かどうかは、どうやってデータが作られたかのみで判断する。
具体的にはデータの型をメタデータとして持つ
Record 型を Type で作る例
code:lisp
(deftype Record (Macro @keys めちゃくちゃ分かりづらいww
Uneval
(Uneval (value value))
'(value value)
1引数を取るマクロ。引数をASTとして扱う。
「,」があればそこだけその場で評価し、その後ASTにする
「,,,」があればそこだけその場で評価し、シーケンスならば良い感じに展開してからASTにする
マクロと Uneval は結構違う。
どちらも、解析済みだが未評価のコード、ASTを扱うことは同じ。
マクロは動的にASTを作るが、Uneval はASTそのものである。
マクロでASTを作る場合には健全さに注意を払う必要があるって話。
パターン束縛
(bind [name age @also] vector)
(bind #[name age @also] deque)
(bind {:name nickname :age year} map)
(bind {@name @age} map)
(bind (User name age) user)
(bind (User :name nickname :age year) user
一部だけじゃなくて全体も欲しい時はどうしよう
(bind [name age]@user array)
コードがデータではない。
Haskell のDO記法みたいなやつ
(scope (let x 1) x + 2)
関数宣言などは内部的に scope を使う
bind や let を現在のスコープの中の変数宣言とする
Scala の _ 記法みたいなやつ
(partial add _ 3)
#(add _ 3)
無名関数を作る構文ではなく、部分適用する構文
# なしでやれたら良さそう
パイプ(関数合成)
(comp #(filter even? _) #(reduce + _))
(pipe 1 #(add 2 _) #(mult 2 _))
(-> 1 #(add 2 _) #(mult 2 _)
第一引数のデータを第二引数に
関数の引数
可変長引数
本当にいるか?
Vector を渡せば良い
畳み込みで処理すれば良いのでは?
畳み込みマクロを用意してやれば良い?
#&(+ 1 2 3 4)
キーワード引数
本当にいるか?
Map を渡せば良い
デフォルト引数
本当にいるか?
nil を渡せば良い
キーワード引数があれば、それでも良い
可変長引数とキーワード引数を同時に指定することはできない。で良い。
1つ1つの引数でパターン束縛を可能にする
(fn [f :init] )
(fn [#[{:name :age} @people] @args] )
WIP:マルチメソッド
同じ名前の関数を別のデータ構造で別の処理として実装したい
後から別のデータ構造も追加できるようにしたい
別の処理なら別の名前をつければ良いだけなのでは?
型を指定するの?Java とかのインターフェース を指定するの?
呼び出し時にそのデータがどの型なのかは分かるが、どの関数が呼べるかまでは分からん。
(def add (hub))
(defhub add)
(attach add [(arg x Int) (arg y Int) (arg_params also Int) (arg_key init Int)] )
(attach add [(arg x Str) (arg y Str) (arg_params also Str)] )
interface / implement , hook / hang , dispatcher / receiver , joint / attach , hub / attach
普通の関数との組み合わせをどうするか?
直感は明確に分けた方がいいと言っている
名前スコープを分けるから頻繁に被ることはないはず
どの実装にも当てはまらなかった場合に普通の関数が選択される?
知らんうちに普通の関数を実装してしまって意図しない動作を引き起こしそう。
名前スコープあるから大丈夫?
たまたま被ってしまうのではなく、既にある関数に対して外から追加したくなると思う
引数の型の組み合わせを一意に表現できれば、関数ディスパッチが速くなるはず
名前スコープ
関数をまとめ、再利用するための機能ではなく、
名前の衝突を回避するための機能!
関数の再利用は、関数である時点で達成している。
まとめる必要もないし。
使い方
(def myapp/x 42)
(add myapp/x 3)
階層構造は不可
スコープ自体に階層があるのに名前スコープの中で更に階層化したらカオス
メリットも浮かばない
擬似的に表現することは可
myapp-core-view/x
名前スコープをマージすることで、名前空間を書かなくて良くなる。
パターン束縛が使えると良いな。
(merge_scope {ok html :cast_string ->str}@util myapp/utilities)
ファイル名やパスと一致させてロードできると良さそう。
ロードとマージを同時にしたりとか
実装
スコープとほぼ同じ
名前で参照できることが違う
マクロ
何の入力を貰って、どんな Uneval を出力するかを定義する。
マクロを考えると、() がリストでないと困るのでは?
() がリストでなくても困らない、はず。普通の Lisp のマクロは
code:clojure
`(if (not ~flag) ~then ~else))
(list 'if (list 'not flag) then else))
Clojure は、未評価の関数呼び出しがリストで、シーケンス
Crossable は、未評価の関数呼び出しが Uneval で、シーケンスではない
code:text
(Uneval (if (not ,flag) ,then ,else))
'(if (not ,flag) ,then ,else))
変数束縛の問題
if などの既にあるシンボルを上書きしてからマクロを呼び出す場合の問題
名前スコープを修飾して記録する。
マクロを定義した時点のシンボルに束縛されてる値を記録する。
クロージャーとか静的スコープと同じやつ
マクロと Uneval は結構違う。
マクロ内で新しく束縛したシンボルを、呼び出し側でも使っている場合の問題
実行時に展開されるマクロは、スコープが別々なので問題ないはず。
コンパイル時に展開されるマクロは、スコープも作ってその中で展開する。
静的マクロ
コンパイル時に展開
ユーザーが構文を作れる
事前の計算などができる
データを差し替えたりできる。
開発用のコードを本番時に削除するのも可能
動的マクロ
実行時に展開
Lisp にあるやつ
括弧マクロ
コンパイル時に展開
[], #[]などの括弧に対するマクロ。展開後は普通のS式に変換される
[val1 val2] -> (Vector val1 val2)
記号マクロ
コンパイル時に展開
Uneval のようなマクロ。値単体にも付くし、括弧に付くとその範囲を変換する
'val -> (Uneval val)
'(val1 val2) -> (Uneval (val1 val2))
'#[val1 val2] -> (Uneval #[val1 val2])、(Uneval (Vector val1 val2))
シーケンス、Ruby モジュール
末尾再帰最適化したい
おまけ
(今のところ)あまり革新的なことがないので、実装はしないかなぁ
マルチメソッドのやり方とか名前は面白いよね
普通の構文で、構文の直交性を高めようとすると結構やりがいあるかもしれんけど、S式だと十分高い。
機能の直交性を高めるためにしてるのは、データ構造や関数のインターフェースの共通化とか既にある手法を使ってるだけで画期的ではない。この辺で面白い機能があれば良いんだけど……
Clojure は Java を扱うのが凄い簡単なように、LLVM でも似たようなことが出来たら面白いかなぁと思ったけど……