LoginSignup
26
11

More than 3 years have passed since last update.

至高の言語Nimの欠点

Last updated at Posted at 2019-06-24

はじめに

今回は至高の言語Nimの唯一の欠点について話します.
もしかしたら人によっては欠点ではないのかもしれません.

About Nim

一応,Nimを知らない人用にNimについて書きます(そんな奴この記事見つけない説).

Nimは高速で書きやすいを目指した言語です.
「記述はpythonなどのスクリプト言語に近く,速度はCと同等」
のようなことを売りにしている言語です.

実際のところは,C(他言語)へと一度変換することで高速にしているといった感じです.
ただ,この変換がなかなかに優秀なので,適当なコードであっても超高速に動いてくれます.

今回は,こんな超優秀(だけど流行らない)言語Nimの欠点について話します.

NimにShallow Copyはない(2019/06/29訂正)

Nimの欠点.それはすべてのオブジェクトがDeep Copyされるということに起因しています.

正確には,NimにはShallow Copyが存在しません
(2019/06/29訂正)正確には,明示しない限り,参照型にならないのでShallow Copyされません
さらに言えば,Classオブジェクトや配列オブジェクトは参照型ではありません
つまり,配列で=を使うとDeep Copyされるってことです.
実際の例を見てみましょう

固定長配列(array)の場合

ソースコード

二つの配列arrayAとarrayBを定義して,片方をもう片方へと代入してみます
その後,arrayAのみ要素を変更してみます

var arrayA, arrayB : array[4,int]
arrayA = [1,2,3,4]
arrayB = arrayA
echo "arrayA ",arrayA
echo "arrayB ",arrayB
arrayA[0] = -1
echo "arrayA ",arrayA
echo "arrayB ",arrayB

実行結果

arrayAもarrayBも参照型ではないので片方の要素を変更しても,もう片方の要素は変更されませんでした.
つまり,arrayB = arrayAがDeep Copyであったということです.

arrayA [1, 2, 3, 4]
arrayB [1, 2, 3, 4]
arrayA [-1, 2, 3, 4]
arrayB [1, 2, 3, 4]

可変長配列(seq)の場合

ソースコード

可変長配列seqでも同じことをやってみます.

var seqA, seqB : seq[int]
seqA = @[1,2,3,4]
seqB = seqA
echo "seqA ",seqA
echo "seqB ",seqB
seqA[0] = -1
echo "seqA ",seqA
echo "seqB ",seqB

実行結果

同様に,片方の要素を変更しても,もう片方の要素は変更されませんでした.
つまり,Deep Copyされたということです.

seqA @[1, 2, 3, 4]
seqB @[1, 2, 3, 4]
seqA @[-1, 2, 3, 4]
seqB @[1, 2, 3, 4]

回避法

では,どうやってDeep Copyを回避するのでしょう?
そのためにNimにはrefという参照型を明示する記法があります.
要はポインタですね.

参照型固定長配列(array)の場合

ソースコード

参照型のルールは次の三つです.
- 定義にrefをつける.
- newで領域確保をする.
- []が参照された値を指す.

var ref_arrayA, ref_arrayB :ref array[4,int]
ref_arrayA = new array[4,int]
ref_arrayA[] = [1,2,3,4]
ref_arrayB = ref_arrayA
echo "arrayA ",ref_arrayA[]
echo "arrayB ",ref_arrayB[]
ref_arrayA[0] = -1
echo "arrayA ",ref_arrayA[]
echo "arrayB ",ref_arrayB[]

実行結果

両方の配列のは参照元が同じなので,両方とも変更されました.

arrayA [1, 2, 3, 4]
arrayB [1, 2, 3, 4]
arrayA [-1, 2, 3, 4]
arrayB [-1, 2, 3, 4]

参照型可変長配列(seq)の場合

ソースコード

可変長配列(ほかのクラスオブジェクト)でも同じ.

var ref_seqA, ref_seqB : ref seq[int]
ref_seqA = new seq[int]
ref_seqA[] = @[1,2,3,4]
ref_seqB = ref_seqA
echo "ref_seqA ",ref_seqA[]
echo "ref_seqB ",ref_seqB[]
ref_seqA[0] = -1
echo "ref_seqA ",ref_seqA[]
echo "ref_seqB ",ref_seqB[]

実行結果

ちゃんと参照されています.

seqA @[1, 2, 3, 4]
seqB @[1, 2, 3, 4]
seqA @[-1, 2, 3, 4]
seqB @[-1, 2, 3, 4]

Nimの欠点

ではこのすべてのオブジェクトがDeep Copyされることの何が良くないのでしょうか?

Deep Copyするということは,それだけfor文が内部で回るということです.Shallow Copyするよりも確実に速度に影響が出ます.しかも,他言語から移ってきたプログラマは,何気なく=を使ってしまうでしょう.そして,速度が落ちていることに気が付かないのです.
実際に検証してみます

普通に配列を定義した場合

ソースコード

要素数100000の配列を定義し,100000回コピーしてみます.

import times
var start_time = cpuTime()
var arrayA, arrayB : array[1..100000,int]
for i in 1..<100000:
  arrayB = arrayA
echo cpuTime() - start_time

実行結果

ぼくの環境では平均して5秒程度かかりました.

5.216

参照型で配列を定義した場合

ソースコード

同様に要素数100000の配列を定義し,100000回コピーしてみます.

import times
var start_time = cpuTime()
var ref_arrayA, ref_arrayB : ref array[1..100000,int]
ref_arrayA = new array[1..100000,int]
for i in 1..<100000:
  ref_arrayB = ref_arrayA
echo cpuTime() - start_time

実行結果

わずか0.004秒です.当たり前ですが,参照しているメモリのアドレスのコピーなので時間がかかりません.

0.004000000000000448

おわりに

Nimの=はなかなかのトラップです.しかも,クラスオブジェクトに対しても同様の挙動を示すというところがもう...
いちいちrefで定義しなければいけないし,refで定義すると,今度は[]が必要になるし.

高速で書きやすい言語に移住してきたのに,「高速にするためには書きにくい表記をしなければならない」というのがNimの最大の欠点でしょう.

26
11
6

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