NimのConcept機能のメモ

## はじめに
最近はScalaばっかりやっててNimをサボってたので、Nimの**Concept機能**について、自分なりに調べたメモ。
余談だが静的型付け言語においてScalaってかなり完成度高い気がするので、Javaにはもう戻れない魅力がある。
んじゃ、Nimの魅力って何だろうね？


## 動機
Nimには、Javaでいう`interface`の様な機能がない。むりやりクロージャなんかでそれっぽい挙動させる事はできるけれど。

ところが、~~マニュアルには記述がないけれど~~ （実験的機能の[マニュアルに記載](https://nim-lang.org/docs/manual_experimental.html)があるとコメントでご教授いただきました）。`concept`なる機能があるようで、これってインターフェイスっぽい使い方できるのでは？と思って調べてみた。

## 結論
- nimの`concetp`は多態性がないためJavaなどの`interface`の代わりにはならない。
- ジェネリックの型パラメータに制約をあたえるものと理解。

間違ってたら、ごめんなさい。

## Concept機能とは？
~~公式マニュアルに記述がない（もしかして見つけられてないだけ？）ので、正直よくわかりません・・・~~
実験的機能の[マニュアルに記載](https://nim-lang.org/docs/manual_experimental.html)があるとコメントでご教授いただきました

- ~~公式マニュアルにも記載がなく、シンタックスもよくわからない機能~~(故に今後仕様が変わる可能性が高い）
- C++にも同じような機能追加が提言されていたが、結局取り込まれなかった模様。C++20から取り込まれる？。[Wikipedia参照](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%B3%E3%82%BB%E3%83%97%E3%83%88_(C%2B%2B))
- 型が持つ制約を宣言的に記述し、コンパイル時にチェックする？
- Goの`interface`に近いのかな(Javaの`interface`と異なり、`implements`しなくても条件さえ満たせば良いという部分では)と思ったが、多態性がないので、そもそも`interface`の様な使い方はできない。

## Conceptの定義方法（例）

例えば、足し算ができる（`+演算子`がある)事を制約するconceptは以下のように記述するみたいです。
（補足:nimでは演算子もプロシージャと同じ）

```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`機能と比較すると・・・エラーになる場所が異なる。

```nim
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`を返す式があった場合は、コンセプトに合致していないと判断しコンパイルエラーになるようです。

```nim
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機能が実装されるかも・・・