LoginSignup
18
11

More than 3 years have passed since last update.

Nimのダウンロード

公式HPからどうぞ。
ドキュメントやフォーラムもここらへんにあります。

choosenimを使うと、Nimのバージョン管理が楽になりおすすめです。(解説記事

インストールしている間、オンライン実行環境でお試しください。

バージョン

1.4.0での動作を確認しています。

基礎

言語の概観

Nimは "Efficient, Expressive, Elegant" を標榜するプログラミング言語です。

Effcient: 効率的

NimはC言語並みに処理速度が速いと巷に評判のコンパイル言語です。(なお、知名度がない)
また、構文がシンプルで親切なので、プログラミング自体が効率的です。

Expressive: 表現豊か

Nimは非常に自由度の高い言語です。
一例として、演算子(+とか-みたいなやつ)が自由に作れます。

import math
proc `$` (x, y: int): int = # 組み合わせ
  return fac(x) div (fac(y) * fac(x - y))

echo 5 $ 2 # => 10

そのほか、関数型にもオブジェクト指向にも対応できる柔軟性を持ちます。

Elegant: 優雅

インデントを使うため、pythonと同じような見た目になります。賛否両論はあると思いますが、すっきりした見た目をしています。
メタプログラミングにも長けており、高いレベルの問題解決ができます。

ファイルのコンパイル・実行

nim c -r [ファイル名]

で実行してくれます。拡張子は.nimがいいでしょう。
コマンドラインのオプションの詳細

これを実行すると、C言語(C++やjavascriptも可)を介してコンパイルし、実行されます。

サンプルコード

fizzbuzzのサンプルコードです。
今はわからなくても、この記事を読めば理解できるようになるでしょう。

from strutils import parseInt

echo "Input max Number: "
let maxNumber: int = readLine(stdin).parseInt

for i in 1..maxNumber:
  if i mod 15 == 0: # 15の倍数
    echo "fizzbuzz"
  elif i mod 3 == 0: # 3の倍数
    echo "fizz"
  elif i mod 5 == 0: # 5の倍数
    echo "buzz"
  else:
    echo $i

標準入力・標準出力

標準入力はreadLine(stdin)で、標準出力はecho hogeです。
以下、サンプルです。

echo "Your name? : "
var name: string = readLine(stdin)
echo "Hello ", name, "!"

コードブロック

pythonと同じく、インデントでブロックを表現しますが、スペース2個を使ってください。
TABを使うとエラーになります。

関数への引数渡し

実行する際の関数の書き方には、さまざまな方法があります。
文字列に文字を追加する関数addで例を示します。

var welcome = "Welcome to"
add(welcome, ' ')
add welcome, 'N'
welcome.add 'i'
welcome.add('m')
welcome.add'!'
echo welcome

# => Welcome to Nim!

hogeの型がstring以外の場合、$を頭につけてください。この$は単に「文字列以外を文字列に変換する関数」で、他言語(Perlのsigil, Haskelのカッコなど)のような意味は持ちません。

コメント

#を書くと、行の最後までコメントになります。
複数行のコメントには、#[ ]#が使えます。

echo "Hello, World" #ハローワールド
#[
echo "お名前は? "
var name: string = readLine(stdin)
echo "やあ、", name, "!"
]#

宣言と代入

var x : int     # 変数の宣言だけ
x = 0           # 代入だけ
var y : int = 1 # 宣言と代入
var z       = 2 # 型が明らかなので省略
let w : int = 3 # 定数

# 同時に宣言や代入
var x, y = 3 # どちらもintの3になる
let
  a = 1
  b = 2
  c = 3

定数にはconstもありますがletとの大きな違いとして、constはコンパイル時に値が決まっている必要があります。例えば「標準入力を定数に格納したい」といった操作は、標準入力がコンパイル時にわからないのでletのみに格納できます。

# どちらもOK
let int_var = 3
const int_var = 3

# letのみ
let str_var = readLine(stdin)
const str_var = readLine(stdin) # コンパイルエラー

