NimのConcept
Genericsの型パラメータに制約を与えるイメージ
例: Comparable
conceptを定義
nimtype
Comparable = concept x, y
(x < y) is bool
Comparable
は、 int
等と同様に一種の型として利用できる
Comparableな型同士は <
によって比較することができることを示す
Comparable
な型は、「<」で比較可能ならば、 int
でも string
でも何でも良い
利用例
nimproc hoge(x,y: Comparable): bool = x>y
echo(hoge(4, 2)) # Success
echo(hoge('4', 2)) # Error
hogeの呼び出し時に型検査が行われる
4
, 2
はいずれもintであり比較可能でなので通る
'4'
、 2
はintとstringの <
比較なので失敗する
つまり、Conceptsの話を一旦忘れて、 echo(x < y)
が実行てきてかつboolであるような x
, y
であるかどうかが検査される
Conceptsを使わない場合にどうなるか
以下のような乗算ができることを示すConceptを定義する
nimtype
Mul = concept x
x * x
乗算できるような型なら Mul
に適用できる
普通にGenericsで関数の型を定義した場合に、かなり制約が弱まってしまう
nimproc mul[T](arr: seq[T]): T =
foldl(arr, a * b)
これは seq[T]
型の値を引数に取るが、これだけ見ると、 seq[string]
も seq[int]
も許容されてしまう
実際には string * string
という演算はできないので、これは型安全ではない
nimecho hoge @['1','2','3','4','5'] # Error
echo hoge @[1,2,3,4,5] # Success
このエラーは型検査時ではなく、実行時に起きている
と思ったが、コンパイルエラーになった。
どういう仕組みだ

なのでconceptを使う
↑genericsでもエラーになったので、ここは「なので」ではない

nimproc hoge(arr: Mul): Mul =
foldl(arr, a * b)
echo hoge @['1','2','3','4','5'] # 型検査でエラー(?)
char型は Mul
制約を満たしていないのでエラーになる
これは理想

