I'm reading the concept documentation in the devel branch and I noticed the new (I think) descriptions of vtref and vtptr. Are these intended to replace method? I haven't seen these features discussed on the forum yet. From the brief description, it appears that these would provide interfaces, a much requested feature for Nim. If that's the intention, maybe IfcRef/IfcPtr could be used as names?
My initial impression is that I like this a lot, to me it appears much more harmonious with the rest of Nim than multimethods, but that having both would not be a good thing. I've still not written anything using the new features, but I generally avoid method so I'm not the ideal judge.
Here's my two cents:
I basically like the approach in streams with proc members used to form an interface, and this could be adapted to allow static dispatch when available as well by making all derived proc s exported and leveraging overloading.
The advantage of a VTable is that we don't have to waste memory on interface pointers for every procedure, for each instance. I believe the streams implementation does this to allow arbitrary runtime selection of an implementation, more like signals. In real code, I am not sure how much the performance of this pattern is a concern. I tend to over-worry about performance and compromise expressiveness. Nim, as I understand, aims for both so the vtref makes sense to me, because it can remove boilerplate code in these streams -like interfaces.
Oh, and I like the powerful idea of concept, especially as a constraint on generics. I hope it stays in the spec - typeclassing and traditional OO classes are definitely not the same, and can coexist in harmony :)
If performance is your concern, use Nim methods.
I benchmarked them vs procs and closures and I found out that they are only 3 times slower than regular proc:
Don't run it on the website or without -d:release
import times
type FooBase = ref object {.inheritable.}
dummy: int
type Foo{.final.} = ref object of FooBase
value : float32
proc inplace_add_proc(x: var Foo, a: float32) =
x.value += a
proc inplace_add_closure(x: var float32, a: float32) =
# Note that we create a closure inside a critical hot path
# if in your use case you can do it outside of a hot loop it will be much faster
proc add_closure(v: var float32) = v += a
add_closure(x)
method inplace_add_method(x: FooBase, a: float32) {.base.} =
discard
method inplace_add_method(x: Foo, a: float32) =
x.value += a
# Procs serve as a warmup, so they might be a little bit faster in reality.
var bar : Foo
new bar
var start = cpuTime()
for i in 0..<100_000_000:
inplace_add_proc(bar, 1.0f)
echo " Proc with ref object ", cpuTime() - start
var x : float32
start = cpuTime()
for i in 0..<100_000_000:
inplace_add_closure(x, 1.0f)
echo " Closures ", cpuTime() - start
var baz : Foo
new baz
start = cpuTime()
for i in 0..<100_000_000:
inplace_add_method(baz, 1.0f)
echo " Methods ", cpuTime() - start
# Results with -d:release on i5-5257U (dual-core mobile 2.7GHz, turbo 3.1)
# Proc with ref object 0.099993
# Closures 2.708598
# Methods 0.3122219999999998