Nim 学習ログ
Nimという言語もやります。この言語はPythonに似たインデントベースのシンタックスをもち、C/C++/Obj-CやJSへのバックエンドをもつ静的型付けのGCする言語。UFCSという特徴的な (D言語にもある) 機能があり、楽しそうなのでやってみる。
なんでもかんでも環境構築をNixでやろうとする謎のやつ。
{
description = "A very basic flake";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system}; in
rec {
packages = flake-utils.lib.flattenTree
{
nim-play = pkgs.nimPackages.buildNimPackage {
name = "nim-play";
src = ./.;
};
};
defaultPackage = packages.nim-play;
apps.nim-play = flake-utils.lib.mkApp { drv = packages.nim-play; };
defaultApp = apps.nim-play;
devShell = pkgs.mkShell {
buildInputs = with pkgs; [ packages.nim-play nim-unwrapped nimble-unwrapped ];
};
}
);
}
Neovimの設定で遊んでいたら時間がたっていた。nimlspというlanguage serverがあるので適当にcoc-nvimと繋ぎ、nim.nvimというプラグインも入れた。nim.nvimの使っているnimsuggestというやつがすぐ落ちるのは何?
-
readline(stdin)
で標準入力から読み取る - ifは式
- main関数みたいなものが不要
echo "Tell me your name: "
# 標準入力
var name = readline(stdin)
echo "Hello, " & name & "!"
let name_len = if len(name) > 5:
"long"
else:
"short"
echo "Your name is " & name_len & ", " & name
let
は再代入不可能で、var
では再代入可能。Swiftと同じ体系かな?
var name_len: string
if len(name) > 4:
name_len = "long"
else:
name_len = "short"
const
はコンパイル時に計算される。 when
はコンパイル時に計算される if
。when
の条件式が false
なので readline
の行はコンパイルされない。本来はコンパイル時にstdinからreadlineすることはできないのでconstに代入するのは間違いなのだが、コンパイル時に消されるのでエラーは出ない。
const compileBadCode = false
let legs = 400
when compileBadCode:
const input = readline(stdin)
タプルにはフィールド名があってレコードに似ているが順序もあるらしい。
let person = (name: "Satoshi", age: 10)
let (name, age) = person
echo "Hello, " & name
- タプルの型は
tuple[k1: t1, kn: tn]
- 数値を文字列にするには
$
関数を使う
var person: tuple[name: string, age: int]
person.name = "Rin"
person.age = 16
echo "You are " & $(person.age) & " years old - right?"
謎関数出てきたな。もしかして単項演算子か?
echo "You are " & $person.age & " years old - right?"
これが通るので単項演算子だったみたいだ。
-
$
関数は数値を取って新しい文字列を返すが、addInt
は既存の文字列と数値を取って文字列に数値を足す - 引数に破壊的変更を行う関数には
proc (x: var string, y: string){.noSideEffect, gcsafe, locks: 0.}
のようなシグネチャがついている - 変更を加える文字列には
x: var string
というものがある
var person: tuple[name: string, age: int]
person.name = "Rin"
person.age = 16
var str = newString(0)
add(str, person.name)
add(str, ", you are ")
addInt(str, person.age)
add(str, " years old - right?")
echo str
&mut
感あるものが出てきた。シグネチャによくわからないプラグマ的なものがたくさん入ってるの面白すぎる。
-
f(x, a, b, ..., z)
のようなプロシージャ呼び出しはx.f(a, b, ..., z)
に書き換えられる (UFCS: Uniform Function Call Syntax)
str.add(person.name)
str.add(", you are ")
str.addInt(person.age)
str.add(" years old - right?")
補完が効きにくいのではないか~?と思っていたが思っていたよりちゃんと効いた。fにはプロシージャ名しか書けないので妥当か。たしかにこれはだいぶありだな~
- シーケンスリテラルは
@[]
-
proc
でプロシージャ定義 -
echo
は可変長引数を取れる
var drinks = @["Water", "Coffee", "Tea"]
proc tell(drinks: seq[string]) =
echo "We have ", drinks[0], " and ", drinks.len - 1, " other drinks"
drinks.tell()
drinks.add("Milk")
drinks.tell()
-
type
で型エイリアスを定義 - 型エイリアスは中身が同じなら相互互換 (newtypeではない)
type Name = string
proc greet(name: Name) = echo "Hello, ", name
let name: string = "Alice"
greet(name)
タプル型にはレコード型っぽい記法がある。
type Person = tuple
name: string
age: int
-
distinct
をつけるとnewtype
相当 - 型を相互に変換する必要が生じる
- プログラム上の実体は整数だが表現している物理量が異なる場合などに有用
type Meters = distinct float
proc say(length: Meters) = echo length.int, "[m]"
let size = 200.Meters
size.say()
こういう変換を .
で書けるのがいいな。
-
enum
はCとかの方向性で、Rustの enum という名前を取ったADTではない - enumはordinalな型
type Color = enum cRed, cBlue, cGreen
echo cRed < cBlue
プロシージャ定義で proc foo ()
とやって何回か怒られている。
subrangesという型。有効な値の範囲を制限する。リテラルに対してはコンパイル時に、int
などからの変換に対しては実行時に検証する。
type Diefaces = range[1..6]
# コンパイルエラー
let roll_invalid: Diefaces = 9
let roll_int = 9
# 実行時エラー
let roll_invalid = roll_int.Diefaces
type Natural = range[0..high(int)]
という型もあるらしい。uint型に比べて0未満へのオーバーフロー時の挙動が嬉しいっぽい?
- 副作用のないプロシージャを
func
で定義できる -
return
しない場合、result
という変数が暗黙的に返される
func sumEven(x: int, y: int): int =
for i in x..y:
if i mod 2 == 0:
result = result + i
echo(sumEven(1, 100))
演算子はただのプロシージャという扱い。自力で定義できる。
proc `++`(n: var int): void =
n = n + 1
イテレータがある。
for i in countup(1, 10):
echo i
JSでいうジェネレータを iterator
で定義する。
proc `++`(n: var int): void =
n = n + 1
iterator countUp(x, y: int): int =
var i = x;
while i <= y:
yield i;
++i
and
mod
or
みたいなのは中置演算子として実装されている。
for i in countup(1, 100):
echo if i mod 3 == 0 and i mod 5 == 0:
"fizz buzz"
elif i mod 3 == 0:
"fizz"
elif i mod 5 == 0:
"buzz"
else:
$i
Prism.jsのパーサの秘孔を突いてしまったようだな……
これがパースできない。スペースを空けないと単項演算子として解釈されてしまうらしい。
echo 4 ==i
object型。
type Person = object
name: string
age: int
let p = Person(name: "John", age: 36)
func introduce(p: Person): string =
"I am " & p.name & ". I'm " & $p.age & " yrs old."
p.introduce.echo