序
以下、Nimのミニサイズな(?)非公式キャラクターの誕生を祝してのNimポエム♪
(引用)
出典 至高の言語、Nimを始めるエンジニアへ
最近、昼の仕事では、日々是pythonという感じ。
データを扱う業務でのpython3(.6.x)の何とでもなる感を味わいつつ、
これって、python2(あるいはpython1?)依頼の積み重ねのなせる、こなれ技(こなれ芸?)なんだよなと思う。
そんな中、年末からひっそりとはじまっていたStatusのモバイル端末向けNim実装の試み。
...StatusがEthereumの何なの、といったブロックチェーン界隈の話は皆さんがググるに任せるとして、
以下は、StatusのNim実装にコミットしたくなっている自分の
https://github.com/status-im/nim-rlp
などの実装をながめていて思った、 Nimのこなれぐあいについてのメモ書き。
1.Nimのiterator
Pythonではジェネレーターというらしい、yield を用いての高階関数。
これで例えば、章立てされた文書の解析など再帰が使われるべき場面が記述しやすくなる。
Pythonのジェネレーターについては、こちらをご参考:
Pythonのジェネレーターってなんのためにあるのかをなるべく分かりやすく説明しようと試みた
Nimでも同様にyield を用いた高階関数が記述可能。
最近、pythonでジェネレーター入りのコードを書いたついでに、Nimでの記法を確認してみた( 以下、nim0.17.2):
var j = 1 #iterator外の変更
iterator myJumpUp(END:int) : int =
var s = 1
while s <= END:
yield s*j
s += 1
j *= s
for i in myJumpUp(5):
echo i
import future
echo lc[ x | (x <- myJumpUp(6)) , int]
pythonとの異同は皆さんへの宿題にするとして(見た目も実際のところもかなり似ている)、
まずは実行結果を:
1
4
18
96
600
@[720, 2880, 12960, 69120, 432000, 3110400]
前半の1~600がforループ、後半の720~3110400がリスト内包表記(lc)となる。
※pythonでは、リスト内包表記は、[x for x in XXX if x...]といった風に書く。
nimに触り始めた皆さん(...私も似たようなものだが...)にとっては、iterator入りのforループは(myJumpUpというオレオレイテレータの中身はさておけば)何を書いているかは一目瞭然で、、後半のリスト内包表記(lc) の方は、実行結果と見比べると、なんとなくこんなものかといった感じなのかな、と。
そして、myJumpUpイテレータの方はwhileがなんかもっさい感じなのはさておき、yieldで呼び出し元に値が返されることさえ分かれば、読むことは難しくないだろう(イテレータでは、クロージャあたりでおなじみのイテレータ外の変数(ここではj)へのアクセスが可能なわけだね)。
こうしたあたりが、いよいよバージョン1.0に近づき始めたNimの「こなれぐあい」の今なのではないかと思う。forループやiteratorは違和感なく書ける。一方で、時としてコードの生産性に関わるの方は、こなれていなかったりする(nimでは、lcはじめ、こなれていないモジュールは、futureパッケージに含まれていることが多い模様)。
Nimにはmacroがあって、一手間か二手間かかければ言語機能を書き換え可能。これは間違いなくNimの美点なわけだろうけれど、一方で、macroを安易に使うな、まずはproc(プロシージャ) で書けるかを考えよう、といったアドバイスも一方であるわけで、さまざまな枯れて使いやすいマクロがパッケージとして展開されていることは、Nimの実用言語としての理想形のひとつと思われる。ここには バージョン1.0前のNimは一歩二歩足りない感じ。これから始めるならば、マクロにも馴染んでおこうというところかな。
2.type〜proc、raise、try〜except
Nimを書いてて皆がちょっとはひっかかるであろうproc(プロシージャ)。
pythonになれってdefにしろ、とはいわないまでも、rustで(そして、clojureでも意味合いは違うが)採用されいるfnのようなより簡潔な表現にしてほしかったな、と、個人的には思う(kotlinのfunもなんかscalaスキーを隠蔽しているようで個人的にちょっといや)。
とはいえ、Nimで、多くの皆さんに役立って使いやすいdefマクロやfnマクロを提供する勇気がないのもまた事実(誰か出しているのかな??)。
ということでprocはprocだと受け入れて、Nimコードを書き始めると、pythonとの類似点(というより一致点)と、pythonとも似る馴染みやすさがみえてくる。
以下、冒頭のnim-rlp(の一番最初のコミット)を大づかみするために書いてみたコード。
import parseutils
type
Byte = uint8
Rlp = object
data: seq[Byte]
position: int
BadCastError = object of Exception
proc rlpFromHex(input: string): Rlp =
if input.len mod 2 != 0:
raise newException(BadCastError,
"入力の文字列数は偶数にしてね (2文字で1バイトなもので)。")
let totalBytes = input.len div 2
newSeq(result.data, totalBytes)
for i in 0 ..< totalBytes:
var nextByte: int
if parseHex(input, nextByte, i*2, 2) == 2:
result.data[i] = Byte(nextByte)
else:
raise newException(BadCastError,
"入力された文字列には不正なキャラクターが含まれます。")
proc hex1(s:string): void =
try:
var rlp = s.rlpFromHex()
echo "入力された文字列、を8ビットバイナリを10進数のseqに変換しました : ",$rlp.data
except BadCastError as e:
echo "あらら、、 " & e.msg
hex1 "hello!"
hex1 "856d6f67365"
hex1 "10ff"
hex1 "856d6f6f7369"
実行結果:
あらら、、 入力された文字列には不正なキャラクターが含まれます。
あらら、、 入力の文字列数は偶数にしてね (2文字で1バイトなもので)。
入力された文字列、を8ビットバイナリを10進数のseqに変換しました : @[16, 255]
入力された文字列、を8ビットバイナリを10進数のseqに変換しました : @[133, 109, 111, 111, 115, 105]
元ネタはnim-rlpの概ねコピペなのだが、ちょこちょこ書きかえて実行してみたところ、なんとなく自分が書いたようになじみがあって処理を追いかけられるコードになっていた。改めて見てみると、proc -> defと脳内変換してみると、あとはES6でもおなじみのvar/letやprint代わりのechoといった容易に働きを類推できるキーワードのみからコードが構成されていたことがわかった。Nim、けっこう、やはり「こなれている」(個人的感想、個人差あり)。
個人的には、 Nimのすべてがしっくりくる(こなれていると感じる)という境地にはまだまだ至っていないんだけれど、なんとかなる感じがひしひしくる。
日々Nimを書いてみるとどうなるのか。
StatusのNim実装の件に気づいたの今年に入って数日すぎてからなので、コミットしてみようと思い立ってNimをいじりはじめても365日nimをいじくってますとは今年中には言えないんだけど、今のpython並に年300日以上nimのコードを書いた後に、Nim のこなれ具合を自分がどう感じているのか、調味があったりする。...ということで、これからも毎晩nimを書いてみる。
現時点でも、Nim、けっこう手になじみそうな感じはあるよ、ということで、興味を持った皆さんは暫くの間ても日々是nimな夜(あるいは昼)をおくってみては、と、いってみたりする:)