👑

PythonistaのためのNim入門

68 min read

この記事は以下の翻訳です。

https://github.com/nim-lang/Nim/wiki/Nim-for-Python-Programmers

目次

比較 オブジェクト cを知らなければならないか? 名前付きタプル
変数 self.__init__() 文字列、F記法 可変長配列
変数の宣言 一貫性のあるスペース スコープ 上書きできない引数
import文 数列 文字列操作 リスト内包表記
try/import/except 静的境界チェック Null合体演算子 withコンテキストマネージャー
固定長配列 スライス タプル 辞書内包表記
集合内包表記 ファイルの読み書き デコレーター ラムダ式
集合 JSON マップ & フィルター インデントの選択肢
辞書型 キャメルケース DocStrings PythonでNimファイルをインポートする
三項演算子 単体テスト def VS proc/func main関数
Nim のための Python 構文 PYPIへのパブリッシュ サイレントコンパイレーション コンパイラのヘルプ
ビルドモード 抽象基底クラス デコレーター WebAssembly
テンプレート ターミナル上でNimを実行する ブラウザ上でのNim 標準ライブラリ比較
非同期 PIPでNimをインストール チートシートPDF アロー関数
関数間で変数を共有するには? ファイルの権限の変更 一時的にフォルダを変更する パターンマッチング
ベストプラクティス インプレースとアウトプレースの違い NodeJSで動かす 実行されるコードを展開する
Arduino, MicroPython, ESP32, FreeRTOS FoxDotやSuperColliderによるライブコーディング PIPでインストール可能なNimをPythonに組み込む PIPからNimパッケージをインストールする

比較

Feature 🐍 Python 👑 Nim
実行環境 仮想環境(インタープリター) C/C++を通して出力された実行バイナリ(コンパイラ)
実装言語 C(CPython) Nim
ライセンス Python Software Foundation License MIT
現行バージョン 3.x 1.x
メタプログラミング ✔metaclass, exec, eval, ast (実行時コード拡張) template, macros (コンパイル時コード拡張)
メモリ管理 GC 様々なメモリ管理方法を選べます (GC, ARC/ORC, マニュアル)
型付け 動的, ダックタイピング 静的
依存型 ✔部分的なサポート
ジェネリクス ダックタイピング
int8/16/32/64 型
uint8/16/32/64 型
float32/float64 型
Char型
部分範囲型
Enum
Bigint型 (任意のサイズ) jsbigints, #14696
組み込みint型の最大値 メモリの上限まで制限なし uint64型による18,446,744,073,709,551,615
配列
型推論 ダックタイピング
クロージャー
演算子の上書き
ユーザー定義演算子
オブジェクト指向
関数
例外
無名関数 ✔ 複数行, 単一式 ✔ 複数行, 複数式
リスト内包表記
辞書内包表記
集合内包表記
ユーザー定義オブジェクト内包表記 ✔ ジェネレータ式
組み込みパターンマッチング
変更不可な型 基本型(number, string, bool), tuple, frozenset
変更不可な変数
上書きできない引数 型による イミュータブル
文字列の中の変数展開 ✔f記法 strformat
FFI ✔ ctypes, C extension API (Cython via pip) ✔ C, C++, Objective C, JS (コンパイル先による)
非同期
マルチスレッド ✔ グローバルインタープリタロック
正規表現 ✔ Perlに同じ ✔ Perlに同じ
ドキュメントコメントからのファイル出力 ✔ 複数行のコメントアウト (Sphinxを使ったreStructuredText) ReStructuredText/Markdown
自作パッケージの公開 ✔ 組み込みでは無いのでtwineが必要 nimbleが組み込まれている
パッケージマネージャー pip nimble
コードの自動整形 blackなどがpipから使用可能 nimprettyが組み込まれている, nimlint
ファイル拡張子 .py, .pyw, .pyc, .pyd, .so .nim, .nims
中間ファイルの拡張子 .pyc (CPython VM bytecode) C, C++, Objective C (LLVM IR via nlvm)
シバン nimr, nimcr
REPL inim, Nim4Colab
インデント 各ブロック毎にタブかスペース。スペース4つが慣例 各ブロック毎にスペースのみ。スペース2つが慣例

注記:

  • Pythonの無名関数ラムダは、通常の関数に比べて遅いことが知られています。
  • Python Regex は PCRE との互換性を主張していますが、実際には PCRE Regex は動作しないかもしれまぜん。
  • Pythonの "複数行 "の無名関数は、;を使う必要があり、Linters/IDEがそれに警告を出す可能性があります。

変数

新しい変数を作るには、varletconstを使います。
Nimには不変性とコンパイル時の関数実行機能があります。
変数に関数を割り当てることができます。

宣言 コンパイル時 実行時 不変 割り当てが必要 自動初期化
var
let
const

上級者向けに、変数の自動初期化をスキップすることが可能です

変数の宣言

変数は「エスケープ」せずに複数行にすることができます。長い行や長い三項演算子の場合に便利です。

variable = 666 +  \
  420 *  \
  42 -   \
  9           

assert variable == 18297

⬆ Python ⬆ ⬇ Nim ⬇

var variable = 666 +
  420 *
  42 -
  9

assert variable == 18297

これは関数呼び出しの時でも同じように動きます。

import strutils

var variable = "  12345  "
  .strip
  .parseInt

assert variable == 12345

変数名にはアンダースコア、改行、空白を使うことができます。

let `this must  be  
     positive`: Positive = 42

assert this_must_be_positive == 42

const `this is my nice named variable` = 42

予約語を変数名として使うことができます。

Nimの学習やクイックプロトタイピングでは、varを使っても問題ありませんが、変数宣言の違いを学ぶ方がはるかに良いでしょう。

一貫性のあるスペース

コードの中では、主に演算子の周りのスペースを統一する必要があります。

echo 2 - 1 # OK
echo 2-1   # OK

一貫性のないスペースはダメ

echo 2 -1 # Error
#      ^ parses as "-1"

コード上でスペースを省略しても何もいいことはありません。
Nimではすべての演算子は関数です。

スコープ

  • スコープの”漏れ”、”バグ”、”誤作動”
for x in range(0, 9):
  if x == 6:
    print(x)

print(x)

出力:

6
8  # スコープ外からアクセスできる!

⬆ Python ⬆ ⬇ Nim ⬇

for x in 0..9:
  if x == 6:
    echo x

echo x

出力:

Error: undeclared identifier: 'x'

この例では、単純な int を使用していることに注意してください。
しかし、もし 変数x が数ギガバイトの RAM サイズだったらと想像してみてください。
これは、forループから外部またはメインスコープの残りの部分に「漏れる」可能性があります。

その他の例:

x = 0
y = 0

def example():
  x = 1
  y = 1
  class C:
    assert x == 0 and y == 1  # ???
    x = 2

example()

⬆ Python ⬆ ⬇ Nim ⬇

var x = 0
var y = 0

proc example() =
  var x = 1
  var y = 1
  type C = object
  assert x == 1 and y == 1
  x = 2

example()

更に他の例:

x = 0
y = 0

def example():
  x = 1
  y = 1
  class C:
    assert x == 0 and y == 0  # ???
    x = 2
    try:
      raise
    except Exception as y:
      pass

example()

⬆ Python ⬆ ⬇ Nim ⬇

var x = 0
var y = 0

proc example() =
  var x = 1
  var y = 1
  type C = object
  assert x == 1 and y == 1
  x = 2
  try:
    raise
  except Exception as y:
    discard

example()

上書きできない引数

def example(argument = [0]):
  argument.append(42)
  return argument

print(example())
print(example())
print(example())

出力:

[0, 42]
[0, 42, 42]
[0, 42, 42, 42]

⬆ Python ⬆ ⬇ Nim ⬇

func example(argument = @[0]): auto =
  argument.add 42
  return argument

echo example()
echo example()
echo example()

出力:

Error: type mismatch: got <seq[int], int literal(42)>

