6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

NimのConcept機能のメモ

Last updated at Posted at 2019-08-12

はじめに

最近は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機能が実装されるかも・・・

6
2
2

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
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?