I have this code:
type
Console* = ref object of RootObj
ViewportConsole* = ref object of Console
method draw*[T](self: Console, engine: T) {.base.} =
echo "Ignoring a Console..."
method draw*[T](self: ViewportConsole, engine: T) =
echo "Drawing a ViewportConsole on ", engine, "..."
I store the consoles in a sequence, and iterate through them, like this:
var consoles: seq[Console] = @[]
console.add(new(Console))
consoles.add(new(ViewportConsole))
for console in consoles:
console.draw("blah blah")
The output for this is:
Ignoring a Console...
Ignoring a Console...
When it should be:
Ignoring a Console...
Drawing a ViewportConsole on blah blah...
The last line should call the different draw method for different console types, but it seems to be only calling the base one. It only calls the ViewportConsole implementation if I explicitly cast console.
Am I doing this the wrong way?
That's obviously supposed to work and it's a bug when using methods in combination with generics. Without generics it works fine:
type
Console* = ref object of RootObj
ViewportConsole* = ref object of Console
method draw*(self: Console, engine: string) =
echo "Ignoring a Console..."
method draw*(self: ViewportConsole, engine: string) =
echo "Drawing a ViewportConsole on ", engine, "..."
var consoles: seq[Console] = @[]
consoles.add(new(Console))
consoles.add(new(ViewportConsole))
for console in consoles:
console.draw("blah blah")
I will open a bug report. Edit: https://github.com/nim-lang/Nim/issues/6283
As a workaround you could use a method and a proc together:
type
Console* = ref object of RootObj
ViewportConsole* = ref object of Console
method drawString*(self: Console, engine: string) {.base.} =
echo "Ignoring a Console..."
method drawString*(self: ViewportConsole, engine: string) =
echo "Drawing a ViewportConsole on ", engine, "..."
proc draw*[T](self: Console, engine: T) =
drawString(self, $engine)
var consoles: seq[Console] = @[]
consoles.add(new(Console))
consoles.add(new(ViewportConsole))
for console in consoles:
console.draw("blah blah")
@def: still seems that the sequence plays a role in a way I don't quite understand here, because this works as intended:
type
Console* = ref object of RootObj
ViewportConsole* = ref object of Console
method draw*[T](self: Console, engine: T) {.base.} =
echo "Ignoring a Console..."
method draw*[T](self: ViewportConsole, engine: T) =
echo "Drawing a ViewportConsole on ", engine, "..."
var
a = new(Console)
b = new(ViewportConsole)
a.draw(1)
b.draw(2)
Edit: Oh that's because b is already typed ViewportConsole at compile time here, so that proc instead of method could be used.
Not quite. The type of a variable in your code is indeed Console but the type of b is ViewportConsole. It's a subtle difference: in the seq exemple it's a variable of a type Console which stores a value of ViewportConsole. The difference is:
# in seq example:
consoles[0] of Console # true
consoles[1] of ViewportConsole # true, it is ViewportConsole type value
consoles[0] is Console # true
consoles[1] is ViewportConsole # false, it is Console type variable
# in var example:
a of Console # true
a of ViewportConsole # true
a is Console # true
a is ViewportConsole # true
It's similar to many other languages, some of which reflect the difference using very explicit syntax:
Thx for your answers guys.
@def: Does that mean that in order to make methods work with generics, Nim would have to be able to instantiate methods for generic parameters after dynamic dispatch (at runtime)?
At least Nim would have to instantiate all methods that could be called at runtime.
I see. Calculating the minimal set of necessary method instances at compile time looks hard-to-impossible to me though, at least with multiple-dispatch methods.
I think there were suggestions about removing methods from the language, but I'm not aware what the result was.
AFAIK, araq favors making methods single-dispatch (only the first parameter determines the method instance called at runtime). Should make fixing this easier.
this language claims to be rather minimalistic
does it?
These are two models, inheritance and type classes, which are different answers to the same problem. I can see no good reason to provide both in the same language..
I see one difference: with concepts the type match is an implicit one, the person creating the matching concrete type doesn't even have to now that the concept exists. With inheritance, it is explicit: the creator of a derived type explicitly mentions the base type in the type definition. This implies that he is aware of the semantics that cannot be checked by a compiler. A concept match only checks that, e.g., a certain proc is defined for the matched type. The creator of that proc never promised that this proc will actually do what we think it will.
two interfaces or concepts requiring a routine of the same name and arguments either have to share it
type
A = concept x
p x
B = concept x
q x
C = distinct int
D = distinct int
proc p(v: C) = discard
proc q(v: D) = discard
proc r(v: A) = echo "r for A"
proc r(v: B) = echo "r for B"
var
c = 5.C
d = 7.D
r c # -> r for A
r d # -> r for B
They do not share it. May be I did not understand you correctly...
Please note the biggest problem of inheritance-based interfaces: you cannot add one to a type already in existence when you define the interface
Certainly you cannot; that is the point - not interfaces state, what types satisfy (implement) them, but the other way round, types state, what they implement, and interfaces can only restrict, what types can state that (say, providing signatures for required procs); not satisfying the interface's restrictions results not in rejecting for the type to be considered as being of that interface, but in a compile-time error. E.g., supposing concepts-syntax for interfaces:
type
I = interface x
p(x)
T = distinct int implements I
# p(t: T) = discard
This should result in a compile-time error, unless the last line is uncommented.
And at the same time
type
I = interface x
p(x)
T = distinct int
proc p(t: T) = discard
echo T is I # -> false
, because T here is not intended to be I.