but expected one of: 
proc add[T](x: var seq[T]; y: sink T)
  first type mismatch at position: 1
  required type for x: var seq[T]
  but expression 'argument' is immutable, not 'var'

import文

import 🐍 Python 👑 Nim 呼び出し
一つのシンボルだけに限定して呼ぶ from math import sin from math import sin sin()
モジュール名無しで全てのシンボルを呼ぶ from math import * import math (推奨) sin()
モジュール名付きで全てのシンボルを呼ぶ import math (推奨) from math import nil math.sin()
"import as" で別名を付ける import math as potato import math as potato potato.sin()
別名を付けたモジュール名付きで全てのシンボルを呼ぶ from math as m import nil m.sin()
モジュール名無しで一つのシンボルを除いて呼ぶ import math except sin cos()
モジュール名無しで複数のシンボルを除いて呼ぶ import math except sin, tan, PI cos()
外部モジュールを取り込む include somemodule somefunc()

モジュールのような名前の型があっても、冷静になってコーディングを続けてみましょう。

Nimでは、import mathは、mathモジュールのすべてのシンボル(sincosなど)をインポートして、無条件で使用できるようにしています。Pythonでは from math import * となります。

もし、すべてのシンボルをインポートせず、常に修飾名を使用したい場合、Nimのコードは from math import nil です。そうすれば、 math.sin()math.cos() などで呼び出すことができます。これに相当するPythonのコードは import math です。

コンパイラは未使用の関数を実際にコンパイルすることはありませんし(つまりオーバーヘッドはありません)、Nim は静的型付けされているので、同じ名前の 2 つのインポートされた関数を、呼び出される引数の型に基づいて通常は区別することができますので、Nim ですべての名前をインポートすることは一般的に安全です。稀に型が同じ場合でも、名前を完全に修飾して曖昧さを解消することができます。

PythonとNimはこれらのimport文は共通です。

import foo, bar, baz

import foo
import bar
import baz

別の書き方:

# Python
import foo, \
       bar, \
       baz
# Nim
import foo,
       bar,
       baz

# importを追加したり取り除いたりするのにファイルの差分が少なくなるので便利
import
  foo,
  bar,
  baz

import 
  foo, bar, baz,
  more, imports

1行に1つのimport文を記述する形式はPythonやNimでよく見られますが、Nimではimport foo, bar, bazという形式もよく使われます。

その他の例:

## このモジュールのドキュメントコメント.
#  これはコメント.
include prelude
import sugar as stevia
from math import nil
from with as what import nil

プログラム的にimport

__import__("math")

⬆ Python ⬆ ⬇ Nim ⬇

template imports(s) = import s
imports math

時々、import文がない野良のコードサンプルやファイルを見かけることがありりますが、動作します(?)

Nim では、コンパイルコマンドから、または .nims ファイルから import を使用することができます。

  • nim c --import:sugar file.nim
  • nim c --import:folder/mymodule file.nim
  • nim js --import:strutils --include:mymodule file.nim

いくつかのプロジェクトやサンプルの例では、入力の手間を省くためにこの機能を使うことがあります。
インポートされたシンボルが使用されていなければ、デッドコード排除のおかげで、コンパイルされた出力には存在しません。

以下も参照してください。

プレリュード

時々、Nim と比較して Python は import なしでデフォルトで利用できるシンボルが多いと感じるかもしれません。
コーディング開始時に基本的な最も一般的なインポートが用意されているのと同じような経験をするには、次のようにします。
プレリュード:

include prelude

echo now()             
echo getCurrentDir() 
echo "Hello $1".format("World")

preludeインクルードファイル で、一般的なモジュールを単純にインポートすることで、入力の手間を省くことができます。 preludeはJavaScriptターゲットでも機能します。

シンボルはどこから来るのか?

  • シンボルが修飾されていない場合、シンボルがどこから来たのかを知るにはどうすればよいでしょうか。

シンボルが foo() であるとします。

一般的に Editor/IDEでは
他のプログラミング言語のように、シンボルがどこから来るのかをヒントを表示する必要があります。

Nimには、エディタ/IDE統合用のNimSuggestが組み込まれています。

Python とは異なり、Nim の型システムはすべてのシンボルに関する情報を持っています。

import macros
macro findSym(thing: typed) = echo thing.getType.lineInfo

findSym:
  echo  # Where echo comes from?.

echoはここから来ています:

lib/system.nim(1929, 12)

Nimの学習やクイックプロトタイピングでは、必要に応じてシンボルを完全修飾で使用しても問題ありません。
そうしてもエラーは発生しませんが、慣用的なNimをコーディングする方がはるかに良いです。

エクスポート

Pythonでは、モジュール内のすべてのシンボルは、そのモジュールをインポートしているモジュールから見え、変更可能です。
モジュール内のシンボルは、そのモジュールをインポートしているモジュールから、モジュール外で使用したり変更したりしてはいけないシンボルも含めて、全て見えます。

Nim ではデフォルトですべてが private であり、他のモジュールからは見えません。
シンボルをパブリックにして他のモジュールから見えるようにするには、アスタリスク * を使用する必要があります。

let variable* = 42
const constant* = 0.0
proc someFunction*() = discard
template someTemplate*() = discard
type Platypus* = object
  fluffyness*: int

アスタリスクは、シンボルを外から見えるようにするだけではありません。
シンボルは、生成されるドキュメント(nim doc)にも表示されます。
モジュールをインポートすると、そのシンボルは自動的に名前空間に追加されます。
しかし、*の付いていないプライベートな(エクスポートされない)シンボルは表示されません。
アスタリスクは、人間がモジュールのソースコードを見ただけで、どのシンボルが「公開API」の一部であるかをすぐに理解するための 視覚的な手がかり のようなものです。
なお、アスタリスク * は「星」と表記されることもあります。

理解を深めるためには、https://narimiran.github.io/2019/07/01/nim-import.html を読むことをお勧めします。

try/import/except

Pythonでは、このようなインポートを含む例外処理を見かけることがあります。

try:
    import module
except ImportError:
    import othermodule as module

try:
    from module import some_func
except ImportError:
    # Fallback implementation
    def somefunc():
        return some_value  

Nim はコンパイル時にすべてのインポートを解決するので、ImportError のようなものは存在しません。
実行時にインポートエラーを処理する必要はありません。

固定長配列

Nim のarrayは固定長で、インデックス 0 から始まり、同じ型の要素を含むことができます。

Nim の関数にarrayを渡す場合、引数は不変的な参照になります。
Nim は、arrayの境界に関する実行時チェックを含みます。

openarrayを使って、関数の引数に任意のサイズのarrayを受け入れることができます。
また、low(your_array)high(your_array)を使ってarrayの範囲を問い合わせることができます。

Nimの stringopenArray[char] と互換性があり,最適化のために不要なコピーを避けることができます.
charintと互換性があるので、stringの操作は透過的にインプレースで計算できます。
そのため、openArray[char]を受け取る関数は、"abcd" ['a', 'b', 'c', 'd']を受け取ります。

こちらもご覧ください。

データタイプのサイズ

  • さまざまなデータタイプのサイズは何でしょう?
import json

type Foo = object            
type Bar = enum true, false  

assert sizeOf( Foo )        == 1
assert sizeOf( Bar )        == 1
assert sizeOf( bool )       == 1
assert sizeOf( {true} )     == 1
assert sizeOf( [true] )     == 1
assert sizeOf( (true) )     == 1
assert sizeOf( int8 )       == 1

assert sizeOf( {'k': 'v'} ) == 2
assert sizeOf( int16 )      == 2

assert sizeOf( int32 )      == 4
assert sizeOf( float32 )    == 4

assert sizeOf( int )        == 8
assert sizeOf( float )      == 8
assert sizeOf( @[true] )    == 8
assert sizeOf( %*{} )       == 8
assert sizeOf( pointer )    == 8

これは64bitの空オブジェクトのサイズを一致しています。

