Closed22

Nim 学習ログ

Nimという言語もやります。この言語はPythonに似たインデントベースのシンタックスをもち、C/C++/Obj-CやJSへのバックエンドをもつ静的型付けのGCする言語。UFCSという特徴的な (D言語にもある) 機能があり、楽しそうなのでやってみる。

なんでもかんでも環境構築をNixでやろうとする謎のやつ。

flake.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 はコンパイル時に計算される ifwhen の条件式が 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

このスクラップは5ヶ月前にクローズされました
ログインするとコメントできます