Docsの目次

コードを書きながら学ぶ

目次 · 前:準備と最初の実行 · 調べる:文法の早見表

変数、関数、配列から順に、短いコードを動かして学びます。Nagiをインストールしたら、作業用のフォルダーを作ってください。コードは.nagiファイルへ保存し、nagic runで実行します。

このページの目次

1. 値と型

次のコードは、mainなどの関数の中に書きます。

count = 10            # i64。型は自動で決まる
rate = 1.5            # f64
enabled = True       # bool。True / Falseと書く
name = "Nagi"        # str。日本語も書ける
age: i32 = 18        # 型を指定するなら「名前: 型 = 値」
count += 1
print(count)

変数は再代入できますが、型を変更することはできません。count = "十"は型エラーです。条件にはboolを使い、if count:のように整数を真偽値として扱うことはできません。

異なる整数型は自動で混ざりません。i64(age)でi32をi64へ広げられます。逆のi32(count)は範囲外の可能性があるのでResult[i32, Error]を返します。失敗の扱いは後で説明します。

2. 関数を定義する

def add(a: i64, b: i64) -> i64:
    return a + b

def main():
    print(add(20, 22))

このコードを保存して実行すると42と表示します。引数は名前: 型、戻り値は-> 型です。戻り値を省略した関数はunit(値を返さない)です。mainがプログラムの入口になります。

3. 配列、class、分岐、繰り返し

配列は[1, 2, 3]、型注釈はList[i64]です。append(values, 4)で末尾に追加します。classは名前付きフィールドをまとめる型です。

次のコードをbasics.nagiとして保存してください。サンプルにも同じコードがあります。

class Point:
    x: f64
    y: f64

def sum_numbers(values: view[i64]) -> i64:
    total = 0
    for value in values:
        total += value
    return total

def main():
    values = [1, 2, 3]
    append(values, 4)
    total = sum_numbers(view(values))
    print(total)

    point = Point(x=3.0, y=4.0)
    print(point.x + point.y)

    if total >= 10 and len(values) == 4:
        print("OK")
    else:
        print("NG")

    for index in range(3):
        print(index)

    count = 0
    while count < 2:
        count += 1
    print(count)
nagic run basics.nagi

プログラムの出力は順に10、7、OK、0、1、2、2です。

  • Point(x=..., y=...)は全フィールドを名前付きで指定します。Point(3.0, 4.0)とは書きません。
  • point.xでフィールドを読みます。現在はclassにmethodや継承を定義できません。
  • for value in valuesは要素を順に読みます。現在は整数・小数・boolなどの基本の値(基本型)や、それだけを含むclassを走査できます。このように所有権を移さずコピーできる型をCopyと呼びます。List[str]の走査は未対応です。
  • range(3)は0, 1, 2です。引数は1つで、終端を含みません。
  • whileは条件がTrueの間繰り返します。break / continueは未対応です。
  • and / or / notで条件を組み合わせます。elifはないため、必要ならelseの中にifを書きます。

view(values)は「配列を渡して手放す」代わりに「読み取り用に借りる」書き方です。次で説明します。

4. 読むだけならviewで借りる

文字列や配列を自作関数へそのまま渡すと、所有権が移動します。受け渡しをmoveと呼びます。渡した変数を後で使うとエラーになります。

# エラーになる例
def take_name(name: str):
    print(name)

def main():
    name = "Nagi"
    take_name(name)
    print(name)       # 自作関数へmoveした後なので使えない

読むだけの関数はview[str]を受け取るようにします。呼び出すときはview(name)です。次は完全なコードで、borrowing.nagiとして実行できます。

def name_size(name: view[str]) -> i64:
    return len(name)

def main():
    name = "Nagi"
    print(name_size(view(name)))
    print(name_size(view(name)))
    duplicate = copy(view(name))
    print(duplicate)
    print(name)

出力は4、4、Nagi、Nagiです。copy(view(name))は別の所有文字列を作ります。コピーが必要なときだけ明示してください。

printやlenは入力を読み取る組み込み関数なので、それらに渡しただけでは文字列をmoveしません。すべての関数が同じ受け渡し方をするわけではありません。

len(str)は文字数ではなくUTF-8のbyte数です。len("あ")は3です。viewが生きている間の元データの変更にも制約があります。詳細は所有権にあります。

5. 失敗する処理はResultで返す

Result[T, Error]は「成功ならT、失敗ならError」という戻り値です。ok(value)で成功、error("理由")で失敗を返します。

try 処理は、成功した値を取り出し、失敗したらその場で呼び出し元へ失敗を返します。そのため、tryを書く関数自身もResultを返す必要があります。

次は入力した整数を2倍にする完全なコードです。input.nagiとして保存してください。サンプルにもあります。

def double_nonnegative(text: view[str]) -> Result[i64, Error]:
    value = try parse_i64(text)
    if value < 0:
        return error("0以上の整数を入力してください")
    return ok(value * 2)

def main() -> Result[unit, Error]:
    write("整数を入力 > ")
    text = try read_line()
    doubled = try double_nonnegative(view(text))
    print(doubled)
    return ok(print("完了"))
nagic run input.nagi

21を入力してEnterを押すと、42と完了を表示します。abcは数値の変換に失敗し、-1は自分で書いたエラーになります。どちらもmainまで伝わり、プログラムは失敗で終了します。

writeは改行なし、read_lineは1行入力です。return ok(print("完了"))は、表示処理が返すunitを成功として返しています。

NagiのtryはPythonのtry: ... except:とは別の構文です。その場で既定値に回復するなど、成功・失敗を分けたいときはmatchを使います。次は関数の例です。

def number_or(text: view[str], fallback: i64) -> i64:
    match parse_i64(text):
        case Ok(number):
            return number
        case Err(_):
            return fallback

OkとErrを1回ずつ書きます。括弧内の名前はそのcase内で使い、不要な値は_にします。Result以外のパターンは未対応です。実行できる完成コードとErrorの調べ方はエラー処理にあります。

6. ファイルを分ける

同じディレクトリに2つのファイルを作ります。

math.nagi:

def add(a: i64, b: i64) -> i64:
    return a + b

imports.nagi(実行するファイル):

import "math.nagi"

def main():
    print(add(20, 22))
nagic run imports.nagi

完成ファイルを実行すると42です。importのパスは、実行したターミナルではなくimportを書いたファイルの場所を基準にします。読み込まれるmath.nagiにはmainは不要です。

現在は全ファイルが1つの名前空間に入ります。math.add(...)やaliasではなくadd(...)で呼び、関数名・class名の重複はエラーです。Rustライブラリを使う場合はimportとRust連携へ進んでください。

7. asyncとAPIへ進む

タイマーやI/Oを待つ関数はasync defで定義し、呼び出す側でawaitします。

async def main() -> Result[unit, Error]:
    await sleep(10)
    print("10ミリ秒待った")
    return ok(print("完了"))

このコードはそのまま保存して実行できます。sleepはunitを返すのでawaitだけ、失敗し得るDB関数などはtry await db_open(...)のように書きます。awaitは非同期処理を待ち、tryは待った結果の失敗を伝えます。

次はHTTPとHTMLで、ブラウザーから呼べるAPIを作ってください。書式だけ調べたいときは文法の早見表、関数の引数を調べたいときは組み込み関数を使えます。

Nagi 0.1のドキュメントこのページのソース