まえがき
いらにか(@happy_packet)です。
この記事はNimアドベントカレンダー2020に向けて書いた記事です。
Nimの良さは「効率的、表現力豊か、エレガント」なところです。
その中でも今回は「表現力豊か、エレガント」に焦点を当てて、コードを読むだけでNimを好きになる。
そんな記事を書きたいと思いました。
この記事では
- Nimクイック入門
- フレーズ集
を通じてNimの魅力を感じ取ってもらいたいと思います。
※本記事では簡便に解説するためにプロシージャ/メソッド/ファンクションをまとめて「関数」と呼ぶことがあります。これらはNimにおいて明確に区分すべきものですが、副作用等に馴染みがない人も読み進められるように便宜上「関数」と表現するのをお許しください。
Nimクイック入門
クイック入門なので複雑な話は割愛します。
クイック・リファレンス
Nim in Actionを参考にクイックリファレンスの画像を作りました。
クイックなので詳しい解説は無しです。
よくわからない人も何となくの理解でOKです。わかった気になっておいてください。
Nimの特徴的な機能 UFCS(≒型の拡張メソッド)
Nimのプロシージャは第一引数の型に実装されているように振る舞います。
C#でいうところの拡張メソッドのようなものです。
メソッドチェーンによる記述はデータ加工の流れが読みやすくなります。
既存の型にも後からメソッドを追加しやすいので便利です。
proc add(a, b: int): int =
a + b
proc sub(a, b: int): int =
a - b
let n = 1
echo n.add(10)
#=> 11
echo n.add(10).sub(1)
#=> 10
モジュール設計の基礎
Nimという言語がなんとなく理解できて来たら、次に気になるのは外部モジュール(ライブラリ)の作り方が気になりますよね。
Nimでは宣言した型、変数、関数は同一ファイル外からの呼出しがデフォルトでは許されておらず、基本的にprivateです。それらをpublicにするには名前の後ろにアスタリスク* をつけます。
const modAVersion = "1.4"
proc getModAVersion*(): string =
modAVersion
import modApkg/version
echo getModAVersion
#=> 1.4
echo modAVersion
#=>modAVersionは公開されていないのでコンパイル時にundeclared identifierになる
NimではClassを作る代わりに、UFCSで型に関数を生やしていきます。
例えばversion.Major
でメジャーバージョン、version.Minor
でマイナーバージョンの整数を取得できるようなVersionモジュールを作りたい場合は以下のように実装ができます。
import strutils
type Version* = string
const modBVersion*: Version = "1.4"
proc Major*(ver: Version): int =
ver.split('.')[0].parseInt
proc Minor*(ver: Version): int =
ver.split('.')[1].parseInt
えっ?僕の作ったMajor関数とMinor関数の実装が💩だから使いたくない?
なら自分で実装してください!!そのためにtype Versionをpublicにしてあるので!!
(つまり型の拡張性もモジュール製作者がどうしたいか決められます)
import modBpkg/version except [Major, Minor] #MajorとMinorはimportしない
proc Major(ver: Version): int =
# Majorバージョンを返す実装を記述
proc Minor(ver: Version): int =
# Minorバージョンを返す実装を記述
echo modBVersion.Major
#=> main.Majorが呼び出される
echo modBVersion.Minor
#=> main.Minorが呼び出される
モジュール設計については他にもトピックがありますが、
上記さえ理解しておけばクイック入門の基礎としては十分だと思います。
フレーズ集
ここからはNimにおける個人的に好きなフレーズを紹介します。
whenとif
whenとifはどちらも条件分岐の文ですが、whenはコンパイル時に評価されるため実行前にはすでに分岐が確定しています。
Cにおける#ifdef相当の機能です。
下記は実行ファイルにexe拡張子を付与する処理です。
import os
# はじめに
block When_defined_windows:
echo "これは必ず実行されます"
when defined(windows):
echo "これはwindowsでのみ実行されます"
else:
echo "これはwindows以外で実行されます"
# 例: ファイル名に拡張子を追加する処理
block Better_case:
const fileExt =
when defined(windows): ".exe"
else: ""
echo "filename" & fileExt
block bestCase:
# osのaddFileExtを利用する
echo "filename".addFileExt(ExeExt)
templateでcd
次のコードはテンプレートを使ってワーキングディレクトリを操作する例です。
choosenimのテストコードを読んでいるときに見つけて面白いなと思いました。
import os
template cd*(dir: string, body: untyped) =
let lastDir = getCurrentDir()
setCurrentDir(dir)
body
setCurrentDir(lastDir)
let rootDir = getCurrentDir().parentDir()
let srcDir = rootDir / "src"
let binDir = rootDir / "bin"
cd rootDir:
# location is rootDir
echo getCurrentDir()
cd srcDir:
# location is srcDir
removeFile("srcFile.nim")
# location is rootDir
echo getCurrentDir()
cd binDir:
# locations is binDir
removeFile("binFile".addFileExt(ExeExt))
関数ポインタのようなもの
Nimのプロシージャはファーストクラスオブジェクトなので、変数への代入も可能ですし関数の引数として受け取ることも可能です。
次のコードはビルトインコマンドを実行する簡易インタプリタの例です。
import rdstdin
proc sayHello(): int =
echo "Hello"
return 0
proc sayGoodbye(): int =
echo "Goodbye"
return 1
proc sayNice(): int =
echo "Nice"
return 0
type BuiltinCommand = object
name: string
fn: proc (): int
var
builtinCommands: seq[BuiltinCommand] = @[
BuiltinCommand(name:"hello", fn: sayHello),
BuiltinCommand(name:"goodbye", fn: sayGoodbye),
BuiltinCommand(name:"nice", fn: sayNice),
]
when isMainModule:
echo "You can input hello, goodbye, nice"
while true:
let input = readLineFromStdin(">")
var execCode = -1
for cmd in builtinCommands:
if input == cmd.name:
execCode = cmd.fn()
if execCode == -1:
echo "Unknown command: ", input
elif execCode == 1:
break
echo "End this program."
おわりに
駆け足で紹介してきましたが「よくわからんかった」って人はぜひ Nimを知ってほしい #Nimを始めるには からNimの一歩を歩んでみてはいかがでしょうか。
他にも「こんないいフレーズがあるよ」などがありましたら、ぜひこの記事を編集してください!!
※別記事に書いてリンク依頼でも構いません
Nimは「表現力豊か、エレガント」なプログラミング言語なので、文芸的な奥ゆかしい楽しさもあります。
特にdom96氏などの生きたコード読むのは楽しいですし勉強にもなるので、
「Nimで何かしたいな」と思っている人はコードリーディングを始めてみるのもお勧めです。
※choosenimはそれほどコード量が多くないので読むのに最適!!
書くだけではなく、読む楽しさもNimを通じて感じてもらえると幸いです。