LoginSignup
20
16

More than 3 years have passed since last update.

Nim初心者が始めるNimとnimpy

Last updated at Posted at 2019-10-16

はじめに

Nimって言語のver 1.0.0がりりーすされたみたいなので触ってみたお話です.
Nimって調べるとPython風な書き方ができる高速な言語って出てきます.

この記事ではNimの導入とnimpyっていうPythonからNimを使うやつのお話をします.(NimからPythonを呼ぶこともできるみたいですがここでは触れません)
Nimの基本的な導入記事としては

が個人的におすすめです.

Nimのインストール方法

先ず,なにはともあれNimをインストールしなければなりません.公式サイトに従ってインストールしましょう.
僕がUbuntu 18.04でインストールした方法を書いておきます.aptでもインストールできるのですが,バージョンが0.19とかなので,公式からダンロードしました.

$ curl https://nim-lang.org/choosenim/init.sh -sSf | sh

これで大丈夫です.このコマンドを実行すると,ホームディレクトリに~/.nimble/が作成されて,~/nimble/bin/にインストールされます.なので,~/.nimble/をパスに追加しましょう.

nimpyのインストールとすごさ

$ nimble install nimpy

これで大丈夫.Nimのライブラリのインストールはpipみたいに使えて楽で:thumbsup:

僕は普段Pythonを使っているので,Pythonから使えるような言語でないとあんまり勉強する気が起きませんし,勉強したところで結局使いません.C++のBoost Pythonを使っていたこともあるのですが,やはりインストールが多少手間で,Pythonを普段書いてるような矮小な私にとってはC++を書くのも結構大変で骨が折れます.

さて,nimpyですがこれはすごいです.これを使うことでPythonからNimの関数を呼び出せるようになります!!!
Boost Pythonと比べての個人的に感じるメリットとして

  • 導入,コンパイルが楽
  • Pythonの型(listやtuple)をNimの型(seq)に勝手に直してくれる (これはBoost Pythonではできない)
  • Nimのプロシージャに{.exportpy.}プラグマをつけるだけでPythonに公開できる.
  • Nimの書き方はPythonと似ているので並列して書いても頭がおかしくならない

などが挙げられます.
Boost Pythonも好きですが,僕が書くことはもうないでしょう.

:upside_down: LET'S USE NIMPY :slight_smile:

Hello nimpy

まず簡単に,以下のようなnimodule.nimを書きます

nimodule.nim
import nimpy # nimpy をインポート 

proc hello(): void {.exportpy.} = 
# {.exportpy.}の書かれたプロシージャが公開される 
  echo "HELLO NIMPY!!!"

以下のコマンドでコンパイルします.

$ nim c --tlsEmulation:off --app:lib --out:nimodule.so nimodule.nim

そしたら,カレントディレクトリにnimodule.soができます.
ここでpython3を起動し,

$ python3
>>> import nimodule
>>> nimodule.hello()
HELLO NIMPY!!!
>>> 

なんと簡単なのでしょうか!!!
CやC++でモジュールを作る場合こんな簡単にはできないです.

+, -, *, / and mod

つぎに,2つの数の四則演算とあまりを表示して,なんとなく1を返す関数をつくってみます.

nimodule.nim
import nimpy, strformat

proc echoAxB(a, b: int): int {.exportpy.} =
  echo fmt"a+b = {a + b}" #pythonのf-strings 要 strformat
  echo fmt"a-b = {a - b}"
  echo fmt"a*b = {a * b}"
  echo fmt"a/b = {a / b}"
  echo fmt"a%b = {a mod b}" # %ではなくmod, 結構罠
  result = 1 # result は特別な変数で,この値が暗黙的にreturnされます.  

同様にコンパイルし,

$ python3
>>> import nimodule
>>> a = nimodule.echoAxB(3,2)
a+b = 5
a-b = 1
a*b = 6
a/b = 1.5
a%b = 1
>>> print(a)
1
>>> b = nimodule.echoAxB(2.0, 1.5)
a+b = 3
a-b = 1
a*b = 2
a/b = 2.0
a%b = 0

はい,こんな感じです.ここで,Python側からfloat型を渡すと,勝手にint型に変換されます.また,float型を引数にもつ関数にint型を渡す場合も変換されます.

list, tuple and dict

ここではpythonの配列→Nimの配列の対応を示します.

list, tuple → seq

例えば,配列の正の要素の総和をもとめる関数をNimでかくと

