I wanted to share a pattern I've been playing with in some scrap code.
type
Person = object
name:string
age:int
proc val(typ:typedesc[Person], name:string, age:int):Person =
# Possibly complicated initialization logic.
result.name = name
result.age = age
proc new(typ:typedesc[Person], name:string, age:int):ref Person =
result.new()
result[] = typ.val(name, age)
proc main =
let person_ref = Person.new("Joe", 30)
let person_val = Person.val("Joe", 31)
echo person_val
echo person_ref[]
when isMainModule:
main()
As little as it matters I like the feel of Person.new() and Person.val().
Side question: does anyone have a pattern they like for sharing logic between the procs that build objects and their ref counterparts?
I like the pattern. You probably already know it and just wanted to make the example simple, but you can keep the ref version generic:
proc new[T](typ:typedesc[T], name:string, age:int):ref T =
result.new()
result[] = typ.val(name, age)
That's a good point.
I think both cases (generic and specific) have use.
The generic case allows a kind of convention: All types defined by (string,int) can create the appropriate val() function and get the new() function for free.
The specific case allows for customization: A type can create a custom new() and bind it (or not) to a custom val() of their choosing.
I like this, but usually either i need value or ref types, not both.
On the other hand, rather than val i'd use init (i'm sure this was a convention).
And i would like to share a macro for same pattern:
import macros
import tables
macro ctor*(none: untyped): auto =
let args = callsite()
if args[1].kind != nnkProcDef:
error("`ctor` pragma is used only with procedures.")
var prc = args[1]
if $(if prc[0].kind == nnkPostfix: prc[0][1] else: prc[0]) notin ["new", "init"]:
error("Constructor must be named `new` if it returns a ref type or `init` if it returns a value type.")
var params = prc[3]
var resultType = params[0]
var typeParam = newNimNode(nnkIdentDefs).add(
newIdentNode("_"),
newNimNode(nnkBracketExpr).add(
newIdentNode("typedesc"),
resultType.copyNimTree()
),
newNimNode(nnkEmpty)
)
params.insert(1, typeParam)
result = newStmtList(prc)
proc init*[A, B](initial_size=64): Table[A, B] {.ctor, inline.} = init_table[A, B](initial_size)
discard Table[int, int].init()
You delcare init or new proc with ctor pragma and it inserts first typedesc parameter automatically based on return type of the proc. Works with templates and everything.