オブジェクト

Nimのオブジェクトは、Pythonのクラスとは全く異なる動作をします。
オブジェクトは継承と OOP をサポートします。Nimではクラスは名前付きの型です。
関数はフリーフローティング関数で、オブジェクトに束縛されません。
(しかし、Pythonとよく似た方法で使うことができます)。
オブジェクトに対しては、Object.function()で関数を呼び出すことができます。
Nim には、暗黙の selfthis はありません。
すべての型をファイルの先頭に置くのが最良の方法ですが、必須ではありません。

関数はコンパイル時にオブジェクトに「接着」されます
そして、実行時にはPythonのクラスやメソッドのように使うことができます。

class Kitten:
    """ Documentation Here """

    def purr(self):
        print("Purr Purr")

Kitten().purr()

⬆ Python ⬆ ⬇ Nim ⬇

type Kitten = object  ## Documentation Here
proc purr(self: Kitten) = echo "Purr Purr"
Kitten().purr()

継承の例:

type Animal = object of RootObj
type Kitten = object of Animal
assert Kitten is Animal

Pythonのようなオブジェクト指向の例:

type Animal = ref object of RootObj ## Animal base object.
  age: int                          
  name: string                      ## Attributes of base object.

type Cat = ref object of Animal     ## Cat inherited object.
  playfulness: float                ## Attributes of inherited object.

func increase_age(self: Cat) =
  self.age.inc()                    # Cat object function, access and *modify* object.

var kitten = Cat(name: "Tom")       # Cat object instance.
kitten.increase_age()               # Cat object function used.

assert kitten.name == "Tom"         # Assert on Cat object.
assert kitten.age == 1

継承の例:

type
  LUCA        = ref object of RootObj
  Archea      = ref object of LUCA
  Prokaryota  = ref object of Archea
  Eukaryota   = ref object of Prokaryota
  Animalia    = ref object of Eukaryota
  Chordata    = ref object of Animalia
  Mammalia    = ref object of Chordata
  Primates    = ref object of Mammalia
  Haplorhini  = ref object of Primates
  Simiiformes = ref object of Haplorhini
  Hominidae   = ref object of Simiiformes
  Homininae   = ref object of Hominidae
  Hominini    = ref object of Homininae
  Homo        = ref object of Hominini
  Homosapiens = ref object of Homo

assert Homosapiens() is LUCA
assert LUCA() isnot Homosapiens
assert sizeOf(Homosapiens) == sizeOf(LUCA)
let human = Homosapiens()
assert human is Homosapiens

こちらもご覧ください:

self.__init__()

Catの例の後、おそらくdef __init__(self, arg):をどのように行うか疑問に思うでしょう。
Pythonの__init__()は、NimのnewObject()initObject()のことで、Catの__init__()を作ってみましょう。

type Cat = object                # Cat object.
  age: int                          
  name: string                   # Attributes of Cat object.

func initCat(age = 2): Cat =     # Cat.__init__(self, age=2)                    
  result.age = age               # self.age = age         
  result.name = "adopt_me"       # self.name = "adopt_me" 

var kitten = initCat()            # Cat object instance.

assert kitten.name == "adopt_me" # Assert on Cat object.
assert kitten.age == 2

名前の付け方は慣例であり、ベスト・プラクティスでもあります。
気づいたかもしれませんが、initCatCatを返すだけの関数です。

  • initFoo()objectに使われる
  • newFoo()ref objectに使われる

慣習やベストプラクティスに従ったネーミングについては、ドキュメントを読んでください。

オブジェクトのプロパティのデフォルト値

オブジェクトのコンストラクタは、オブジェクトのプロパティに独自のデフォルト値を設定する手段でもあります。

type Cat = object
  age: int                 # 0で初期化される
  name: string             # ""で初期化される
  playfulness: float       # 0.0で初期化される
  sleeping: bool           # falseで初期化される 

func initCat(): Cat =    
  result.age = 1           # 1の初期値を設定する
  result.name = "Bastet"   # "Bastet"の初期値を設定する
  result.playfulness = 9.0 # 9.0の初期値を設定する
  result.sleeping = true   # trueの初期値を設定する

実行されるコードを展開する

内部的にコード生成を行うPythonオブジェクトは非常に遅いです。
サイズが大きくなればなるほど遅くなります。
dataclass, metaclass, デコレータなどは、通常の class よりも 25 ~ 50 倍以上遅くなります。
pathlib.Path とそのメソッドは、通常の str よりも 25 ~ 50 倍以上遅くなります,
これでは、.pyc ファイルを含めたあらゆる最適化ができません。
Cython は CTFE を持っていないので、この問題の解決にはなりません。

  • Nim のコード展開はコンパイル時に行われるため、ランタイムでのコード生成のコストはゼロになります。

--expandArcを使って、コンパイル時にARCのコードを展開した例です。
Nimのコンパイル時のメモリ管理はこのようになっています(近似)。

こちらもご覧ください。

数列

Pythonでは、単純な整数のforループは、rangeジェネレータ関数を使用します。この関数の1引数と2引数の形式では、nimの..イテレータがほぼ同じように動作します。

for i in 0 .. 10:
  echo i  # Prints 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

for i in 5 .. 10:
  echo i  # Prints 5, 6, 7, 8, 9, 10

Pythonのrange(a, b)bを含まないのに対し、..演算子は範囲の端を含むことに注意してください。この動作を好む場合は,代わりに ..< イテレータ を使用してください.

for i in 0 ..< 10:
  echo i  # Prints 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

Pythonのrange()には,オプションで3番目のパラメータがあります。
これは,各ステップごとに増分する正でも負でもよい値です。
この動作が必要な場合は,countup またはcountdown イテレータを使用してください。

for i in countup(1, 10, 2):
  echo i  # Prints 1, 3, 5, 7, 9

for i in countdown(9, 0, 2):
  echo i  # Prints 9, 7, 5, 3, 1

rangeからseqへの変換:

import sequtils

const subrange = 0..9
const seqrange = toSeq(subrange)
assert seqrange is seq[int]

こちらもご覧ください。

スライス

スライス範囲の構文は異なります。Python の a[x:y] は Nim の a[x ..< y] です。

let variable = [1, 2, 3, 4]
assert variable[0 .. 0] == @[1]
assert variable[0 .. 1] == @[1, 2]
assert variable[0 ..< 2] == @[1, 2]
assert variable[0 .. 3] == @[1, 2, 3, 4]

スライスのインデックスを後ろから数える

Nimでは、逆方向のインデックスは ^1 のように ^ を数字と一緒に使います。
逆方向のインデックスには、特定の型 BackwardsIndex があります。
また、コンパイル時に const として「準備」することもできます。

const lastOne = ^1  # コンパイル時に定義
assert lastOne is BackwardsIndex
assert [1, 2, 3, 4, 5][2 .. lastOne] == @[3, 4, 5]
assert [1, 2, 3, 4, 5][2 .. ^1] == @[3, 4, 5]
var another = ^3    # 実行時に定義
assert [1, 2, 3, 4, 5][0 .. another] == @[1, 2, 3]
assert [1, 2, 3, 4, 5][^3 .. ^1] == @[3, 4, 5]

静的境界チェック

非常に単純な例を比較してみましょう。

[0, 1, 2][9]  # no Index 9

インデックス9が存在しないため、実行時にクラッシュします。

$ python3 example.py
Traceback (most recent call last):
  File "example.py", line 1, in <module>
    [0, 1, 2][9]
IndexError: list index out of range

$

Nimではどうでしょう:

discard [0, 1, 2][9] # No Index 9

コンパイル&実行:

$ nim compile --run example.nim
example.nim(1, 19) Warning: can prove: 9 > 2  [IndexCheck]
example.nim(1, 18) Error: index 9 not in 0..2 [0, 1, 2][9]

$

Nim はコンパイル時に [0, 1, 2] がインデックス 9 を持たないことをチェックしますが、これは 9 > 2 であるため、コンパイルも実行もできません。

