コードを書きながら学ぶ
変数、関数、配列から順に、短いコードを動かして学びます。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を作ってください。書式だけ調べたいときは文法の早見表、関数の引数を調べたいときは組み込み関数を使えます。