いろいろな定義例
x
は +
で演算できる制約を示す
nimtype
Addable = concept x
x + x
nimBowable = concept x
bow(x) is string # プロシージャの制約
x.name is string # フィールドの制約. xはnameを持っている
Concepts内で関数を使用できるというわけではないのか?
Conceptとinterfaceの違い
Interface
ある型が提供するメソッドの集合
Concept
ある型が満たすべき要求の集合
interfaceのように、「あるメソッドをもつ」こと規定したり、
「その型の変数を用いたある式が有効である」ことを規定したり。
Interface
run-timeのポリモーフィズム
Concept
compile-timeのポリモーフィズム
ぜんぜん違う
そもそもConceptsは既存の型を拡張していない
既存の型が持っている性質を減らして制約を強めている感じ
関連
だいたいおなじ
参考
C++のconceptについて
]
Nim# これでも動くわけだが、Personクラス(?)にgreetingメソッドを持つことを明示したい
type
OutputStream = concept var s
s.greeting(string)
type Person = object
name: string
proc greeting(self: Person, greeting: string) =
echo greeting, self.name
var p = Person(name:"hanako")
p.greeting("good morning ")
# ...うまく実装できなかった
Nim# https://stackoverflow.com/questions/37550948/how-do-i-use-a-concept-in-nim
type
T = concept t
t.a is string
T0 = ref object
a: string
T1 = ref object
a: string # ←コメントアウトするとコンパイルエラー
q: string
proc echoT(t: T) : void =
echo "hello " & t.a
echoT(T0(a: "T0"))
echoT(T1(a: "T1", q: "q"))
Interfaceと何が違うのか
動く例
Nimtype
CanDance = concept x
x.dance # `x`はdanceというprocedureを持っている
# ---
type
Person = object
Robot = object
proc dance(p: Person) =
echo "People can dance, but not Robots!"
# ---
let p = Person()
let r = Robot()
proc doBallet(dancer: CanDance) =
# `dancer` can be anything that `CanDance`
dance(dancer)
doBallet(p) # pはdanceメソッド(?)持っているので「People can dance, but not Robots!」と出力される
# doBallet(r) # rはdanceメソッド(?)を持っていないのでコンパイルエラーになる
途中
Nim# https://gist.github.com/honewatson/583135c1b191119a3b3be3fdbfe8607b
import strutils, strformat
type
Dispatch = enum
Reveal
IKind = enum
Human, NonHuman
ObIkind = ref object of RootObj
kind: IKind
Person = object of ObIkind
name: string
Robot = object of ObIkind
name: int
Place = object of ObIkind
description: string
Data = concept x
toString(x)
x.kind is IKind
# We can use a generic proc as a starting point to satisfy `Data` concept proc `toString`
proc toString[T](data: T): string =
return fmt"{data.name} is a {data.kind}"
# We can provide a specific `toString` proc for place which does not have a property of `name`
proc toString(place: Place): string =
return fmt"{place.description} is a {place.kind}"
var
d1 = Person(kind: Human, name: "Jim")
d2 = Place(kind: NonHuman, description: "Hill")
d3 = Robot(kind: NonHuman, name: 738191)
# Here we create a proc actions which has takes concept of type Data as one of the parameters
# The concept of type Data is essentially a kind of restricted generic
# This should work for both the C and JS Backends
proc actions(dispatch: Dispatch, data: Data): string =
case dispatch:
of Reveal:
data.toString()
echo actions(Reveal, d1)
# Jim is a Human
echo actions(Reveal, d2)
# Hill is a NonHuman
echo actions(Reveal, d3)
# 738191 is a NonHuman
TypeScriptenum Dispatch {
Reveral
}
enum IKind {
Human,
NonHuman
}
class ObIkind {
kind: IKind;
}
class Person {
constructor(public kind: IKind, public name: string) {}
}
class Robot extends ObIkind {
constructor(public kind: IKind, public name: number) {
super();
}
}
class Place extends ObIkind implements Data {
constructor(public kind: IKind, public description: string) {
super();
}
toString() {
return `${this.description} is a ${this.kind}`;
}
}
interface Data {
toString: (x) => string;
kind: IKind;
}
const d1 = new Person(IKind.Human, 'Jim');
const d2 = new Place(IKind.NonHuman, 'Hill');
const d3 = new Robot(IKind.NonHuman, 738191);
const actions = (dispatch: Dispatch, data: Data) => {
switch (dispatch) {
case Dispatch.Reveral:
data.toString();
default:
break;
}
};
わからん
Nimtype
Value = concept v
v.value is bool # bool型のプロパティ`value`を持っている
MinValue = concept v
v is Value
v.minVal is bool # bool型を返すminValメソッドを持っている
MaxValue = concept v
v is Value
v.maxVal is bool
type BoolParam = ref object
value: bool
proc set(self: Value, v: bool): bool =
# following block only compiled when expr is true
when self is MinValue:
if v < self.minVal: return false
when self is MaxValue:
if v > self.maxVal: return false
self.value = v
return true
let b = BoolParam(value: false)
echo b.set(true)
echo b.value
Conceptsを使わずともDuck Typingができる
どんな機能が提供されなければならないかをより明確にしたいときはconceptsを使う
Nimproc energy[PhysicalObject](o: PhysicalObject): float =
0.5 * o.mass * o.speed * o.speed
# プロパティとして,mass,speedを持つ
type SimpleObject = object
mass: float
speed: float
# メソッドとしてmass,speedを持つ
type ComposedObject = object
proc mass(self: ComposedObject): float = 42
proc speed(self: ComposedObject): float = 42
let simple = SimpleObject(mass: 1.0, speed: 5.0)
echo simple.energy # 0.5 * 1.0 * 5.0 * 5.0 = 12.5
let composed = ComposedObject()
echo composed.energy # 0.5 * 42 * 42 * 42 = 37044.0
このコードはGenericsが良い仕事をしているのがわかる
試しに1行目の角括弧を消すとコンパイルエラーになる
動かん
Nimにinterfaceがないならタプル使ってinterfaceっぽっくしようぜ!
利用例 その1
nimtype
Comparable = concept x, y
(x < y) is bool # xとyは比較できる
# 利用例
proc hoge(x,y: Comparable): int =
return y
echo hoge(4, 2)
echo hoge('4', 2) # コンパイルエラー!!
参考
Is there interfaces in NIM language? - Nim forum