これは Subrange でも使えます。例えば、正でなければならない整数の変数があるとします。

let must_be_positive: Positive = -9

コンパイル&実行:

$ nim compile --run example.nim
example.nim(1, 34) Warning: can prove: 1 > -9 [IndexCheck]
example.nim(1, 34) Error: conversion from int literal -9 to Positive is invalid.

$

Nim は must_be_positive1 > -9Positive ではないことをコンパイル時にチェックしますが、コンパイルも実行もされません。

これは、--staticBoundChecks:onまたは--staticBoundChecks:offで制御できます。

staticBoundChecks:off`では、Pythonのように実行時にエラーが発生する可能性があります。

Null合体演算子

PythonにはNull合体演算子(日本語Wikipedia)がありません(執筆時点)。

Pythonはこのような構文を使用します。

other = bar if bar is not None else "default value"  # "bar" may be Null?, or not ?.

Nimには?.を使ったwrapnilsモジュールがあり、 Null合体演算子は、Nullかもしれない中間値の周りにあるif...elif...elseの分岐を減らしてコードを単純化するものです。

assert ?.foo.bar.baz == ""  # "bar" may be Null?, or not ?.

NullはPythonではNoneになり、Nimではnilになります。

こちらもご覧ください

https://nim-lang.github.io/Nim/wrapnils.html

withコンテキストマネージャー

NimでPythonと同じようなwith句を使うには、次のような選択肢があります。

例はテンプレートのセクションを見てください

文字列

言語 文字列 複数行の文字列 Raw String Multi-line Raw string 文字列内展開 クォーテーション
🐍 Python "foo" """foo""" r"foo" r"""foo""" f"""{1 + 2}""" " '
👑 Nim "foo" """foo""" r"foo" r"""foo""" fmt"""{1 + 2}""" "

文字列操作

Ops 🐍 Python 👑 Nim
小文字へ "ABCD".lower() "ABCD".toLowerAscii()
先頭と末尾の空白を除去 " ab ".strip() " ab ".strip()
任意の文字(列)で分割 "a,b,c".split(",") "a,b,c".split(",")
結合 "a" + "b" "a" & "b"
指定の文字の位置を検索 "abcd".find("c") "abcd".find("c")
指定の文字列で始まっているかどうか "abc".startswith("ab") "abc".startswith("ab")
指定の文字列で終わっているかどうか "abc".endswith("ab") "abc".endswith("ab")
改行で分割 "1\n2\n3".splitlines() "1\n2\n3".splitlines()
指定した範囲を取得 "abcd"[0:2] "abcd"[0 ..< 2]
n番目の1文字を取得 "abcd"[2] "abcd"[2]
逆から数えてn番目の1文字を取得 "abcd"[-1] "abcd"[^1]
小文字へ unicodedata.normalize("NFC", "Foo") "Foo".normalize()
改行を数える len("1\n2\n3".splitlines()) "1\n2\n3".countLines()
繰り返す "foo" * 9 "foo".repeat(9)
インデントを加える textwrap.indent("foo", " " * 9) "a".indent(9)
インデントを減らす textwrap.dedent("foo") "foo".unindent(9)
Boolに変換 bool(distutils.util.strtobool("fALse")) :question: parseBool("fALse")
Intに変換 int("42") parseInt("42")
Floatに変換 float("3.14") parseFloat("3.14")
文字列内展開 f"foo {1 + 2} bar {variable}" fmt"foo {1 + 2} bar {variable}"
レーベンシュタイン距離 editDistance("Kitten", "Bitten")

文字列の効率

文字列のメモリ確保は,newStringOfCap(capacity = 42)で行うことができます.
これは、1つの新しい空の文字列 "" を返しますが、割り当てられた capacity42 です。
しかし、capacity を超えて渡しても、クラッシュやバッファオーバーフローは起こりません。

variable = ""
assert variable == "" # length is 0, capacity is 0, 1 allocations, 0 copies
variable += "a"       # length is 1, capacity is 1, 2 allocations, 1 copies
variable += "b"       # length is 2, capacity is 2, 3 allocations, 2 copies
variable += "c"       # length is 3, capacity is 3, 4 allocations, 3 copies
variable += "d"       # length is 4, capacity is 4, 5 allocations, 4 copies
assert variable == "abcd" 
# TOTAL: 5 allocations, 4 copies

⬆ Python ⬆ ⬇ Nim ⬇

var variable = newStringOfCap(2)
assert variable == "" # length is 0, capacity is 2, 1 allocations, 0 copies
variable.add "a"      # length is 1, capacity is 2, 1 allocations, 0 copies
variable.add "b"      # length is 2, capacity is 2, 1 allocations, 0 copies
variable.add "c"      # length is 3, capacity is 3, 2 allocations, 0 copies
variable.add "d"      # length is 4, capacity is 4, 3 allocations, 0 copies
assert variable == "abcd" 
# TOTAL: 3 allocations, 0 copies

この差は、forループやwhileループの中にある文字列では大きくなる可能性があります。

Nim の stringopenArray[char] と互換性があり、最適化のために不要なコピーを避けることができます。
charint と互換性があるので、string の操作は、透過的にインプレースで計算することができます。
つまり、openArray[char]を受け取る関数は、"abcd" * and* ['a', 'b', 'c', 'd']を受け取ります。

F記法

Nim の strformat は Python のF記法にインスパイアされたフォーマットされた文字列リテラルを実装しています。
strformat はメタプログラミングを用いて実装されており、コードの拡張はコンパイル時に行われます。
また、JavaScriptターゲットでも動作します。

PythonのF記法と同様に、以下のことができます。
等号を使って文字列内のキー・値をデバッグする
例えば,fmt"{key=}"fmt "key={value}"に展開されます.

let x = "hello"
assert fmt"{x=}" == "x=hello"
assert fmt"{x   =  }" == "x   =  hello"

Nim の strformat は バックスラッシュ をサポートしていますが、Python のF記法はサポートしていません。

>>> print( f"""{ "yep\nope" }""" ) # 実行時エラー.
Error: f-string expression part cannot include a backslash.

⬆ Python ⬆ ⬇ Nim ⬇

echo fmt"""{ "yep\nope" }"""       # Nim works.

yep
ope

文字列内のフォーマットを開いたり閉じたりするために、カスタムの文字ペアを選択することができます。引数として char を渡すだけです。

import strformat
let variable = 42
assert fmt("( variable ) { variable }", '(', ')') == "42 { variable }"
assert fmt("< variable > { variable }", '<', '>') == "42 { variable }"

バッククォートやスペース、' 'などの文字を使うと効果的です。

import strformat
let variable = 42
assert fmt(" variable`{variable}", ' ', '`') == "42{variable}"

標準ライブラリ比較

