Hi, I've got this code (https://play.nim-lang.org/#ix=3nIN):
type
Token* = ref object of RootRef
position*: int
DigitToken = ref object of Token
value: uint
NumberToken = ref object of Token
value: float
var digits: seq[Token]
digits.add DigitToken(value:1, position:0)
digits.add NumberToken(value:1.2, position:5)
# Casting makes it work
# echo cast[DigitToken](digits[0]).value
# echo digits[0].DigitToken().value
echo digits[0].value
echo digits[1].value
I want to have a sequence with a parent type, so I can access the value, but without casting it every time.
I know that this is OOP way and a type with case of field would be better, but this would be a standard way to do this thing in C# for example.
And the code above gives this error:
/usercode/in.nim(18, 15) Error: undeclared field: 'value' for type in.Token [declared in /usercode/in.nim(2, 3)]
I think I'm missing something, but I don't even know what. Like the .value is resolved based on static type, but it needs to be runtime dispatched?
Oh, you're right! That's what I was missing. C# needs cast too. 😅
Ok, so, can I get the "real" runtime type of this object and cast it programmatically?
Yeah, sure, but sometimes you can use genericity, like in this case. I've checked how C# behaves, and it spits out runtime type (child one).
This code is now more of a thought-experiment, to check if Nim is capable of patterns from OOP, than an actual use-case. I started to hate OOP because of cases like this and overall complication of simple concepts under convoluted terms like polymorphism, dependency injection, interfaces, virtual members, my head is starting to hurt. :D
Have you tried with a value access method?
method getValue(this: Token): int {.base.} =
echo "interface!"
raise
method getValue(this: DigitToken): int {.base.} =
return this.value
etc. Haven't tried it myself and since your return types are different (might want to make sure they all return the same type) it may complicate things. However you should get proper dispatch with methods on ref object.
Hmmm, somewhat yes, this would be it.
https://play.nim-lang.org/#ix=3nJC
type
Token* = ref object of RootRef
position*: int
DigitToken = ref object of Token
value: uint
NumberToken = ref object of Token
value: float
var digits: seq[Token]
digits.add DigitToken(value:1, position:0)
digits.add NumberToken(value:1.2, position:5)
method getValue(this: Token) {.base.} =
echo "interface!"
raise
method getValue(this: DigitToken) =
echo this.value
method getValue(this: NumberToken) =
echo this.value
digits[0].getValue()
digits[1].getValue()
Caveat: return types must be the same for all methods or else base method gets called.
I'm looking for something simpler, like:
let rType = digits[0].getRuntimeType()
echo cast[rType](digits[0]).value
But keeping in mind that methods were designed specifically for runtime type dispatch, probably your solution is The Nim Wayâ„¢.
Do not use cast unless you're doing Quake-fast-sqrt level stuff.
The thing is, you can't make a method that returns the value of each of your objects, because what would be the return type of such a method?
I would just make your methods all return float:
method getValue(this: DigitToken): float =
return float(this.value)
since you don't lose information doing that anyway.
Technically you can do it by using "of" to do a type check:
import sequtils
import tables
type
MyBaseType = ref object of RootObj
TypeA = ref object of MyBaseType
name: string
# method foo(this: MyBaseType) {.base.} =
# echo "Base method called"
# method foo(this: TypeA) =
# echo "TypeA method called. Name is \"", this.name, "\""
proc foo(this: MyBaseType) =
if this of TypeA:
echo "TypeA found. Name is \"", TypeA(this).name, "\""
else:
echo "BaseType found"
proc bar(stuff: MyBaseType) =
stuff.foo()
let base = MyBaseType()
let a = TypeA(name: "abc")
var my_seq: seq[MyBaseType] = toSeq(@[base, a])
for stuff in my_seq:
stuff.bar
var my_table = newTable[string, MyBaseType]()
my_table["base"] = base
my_table["a"] = a
for key in my_table.keys():
my_table[key].bar
Notice that I didn't cast but used a type conversion (TypeA(this)). See https://nim-lang.org/docs/tut2.html
Nim does not store type information at runtime, except for inheritable objects.
So how method would work without it?
it's not the way to do it.
Yes, I know. The correct way would be this:
https://play.nim-lang.org/#ix=3nNT
type
TokenKind = enum
tkDigit,
tkNumber
Token* = ref object of RootRef
case kind: TokenKind
of tkDigit:
valueInt: uint
of tkNumber:
valueFloat: float
position*: int
var digits: seq[Token]
digits.add Token(kind: tkDigit, valueInt:1, position:0)
digits.add Token(kind: tkNumber, valueFloat:1.2, position:5)
echo digits[0].valueInt
echo digits[1].valueFloat
From what I remember, I've been doing something similar in C# (2 years ago or so) and tried it in Nim, and then found out that it didn't work, because value would need to be different label for each kind (Error: attempt to redefine: 'value').
It's nothing important, just a poor attempt at writing lexer, with wrong assumptions etc. Now I'm just curious if my favourite language can stand up to the challenge of bad OOP :D