Hi,
I have a proc that needs to return many different and unrelated objects (structs) base on internal decisions. I tried model this using SUM return type (A or B). But this does not seem to work as I get a type mismatch on B path.
I know I can model SUM types using object hierarchies, but the types share nothing and the syntax is a bit heavy for my simple usecase
Do procs in Nim support returning SUM types? (A or B)
Ex:
Ret = A or B
let a = test(10) let b = test(-5)
A or B needs to be collapsed to one definite type at compile time. It is typically used for parameter types where the compiler sees which type is given at the calling site. This is not applicable if you want your return values to have different types at runtime.
There are multiple solutions. First solution is derived types:
type
Base = ref object of RootObj
A = ref object of Base
a_value: int
B = ref object of Base
b_value: int
proc test(arg: int): Base =
if arg < 0: return A(a_value: arg)
else: return B(b_value: arg)
let a = test(10)
let b = test(-5)
Another solution is object variants:
type
RetKind = enum
A, B
Ret = object
case kind: RetKind
of A: a_value: int
of B: b_value: int
proc test(arg: int): Ret =
if arg < 0: return Ret(kind: A, a_value: arg)
else: return Ret(kind: B, b_value: arg)
let a = test(10)
let b = test(-5)
Supports, if the sum type can be treated as generic, i.e. if compiler can decide at compile-time for each use of the sum type, which concrete type will it substitute. Like in using such a proc:
proc test(arg: static[int]):Ret =
when arg < 0:
return A(a_value:arg)
else:
return B(b_value:arg)
But the argument, on which the type choice is made, is static, that is, compile-time: really 2 different zero-argument procs will be instantiated by compiler for test(10) and test(-5), and their arguments should be able to be evaluated at compile-time.
Thanks! I tried the "derived" version earlier, but with stack allocated objects, and I did not manage to get dispatch working correctly at callsite. But after changing to ref values, it works. Is ref values required to differentiate sub-types?
type
# If I remove ref from base, A and B
# no output is printed
Base = ref object of RootObj
A = ref object of Base
a_value: int
B = ref object of Base
b_value: int
proc test(arg: int): Base =
if arg < 0: return A(a_value: arg)
else: return B(b_value: arg)
let a = test(10)
if a of A:
echo "Got A ",A(a).a_value
if a of B:
echo "Got B ",B(a).b_value
Ok, so object variants, which does not require allocations?, is preferred in cases like this where we simply want to return different values from a function? If yes, no need to reply :)
Thanks
Generally speaking, whenever you are using if v of SomeType, you are holding it wrong. Polymorphism gives you the power of defining dispatching methods for operations whose implementation differs based on which subtype your variable holds.
If implementing your code with dispatching methods seems clumsy or simply does not fit your coding style, use object variants instead. The question of whether the values should reside on the stack or on the heap is a different one. For example, JsonNode is an object with variants but also a ref type residing on the heap.
Regarding pointers being the only option for polymorphic returns; I guess this depends on the object layout used? For instance, I assume object variant does not need to be same size but still allows polymorphism in return value, simply by reserving space for the biggest variant in the calling stackfram.
Object variants are not polymorphism (at least not in the computer science semantics of the word). But you are right, it works because it reserves the size of the largest possible variant. One obvious difference to polymorphism is that whoever defines the object type has complete control over any variants. With polymorphism, a user of your type can define a new subtype. This is why using if v of SomeType is bad style – you cannot know all derived types, because other code using your code may define new derived types.
Yes, I understand and agree regarding bad style. But my brain is having a hard time letting go of scala and typescript idioms (where pattern matching on return type is common).
In time this problem will be fixed (I hope). Anyway, thanks for your help!