NimのConcept
Genericsの型パラメータに制約を与えるイメージ
例: Comparableconceptを定義
code:nim
type
Comparable = concept x, y
(x < y) is bool
Comparableは、int等と同様に一種の型として利用できる
Comparableな型同士は<によって比較することができることを示す
Comparableな型は、「<」で比較可能ならば、intでもstringでも何でも良い
利用例
code:nim
proc 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を定義する
code:nim
type
Mul = concept x
x * x
乗算できるような型ならMulに適用できる
普通にGenericsで関数の型を定義した場合に、かなり制約が弱まってしまう
code:nim
proc mulT(arr: seqT): T = foldl(arr, a * b)
これはseq[T]型の値を引数に取るが、これだけ見ると、seq[string]もseq[int]も許容されてしまう
実際にはstring * stringという演算はできないので、これは型安全ではない
code:nim
このエラーは型検査時ではなく、実行時に起きている
と思ったが、コンパイルエラーになった。
どういう仕組みだmrsekut.icon
なのでconceptを使う
↑genericsでもエラーになったので、ここは「なので」ではないmrsekut.icon
code:nim
proc hoge(arr: Mul): Mul =
foldl(arr, a * b)
char型はMul制約を満たしていないのでエラーになる
これは理想mrsekut.icon
いろいろな定義例
xは+で演算できる制約を示す
code:nim
type
Addable = concept x
x + x
code:nim
Bowable = 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について
]
code: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 ")
# ...うまく実装できなかった
code: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と何が違うのか
動く例
code:Nim
type
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メソッド(?)を持っていないのでコンパイルエラーになる
途中
code:Nim
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 toStringT(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
code:TypeScript
enum 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;
}
};
わからん
code:Nim
type
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を使う
code:Nim
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
code:nim
type
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