class A of RootObj : var it : ref B class B of RootObj : var it : ref A
Since my nim coding skills are pretty lame at the moment is there a more capable version of the class macro floating around that will handle this case?
type A = ref object of RootObj it : ref B B = ref object of RootObj it : ref A
but I can't figure out why the following fails.
type A = ref object of RootObj it = initSinglyLinkedList[ref B]() B = ref object of RootObj it = initSinglyLinkedList[ref A]()
Any suggestions?
import lists
type
A = ref object of RootObj
it: SinglyLinkedList[B]
B = ref object of RootObj
it: SinglyLinkedList[A]
proc newA: A =
new result
result.it = initSinglyLinkedList[B]()
proc newB: B =
new result
result.it = initSinglyLinkedList[A]()
I'm not sure what you mean by ref of a ref.
Does that mean that initSinglyLinkedList[B] will only store refernces to B rather than copies of B?
In the past, this has been a big pain point for me as well, as I prefer localizing code around types, and enjoy the simplicity of auto-injected first parameters (both for reading and writing). However, there is good news: there is a better design! I know that may not sounds like good news at first, but let me explain and give some skeleton code you can use (see gist link at bottom).
First, there are two main reasons class macros are good. A) they help localize a type's general procedures, and B) they eliminate having to type and read a first parameter to distinguish procedure type association.
The first reason is actually not ideal in many cases, since you often want to compare one type's structure against another type without having to jump around in the source. Nim's type-sections are a much better way to classify and read types within a module in that aspect. As for the second reason, we can make a different macro to eliminate cruft and associate procedure with a type. Eg:
# `impl`: A macro which auto-injects a `this` param.
# `constructor`: A macro which modifies a proc to work as such, based on
# it's first parameter (which is injected by `impl`).
type Person = object
name: string
age: int
impl Person:
proc new(name:string, age = 0) {.constructor.} =
this.name = name
this.age = age
proc greet =
echo "Hello, I'm ", this.name, "."
echo "I am ", this.age, " years old."
let p = Person.new("Jon", 18)
p.greet()
This means we get the best of both worlds. We can declare types in type-sections for better type-to-type comparison, without sacrificing procedure locality (due to impl's declaration-style syntax) and ease-of-use.
We can also go a step further and extend the impl macro to tie into domain specific interface machines. Eg:
type Button = object
text: string
image: Image
impl Button as GuiItem:
proc hover = ...
proc click = ...
impl Button as ViewItem:
proc draw = ...
proc mask = ...
The impl macro can do much more than simply inject first params, it can check that the provided procedures meet the specific interface's requirements, register them with higher-level state machines, etc.. it's really up to you.
I started making this to replace my existing class macros, but I haven't yet implemented the as Interface stuff fully (and that ties into my own engine anyways). Here is a basic version of the code (which also includes a class macro extending the impl one): Source Code Gist
I'm not sure what you mean by ref of a ref.
You defined A and B as ref objects already. So you can think of them as pointers to the heap. If you make a ref of them again, you have a pointer to a pointer to the heap, which is a useless level of indirection here.
I forgot to mention that the code (as it's written) only really supports 'ref object's, as currently it doesn't consider the need to pass around a 'this' parameter as a 'var T'. It's pretty easy to make that work though (I just haven't fully incorporated it myself), eg:
import macros
# --- --- --- #
proc removePragma(procedure:expr, name:string): bool {.compileTime.} =
# checks if a procedure has a pragma and removes it
var i = 0
for p in procedure.pragma.children:
if p.repr == name:
procedure.pragma.del i
return true
i += 1
# ---
macro implBody(T:expr, body:stmt): stmt {.immediate.} =
# impliments the actual OOP-style statement body
result = newStmtList()
for child in body.children:
case child.kind:
of nnkProcDef:
# if proc is {.pure.} and Type is not ref, don't make this-param 'var'
if T.kind == nnkVarTy and child.removePragma("pure"):
child.params.insert 1, newIdentDefs(ident"this", T[0])
else:
child.params.insert 1, newIdentDefs(ident"this", T)
# add child to body
result.add child
else:
discard
# ---
template impl*(T:expr{nkType}, body:stmt): stmt {.immediate.} =
# creates an OOP-style statement body for a type or ref type
when T is ref:
implBody(T, body)
else:
implBody(var T, body)
# --- ---- --- #
# --- TEST --- #
# --- ---- --- #
type
Particle = object
x, y: float
Person = ref object
name: string
age: int
# ---
impl Particle:
proc place(x, y:float) =
this.x = x # WORKS: `this` is passed as var
this.y = y # ditto
proc print {.pure.} =
this.x = 0 # ERROR: we cannont modify `this` here!
echo this.x # WORKS!
# ---
impl Person:
proc setup(name:string, age = 0) =
this.name = name # WORKS: `this` is ref-type
this.age = age # ditto
proc print {.pure.} = # ERROR: invalid pragma: {.pure.}
this.age += 1 # WORKS!
echo this.age