目的 🐍 Python 👑 Nim
OS os os
文字列操作 string strutils
日付&時刻 datetime times
ランダム random random
正規表現 (バックエンド) re re
正規表現 (フロントエンド) jsre
HTTP urllib httpclient
ログ logging logging
外部コマンドの実行 subprocess osproc
Pathの操作 pathlib, os.path os
計算 math, cmath math
MIME Types mimetypes mimetypes
SQLite SQL sqlite3 db_sqlite
Postgres SQL db_postgres
レーベンシュタイン距離 editdistance
オブジェクトのファイル出力 pickle json, jsonutils, marshal
Base64 base64 base64
ブラウザでURLを開く webbrowser browsers
非同期処理 asyncio asyncdispatch, asyncfile, asyncnet, asyncstreams
単体テスト unittest unittest
差分 difflib diff
colorsys colors
MD5 hashlib.md5 md5
SHA1 hashlib.sha1 sha1
HTTP Server http.server asynchttpserver
Lexer shlex lexbase
マルチスレッド threading threadpool
URL & URI urllib.parse uri
CSV csv parsecsv
CLIの引数 argparse parseopt
SMTP smtplib smtp
HTTP Cookies http.cookies cookies
統計解析 statistics stats
テキストの改行 textwrap wordwrap
Windowsレジストリ winreg registry
POSIX posix posix, posix_utils
SSL ssl openssl
CGI cgi cgi
組み込みプロファイリング cprofile, profile nimprof
モノトニック時刻 time.monotonic monotimes
Run functions at exit atexit exitprocs
ファイルの権限 os, stat os, filepermissions
ファイルシステムの再帰的操作 os.walk os.walkDirRec, globs.walkDirRecFilter
テンプレートエンジン string.Template Source Code Filters
両端キュー collections.deque deques
B木辞書型 btreetables
Critical Bit Tree Dict/Set critbits
Pooled Memory Allocation pools
Parse JSON json parsejson, json
Parse INI configparser parsecfg
Parse XML xml parsexml, xmltree
Parse HTML html.parser htmlparser
Parse SQL parsesql
ターミナルの色 terminal
Linuxディストリビューション検知 distros
HTML Generator htmlgen
アロー関数 sugar
In-Place to Work-on-Copy sugar.dup
糖衣構文 sugar
JavaScript & フロントエンド dom, asyncjs, jscore, jsffi, dom_extensions, jsre

タプル

タプルは固定サイズで、インデックス 0 から始まり、さまざまな型を含むことができます。
名前付きタプルは無名タプルと比べて余分なオーバーヘッドはありません。

無名タプル

(1, 2, 3)

⬆ Python ⬆ ⬇ Nim ⬇

(1, 2, 3)

名前付きタプル

  • キーには名前が付いていますが、タプル自体には名前が付いていない場合。Python の 名前付きタプル は import collections を必要とします。
collections.namedtuple("_", "key0 key1")("foo", 42)

⬆ Python ⬆ ⬇ Nim ⬇

(key0: "foo", key1: 42)
  • キーにもタプル自体にも名前が付いている場合。
collections.namedtuple("NameHere", "key0 key1")("foo", 42)

⬆ Python ⬆ ⬇ Nim ⬇

type NameHere = tuple[key0: string, key1: int]
var variable: NameHere = (key0: "foo", key1: 42)

Nimのタプルは、タプル自身にも名前があるという点で、Pythonの名前付きタプルによく似ています。

タプルの詳細については manual を参照してください。

可変長配列

Nimシーケンスは、固定サイズではなく、成長も収縮も可能で、インデックス 0 から始まり、同じタイプの要素を含むことができます。

["foo", "bar", "baz"]

⬆ Python ⬆ ⬇ Nim ⬇

@["foo", "bar", "baz"]

@arrayseq に変換する関数です。

リスト内包表記

variable = [item for item in (-9, 1, 42, 0, -1, 9)]

⬆ Python ⬆ ⬇ Nim ⬇

let variable = collect(newSeq):
  for item in @[-9, 1, 42, 0, -1, 9]: item

const にも内包表記を割り当てることができ、コンパイル時に実行されます。

内包表記はコンパイル時に展開されるマクロとして実装されます。
展開されたコードは--expandMacroコンパイラオプションで見ることができます。

let variable = 
  var collectResult = newSeq(Natural(0))
  for item in items(@[-9, 1, 42, 0, -1, 9]):
    add(collectResult, item)
  collectResult

内包表記は、ネスト化、複数行、複数式、ゼロオーバーヘッドなどが可能です。

import sugar

let values = collect(newSeq):
  for val in [1, 2]:
    collect(newSeq):
      for val2 in [3, 4]:
        if (val, val2) != (1, 2):
          (val, val2)
        
assert values == @[@[(1, 3), (1, 4)], @[(2, 3), (2, 4)]]

一行での例:

print([i for i in range(0, 9)])

⬆ Python ⬆ ⬇ Nim ⬇

echo(block: collect newSeq: (for i in 0..9: i))

Pythonの内包表記では、コードをジェネレーターに変換します。
しかし、Nimの内包表記はコードをイテレータに変換しません。

import sugar

func example() =
  discard collect(newSeq):
    for item in @[-9, 1, 42, 0, -1, 9]: 
      if item == 0: return
      item

example()

⬆ Nim ⬆ ⬇ Python ⬇

def example():
  [item for item in [-9, 1, 42, 0, -1, 9] if item == 0: return]
      
example()

Pythonエラー:

SyntaxError: invalid syntax.

Pythonでは動作しないがNimでは動作するコードがあります。
なぜなら、コードはPythonでは暗黙的にジェネレーターに変換されますが、Nimでは通常のコードに展開されるからです。

  • collect()とは?.
    collect
    は返り値がコンストラクタとして使用するものは何でも引数として受け取ります。

辞書内包表記

variable = {key: value for key, value in enumerate((-9, 1, 42, 0, -1, 9))}

⬆ Python ⬆ ⬇ Nim ⬇

let variable = collect(initTable(4)):
  for key, value in @[-9, 1, 42, 0, -1, 9]: {key: value}

集合内包表記

variable = {item for item in (-9, 1, 42, 0, -1, 9)}

⬆ Python ⬆ ⬇ Nim ⬇

let variable = collect(initHashSet):
  for item in @[-9, 1, 42, 0, -1, 9]: {item}

集合

言語 Set Ordered Set Bitset Bit Fields(日本語Wikipedia) Imports
🐍 Python set()
👑 Nim HashSet() OrderedSet() set Bit Fields import sets
  • PythonのsetはHashSetに置き換えることができます。

PythonのsetはNimのset型とは異なります。
「デフォルト」のSetはBitset(英語Wikipedia)です。
これは、含まれる型のすべての可能な値に対して集合に含まれる型の可能な値ごとに、それが集合に存在するかどうかを示す1ビットを格納します。
そのため、型が可能な値の範囲を有限に限定している場合に使用するとよいでしょう。
セットに入る可能性のある値が有限であり、コンパイル時にわかっている場合は、それらのためにEnumを作成することができます。

通常、セットに入れることができる最大の整数は 65535 であり、これは high(uint16) に相当します。

小さな整数が必要でなければ、整数のサブレンジを使ってより大きな整数を収めることができます。
以下の例では、コンパイル時に 2,147,483,647 すなわち high(int32) をセットに収めるようにサイズを明示します。

const x = {range[2147483640..2147483647](2147483647)}
assert x is set  # Equals to {2147483647}

Nimのset型は、より高速でメモリ効率の良いものとなっています。
実際、HashSetが辞書として実装されているのに対し、setはビットベクターで実装されています。
単純なフラグ型や小さな数学的なセットには、setを使います。

  • 勉強中の方は、HashSetを使ってください。

辞書型

Python の辞書型には Tables を使います。

言語 Dictionary Ordered Dictionary Counter Imports
🐍 Python dict() OrderedDict() Counter() import collections
👑 Nim Table() OrderedTable() CountTable() import tables

テーブルの初期化

dict(key="value", other="things")

⬆ Python ⬆ ⬇ Nim ⬇

to_table({"key": "value", "other": "things"})

Ordered Dictionary

collections.OrderedDict([(8, "hp"), (4, "laser"), (9, "engine")])

⬆ Python ⬆ ⬇ Nim ⬇

to_ordered_table({8: "hp", 4: "laser", 9: "engine"})

Counters

collections.Counter(["a", "b", "c", "a", "b", "b"])

⬆ Python ⬆ ⬇ Nim ⬇

to_count_table("abcabb")

例:

import tables

var dictionary = to_table({"hi": 1, "there": 2})

assert dictionary["hi"] == 1
dictionary["hi"] = 42
assert dictionary["hi"] == 42

assert len(dictionary) == 2
assert dictionary.has_key("hi")

for key, value in dictionary:
  echo key, value

テーブルはタプルの配列の糖衣構文です。

assert {"key": "value", "k": "v"} == [("key", "value"), ("k", "v")]
assert {"key": true, "k": false} == @[("key", true),  ("k", false)]

B木 テーブル

