はじめに
今回は至高の言語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の最大の欠点でしょう.