import sequtils
proc sumPositiveElements[T](x: seq[T]): T =
  x.filter(proc (x:T): bool = x>0).foldl(a+b)
  # 最後に評価される式が暗黙的に返り値になります
  # filterはPythonと同様,引数はLambda関数
  # foldlはPythonのreduce (foldrもある)

if isMainModule: # pythonのif __name__ == "__main__":
  echo sumPositiveElements(@[-2, -4, 5, 3]) # => 8

となります.ここで[T]はジェネリックスと呼ばれるもので,C++のテンプレートのようなもので,型をパラメータ化できます.しかしこれに{.exportpy.}プラグマをつけてもコンパイルできません.intfloatを明示的に書かなくてはいけません.intとして実装すると,

>>> nimodule.sumPositiveElements([-5, -3, -7, 1, 3])
4
>>> nimodule.sumPositiveElements((-5, -3, -7, 1, 3))
4

となります.ListとTupleは動的配列seqに変換されます.

list, tuple → tuple

さて,先程のような場合,配列の要素が全て同じ型でなければなりません.異なった型の要素を持つ配列の場合は以下のようにseqでなく,tupleに変換するすることで対応できます.

proc echoTuple(t: tuple[name:string, age:int]): void {.exportpy.} =
  echo fmt"Name: {t.name}, Age: {t.age}"

Nim側では,引数をtupleとし,それぞれの型を明示することでPythonの配列をうけとれます.Nimのtupleはフィールド名を持ち,簡単な構造体のように扱えます.とても便利!!さて,Python側からは以下のように呼び出せます.

>>> import nimodule
>>> nimodule.echoTuple(("Taro", 32)) # list型["Taro", 32]でもおk
Name: Taro, Age: 32

dict → dict

Pythonの基本的な型として辞書型dictがありますが,これはNimのtable型に変換できます.以下は配列を要素に持つ辞書から,最大の平均の配列のキーとその平均値を返す関数です.Nimで複数の返り値を設定する場合もtupleを使うことで実現できます.

import tables

proc maxMeanColumn(d: Table[string, seq[int]]): tuple[key: string, mean: float] {.exportpy.} =
  result = (key: "nil", mean: -high(float)) # high(float)でfloatの最大値
  for key, values in d: # pythonでのd.items()
    let mean = values.foldl(a+b).float/values.len.float # 型キャスト
    if mean > result.mean:
       result = (key, mean) # フィールド名は省略可能

Python側から適当な辞書を渡します.

>>> import nimodule
>>> d = {"a": list(range(10)),
... "b": [10, 20, 0],
... "c": [30, 5]}
>>> nimodule.maxMeanColumn(d)
('c', 17.5)

はい,便利ですね.
NimとPythonのパフォーマンスの差なども検証してみたいです.(参考

function as an argument and PyObject

さて,引数として関数を渡したい場合があります(高階関数).
$f: \{0,...,10\} \to \{0,1\} $な関数を考えてみます.$f(0)=f(x)$となる$x (x>0)$をすべて探すプログラムは以下のようにかけます.

proc enumerateAll(f: proc(x: int): int): seq[int] {.exportpy.} =
# このように引数を書いたら関数を渡せる
  let y = f(0)
  var solutions: seq[int] = @[] # ここで:seq[int]の明記が必要
  for i in 1..10: # Pythonのrange(1, 11), 10も含まれることに注意
    if y == f(i):
      solutions.add(i) # seqではappendではなくadd
  return solutions # returnもちゃんとあります

$f$をPython側で以下のように実装し,解を求めます.

>>> def f(x):
...     if x in [0, 3, 7, 8, 10]:
...         return 1
...     else:
...         return 0
>>> nimodule.enumerateAll(f)
[3, 7, 8, 10]

今のままでは,返り値がintでないといけません.もう少し一般的に引数のところをf: proc(x: int): PyObjectとすれば,比較可能なPythonオブジェクトを返す関数を渡すことができます.

nimpyの欠点

個人的に思うnimpyの最大の欠点はnimpyでグーグル検索するとnumpyが引っかかること."nimpy"のようにクォートつけないとまともに記事が見つからない.
あと,Nimのtype(構造体)を返り値にできないらしい.

おわりに

Nimはいいぞ
ここで書いたプログラムに実用的なものはないと思いますが,調べたことをまとめてみました.
nimpyが公式サポートになったらとさらにNimユーザが増えると思いますが,どうなのでしょうか...(nimpyの作者はnimのcontributorなので,期待できそう?)

20
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
16