同じAPIを使ったB木に基づくソートされたテーブルです。

こちらもご覧ください。

三項演算子

"result0" if conditional else "result1"

⬆ Python ⬆ ⬇ Nim ⬇

if conditional: "result0" else: "result1"

三項演算子が単なるif...elseのインラインであることがわかります。

ファイルの読み書き

一行ずつ読む

with open("yourfile.txt", "r") as f:
    for line in f:
        print(line)

⬆ Python ⬆ ⬇ Nim ⬇

for line in lines("yourfile.txt"):
  echo line

ファイルの読み書き:

write_file("yourfile.txt", "this string simulates data")
assert read_file("yourfile.txt") == "this string simulates data"

コンパイル時のファイルの読み書き:

const constant = static_read("yourfile.txt")  # コンパイル時に文字列を返す

ファイルの権限の変更

import os
os.chmod("file.txt", 0o777)

⬆ Python ⬆ ⬇ Nim ⬇

import fusion/filepermissions
chmod "file.txt", 0o777

例では、ファイル "file.txt" が存在すると仮定しています。
どちらもUnixの8進法のファイルパーミッションを使用しています(日本語Wikipedia)
また、osモジュールでは、より低レベルのAPIが利用できます

https://nim-lang.github.io/fusion/src/fusion/filepermissions.html を参照してください。

一時的にフォルダを変更する

import os

class withDir:
    # __del__()がないと安全ではない

    def __init__(self, newPath):
        self.newPath = os.path.expanduser(newPath)

    def __enter__(self):
        self.savedPath = os.getcwd()
        os.chdir(self.newPath)

    def __exit__(self, etype, value, traceback):
        os.chdir(self.savedPath)


with withDir("subfolder"):
  print("Inside subfolder")
print("Go back outside subfolder")

⬆ Python ⬆ ⬇ Nim ⬇

import fusion/scripting

withDir "subfolder":
  echo "Inside subfolder"
echo "Go back outside subfolder"

例では、フォルダ "subfolder" が存在すると仮定しています。
Pythonにはオプションでサードパーティの依存関係があり、同じように動作しますが、例では標準ライブラリを使用しています。
Pythonのサードパーティの依存パッケージの中には、withDirの中のコードをジェネレーターに変換するものがあります。
withDir内のコードをジェネレーターに変換してしまい、コードの変更を余儀なくされることがあります(returnからyieldなど)。

https://nim-lang.github.io/fusion/src/fusion/scripting.html を参照してください。

マップ & フィルター

def isPositive(arg: int) -> bool: 
  return arg > 0

map(isPositive, [1, 2,-3, 5, -9])
filter(isPositive, [1, 2,-3, 5, -9])

⬆ Python ⬆ ⬇ Nim ⬇

proc isPositive(arg: int): bool = 
  return arg > 0 

echo map([1, 2,-3, 5, -9], isPositive)
echo filter([1, 2,-3, 5, -9], isPositive)

ラムダ式

variable: typing.Callable[[int, int], int] = lambda var1, var2: var1 + var2

⬆ Python ⬆ ⬇ Nim ⬇

var variable = proc (var1, var2: int): int = var1 + var2

Multi-line example:

var anon = func (x: int): bool =
             if x > 0:
               result = true
             else: 
               result = false

assert anon(9)

Python の無名関数は return を使用できませんが、Nim では使用できます。

example = lambda: return 42
assert example() == 42

エラー SyntaxError: invalid syntax.

⬆ Python ⬆ ⬇ Nim ⬇

let example = func: int = return 42
assert example() == 42

Python の無名関数は yield を使用できませんが、Nim では使用できます。

example = lambda: for i in range(0, 9): yield i

for _ in example(): pass

エラー SyntaxError: invalid syntax.

⬆ Python ⬆ ⬇ Nim ⬇

let example = iterator: int = 
  for i in 0..9: yield i

for _ in example(): discard

Nimの匿名関数は、基本的に名前のない関数です。

デコレーター

  • テンプレートとマクロは、Pythonのデコレーターと同様に使用できます。
def decorator(argument):
  print("This is a Decorator") 
  return argument

@decorator
def function_with_decorator() -> int:
  return 42

print(function_with_decorator())

⬆ Python ⬆ ⬇ Nim ⬇

template decorator(argument: untyped) =
  echo "This mimics a Decorator"
  argument

func function_with_decorator(): int {.decorator.} =
  return 42

echo function_with_decorator()
  • なぜNimは@decorator構文を使わないのか?

Nim が {. .} を使うのは、複数のデコレータを一緒に使うことができるからです。

また、Nimは変数や型にも対応しています。

func function_with_decorator(): int {.discardable, inline, compiletime.} =
  return 42

let variable {.compiletime.} = 1000 / 2

type Colors {.pure.} = enum Red, Green, Blue

JSON

Pythonは複数行の文字列の中にJSONを入れて使用しますが、NimはリテラルのJSONを直接コード上で使用します。

import json

variable = """{
    "key": "value",
    "other": true
}"""
variable = json.loads(variable)
print(variable)

⬆ Python ⬆ ⬇ Nim ⬇

import json

var variable = %*{
  "key": "value",
  "other": true
}
echo variable
  • JSONにはJsonNodeという型があり、%*は中括弧内に変数やリテラルを持つことができます。
  • JSONは%*の中括弧の中にコメントを入れることができますが、これはNimのコメントです。
  • JSONが有効なJSONでない場合、コードはコンパイルされません。
  • JsonNodeは、型が混在していたり、増減が可能な型なので、Nimでは便利です。
  • JSONをコンパイル時に読み込んで、文字列として定数に格納することができます。
  • 文字列からJSONを解析するには、parseJson("{}")を使います。
  • ファイルからJSONを解析するには、parseFile("file.json")とします。
  • JSONドキュメント

main関数

if __name__ == "__main__":
  main()

⬆ Python ⬆ ⬇ Nim ⬇

when is_main_module:
  main()

単体テスト

import unittest


def setUpModule():
    """Setup: Run once before all tests in this module."""
    pass

def tearDownModule():
    """Teardown: Run once after all tests in this module."""
    pass


class TestName(unittest.TestCase):
    """Test case description"""

    def setUp(self):
        """Setup: Run once before each tests."""
        pass

    def tearDown(self):
        """Teardown: Run once after each test."""
        pass

    def test_example(self):
        self.assertEqual(42, 42)


if __name__ == "__main__":
    unittest.main()

⬆ Python ⬆ ⬇ Nim ⬇

import unittest

suite "Test Name":

  echo "Setup: Run once before all tests in this suite."

  setup:
    echo "Setup: Run once before each test."

  teardown:
    echo "Teardown: Run once after each test."

  test "example":
    assert 42 == 42

  echo "Teardown: Run once after all tests in this suite."

ユーザー定義メッセージ付きのAssert

  • assertblock を取ることができるので、より良いユーザー体験のためにメッセージをカスタマイズすることができます。
let a = 42
let b = 666
doAssert a == b, block:
  ("\nCustom Error Message!:" &
   "\n  a equals to " & $a &
   "\n  b equals to " & $b)

Testament

unittestの他の方法であり、大規模プロジェクトに対応し、より多くの機能を搭載しています。

Docstrings

NimのDocstringsは、##で始まるReSTructuredText および MarkDownコメントです。
※ReSTructuredTextとMarkDownは必要に応じて混在させることができます。

nim doc file.nimでソースコードからHTML、Latex(PDF)、JSONのドキュメントを生成できます。

nim genDepend file.nim を使えば、ソースコードから依存関係グラフ DOT .dot ファイルを生成することができます。

runnableExamplesでドキュメントをUnittestとして実行できます。

"""モジュールのドキュメント"""

class Kitten(object):
    """クラスのドキュメント"""

    def purr(self):
        """関数のドキュメント"""
        print("Purr Purr")

⬆ Python ⬆ ⬇ Nim ⬇