type(x)としてください。

var x = 'a'
echo type(x) # => char

int/float/bool型

# 宣言
var
  int_var : int # 整数型
  flt_var : flaot # 浮動小数点型
  bol_var : bool # ブール型

# 各種演算
## int同士の加減乗除
int_var = 1 + 1 - 2 * 3 # => -4
int_var += 1    # => -3
int_var = 21 div 13 # => 1 (整数の商)
int_var = 21 mod 13 # => 8 (余り)

## float同士の加減乗除
flt_var = 2.0 + 3.0 - 5.0 * 4.0 # => -15.0
flt_var = 5.0 / 2.0 # => 2.5 (intとの相違点)

## int同士の比較(floatも同様)
bol_var = 2 <  3 # => true  # >, <=, >= も使える
bol_var = 2 == 4 # => false
bol_var = 2 != 3 # => true

## intとfloat同士も演算可能
flt_var = 2.0 - 3 * 5.2 # => -13.6

## bool同士の演算
bol_var = not true # => false
bol_var = true and false # => false # or, xorも使える
bol_var = (3 > 0) == true # => true
bol_var = false < true # => true

## 型変換
int_var = int(32.2 / 3) # => 10
int_var = toInt(32.2 / 3) # => 10
flt_var = float(32 div 3) # => 10
flt_var = toFloat(32 div  3) # => 10

ほとんど予想通りかと思いますが、以下の点に注意してください。

  • floatの割り算は/ですが、intでの割り算はdiv(商), mod(余り)です。
  • 累乗はmathライブラリにある、powあるいは^(指数が自然数のときのみ)を使います。

