Hi all,
I have a Something which takes a specialised thing. I then have a bunch of types that are specialised Something`s. I then want a proc which will return any of the `SpecialisedSomethings.
Code will make it really clear (please forgive syntax errors):
type
Something[T] = object
a: int
special: T
SpecialisedTA = object
c: int
SpecialisedA = Something[SpecialisedTA]
SpecialisedTB = object
d: int
SpecialisedB = Something[SpecialisedTB]
proc doIt(strategy: int): Something =
if (...):
result = SpecialisedTA(a = 10, c = 30)
else:
result = SpecialisedTB(a = 10, d = 10)
let myThing = doIt(...)
The problem is that the proc definition and the let myThing... don't compile (and of course, this is all numpty code to focus on the problem ;-)).
I _think the answer is in https://nim-lang.org/docs/manual.html#type-relations-subtype-relation, and I know when it clicks I will kick myself, but I just can't figure it out.
What am I missing?
Thanks!
The proc needs to return a concrete type, while you're trying to return Something, which is a type class. You'll be better here with either runtime dispatch (ref object hierarchy + methods) or variant objects.
# runtime dispatch
type
Something = object
a: int
special: Special
Special = ref object {.inheritable.}
SpecialisedTA = ref object of Special
c: int
SpecialisedTB = ref object of Special
d: int
method foo(s: Special) {.base.} = assert(false, "Kinda abstract")
method foo(s: SpecialisedTA) = echo "SpecialisedTA"
method foo(s: SpecialisedTB) = echo "SpecialisedTB"
proc doIt(strategy: int): Something =
if (...):
result = Something(a: 10, special: SpecialisedTA(c: 30))
else:
result = Something(a: 10, special: SpecialisedTB(d: 10))
let myThing = doIt(...)
myThing.special.foo()
# variant object
type
SpecialKind = enum
TA
TB
Something = object
a: int
case kind
of TA:
specialA: SpecialisedTA
of TB:
specialB: SpecialisedTB
SpecialisedTA = object
c: int
SpecialisedTB = object
d: int
proc doIt(strategy: int): Something =
if (...):
result = Something(a: 10, kind: TA, specialA: SpecialisedTA(c: 30))
else:
result = Something(a: 10, kind: TB, specialB: SpecialisedTB(d: 10))
let myThing = doIt(...)
case myThing.kind
of TA:
echo "SpecialA: ", myThing.specialA
of TB:
echo "SpecialB: ", myThing.specialB
Are the types known at compile-time or do they depend on runtime data.
Otherwise you can do compile-time dispatch:
type
Something[T] = object
a: int
special: T
SpecialisedTA = object
c: int
SpecialisedA = Something[SpecialisedTA]
SpecialisedTB = object
d: int
SpecialisedB = Something[SpecialisedTB]
Smth = Something[SpecialisedTA] or Something[SpecialisedTB]
proc doIt(strategy: static[int]): Smth =
when strategy == 0:
result = Something[SpecialisedTA](a: 0, special: SpecialisedTA(c: 30))
else:
result = Something[SpecialisedTB](a: 0, special: SpecialisedTB(d: 30))
let myThing = doIt(0)
echo myThing
On a side note, it seems like you are trying to use the strategy pattern and dispatch according to an integer. You should use an enum and case statement instead or an object variant.
In most cases all "patterns" that might be used in OO languages can be replaced by idiomatic Nim code that is in my opinion, more succinct, maintainable and readable.
Therefore, because I want each Specialised Something to have the same set of properties, runtime dispatch is my friend.
Thanks both :-)
Actually, using dynamic dispatch forces a chicken and egg situation, as I want the dynamic dispatch method to construct (from JSON) the thing it is switching on!
The following represents my conundrum:
method foo(s: SpecialisedTA, node: JsonNode) =
s.abc = node["abc"].getStr()
method foo(s: SpecialisedTB, node: JsonNode) =
s.def = node["def"].getStr()
var a: SpecialisedTA
foo(a, jsonNode)
As expected, I am getting compiler cannot prove that 'r' is initialised, which makes sense.
I think my only option is to change the builders from methods to named procs.
I get no compiler error for your example (maybe there is some problem in your full code), just a run-time error from dispatcher, because you didn't allocate the object for a (via new, as the variable is of reference (pointer) type, not object itself). The full code:
type
Something = object
a: int
special: Special
Special = ref object {.inheritable.}
SpecialisedTA = ref object of Special
abc: string
SpecialisedTB = ref object of Special
def: string
import json
method foo(s: Special, node: JsonNode) {.base.} = assert(false, "Kinda abstract")
method foo(s: SpecialisedTA, node: JsonNode) =
s.abc = node["abc"].getStr()
method foo(s: SpecialisedTB, node: JsonNode) =
s.def = node["def"].getStr()
var a = new SpecialisedTA
var jsonNode = parseJSON"""{"abc": "5", "def": "7"}"""
echo jsonNode
foo(a, jsonNode)
echo a.repr
Wrapping your code in posts with "```nim" and "```" makes it highlighted.
runtime dispatch uses dispatch trees which are more performant than the more common indirect branches
Don't quite understand your decision: the types seem to be known at compile time and nothing is faster than compile time dispatch. So, the solution by @mratsim should be fine. Btw., if you want a less restrictive type class to be returned by doIt, try a concept:
type
Something[T] = object
a: int
special: T
SpecialisedTA = object
c: int
SpecialisedTB = object
d: int
Smth = concept s
$s is string
# plus this if needed later:
# s.special is (SpecialisedTA or SpecialisedTB)
proc doIt(strategy: static[int]): Smth =
when strategy == 0:
result = Something[SpecialisedTA](a: 0, special: SpecialisedTA(c: 30))
else:
result = Something[SpecialisedTB](a: 0, special: SpecialisedTB(d: 30))
let myThing = doIt(1)
echo myThing
Hi @gemath. The types are known at compile time, but they are being populated by a JSON file at runtime, so doIt would be called multiple times at runtime and will take in a JsonNode.
I am not challenging your assertion about compile times, so the above might still be possible? I am merely stating my ignorance here :-).
when is resolved at compile-time, at run-time you get just one of two branches, which already knows what field to read from JSON and where to put it. So if you explicitly pass strategy (type disambiguator) as compile-time known (static) argument, a proc with when is the simplest and most performant way.
With those foo 's, in my example above Something type was not used and the variable was of a specialised type, so methods can be replaced with just procs too.
You'll need methods if you want variable of a general (Something) type and no explicit disambiguator:
# using the code from previous example with `foo`'s, except the last paragraph
# prettifying echo'ing
method `$`(s: Special): string {.base.} = assert(false, "Kinda abstract")
method `$`(s: SpecialisedTA): string = "[abc: " & s.abc & "]"
method `$`(s: SpecialisedTB): string = "[def: " & s.def & "]"
proc `$`(s: Something): string = "[a: " & $s.a & ", special: " & $s.special & "]"
var jsonNode = parseJSON"""{"abc": "5", "def": "7"}"""
var a: Something
a.a = 3
a.special = new SpecialisedTA
# `a` can store either `SpecialisedTA` or `SpecialisedTB`,
# and you don't pass any explicit disambiguator to `foo`
foo(a.special, jsonNode)
echo a # => [a: 3, special: [abc: 5]]
a.special = new SpecialisedTB
foo(a.special, jsonNode)
echo a # => [a: 3, special: [def: 7]]
Ah, so strategy could never be static but you would select it at will by passing a SpecialisedTA/B to fill. Took a while, but I think I get it now :o)
I am merely stating my ignorance here :-).
This is Nim, so that's what we're all doing here to a degree, with a few exceptions.