## モジュールのドキュメント *ReSTructuredText* と **MarkDown**

type Kitten = object ## 型のドキュメント
  age: int  ## フィールドのドキュメント

proc purr(self: Kitten) =
  ## 関数のドキュメント
  echo "Purr Purr"

インデントの選択肢

短い行の場合、1行でコードを書き、エラーや警告のないインライン構成にすることができます。

let a = try: 1 + 2 except: 42 finally: echo "Inline try"

let b = if true: 2 / 4 elif false: 4 * 2 else: 0

for i in 0 .. 9: echo i

proc foo() = echo "Function"

(proc   () = echo "Anonymous function")()

template bar() = echo "Template"

macro baz() = echo "Macro"

var i = 0
while i < 9: i += 1

when is_main_module: echo 42

キャメルケース

  • なぜ Nim は snake_case ではなく CamelCase なのでしょうか?

実際にはそうではありません。Nimはスタイルにとらわれません。

let camelCase = 42      # キャメルケースとして宣言 
assert camel_case == 42 # スネークケースとして使う

let snake_case = 1      # スネークケースとして宣言
assert snakeCase == 1   # キャメルケースとして使う

let `free style` = 9000
assert free_style == 9000  

この機能により、Nimは異なるスタイルを持つ多くのプログラミング言語とシームレスに相互運用することができます

より均質なコードのためには、デフォルトが選択され、必要に応じて強制することもできます。
デフォルトのコードスタイルを強制するには、コンパイルコマンドに --styleCheck:hint を追加します。
Python の pycodestyle と同様に、Nim はコンパイルの前にコードのスタイルを チェック します。
もっと厳密なスタイルにしたければ、--styleCheck:error を使うことができます。
Nim には Nimpretty という名前の内蔵コードオートフォーマッタが付属しています。

多くのプログラミング言語では、大文字と小文字を区別しない機能があります。
PowerShell、SQL、PHP、Lisp、Assembly、Batch、ABAP、Ada、Visual Basic、VB.NET、Fortran、Pascal、Forth、Cobol、Scheme、Red、Rebolなどです。

ゼロから始める場合は、学習中にPython風の名前を使うことができます。
もっと勉強するまでは、そのような名前を使ってもエラーにはなりません。

def VS proc/func

  • なぜ Nim は proc の代わりに def を使わないのか?

Nimは、"Procedure"というネーミングから、通常の関数にはprocを使います。

func は、グローバル変数やスレッドローカル変数にアクセスできない場合や、アクセスすべきでない場合に使用します。

Nimには副作用の追跡があります

なぜなら、echostdout を変異させてしまい、副作用となるからです。

代わりに debugEcho を使ってください。

ゼロから始める場合は、学習中にすべての関数に proc を使うことができます。
もっと勉強するまでは、そのようにしてもエラーにはなりません。

非同期

Nim にはずいぶん前から Async が組み込まれていて、async, await, Future などで期待通りの動作をします。

asyncdispatchは、async/await構文を使って非同期処理を書くためのモジュールです。

Futureは型です(PythonのFutureのような、JavaScriptのPromiseのようなもの)。

{.async.}は関数をAsyncに変換するプラグマです(Pythonのasync defのようなもの)。

公式のPython AsyncioのHello WorldをNimに変換してみましょう。

async def main():
    print("Hello ...")
    await asyncio.sleep(1)
    print("... World!")

asyncio.run(main())

⬆ Python ⬆ ⬇ Nim ⬇

proc main() {.async.} =
  echo("Hello ...")
  await sleep_async(1)
  echo("... World!")

wait_for main()

内部的には、Asyncはメタプログラミング(マクロ、テンプレート、プラグマなど)を使って実装されています。

詳細 asyncCheck waitFor await
Futureが完了するまで待つ
Futureを無視する
Future内の結果を返す
asyncの中だけで使える
  • なぜNimはasync defを使わないのか?

Async は Nim では単なる マクロ であり、言語のシンタックスを変更する必要はなく、Python の Decorator のようなものです。

また、Nim では同じ関数が、同じコード、同じ名前で、同時に Async Sync になることができます。

Python の場合、ライブラリがあって、例えば "foo " とすると、foo (Sync) と aiofoo (Async) がありますね。
通常、プロジェクト、リポジトリ、開発者、API は全く異なります。
これは Nim では必要ありませんし、この機能のおかげでほとんど見られません。

Nim では Async は単なる マクロ なので、自分の好きなように Async を作ることもできます

以下も目を通してください。
asyncfile
asyncnet
asyncstreams
asyncftpclient
asyncfutures

Cを知らなければならないか?

PythonでPYCファイルを手動で編集しないのと同じように、C言語を手動で編集する必要はありません。

NimではNimを書いてコーディングし、PythonではPythonを書いてコーディングするのと同じです。

テンプレート

テンプレートは、コンパイル時にその呼び出しをテンプレート本体で置き換えます。

コンパイラがコードの塊をコピー&ペーストしてくれるようなものだと想像してください。

テンプレートを使うと、オーバーヘッドのない関数のような構造を持つことができます。
巨大な関数を小さな部分に分割することができます。

関数や変数の名前が多すぎると、ローカルな名前空間を汚染する可能性があります。
テンプレート内の変数は、そのテンプレートの外には存在しません。
テンプレートは、実行時には名前空間に存在しません(エクスポートしない場合)。
テンプレートは、コンパイル時に特定の値がわかっていれば、その値を最適化することができます。

テンプレートは、暗黙のうちに自動的にライブラリの「インポート」や「エクスポート」を行うことはできません。
テンプレートは、自分自身の内部で使用されているシンボルを「自動インポート」することはありません。
インポートされたライブラリをテンプレートのボディで使用する場合、そのライブラリをインポートする必要があります。
インポートされたライブラリをテンプレート本体で使用する場合は、そのテンプレートを起動する際にそのライブラリをインポートする必要があります。

テンプレートは関数ではないので、テンプレート内では return は使用できません。

テンプレートを使用すると、日常的に使用できる非常に高レベルで美しいAPIを実装することができます。
低レベルな最適化がされたものは考えないまま、DRY(日本語Wikipedia)にすることができます。

Pythonの with open("file.txt", mode = "r") as file: 1つのテンプレートを使って再現しています。

テンプレートの例のアニメーション

GIFは完璧ではありませんが、ほぼ同じです。

テンプレートは完璧ではありませんが、簡単に手を抜くことができます。読者が開発を改善しようとするための練習です。

template withOpen(name: string, mode: char, body: untyped) =
  let flag = if mode == 'w': fmWrite else: fmRead  # "flag"はテンプレートの外部には存在しない
  let file {.inject.} = open(name, flag)   # 依存性を注入し変数`file`を作る。 `file`は{.inject.}で持ってくるためテンプレートの外部に存在する
  try:
    body                                   # `body`は引数から持ってくる
  finally:
    file.close()                           # 引数が渡された後の処理

withOpen("testing.nim", 'r'): # Pythonの`open("file", mode='r') as file`の真似
  echo "Hello Templates"      # テンプレート内部に入るコード。この2行は引数"body"に渡される
  echo file.read_all()        # テンプレート内部で作られた変数"file"を呼び出す

学びながら、すべてに関数を使うことができるので、ゼロから始める人も安心です。

関数間で変数を共有するには?

関数間で変数を共有する方法は、Pythonに似ています。

グローバル変数:

global_variable = ""

def function0():
    global global_variable
    global_variable = "cat"

def function1():
    global global_variable
    global_variable = "dog"

function0()
assert global_variable == "cat"
function1()
assert global_variable == "dog"
function0()
assert global_variable == "cat"

⬆ Python ⬆ ⬇ Nim ⬇

var global_variable = ""

proc function0() =
  global_variable = "cat"

proc function1() =
  global_variable = "dog"

function0()
assert global_variable == "cat"
function1()
assert global_variable == "dog"
function0()
assert global_variable == "cat"

オブジェクトのパラメータ:

