はじめに
前回、から随分時間がたってしまったが、積み残していた「継承」などを中心に。Javaと比較しつつ。
nimのバージョンは以下の通り。
$nim -v #=>0.19.0
まとめ
- 継承は多重継承できない
- 親メソッド呼びは、ちょっと独特
- インターフェイス型がない
JavaとNimの比較
比較項目 | Java | Nim |
---|---|---|
継承 | class Child extends Base{ | type Child = ref object of Base |
キャスト(型変換) | Base b = (Base) child; | var b:Base = Base(child) |
動的ディスパッチ・オーバライド |
@Override public void hoge(){...} |
method hoge(self:Child) |
親メソッド呼び | super.hoge(); | procCall Base(self).hoge() |
インターフェイス型 |
interface IHogeable{ void hoge() } |
ない・・・と思う。 クロージャで、っぽい事はできる |
Nimにおける継承
NimはJavaなどに比べて、「継承」機能はあまり充実していない。公式でも「”is a”より”has a”の方がいい場合が多いよね」みたいなスタンスの模様。ただ、必要十分な機能は備えているかなと思う。
基本
Nimでの継承の基本は
- 親クラスは
RootObj
を継承しておく。(でないとサブクラスを作れない1) - 子クラスは
of
で継承元を指定する
type
Person = object of RootObj #Javaの`Object`相当である、RootObjを継承しておく
name:string
age:int
proc hello(p:Person)=
echo fmt"I am {p.name}. I am Person!"
proc howOld(p:Person)=
echo fmt"I am {p.age} years old."
type
Student = object of Person #Personを継承
grade:int
proc hello(s:Student)=
echo fmt" am {s.name}. I am {$s.grade} grade Student!"
let p = Person(name:"Bob",age:20)
let s = Student(name:"Julia",age:18,grade:4)
p.hello #=> 親のhelloプロシージャ
s.hello #=> 子のhelloプロシージャ
s.howOld #=> 子に定義はない。親のhowOldプロシージャが呼ばれる
ここまでは、いたって想定通りの挙動。
キャスト(型変換)
型変換は型名(変数)
で行うが、以下の2行目はエラーになる。
Person(s).hello #=>親のプロシージャがちゃんとよべる
var p2:Person = Person(s) #=> 実行時エラー!!
これは、Person
もStudent
も値型のため代入時のコピーでエラーになっている模様。型定義で参照型にしてやればよい。(というような事があるので、原則的にobjectタイプは参照型で定義するのがよいと思われる。)
type
Person = ref object of RootObj
name:string
age:int
Student = ref object of Person
grade:int
動的ディスパッチ(メソッドのオーバーライド)
プロシージャのオーバーライドは、C++のvirtual
みたいに(C++に明るくないけど)明示的にする必要がある。明示の仕方はproc
をmethod
にすればよい。この時、ベースのメソッドには{.base.}
プラグマを付けろとウォーニングが出るのでつけておく。
type
Person = ref object of RootObj
Student = ref object of Person
method hello(self:Person){.base.} =
echo "Person"
method hello(self:Student)=
echo "Student"
var persons:array[2,Person] = [new Person,new Student]
for p in persons:
p.hello
#=> Person
#=> Student
では、やっぱり親のメソッドを呼び出したい時はどうするのというと(Javaでいうsuper.hello();
みたいなの)、ちょっと冗長だけれどprocCall
と型変換で実行できる。
method hello(self:Student)=
Person(self).hello # ダメ。オーバライドしているのが呼び出される(この例だと無限ループでスタックオーバーフローする)
procCall Person(self).hello # procCallをつけて呼ぶと、superみたいな挙動する。
インターフェイス型
Javaでいうinterface
相当のものはない。Javaのinterface
の役割は
- 多態性としてinterface型に変換できる
- interfaceで定義されたメソッドの実装を強要できる
ことかなと思うけれど、これを実現するのは、ちと難しそう。代わりに以下のようにクロージャを利用すれば、似たような事もできなくはないけれど…冗長だな。何か他にいい案ないものか。
interface Soundable{
public String sound();
}
class Duck implements Soundable{
public String sound(){
return "quack";
}
}
class Dog implements Soundable{
public String sound(){
return "bow!!";
}
}
class Test{
public static void main(String[] args){
Duck duck = new Duck();
Dog dog = new Dog();
Soundable[] animals = {duck, dog};
for (Soundable animal:animals) System.out.println(animal.sound());
System.out.println(animals[0] == duck); // => true
}
}
type
Soundable = ref object of RootObj #interface相当の型
sound:proc ():string
Duck = ref object of RootObj
Dog = ref object of RootObj
proc getSoundable(self:Duck):Soundable= #Soundableを返すメソッド、Javaのimplements相当
const s = "quack"
Soundable(sound:proc():string=s) #Duckクラスのsoundの実装相当
proc getSoundable(self:Dog):Soundable= #Soundableを返すメソッド、Javaのimplements相当
const s = "bow"
Soundable(sound:proc():string= #Dogクラスのsoundの実装相当
result.add(s)
result.add("!!")
)
let duck = new Duck
let dog = new Dog
let animals:array[2,Soundable] = [duck.getSoundable,dog.getSoundable]
for animal in animals:
echo animal.sound() #=>"quack" "bow!!"
echo animals[0] == duck #=>ただし、当然これは型が違うのでコンパイルエラー
書いた後に、nim interfaceでGoogle先生にきいてみると、いくつか有用そうなのがヒットした。「Nimにインターフェイスは必要か?」はNimコミュニティでも議論されている模様。
objectではなくtupleでインターフェイス型をつくるパターン
基本的には上記と同じだが、インターフェイス型相当をref object
ではなくtuple
にしている。
マクロでやっちゃうパターン
interfaced
やってることは基本的に上記と同じ模様。createInterface(インターフェイス名):
でインターフェイスを作り、toインターフェイス名
でインターフェイス型に変換するマクロになっている。
import interfaced
createInterface(Soundable):
proc sound(self:Soundable):string
proc sound(self:Duck) = "quack"
proc sound(self:Dog ) = "bow!"
let duck = new Duck
let dog = new Dog
let animals = [duck.toSoundable,dog.toSoundable]
for animal in animals:
echo animal.sound()
Templete? Concept? マクロ?
まだ、TempleteやConceptをちゃんと理解できていないので、要調査。
おわりに
インターフェイスもなく、多重継承も認めていないという点で、ちょっと辛いところはあるかもしれないが、十分にオブジェクト指向な設計ができると思う。
まだまだNimはマイナー言語だけど、Go言語よりはやってて楽しい。2 ただ、問題はエコシステムがまだ確立していない。
でもNim超偉い。もっとがんばれ!超がんばれ!