https://play.nim-lang.org/#ix=2EhX
Is there a way to structurally get all the fields from several types into a new type? something like a shallow inheritance?
type A = object
a:int
type B = object
b:int
type ABCombined = A + B # imagined syntax
let abc = ABCombined()
abc.a = 7
abc.b = 90
Indeed, here's a macro I made to do this!
https://gist.github.com/exelotl/1f387c47468d02be4587044ed270c907
Example:
type
Vec2 = object
x, y: float
Player = object
position: Vec2
compose Player, position
var p: Player
p.y += 20
p.x = p.y + 10
echo p.x # 30
echo p.y # 20
Something like this works, not the prettiest in the world though:
import macros
type
A = object
a: int
B = object
b: int
macro combine(name: untyped, x, y: typed): untyped =
result = nnkTypeSection.newTree(nnkTypeDef.newTree(name, newEmptyNode(), getImpl(x)[2]))
for def in getImpl(y)[2][2]:
result[0][2][2].add def
combine(ABType, A, B)
var ab: ABType
ab.a = 100
ab.b = 200
echo ab
Well, already plenty of answers around haha. Here's mine that is pretty close to @PMunch's, but tries to make it look a bit more like something in a type section:
import macros
macro unionType(types: varargs[typed], sec: untyped): untyped =
var typ = nnkRecList.newTree()
for t in types:
let tImpl = t.getImpl()
let fields = tImpl[2][2]
for f in fields:
typ.add f
result = sec.copyNimTree()
doAssert eqIdent(sec[0][0][2], "union"), "RHS has to be `union`!"
# replace the `union` magic word by the combined type
result[0][0][2] = nnkObjectTy.newTree(newEmptyNode(), newEmptyNode(), typ)
type
A = object
a: int
B = object
b: int
C = object
c: float
unionType(A, B, C):
type
ABC = union
var abc = ABC()
abc.a = 7
abc.b = 90
abc.c = 4.2
echo abc
The problem is that one cannot have a macro evaluated on the RHS in a type section where the object is written unfortunately.
Made a slightly prettier version that is pretty much exactly what you wanted:
nim
import macros
macro `+`(x, y: typedesc): untyped =
result = nnkStmtListType.newTree(getImpl(x)[2])
for def in getImpl(y)[2][2]:
result[0][2].add def
type
A = object
a: int
B = object
b: int
ABType = A + B
var ab: ABType
ab.a = 100
ab.b = 200
echo ab
Note: a problem with solutions that copy the fields into the type, is that they don't let you use type ABCombined in a place where A or B is expected.
This means you lose one of the main advantages of composition / inheritance.
This is why I opted for a solution that keeps the sub types as fields of the composed type.
type
Vec2 = object
x, y: float
Player = object
position: Vec2
proc moveBy(a: var Vec2, b: Vec2) =
a.x += b.x
a.y += b.y
compose Player, position
var p: Player
p.position.moveBy(Vec2(x:13, y:14)) # we can still use `position` when we need to
echo p
This would also be a good use case for converters, allowing you to use Player anywhere that a Vec2 is expected:
converter toVec2(p: var Player): var Vec2 = p.position
converter toVec2(p: Player): Vec2 = p.position
p.moveBy(Vec2(x:13, y:14))
You could tweak the compose macro to output these converters automatically if desired.