class IceCream:

  def __init__(self):
    self.object_attribute = None

def function_a(food):
    food.object_attribute = 9

def function_b(food):
    food.object_attribute = 5

food = IceCream()
function_a(food)
assert food.object_attribute == 9
function_b(food)
assert food.object_attribute == 5
function_a(food)
assert food.object_attribute == 9

⬆ Python ⬆ ⬇ Nim ⬇

type IceCream = object
  object_attribute: int

proc functiona(food: var IceCream) =
  food.object_attribute = 9

proc functionb(food: var IceCream) =
  food.object_attribute = 5

var food = IceCream()
functiona(food)
assert food.object_attribute == 9
functionb(food)
assert food.object_attribute == 5
functiona(food)
assert food.object_attribute == 9

Pythonのように、関数の引数として関数を渡すことができます。

インプレースとアウトプレースの違い

Python や JavaScript などのインタプリタ型言語から移行した場合、Nim のどこかに「In-Place」と「Out-Place」という奇妙な記述があるのに気づくでしょう。
この意味がわからない場合、Nim は重複した関数を持っているように見えます。

Pythonは、文字列やオブジェクトに何か変化があったときに、新しい文字列やオブジェクトを確保します。
例えば、巨大な文字列が変数に入っていて、そのうちの1文字を変更したとします。
メモリ上の文字列は複製されますが、新しいコピーは1文字が変更されています。
これは新しいコピーでの作業で、「Out-Place」と名付けられていますが、ほとんどのPythonはこのように動作します。
大きな文字列の例では、Nimは変更したい文字を変更するだけです。
メモリ上で文字列を複製することはありません。
いくつかの関数は新しいコピーで動作し、ドキュメントにはたいていこう書かれています。
マクロ を使うと、Nim はインプレースの関数からアウトプレースの関数に変えることができます

JavaScript ターゲットのために設計された Nim の標準ライブラリモジュールは、通常、新しいコピーで動作します。
なぜなら、JavaScript ターゲットにすると、インプレース API がないか、それを使用するメリットがないからです。

新しいコピーで動作するいくつかのNim標準ライブラリモジュールは、将来的にインプレースで動作するように変更されるかもしれませんし、変更されないかもしれません。

例:

import sugar  # sugar.dup

func inplace_function(s: var string) =  # "string"ではなく"var string"を使う
  s = "CHANGED"

# In-Place アルゴリズム.
var bar = "in-place"
inplace_function(bar)  ## 変数はin-placeで変化した
assert bar == "CHANGED"

# Out-Place アルゴリズム.
assert "out-place".dup(inplace_function) == "CHANGED"  ## 変数は新しくコピーされたものに変化した

ベストプラクティス

実用的な「ベストプラクティス」のランダム提案ガイドです。TLDR スタイルで、任意の順序で、あらゆるレベルのスキルに対応します。

  • 可能であれば func を使用してください
  • any の代わりに auto を使用してください。
  • this の代わりに self を使用してください。
  • 可能であれば constlet を使用してください。
  • 型はファイルの先頭付近に置いてください。
  • publicなコードには、includeではなくimportを使用してください。
  • プライベートなコードを 単体テスト する場合には、import ではなく、include を使用してください。
  • unittestの代わりに、Testamentを使用してください。
  • ドキュメントには runnableExamples を使用してください。
  • 異なる型の複数の値を返すには tuple を使用します。
  • os などのように、`` をプレフィックスとする標準ライブラリのインポートを使用します。
  • when isMainModule: を使用して、コードがライブラリと実行ファイルとして動作するようにします。
  • インプレースで動作するようにコードを設計し、必要に応じて sugar.dup を使ってアウトプレースする
  • 必要に応じて sorted の代わりに sort を使うなど、インプレースの関数を使うようにしましょう。
  • オプションの型やオプションの戻り値には options を使用してください。Option は軽量です。
  • 非負整数の引数や入力型には NaturalPositive を使用してください。
  • 例えば、"foo" & "\n" の代わりに "foo" & '\n' を使用します。
  • デバッグのために、コードに {.deprecated.} 一時的に マークを付けて、その関数が使われている場所を探します。
  • デバッグには echo, repr, astToStr, assert, expandMacros, expandArc が便利です。
  • マクロでは parseStmt や文字列操作の代わりに quote do: を使用します。
  • 新しいマクロを設計する際には、生成したいコードを dumpAstGen ブロック内に記述して、出力されたマクロを確認してください。
  • 生のバイナリ blob ではない、ASCII または Unicode の文字列を表すには、string を使用します。
  • ASCII や Unicode の文字列ではない生のバイナリ blob を表すには、seq[byte] を使用します。
  • OOPであっても funcproc を使用し、method は使用しないでください。
  • distinct tuple は使わないでください。
  • お金に float を使ってはいけない。
  • Futurediscard を使用してはいけません。
  • block未使用 のラベル名を追加してはいけません。
  • FFI目的でない限り、変数名をすべて大文字にしてはいけません。
  • 整数の戻り値として NaturalPositive を使用しないでください。代わりに intuint を使用してください。
  • 代わりに明示的な制御フローを使用してください。
  • 文字列を連結するために & を繰り返しすぎないでください。
  • 文字列をフォーマットするために & を繰り返しすぎてはいけません。
  • APIライブラリを random でコーディングするときに、randomize() をハードコードしないでください。

また、以下も参照してください。

PythonでNimファイルをインポートする

Nim のための Python 構文

PYPIへのパブリッシュ

サイレントコンパイレーション

コンパイル時に完全にサイレントにしたい場合(重要な警告やヒントを見逃す可能性があります)。
コンパイルコマンドに --hints:off --verbosity:0 を追加することができます。

コンパイラのヘルプ

コンパイラのヘルプは長いので、より使いやすくするために、最も頻繁に使うコマンドだけを --help で表示しています。
完全なヘルプを見たい場合は、--fullhelpを使用してください。

ビルドモード

開発したコードが製品として出荷できる状態になったら、リリースビルドを使用します。
この場合、コンパイルコマンドに -d:release を追加します。

特徴 リリースビルド デバッグビルド
実行速度 速い 遅い
ファイルサイズ 小さい 大きい
最適化
エラー発生箇所のトレース
実行時チェック
コンパイル時チェック
assert
doAssert

MicroPython

NimはC言語にコンパイルされるので、Arduinoや同様のハードウェアで動作します。

ニーズに合わせて、完全な手動メモリ管理を含むいくつかのメモリ管理戦略を持っています。
リリース用にビルドされたNimのバイナリは小さく、ハードウェアの小さなストレージに収めることができます。

ライブコーディング

https://ja.wikipedia.org/wiki/SuperCollider

SuperCollider は C++ なので、Nim を使って再利用することができます。

理論的には、Nim SuperCollider のプラグインは C コードと同じくらい速くなるはずです。
Nim メタプログラミングは LiveCoding に適した DSL を作ることができます。

Nim ライブコーディングのためのいくつかのプロジェクト。

抽象基底クラス

これを見てください

哲学

Nim を理解する上で重要なことは、Nim は C 言語と同じくらい速く、かつより安全に設計されているということです。設計上の決定事項の多くは、自分の足を撃つことを難しくすることに基づいています。
Pythonでは、ポインタはありません(すべてが参照として扱われます)。
Nimはポインターを提供しますが、Nimは日常的に必要な他の安全なツールを提供し、ポインターは主にCとのインターフェースや低レベルのシステムプログラミングを行うためのものです。

Pythonとは逆に、ほとんどのNimコードはコンパイル時に実行してメタプログラミングを行うことができます。
Python のデコレーターやメタプログラミングで可能な DSL の多くを Nim のマクロやプラグマで実現できます。
(できないこともありますが!)。もちろん、そのためにはいくつかの異なるパターンと、より多くの型安全性が必要です。

⬆⬆⬆⬆⬆トップへ

この記事に贈られたバッジ

Discussion

ログインするとコメントできます