はじめに
最近はScalaばっかりやっててNimをサボってたので、NimのConcept機能について、自分なりに調べたメモ。
余談だが静的型付け言語においてScalaってかなり完成度高い気がするので、Javaにはもう戻れない魅力がある。
んじゃ、Nimの魅力って何だろうね?
動機
Nimには、Javaでいうinterface
の様な機能がない。むりやりクロージャなんかでそれっぽい挙動させる事はできるけれど。
ところが、マニュアルには記述がないけれど (実験的機能のマニュアルに記載があるとコメントでご教授いただきました)。concept
なる機能があるようで、これってインターフェイスっぽい使い方できるのでは?と思って調べてみた。
結論
- nimの
concetp
は多態性がないためJavaなどのinterface
の代わりにはならない。 - ジェネリックの型パラメータに制約をあたえるものと理解。
間違ってたら、ごめんなさい。
Concept機能とは?
公式マニュアルに記述がない(もしかして見つけられてないだけ?)ので、正直よくわかりません・・・
実験的機能のマニュアルに記載があるとコメントでご教授いただきました
-
公式マニュアルにも記載がなく、シンタックスもよくわからない機能(故に今後仕様が変わる可能性が高い) - C++にも同じような機能追加が提言されていたが、結局取り込まれなかった模様。C++20から取り込まれる?。Wikipedia参照
- 型が持つ制約を宣言的に記述し、コンパイル時にチェックする?
- Goの
interface
に近いのかな(Javaのinterface
と異なり、implements
しなくても条件さえ満たせば良いという部分では)と思ったが、多態性がないので、そもそもinterface
の様な使い方はできない。
Conceptの定義方法(例)
例えば、足し算ができる(+演算子
がある)事を制約するconceptは以下のように記述するみたいです。
(補足:nimでは演算子もプロシージャと同じ)
type
Addable = concept x
x + x
# `+`(x,x) プロシージャ呼出方式でも可
let a:Addable = 10
let b:Addable = "hello" #Error: type mismatch: got <string> but expected 'Addable = test'
10:int
は+演算子
をもっているが、"hello":string
は+演算子
を持っていないのでコンパイルエラーになると。
使いドコロ、メリット
いまいち、使い途がワカランのですが、たとえばGeneric
機能と比較すると・・・エラーになる場所が異なる。
func sumByGeneric[T](arr:openarray[T]):T=
for v in arr:
result = result + v
func sumByConcept(arr:openarray[Addable]):Addable=
for v in arr:
result = result + v
#int型は+演算できるのでどちらの方法も合計が計算できる
echo sumByGeneric([1,2,3]) #=> 6
echo sumByConcept([1,2,3]) #=> 6
#string型は+演算できないのでどちらの方法もコンパイルエラーになることを期待するが…
echo sumByGeneric(["a","b","c"]) #=> ここでエラーにならず、sumByGenericの+演算でコンパイルエラーになる。
echo sumByConcept(["a","b","c"]) #=> ここでコンパイルエラーになる
上記のsumByGeneric
sumByConcept
関数はどちらも合計を計算する関数で、引数の配列の要素が+演算子
で計算できる事を期待している。
しかし、ジェネリクスを用いたやり方ではT型
が+演算子
もっているか解るのは+演算子
を用いる呼出先関数で、そこでコンパイルエラーになる。
コンセプトを用いたやり方では、呼出時にコンセプトと合致しているかチェックするため、呼出元でコンパイルエラーになる。
これの何が嬉しいかというと、例えばジェネリックを用いたsumByGeneric
関数が、自作関数/自作プロシージャでなく他人の作ったライブラリだったりした場合、エラーの原因が何なのか理解・判別するのは難しい。**「型T
が+演算子
を備えないといけないなんて知らんがな・・・」**となる。
一方、コンセプトを用いたsumByConept
の場合は、Addable
のコンセプトが開示されているので、**「ああ、+演算子
があったらええんやね」**とエラー原因の理解・判別できるようになる…といった具合。
いまいち良くわからないコンセプトの定義方法
concept
以下にはステートメントが記述できるようである。
普通は以下のように式を書けばよさそう。
false
を返す式があった場合は、コンセプトに合致していないと判断しコンパイルエラーになるようです。
import strformat
type
Any = concept x #どの型も合致するコンセプト
true
Nothing = concept x #どの型も合致しないコンセプト
false
Bowable = concept x
bow(x) is string # プロシージャの制約 bowプロシージャが実装されていれば`true`を返すハズ
x.name is string # フィールドの制約 同じくフィールドを持っていれば`true`を返すハズ
Japanese = object
name:string
American = object
name:string
func bow(self:Japanese):string = fmt"こんにちは。{self.name}です"
func bow(self:Amaerican):string = fmt"Hello,I'm {self.name}."
proc hello(self:Bowable):void = echo(self.bow)
let jp = Japanese(name:"太郎")
let am = American(name:"Bob")
hello(jp)
hello(am)
let arr:seq[Bowable] # 残念ながらこれはコンパイルエラー。Java等の`interface`っぽい振る舞いはできない。
要するにジェネリックの型パラメータに、なんらかの制約(概念)を定義付けるもののようです。なので、interfaceの様な使い方はできない。
さいごに
まだ現段階(ver0.20)では、正式機能ではないようなので、今後ちゃんとマニュアルに記述されることに期待。
あと、今回Nimの言語仕様を眺めていると interface
が予約語になっていたので、もしかすると今後interface機能が実装されるかも・・・