Open17
onyxの文法で遊んでみる
公式ドキュメント
検証する際は wasm build -r wasi
でwasmer対象のwasixを吐くのがよさそう(デフォルトだとonyxを対象にビルドされる)
Onyxのメソッド呼び出し、foo->method(123)
は foo.method(foo, 123)
のシンタックスシュガー。
メソッドと関数に区別は無さそう
レシーバをどう扱うかに言語の考え方が出てきて面白い
use core {*}
// 構造体を定義(メソッドも定義可能)
Person :: struct {
name: str;
age: i8;
say_hello :: (p: Person) {
printf("My name is {}.\n", p.name);
}
can_drink :: (p: Person) -> bool {
return p.age >= 20;
}
// メソッドはただの関数なので、staticメソッド的なこともできる
num :: () -> i64 {
return 8000000000;
}
}
main :: () {
person := Person.{name="Taro", age=18}; // メンバ名は省略可能
// メンバ参照
println(person.age); // 18
// メソッド呼び出し
println(person->can_drink()); // false
// 上記は以下のシンタックスシュガー
println(person.can_drink(person)); // false
// staticメソッド的な使い方
println(Person.num()); // 8000000000
}
スライスの書き方が独特
data := .[2, 4, 6, 8];
println(data[1]); // 4
文法は Go
、Jai
、Odin
の影響を受けている
確かに、Goと共通する機能がいくつかある
-
defer
文 -
interface
(暗黙的に実装される) -
#distinct
(Goでいうdefined type
) - 複数戻り値 (これはWASMにもともと実装されているという理由が大きそう)
- ジェネリクスは
$
を付けることで表現 -
where
で制約をかけられる- interface実装を要求
- boolの式
- 他にもある?
// このメソッドを持つものは自動的にGreetableを実装する
Greetable :: interface (t: $T) {
{ t->greet() } -> void;
}
// ジェネリクスを使った関数で、型引数がinterfaceを実装していることを強制
print_greeting_card :: (g: $T) where Greetable(T) {
println("~~~~~~~~~~~~~~~");
// ポリモーフィックに扱える
g->greet();
println("~~~~~~~~~~~~~~~");
println("");
}
main :: () {
print_greeting_card(Duck.{});
print_greeting_card(Person.{"Taro", 18});
}
ただ、他にも強力な機能がある
-
? T
でOptional型を表わす - オーバーロードできる
-
enum
(名前を実行時に取得可能) - パイプ演算子
|>
- 関数呼び出しで引数名指定可能/仮引数にデフォルト値設定可能
- ex:
n: i32 = 1
- ex:
Color :: enum {
Red :: 123;
Green :: 456;
Blue :: 789;
}
main :: () {
println(Color.Red); // Red
println(cast(u32) Color.Red); // 123
}
ローカルで動かす場合の注意点
- WSL2だと curlインストールの方法がうまくいかない(12/16現在)
部分型がある
use core {*}
Person :: struct {
name: str;
age: u32;
}
Joe :: struct {
use base: Person; // use baseで継承
pet_name: str;
}
// Personまたはその部分型であればok (これはジェネリクスとは別の概念みたい)
say_name :: (person: ^Person) {
printf("Hi, I am {}.\n", person.name);
}
main :: () {
joe: Joe;
joe.name = "Joe";
// 呼び出し側でも ^ を指定
say_name(^joe); // Hi, I am Joe.
// say_name(joe); // 指定しないとコンパイルエラー
}
標準パッケージも豊富
use core {*}
use core.string {concat}
main :: () {
println(concat("Hello, ", "world!"));
}
関数の引数でジェネリックな型を使用した場合、メンバの型を別引数の型に使用可能 (便利!)
use core {*}
// Tは型引数(type_expr指定)
List :: struct (T: type_expr) {
head: T;
tail: &List(T);
}
// Listの型引数Elemを第2引数の型にできる!(そのために$で多態にしている)
// (説明のために関数化しているが、もちろんListのメソッドにもできる)
has :: (list: &List($Elem), elem: Elem) -> bool {
cell := list;
while true {
if cell.head == elem {
return true;
}
if cell.tail == null {
// 終端
return false;
}
cell = cell.tail;
}
return false;
}
main :: () {
list := &List(i32).{1, &List(i32).{2, &List(i32).{3, null}}};
println(list
|> has(2)); // true
}
コンパイル時に計算させたい
#if
を使ってコンパイル時に分岐
失敗例:分岐のどちらに入るかは実行時にならないと分からないため速くならない
use core {*}
fib :: #match {
($N: i32) -> i32 where N <= 1 {
return 1;
},
($N: i32) -> i32 where N > 1 {
return fib(N-1) + fib(N-2);
},
}
main :: () {
println(fib(40));
}
さっきよりましだけどまだ失敗:マクロを使用
use core {*}
fib :: macro ($N: i32) -> i32 {
// ※コンパイル時なので if では判定できない
#if N <= 1 {
return 1;
} else {
return fib(N-1) + fib(N-2);
}
}
main :: () {
// 19以上にするとコンパイルが終わらない(スタックオーバーフローした?)
println(fib(18));
}
マクロは動くが式を評価してくれない(1+1
は 1+1
のまま)なので、出力コードが 1+1+1+...+1
になるだけ
マクロのまじめな使い方は以下のパターンみたい
(実行時のオーバーヘッドが無くなる)
- メソッド名のエイリアス
- 1行メソッド
https://github.com/onyx-lang/onyx/blob/master/core/string/string.onyx#L689 より抜粋
append :: #match {
macro (x: &dyn_str, other: str) {
use core.array
array.concat(x, other);
},
macro (x: &dyn_str, other: u8) {
use core.array
array.push(x, other);
},
}