Open17

onyxの文法で遊んでみる

syuparnsyuparn

Onyxのメソッド呼び出し、foo->method(123)foo.method(foo, 123) のシンタックスシュガー。
メソッドと関数に区別は無さそう

https://docs.onyxlang.io/book/operators/methods.html

レシーバをどう扱うかに言語の考え方が出てきて面白い

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
}
syuparnsyuparn
  • 構造体のサイズが0の場合関数の引数には使えない(制約の理由は謎。実行時に型を区別できないから?)
    • メンバを持たない or void型フィールドしか持たない
    • 型でガチャガチャ遊ぶのはちょっと難しくなりそう
Function parameters cannot have zero-width types.
syuparnsyuparn

スライスの書き方が独特

data := .[2, 4, 6, 8];
println(data[1]); // 4
syuparnsyuparn

文法は GoJaiOdin の影響を受けている

https://wasmer.io/posts/onyxlang-powered-by-wasmer

確かに、Goと共通する機能がいくつかある

  • defer
  • interface (暗黙的に実装される)
  • #distinct (Goでいう defined type)
  • 複数戻り値 (これはWASMにもともと実装されているという理由が大きそう)

https://docs.onyxlang.io/book/types/distinct.html#distinct

syuparnsyuparn
  • ジェネリクスは $ を付けることで表現
  • 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});
}
syuparnsyuparn

ただ、他にも強力な機能がある

  • ? T でOptional型を表わす
  • オーバーロードできる
  • enum (名前を実行時に取得可能)
  • パイプ演算子 |>
  • 関数呼び出しで引数名指定可能/仮引数にデフォルト値設定可能
    • ex: n: i32 = 1
syuparnsyuparn
Color :: enum {
    Red   :: 123;
    Green :: 456;
    Blue  :: 789;
}

main :: () {
    println(Color.Red); // Red
    println(cast(u32) Color.Red); // 123
}
syuparnsyuparn

部分型がある

https://docs.onyxlang.io/book/types/structures.html#sub-type-polymorphism

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); // 指定しないとコンパイルエラー
}
syuparnsyuparn

関数の引数でジェネリックな型を使用した場合、メンバの型を別引数の型に使用可能 (便利!)

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
}
syuparnsyuparn

コンパイル時に計算させたい

syuparnsyuparn

#if を使ってコンパイル時に分岐

https://docs.onyxlang.io/book/directives/if.html

失敗例:分岐のどちらに入るかは実行時にならないと分からないため速くならない

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));
}
syuparnsyuparn

さっきよりましだけどまだ失敗:マクロを使用

https://docs.onyxlang.io/book/procedures/macros.html

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+11+1 のまま)なので、出力コードが 1+1+1+...+1 になるだけ