また、int, floatの型にはいくつか種類があります。(詳細

char/string型

# 宣言
var
  chr_var : char # 文字型
  str_var : string # 文字列型

  int_var : int
  bol_var : bool

chr_var = 'V' # リテラルは単引用符
str_var = "Variable" # リテラルは二重引用符
str_var = """long literal that can use " " too
and multiple lines"""
    # `"""`で挟むとエスケープ不要で改行可能な文字リテラルが作れる。

# ASCII文字コードとの変換
var int_var = ord('A') # => 65
chr_var = chr(65) # => 'A'

# 文字列操作
str_var = "aa" & "bb" # => "aabb" # 連結
str_var = $1.0 # => "1.0" # 文字列へ変換
bol_var = 'a' in "abc" # => true # 文字が文字列に含まれる
bol_var = "ab" in "abc" # => true # 文字列が文字列に含まれる
int_var = len("abc") # => 3 # 文字数

より詳しい文字列操作は標準ライブラリを参照してください。

配列(コンテナ型)

配列のような型(コンテナ型)のうち重要なものは以下の5つあります。

  • array型(固定長配列)
  • seq型(可変長配列)
  • set型(集合型)
  • tuple型(タプル型)
  • Table型(連想配列)

また、関数の引数として、

  • openArray型
  • varargs型

を使うことができます。

set型tuple型openArray型varargs型に関しては、マニュアルを参照してください。
Table型に関しては、標準ライブラリを参照してください。

array型(固定長配列) / seq型(可変長配列)

# 宣言
var
  int_arr: array[4, int] =  [0, 3, 5, 2] # リテラルは`[]`
  int_seq: seq[int]      = @[5, 2, 8, 1] # リテラルは`@[]`

  int_var : int
  bol_var : bool

# for文で順番にアクセスできる 
for value in int_arr: # int_seqも同様
  echo value
# => 0
# => 3
# => 5
# => 2

# 基本的な配列操作
## array, seqに共通してできること
int_var = len(int_arr) # => 4 # 配列長
int_var = int_arr[2] # => 5 # 要素にアクセス
var int_subarr = int_arr[1..3] # => [3, 5, 2] # 部分列の取り出し
int_arr[0] = 4 # => int_arr <- [4, 3, 5, 2] # 要素を書き換え
int_var = max(int_arr) # => 5 # 最大値(最小値はmin)
bol_var = int_arr == [4, 3, 5, 2] # => true # 比較

## arrayからseqに変換
int_seq = @int_arr # => @[4, 3, 5, 2]

## arrayにできるが、seqにできないこと
   # 特にないかな?

## seqにできるが、arrayにできないこと
int_seq = @[5, 2] & @[8, 1] # => @[5, 2, 8, 1] # 要素の連結
add(int_seq, 4)  # int_seq    <- @[5, 2, 8, 1, 4] # 要素の追加
add(int_seq, [1, 3]) # 配列の追加
                 # int_seq    <- @[5, 2, 8, 1, 4, 1, 3]
del(int_seq, 1)  # int_seq    <- @[5,    8, 1, 4, 1, 3] # 要素の削除

上記コードのように、@は固定長配列(array)の頭につけることで動的配列(seq)を意味します。
より詳しい配列操作は標準ライブラリを参照してください。

制御文

制御文には以下のものがあります。

  • if-elif-else
  • case-of-else
  • while
  • for
  • try-except

前述した通り、ブロックはインデント(スペース2個)で表現します。

var int_var = 1

# if文
if (int_var == 1):
  echo "value is 1"
elif (int_var > 1):
  echo "value is greater than 1"
else:
  echo "value is smaller than 1"

# case文
case (int_var)
of 1:
  echo "value is 1"
of 2..100:
  echo "value is greater than 1"
of -100..0:
  echo "value is smaller than 1"
else:
  echo "value is extreme"

# while文
while true:
  int_var += 1
  if int_var == 3:
    continue
  if int_var > 5:
    break
  echo $int_var
# => 1
# => 2
# => 4
# => 5

# for文 # continue, breakも使える
for i in 2..5: # 2..<6とも書ける
  echo $i
# => 2
# => 3
# => 4
# => 5

# try文
echo "Please input:"
var str_var = readLine(stdin)
try:
  echo str_var[100000]
except IndexDefect:
  echo "line too short"
finally: 
  raise newException(ValueError, "you are doomed")

for文で配列のインデックスを使う場合

for i in 0..<len(arr):

ではなく

for i in low(arr)..high(arr):
for i, v in arr: # iにインデックス、vに値が入る

を使った方がいいでしょう。1

関数

関数2を定義するときはprocを使います。

proc mean0(x: int, y: int, z = 2.0): float =
  return float(x + y) / z

proc mean1(x, y: int, z: float = 2.0): float =
  result = float(x + y) / z

proc no_return(x, y: int) =
  echo $x & $y

echo mean0(1, 2) # => 1.5
echo mean0(1, 5, 3.0) # => 2.0
echo mean0(z = 1.0, x = 3, y = 2) # => 5.0
echo 3.mean0(3) # => 3.0 # わからなかったら「関数への引数渡し」を読んでね!
no_return(1, 2) # => 12

int_var = (proc(x, y: int): int = x + y)(1, 2) # => 3 # 無名関数

返り値は、return文を使うほか、関数内であらかじめ定義されているresult変数に代入する、一行で書けるなら=につなげる、などで表現できます。

オーバーロード

Nimの関数はオーバーロードができます。
これはつまり、名前が同じ関数であっても引数の型が違えば、別の関数として定義できます。

proc twice(x: int): int = x * 2 # 一行ならこんな書き方もあるよ
# proc twice(x: int): int = ... # 既に同じ名前の関数があるのでエラー
proc twice(x: float): float = x * 2 # 型が違うならOK

ジェネリクス

オーバーロードとは逆に、いくつかの型に共通した処理をする関数を作りたい場合、ジェネリクスが使えます。ジェネリクスは関数の横の[]で表現します。
例えば、上記のtwice(int)twice(float)は下の関数でまとめられます。

proc twice[T](x: T): T = x * 2

型の定義

ユーザーが新しく型を定義することができます。オブジェクト指向プログラミングと相性がいいでしょう。

type Character = object of RootObj # 継承されるには`of RootObj`が必要
  name: string
  hp: int

proc echoName(self: Character) = # メソッドは型を
  echo self.name

type Player = object of Character # 継承
  atk: int

let Hero = Player(name: "あああああ", hp: 10, atk: 2)
Hero.echoName # => "あああああ"

type文はそのほか、特定の配列を定義したり、列挙型を定義するのにつかわれます。

type
  Direction = enum
    north, east, south, west

var dir_var: Direction = south

type
  IntArray = array[5, int] # 長さ5, 型がintの固定長配列
var x: IntArray
x = [1, 2, 3, 4, 5, 6]

ファイル入出力

# ファイルを読む
var lines: seq[string] = @[]
block:
  var f : File = open("read_from.txt" , FileMode.fmRead)
  defer :
    close(f)
  while not f.endOfFile:
    lines.add(f.readLine())

# ファイルに書く
block:
  var f : File = open("write_to.txt" , FileMode.fmWrite)
  defer:
    close(f)
  for line in lines:
    f.writeLine(line)

block, deferに関しては、マニュアルを参照してください。

モジュール・ライブラリ

ほかのモジュールをインポートするにはimport文を使います。
ほかのモジュールへエクスポートしたい変数、関数には*をつけて置きましょう。

module_a.nim
var
  export_this* = 1
  dont_export = 2
module_b.nim
import module_a

echo export_this
echo dont_export # これはエラー

from module import hogeを使うと、欲しいものだけをインポートできます。

標準ライブラリ

Nimは"battery included"で、標準ライブラリが充実しています。外部ライブラリはNimbleが使われます。choosenimを使ったら一緒に入っています。
ここでは、

について扱います。

以下で使う変数を宣言しておきます。

var
  chr_var : char
  str_var : string
  int_var : int
  flt_var : float
  bol_var : bool
  str_seq : seq[string]
  int_seq : seq[int]

math

import math

## 定数
echo PI # => 3.141592653589793
echo E  # => 2.718281828459045

## 関数
int_var = sum([1, 2, 3]) # => 6  # 合計
int_var = fac(4)         # => 24 # 階乗
int_var = 2.pow(5)       # => 32 # 累乗
    # そのほか、sin, log, roundなどがあります

random

import random

randomize() # seedのランダム化。やらないと以下の結果が同じになる。

int_var = rand(100) # => 0-100までの乱数
str_var = ["Ace", "King", "Queen", "Jack"]
shuffle(str_var) # str_varがランダムな順番になる

strutils

import strutils

# 文字列操作
str_seq = "ab,bc,cd".split(",") # => @["ab", "bc", "cd"] # 分割
str_var = "hello".repeat(3) # => "hellohello" # 繰り返し
bol_var = startsWith("Hello", "He") # => true # 開始文字
int_var = "Hello".count('l') # => 2 # カウント
str_var = "Hello".replace("lo", 'p') # => Help # 置き換え

# 型変換
int_var = "1".parseInt # => 1
flt_var = "2.0".parseFloat # => 1.0
    # 文字列への変換は前述した`$`

# 文字の種類の判定
bol_var = isAlphaAscii('e') # => true # アルファベットか判定
bol_var = isDigit('8') # => true # 0-9か判定
    # そのほか、大文字小文字やの判定・変換があります。

strformat

import strformat

flt_var = 2.0

str_var = &"---{flt_var + 1}---" # => "---3.0---" # 変数や計算
str_var = echo &"{flt_var = }" # => "flt_var = 2.0" # 変数の値の表示
str_var = &"{-12:05}" == " -0012" # 0埋め
str_var = &"{12.34:@>8.5f}" # => "@@12.340" # 小数点以下桁数、右寄せ、文字埋め
str_var = &"{12.0:!^16.3e}" # => "!!!1.200e+01!!!!" # 指数表記

unicode

import unicode

str_var = reversed("123456abc") # => "cba654321"
str_var = "日本語も使えるよ"
int_var = len(str_var) # => 24
    # 非ASCII文字の長さは`len(string)`で取得できない
int_var = str_var.runeLen # => 8
str_seq = str_var.toRunes # => @["日", "本", "語", "も", "使", "え", "る", "よ"]
    # 配列操作でできることができる(長さを取得したり、置換したり)

algorithm

import algorithm

str_seq = @["ab", "aa", "bc", "ba"]
str_seq.sort()    # str_seq <- @["aa", "ab", "ba", "bc"]
str_seq.reverse() # str_seq <- @["bc", "ba", "ab", "aa"]
int_var = binarySearch(str_seq, "ba") # => 1

sequtils

import sequtils

int_var = "abracadabra".count('a') # => 5
int_seq = repeat(5, 3) # => @[5, 5, 5]
int_seq = cycle(@[1, 2], 3) # => @[1, 2, 1, 2, 1, 2]
int_seq = @[1, 2, 3].map(proc(x: int): float = x.toFloat) 
    # => @[1.0, 2.0, 3.0]

tables

Nimにはtablesをインポートしなくても{key0: value0, key1: value1}という記法は存在しますが、これはあくまでも[(key0, value0), (key1, value1)]と同等のシンタックスシュガーです。(()はタプル)
hoge[key0]という形でvalue0にアクセスするには、Tableに変換する必要があります。

import tables

var int_str_table = {1: "one", 2: "two"}.toTable # 変換
int_str_table[3] = "three" # 代入
str_var = int_str_table[2] # => "two"

json

jsonを扱う場合、JsonNode型を使います。これは文字列としてのjsonとNimのオブジェクトをつなぐ役割をします。

以下のコードでも説明しますが、模式図を載せます。

+--------+   %*   +----------+       $       +--------+
|        | -----> |          | ------------> |        |
| Object |        | JsonNode |               |  json  |
|        | <----- |          | <------------ |        |
+--------+   to   +----------+   parseJson   +--------+
import json

type
  User = object # 継承しないので`of RootObj`は不要
    name, surname: string
    age: int 


var user_seq: seq[User] = @[
User(name: "Hatiman", surname: "Hikigaya", age: 17),
User(name: "Yukino", surname: "Yukinoshita", age: 16),
User(name: "Yui", surname: "Yuigahama", age: 17),
]
# オブジェクト -> JsonNode
var user_json_node: JsonNode = %* user_seq


echo user_json_node[0]["name"].getStr()
# => Hatiman

user_json_node[0]["name"] = %* "Hachiman"
# もっと複雑な型(ネストが深いなど)も代入可能

echo user_json_node[0]["name"].getStr()
# => Hachiman


# JsonNode -> json(の文字列)
str_var = $user_json_node
    # => """[{"name":"Hachiman","surname":"Hikigaya","age":17},{"name":"Yukino","surname":"Yukinoshita","age":16},{"name":"Yui","surname":"Yuigahama","age":17}]"""
    # $を使ってJsonNodeの文字列表現を取り出す。

str_var = """{"name":"Iroha","surname":"Issiki","age":16}"""

# json(の文字列)-> JsonNode
user_json_node = parseJson str_var

# JsonNode -> オブジェクト
var new_user : User = user_json_node.to(User)

user_seq.add(new_user)
    # user_seq <-
    # @[
    #   User(name: "Hachiman", surname: "Hikigaya",    age: 17),
    #   User(name: "Yukino",   surname: "Yukinoshita", age: 16),
    #   User(name: "Yui",      surname: "Yuigahama",   age: 17),
    #   User(name: "Iroha",    surname: "Issiki",      age: 16),
    # ]

そのほかの機能

ここで紹介した基礎的な機能のほか、Nimには

  • ポインタ
  • Pragma
  • メタプログラミング(Template, Macro)
  • javascriptへの変換
  • 多言語との連携

といった多くの機能があります。ご興味があればぜひ試してみてください。


  1. array型の場合、インデックスが0から始まると限りません。(マニュアル) 

  2. Nimでは副作用を有しうるprocと副作用のないfuncの区別があるが、副作用のないprocも作れるので本稿では区別しない。 

18
11
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
18
11