Open4
自作言語 Phosphor 進捗
現状の見た目
/////////////
/* Library */
/////////////
data Nat: * [
Zero: Nat;
Suc: Nat => Nat;
];
showInt: Int => String
= (x: Int) => `$x.toString()`: String;
showChar: Char => String
= (x: Char) => `$x.toString()`: String;
showBool: Bool => String
= [
(true) => "true";
(false) => "false";
];
showFloat: Float => String
= (x: Float) => `$x.toString()`: String;
log: String => {Unit}
= (x: String) => `() => console.log($x)`: {Unit};
natToInt: Nat => Int
= [
(Zero) => 0;
(Suc(x: Nat)) => `$natToInt(x) + 1`: Int;
];
//////////
/* Main */
//////////
main: {Unit} = do{
log(showInt(10));
};
リポジトリ
0.0.1までにやるべきこと
- 多相型(ジェネリクス)
1.0.0までにやるべきこと
- 型クラス
- 型推論
- 不完全なパターンマッチの検出
- import機能の追加
Document
基本的に上から順番に処理される.
副作用は起こさない.
JavaScriptにトランスパイルされる.
型推論がないのでいたるところに型を書かなければいけなく,やや不便.
コメント
// 一行コメント
/*
複数行コメント
*/
プリミティブ型
// 型: カインド
String: * // "abc", "str" ...
Int: * // 1, 2, -4 ...
Float: * // 3.14, -1.23 ...
Bool: * // true, false
Unit: * // Unit
Char: * // 'a', 'b'
Array: * -> * // [1, 2, 3]
=>: * -> * -> * // 演算子
{}: * -> * // Effect型構築子
定数定義
// 定数名: 型 = 式;
x: Int = 10;
// パターンマッチスタイルもある(後述)
関数型,ラムダ式,関数適用
// Int => Int はIntをとってIntを返す関数の型
// ラムダ式 (変数: 型) => 式
f: Int => Int = (x: Int) => add(x, 1);
// 複数の値をとる関数は,関数を返す高階関数として表現
g: Int => Int => Int = (x: Int) => (y: Int) => add(x, y);
// 次のようにも書ける
g: Int => Int => Int = (x: Int, y: Int) => add(x, y);
//関数適用
x: Int = f(2);
// 複数の値をとる関数への適用
y: Int = g(3)(4);
// 次のような糖衣構文がある
y: Int = g(3, 4);
配列型
// [要素0, 要素1,要素2]: Array(要素の型)
// 要素が0個のArrayの型を特定するために型情報が必要
x: Array(Int) = [1, 2, 3]: Array(Int);
// 現状アクセサがないので全く意味のない値になってしまっている.
データ型,パターンマッチ
// ex) IntとStringの直積
data TupleIntString: * [
Cons: Int => String => TupleIntString;
];
// データ生成
x: TupleIntString = Cons(1, "one");
// データ取り出し
Cons(i: Int, str: String) = x; // i == 1, str == "one"
// ex) IntとStringの直和
data SumIntString: * [
ConsInt: Int => SumIntString;
ConsString: String => SumIntString;
];
//データ生成
intX: SumIntString = ConsInt(2);
stringX: SumIntString = ConsString("two"):
// パターンマッチ
// 複数のラムダ式を組み合わせたような構文
f: SumIntString => String = [
ConsInt(x: Int) => showInt(x);
ConsString(x: String) => x;
];
// ex) 非負整数型
// データ型の定義
data Nat: * [
Zero: Nat;
Suc: Nat => Nat;
];
// 非負整数どうしの足し算
addNat: Nat => Nat => Nat
= [
(Zero, x: Nat) => x;
(Suc(x: Nat), y: Nat) => add(x, Suc(y));
];
let式でスコープを切る
// 即時関数のように使える
fuga: Int = let{
hoge: Int = 3;
piyo: Int = add(hoge, 3);
return piyo; //6を返す
};
foo: Int = fuga; // OK,foo==6
bar: Int = hoge; // エラー,hogeはスコープに存在しない
副作用型とdo式,main関数
// 副作用を起こす型は{...}で表される
// 型 {x} を x にしたときに副作用が実行されると思ってよい.
// 0 ~ 1のランダムな数値生成器
random: {Float}
// コンソールに出力する関数
log: String => {Unit} // UnitはTSにおけるvoidのようなもの
// 中身を取得できない以外は普通の値
hello: {Unit} = log("Hello World"); // 代入もできる
// do式の中で実行できる
// 実行するには =< という記号を使う
f: {Float} = do{
x: Float =< random; // {Float}型がFloatになったので副作用が実行される
y: Float =< random; // randomは実行されるたびに別の値を返すので一般に x != y
ignore: Unit =< log("hoge"); // コンソールに"hoge" と表示する
log("hoge"); // 左辺の値がUnitである場合は省略できる(通常使わないので)
hello; // "Hello World"と出力する
piyo: String = "piyo" // =による代入も普通に使える(これはlet式と同じ構文)
log(piyo); // "piyo"と出力
return add(x, y); //xとyの合計を返す
};
// f 全体の型は{Float}となっていることに注意
// f もまた別のdo式の中に埋め込むことができる
// main 関数は型{Unit}を持つ
main: {Unit} = do{
x: Float =< f; // コンソールに"hoge"と二回出力して,"Hello World","piyo"と出力し,xに0 ~ 1のランダムな数二つの和を代入する.
}; // return Unit は省略できる.
FFIと実行時表現
// コードの一番頭に任意の行数埋め込める
// require文などを書く
// グローバル変数を書くのは非推奨
`
const X = require("...");
...
`
// 式にもJavaScriptのコードを埋め込める
// `JavaScript式`: 型
// 型は必須
x: Int
= `3 + 5`: Int; // x == 8
// 導入された変数には頭に$がつく
y: Int
= `3 + $x` // y == 11
// Int, Float, String, Char, Bool に対しては number, number, string, string, booleanが対応
// 1 == `1`
// 3.0 == `3.0`
// "str" == `"str"`
// 'a' == `"a"`
// true == `true`
// Array はただの配列
// [1, 2, 3] == `[1, 2, 3]`;
// Function もただの関数
// (x: Int) => x == `(x) => x`
// 副作用型 {x} は0変数関数としてトランスパイルされる
// random: {Float} == `() => random()`
// したがってlog関数は次のように書ける
log: String => {Unit}
= (str: String)
=> `() => console.log($str)`: {Unit};