-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Borlangは新しい型システムの実験場として開発されたインタプリタ言語です。
複雑な型システムを作りたい場合、相応に複雑な言語仕様が要求されます。 Borlangはその要求に応えるため、そのへんの言語にありがちな機能を全部搭載することを目標としています。すなわち、
- オブジェクト指向
- 高階関数
- エフェクト
- ジェネレータ
- 例外
- Enum
- レコード
- 型クラス
- モジュール
- パターンマッチ
- オーバロード
などなど。
こいつにTypeScriptを複雑にしてSMTソルバー乗せたような型システムを乗せるんじゃ……。
- ランタイムは正しく動けば十分なのでナイーブな実装でよい。
- 文法はなるべく工夫せずにありがちなものを採用する。
- 機能がたくさんあると偉い。
borlang {path_to_module_root} [module.][target]
で指定した関数を実行します。
module
省略時にはmain
モジュール、target
省略時にはmain
関数が使用されます。
// src/command/main.borlang
pub fn main() {
println("Hello");
}
$ borlang src command/main.main
Hello
borlang cli
でREPLが実行されます。--path=path1,..
オプションでモジュールのルートパスを追加できます。
$ borlang cli
> 1 + 1
=> 2
// comment
形式のみ対応。
1ファイルが1モジュールとして扱われます。
// m1/foo.borlang
// module name: "m1/foo"
pub let value = 1;
import
式で他のモジュールを参照できます。(モジュール名として指定できるのは文字列リテラルのみ)
// m2/bar.borlang
let {value} = import "m1/foo";
- Int:
1
- Double:
1.0
- String:
"foo"
- Template:
`foo ${x}`
- Tagged template:
sql`select * from users where id = ${id}`
- Record:
{a: 1, [key]: value, key_and_value, ..other_record}
- Array:
[1, 2, ...another_array]
Record, Arrayについてはmutabilityの指定できてほしい気がする。
let x = 1;
let mut y = [1, 2, 3];
同じ名前を定義するとシャドウイングします。
C++でいうところのcont &
と& const
の区別をつけたい気がする。
if(expr_cond) expr_th else expr_el;
for(let i = 0; i < N; i++) {
}
for(let x in EXPR) {
}
while(EXPR) {
}
break
とcontine
が使えます。
x match {
PATTERN => EXPR,
}
continue EXPR
できる。
パターン:
- 各種リテラル
- 識別子: その名前でキャプチャされる
-
_
: dont care - レコード
-
{key: PATTERN}
: Exactな形状でマッチ -
{key: PATTERN, ..}
: 他のメンバを許す -
{key: Some(_)@k, ..@rest}
: キャプチャ
-
-
Some(n)
None
Foo{x: 1}
: Enum -
[1, 2, ..]
: Array
x match PATTERN; // booleanが得られる
if(let PATTERN = EXPR) EXPR else EXPR;
while(let PATTERN = EXPR) EXPR;
for(let PATTERN in EXPR) EXPR;
let PATTERN = EXPR; // 必ず成功する場合のみ
let PATTERN = EXPR else EXPR;
{
EXPR;
EXPR;
EXPR // セミコロンをつけないEXPRが末尾だと、それがブロックの値となる
}
fn f<T: TC>(arg: T, ..args: T[]): Int throw IOError perform IOEffect {
// ...
}
fn *range(n: Int): yield Int {
for(var i = 0; i < n; i++) {
yield i;
}
}
for(val i in range(10)) {
println(i);
}
ほとんどエフェクトで模倣できるんだけど、range(100)
の結果はGenerator<Int>
型になって任意のタイミングで再開できる必要があるため専用構文はやはり必要か。
class A in B & C {
// primary constructor
this(val a: Int) {
}
this() { this(0) }
fn get_a(): Int = this.a;
}
val a = new A();
interface Foo {
fn foo(): Int;
}
class FooImpl in Object & Foo {
impl fn foo(): Int = 42;
}
インタフェースと相互運用可能な仕組みを入れたい。
type class ToString {
fn to_string(this): String;
}
enum Foo();
impl ToString for Foo {
fn to_string(this): String { "Foo" }
}
type class Functor for M<_> {
fn pure<T>(v: T): M<T>;
fn fmap<T, U>(this: M<T>, f: T => U): M<U>;
}
impl <T> Functor for T[] {
fn pure(): Self = [];
fn map<U>(this, f: T => U): U[] = ...;
}
val a = <Functor for Array>.pure<Int>();
val foo = [1, 2, 3]~map(_ + 1);
fn zerofill<F: Functor, X>(f: F<X>): F<Int> {
f.map(x => 0)
}
Exception
クラスを継承したものをthrow
できる。
enum ParseError in Exception {
// ...
}
fn parse(s: String): Expr throw ParseError {
if(s[0] != '!') throw ParseError.Foo();
}
fn main(): throw Exception {
val expr = parse(read_line()) catch {
case ParseError.Foo() => // ...
}
}
レコードリテラルで作れる値。Record
のサブクラスになる。
val r = {a: 1};
r.a // 1
val r = {..r, b: 2}; // r = {a: 1, b: 2}
type new Foo(String) {
// Foo(s)を呼べるのはメンバのみ
fn parse(s: String): Foo = Foo(s);
}
// 1要素のenum
enum Email(String);
enum Option<T> {
case Some(T);
case None;
}
// これらの型がどうなるかはこれから考える
val Some = Option.Some;
val None = Option.None;
// GADTs
enum Expr<T> {
// 「なるべく既存の文法を使う」と言いつつ、extendsと書きたくないのでinにしてしまいました。
case Plus(Expr<Int>, Expr<Int>) in Self<Int>;
case Str(T) in Self<String>;
case Num(Int) in Self<Int>;
}
Effect<T>
クラスを継承したものをperform
できる。
関数はperform
から最大1回復帰する。継続について考えたくないため。
ハンドラ内でのエフェクトや例外は外側に飛ぶ。
class Effect {
type T;
}
enum Console in Effect {
case ReadLine() { impl type T = String; }
case Write(String) { impl type T = Unit; }
}
// path dependent typeが出てきてしまった……
fn perform<E in Effect>(e: E): E.T;
// {x in T | x not in U} を表す型演算が出てきてしまった……
fn handle<E in Effect, HE in E, T>(h: HE => E.T, f: () => T perform E): T perform E ! HE;
fn read_line(): String perform Console {
perform(ReadLine())
}
// whereが出てきてしまった……
fn use_console<T, E>(f: () => T perform E): T perform E ! Console
where E in Effect, Console in E
{
handle(_ match {
Console.ReadLine() => internal.read_line(),
Console.Write(s) => internal.write(s),
}, () => { read_line() })
}
これもジェネレータと同じく、関数を呼んだときに処理中であることを表現する値が返ってくるべきなので専用構文がほしい。 ジェネレータとは制御の経路が違うためジェネレータとも統一できない。どうにかならんか?
多段階計算に手を出さないことを当面の目標としたい(全然知らないので)
- Literal types for Int/String
- Union(
|
), Intersection(&
) - Exclude(
T ! U
) ←マジで - Record
{key1: T1, key2: T2, ..T3, ..}
-
{a: 1}
(a: 1
のみ持つrecord)と{a: 1, ..}
(少なくともa: 1
を持つrecord)は区別される。 - Subtype関係は
{a: 1, b: 1} <: {a: 1, ..} <: {..} <: {}
,({} | {a: Int}) <: {a?: Int}
といった具合。- 健全性のため、
{} <: {a?: Int}
だが、{..} !<: {a?: Int}
になる。
- 健全性のため、
- Top(
Object
), Bottom(Never
) -
Fail<Payload>
: 評価中に出現するとコンパイルエラー。 - Type fn
type fn ArgumentOf<X in AnyFunction> = X match { case (..args): _ => args}