WIP:俺言語(概念編)
動機
俺の納得いく言語を作りたい
俺が隅々まで制御できる言語を作りたい
構文を考えず概念から考えることで良さげな言語ができるのでは?
構文はビジュアルプログラミングなどいくらでも表現方法はありそう
方針
構文を考えず概念から考えることで良さげな言語ができるのでは?
構文はビジュアルプログラミングなどいくらでも表現方法はありそう
基本は関数型
データとそれを操作する処理で構成する
とりあえずコンパイル時と実行時などで分けて考えない
おそらくコンパイル時でも実行時でも概念は共通する
できればコンパイル時は、コンパイラの実行時であり、俺言語の実行時と同じ処理な感じにしたい
概念
データム(Datum)
「1」といった数値や、「あいう」といった文字列などのデータそのものを指す概念
型(Type)
データムの種類のこと
データムの構造の定義と、コンストラクタ関数から構成される
関数(Function)
データムの変換処理のこと。足し算や引き算など
ラベルグループを持ち、その中で
ラベル(Label)
データムにつける名前のこと
ラベルグループ(Lable Group)
ラベルのグループを指す概念
ラベルが同じであってもラベルグループが違うならば、違うラベルとして扱う
ディレクトリやスコープと同じような概念
しかし親子関係などはなく、独立している
どこまでもネストしてしまい、把握しきれなくなるため
ラベルグループ同士でラベルをリンクさせることができる
ラベルグループAのラベル「aaa」とラベルグループBのラベル「bbb」をリンクさせると、両方のラベルが同じデータムを参照するようになる
ラベル単位で公開/非公開が設定でき、非公開のラベルはリンクできない
リンクする際にデータムのライフタイムをラベルグループの長い方に合わせて更新する
ラベルグループを作成したときに、作成した側か、された側のどちらが長いライフタイムか分かるはず
リンクはライフグループの生成時にしか行えないものとすれば更に良さそう
if や for などのブロックとは全く別の概念
モジュール(Module)
関数と型のみのラベルグループ
再利用のために関数と型をグループ化したもの。
ライフタイム(Lifetime)
データムが有効な期間のこと。
ほとんどのデータムはラベルグループが削除される際にライフタイムが切れる
しかし他のラベルグループとリンクされたデータムは長い方のライフタイムに合わせる
メタデータム(Meta Datum)
データムのメタ情報のこと
具体的には型、ライフタイムなどのこと
型変数
(サブタイピング多相)
型が違うデータであっても、一部が一致していればその構造に対して共通の処理ができる
返り値の型をどうするか?
一部が一致しているのだから、代入してやれば良い
代入するだけのラッピングを導出してやるのはアリ
Array などのコレクションの部分一致をどう扱うか?
Struct 専用にしても良い
ブロック
コンパイル時に動くか、実行時に動くかがブロック単位で決まっている
型変数やその制約を定義するブロックはコンパイル時に動く
関数の中身などは実行時に動く
if などは親のブロックの属性を継承する
殴り書き
オーバーロードは何が問題だっけ?
オーバーロードだけならそう難しくはなさそう
型ごとに基本的な関数を書くのがダルい
Haskell の deriving があれば良さそう
プリミティブな型に予め色々書いておき、導出する
code:text
interface(equal(T, T))
func equal(a Array<T>, b Array<T>){}
func equal(a Struct, b Struct){
field_keys(a, func(key string){
equal(get(a, key), get(b, key))
})
}
type User = {name: string, age: int}
func equal(a User, b User) bool {
a.name == b.name
}
デフォルト実装
パターンマッチ欲しいな
オーバーロードをどうする?
できるだけなしにしたい
しかし例えば、エラーの種類に関わらずバックトレースを出力したい場合はどうする?
トレイト
できるだけなしにしたい
マクロ
できるだけなしにしたい
クロージャー
積極的に載せる機能ではない。
自然にできたらできたで良い
コンパイラだけど、ノリはインタープリタ的に行きたい
エラー処理
インターフェース
型クラス(というか再帰的な型)
Struct や Array で必要
Struct に対する関数や、Array に対する関数は共通で使える必要がある
Struct で生成した型には名前を付けたいが、Array<Int> などに名前は付けたくない
ラベルらへんで解決できそう
型変数
できるだけなしにしたい
並列
モジュールをオブジェクトっぽく扱うのもアリかも。いやそれは違うのでは
なんで型に関数を紐づけるのが嫌なんだっけ?
思考が狭まるってのは1つある
コウモリ問題がある
ミックスインのトレーサビリティは最悪
JavaScript の import みたいにすれば解決しそう
<=> から、==, <, > などを導ける機能が欲しい。Ruby の Enumerable みたいなやつ
シンプルか?凝ったことになってないか?
シンプルな概念の組み合わせで実現したい
equal などの基本的な関数を毎度書くのはやめたい……
祖先の型を辿る?
型に関数が付属している必要がある
モジュールで型と関数を結びつける?
型と関数をセットにするのはダメだが、実装の継承は行って良さそう
モジュールAの関数をモジュールBの関数に継承する
意味なさそう。
全く同じであるなら、モジュールA.関数で呼び出せばいいでしょ。
この辺の問題はオーバーロードで解決できそう
問題
インターフェースがない
共通のエラー処理などが書けない
何かの関数があることを期待することができない
おそらくオーバーロードで解決できそう
Struct や Array などの型を生成する型の共通処理をどうするか
型を生成した際に、良い感じで関数も生成されると良さそう
Array<Int> を生成した際に、func equal(a Array<Int>, b Array<int>) bool が生成されるイメージ
実装をどこから導くか
型変数を使えば良い
code:text
func equal(a Array<interface(equal)>, b Array<interface(equal)>) bool {
return false unless size(a) == size(b)
times(size(a), func(index int){
return false unless equal(get(a, index), get(b, index))
})
return true
}
実装がムズい
equal は単一の型の引数だから良いが、2つあったらどうする?
祖先に対して処理を書く
子孫は祖先の構造を変更したりせず、追加のみであることが前提。
部分的構造型
is-a の関係
関数が見つかるまで祖先を遡る
返り値の型をどうするか?
Self 型?
code:text
func equal(a Array, b Array) bool {
i = size(a)
times(i, func(){})
}
Eq のような制約をつける事ができない
equal などの基本的な関数を毎回書くのはシンドイ
型の違うデータに対して共通処理が書けない
例えば
エラー処理
コレクション操作
そもそも関数とデータ構造で分けたのは、構造が違うなら違う処理になるし、同じなら同じ処理でできる、という仮説に基づいている
部分的に構造が一致していれば、それに対して共通化して良い
これでけっこう解決できると思う
エラー処理はメッセージとトレースが部分一致すれば良い
配列は中身ではなくコレクション部分が一致してれば良い
ただ、配列の equal や each が解決できない
型変数か?リフレクションか?もっと良いアイデアはないか?
each はなんかイケる気がする
問題は高階関数に渡す型をどうするか、って話
でも配列自体が中身の型を分かってるし、それはコンパイル時でも実行時でも分かってるはず
each を定義する時には決まってない。each を使う時には決まっている。
やはり型変数か……?
定義の時には決まってないけど、呼び出す時には決まってますマークを作る?
呼び出す際には、ここの情報から型情報を取ります、的な
equal は最悪無くてもいい。毎回 each で比較してくれって感じ
each が解決できれば型は提供できるので
比較関数を高階関数として渡しても良さそう
なんならインターフェースより良いかも
型クラスが必要な箇所は全部、高階関数(と型変数)で解決できそう