Pythonistaのための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がそれに警告を出す可能性があります。
変数
新しい変数を作るには、var
やlet
、const
を使います。
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
モジュールのすべてのシンボル(sin
、cos
など)をインポートして、無条件で使用できるようにしています。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()
であるとします。
- Python:
module.foo()
ではなく、object.foo()
が一般的で、UFCSはありません。 - Nim: 一般的には
foo()
があります。UFCS サポートあり(英語Wikipedia)です。
一般的に 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の string
は openArray[char]
と互換性があり,最適化のために不要なコピーを避けることができます.
char
はint
と互換性があるので、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 には、暗黙の self
や this
はありません。
すべての型をファイルの先頭に置くのが最良の方法ですが、必須ではありません。
関数はコンパイル時にオブジェクトに「接着」されます。
そして、実行時には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
名前の付け方は慣例であり、ベスト・プラクティスでもあります。
気づいたかもしれませんが、initCat
はCat
を返すだけの関数です。
-
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]
静的境界チェック
- Nimには、コンパイル時に静的な境界チェックを行う(英語Wikipedia)という機能があります。
非常に単純な例を比較してみましょう。
[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_positive
が 1 > -9
で Positive
ではないことをコンパイル時にチェックしますが、コンパイルも実行もされません。
これは、--staticBoundChecks:on
または--staticBoundChecks:off
で制御できます。
staticBoundChecks:off`では、Pythonのように実行時にエラーが発生する可能性があります。
- より詳しくは https://nim-lang.github.io/Nim/drnim.html を参照してください。
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
になります。
こちらもご覧ください
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") |
- Nim の文字列操作には
import strutils
が必要です。 - 非常に詳細な比較
文字列の効率
文字列のメモリ確保は,newStringOfCap(capacity = 42)
で行うことができます.
これは、1つの新しい空の文字列 ""
を返しますが、割り当てられた capacity
は 42
です。
しかし、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 の string
は openArray[char]
と互換性があり、最適化のために不要なコピーを避けることができます。
char
は int
と互換性があるので、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 |
- 完全ではありませんが、簡単な概要です。詳しい情報は https://nim-lang.org/docs/lib.html をご覧ください。
タプル
タプルは固定サイズで、インデックス 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"]
@ は array
を seq
に変換する関数です。
リスト内包表記
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}
-
collect()
はimport sugar
が必要です。
集合内包表記
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}
-
collect()
はimport sugar
が必要です。
集合
言語 | 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
-
lines()
Documentation https://nim-lang.org/docs/io.html#lines.i%2Cstring
ファイルの読み書き:
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)
- MapとFilterの操作には
import sequtils
が必要です。
ラムダ式
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."
- Unittestドキュメント
- パッケージマネージャーのNimbleはUnittestの実行も出来ます
- NimScript も Unittest を実行できます
- ドキュメントを
runnableExamples
でUnittestとして実行できます
ユーザー定義メッセージ付きのAssert
-
assert
はblock
を取ることができるので、より良いユーザー体験のためにメッセージをカスタマイズすることができます。
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
は、グローバル変数やスレッドローカル変数にアクセスできない場合や、アクセスすべきでない場合に使用します。
なぜなら、echo
は stdout
を変異させてしまい、副作用となるからです。
代わりに 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
を使用してください。- 可能であれば
const
とlet
を使用してください。 - 型はファイルの先頭付近に置いてください。
-
publicなコードには、
include
ではなくimport
を使用してください。 - プライベートなコードを 単体テスト する場合には、
import
ではなく、include
を使用してください。 -
unittest
の代わりに、Testamentを使用してください。 - ドキュメントには
runnableExamples
を使用してください。 - 異なる型の複数の値を返すには
tuple
を使用します。 -
os
などのように、`` をプレフィックスとする標準ライブラリのインポートを使用します。 -
when isMainModule:
を使用して、コードがライブラリと実行ファイルとして動作するようにします。 - インプレースで動作するようにコードを設計し、必要に応じて
sugar.dup
を使ってアウトプレースする - 必要に応じて
sorted
の代わりにsort
を使うなど、インプレースの関数を使うようにしましょう。 - オプションの型やオプションの戻り値には
options
を使用してください。Option
は軽量です。 - 非負整数の引数や入力型には
Natural
やPositive
を使用してください。 - 例えば、
"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であっても
func
やproc
を使用し、method
は使用しないでください。 -
distinct tuple
は使わないでください。 - お金に
float
を使ってはいけない。 -
Future
にdiscard
を使用してはいけません。 -
block
に 未使用 のラベル名を追加してはいけません。 - FFI目的でない限り、変数名をすべて大文字にしてはいけません。
- 整数の戻り値として
Natural
やPositive
を使用しないでください。代わりにint
やuint
を使用してください。 - 代わりに明示的な制御フローを使用してください。
- 文字列を連結するために
&
を繰り返しすぎないでください。 - 文字列をフォーマットするために
&
を繰り返しすぎてはいけません。 - APIライブラリを
random
でコーディングするときに、randomize()
をハードコードしないでください。
また、以下も参照してください。
PythonでNimファイルをインポートする
Nim のための Python 構文
PYPIへのパブリッシュ
- https://github.com/yglukhov/nimpy/wiki#publish-to-pypi
- https://github.com/sstadick/ponim/blob/master/README.md#nim--python--poetry--
- https://github.com/sstadick/nython#nython
サイレントコンパイレーション
コンパイル時に完全にサイレントにしたい場合(重要な警告やヒントを見逃す可能性があります)。
コンパイルコマンドに --hints:off --verbosity:0
を追加することができます。
コンパイラのヘルプ
コンパイラのヘルプは長いので、より使いやすくするために、最も頻繁に使うコマンドだけを --help
で表示しています。
完全なヘルプを見たい場合は、--fullhelp
を使用してください。
ビルドモード
開発したコードが製品として出荷できる状態になったら、リリースビルドを使用します。
この場合、コンパイルコマンドに -d:release
を追加します。
特徴 | リリースビルド | デバッグビルド |
---|---|---|
実行速度 | 速い | 遅い |
ファイルサイズ | 小さい | 大きい |
最適化 | ✔ | ❌ |
エラー発生箇所のトレース | ❌ | ✔ |
実行時チェック | ✔ | ✔ |
コンパイル時チェック | ✔ | ✔ |
assert |
❌ | ✔ |
doAssert |
✔ | ✔ |
MicroPython
NimはC言語にコンパイルされるので、Arduinoや同様のハードウェアで動作します。
ニーズに合わせて、完全な手動メモリ管理を含むいくつかのメモリ管理戦略を持っています。
リリース用にビルドされたNimのバイナリは小さく、ハードウェアの小さなストレージに収めることができます。
- https://github.com/zevv/nim-arduino
- https://github.com/elcritch/nesper#example-code
- https://gitlab.com/endes123321/nimcdl/tree/master#nimcdl-nim-circuit-design-language
- https://github.com/cfvescovo/Arduino-Nim#arduino-nim
- https://gitlab.com/nimbed/nimbed#nimbed
- https://gitlab.com/endes123321/led-controller-frontend#led-controller-frontend
- https://gitlab.com/jalexander8717/msp430f5510-nim
- https://github.com/mwbrown/nim_stm32f3
- https://github.com/gokr/ardunimo
- https://gitlab.com/NetaLabTek/Arduimesp
- https://ftp.heanet.ie/mirrors/fosdem-video/2020/AW1.125/nimoneverything.webm
ライブコーディング
SuperCollider は C++ なので、Nim を使って再利用することができます。
理論的には、Nim SuperCollider のプラグインは C コードと同じくらい速くなるはずです。
Nim メタプログラミングは LiveCoding に適した DSL を作ることができます。
Nim ライブコーディングのためのいくつかのプロジェクト。
- https://github.com/vitreo12/omni#omni
- https://github.com/capocasa/scnim#scnim---writing-supercollider-ugens-using-nim
抽象基底クラス
これを見てください
哲学
Nim を理解する上で重要なことは、Nim は C 言語と同じくらい速く、かつより安全に設計されているということです。設計上の決定事項の多くは、自分の足を撃つことを難しくすることに基づいています。
Pythonでは、ポインタはありません(すべてが参照として扱われます)。
Nimはポインターを提供しますが、Nimは日常的に必要な他の安全なツールを提供し、ポインターは主にCとのインターフェースや低レベルのシステムプログラミングを行うためのものです。
Pythonとは逆に、ほとんどのNimコードはコンパイル時に実行してメタプログラミングを行うことができます。
Python のデコレーターやメタプログラミングで可能な DSL の多くを Nim のマクロやプラグマで実現できます。
(できないこともありますが!)。もちろん、そのためにはいくつかの異なるパターンと、より多くの型安全性が必要です。
Discussion
遅延リスト的なのとかpythonでいうジェネレーター内包表記、itertoolsのようなものはありますか?sequtilsという配列をあつかうっぽいのは見たのですがこれのイテレータ版のようなものはありますか?