Is there a syntax that I can copy and update attribute in single expression like 'with' in this code?
type
Customer = object
name: string
age: string
address: string
var c1 = Customer(name: "wk", age: 20, address: "420")
var c2 = with(c1, name: "jw", address: "440")
var c3 = with(c2, age: 30)
Hi @wearetherock, I had a with macro that very close to what you wanted but took blocks. Here it is extended with varargs so your syntax can be used:
import macros
macro with*(value: untyped, updates: varargs[untyped]): untyped =
## Return a copy of `value` with specific field updates.
## ``updates`` expects one or more assignments in the form ``field = value``
## or ``field: value``. Duplicate assignments are not allowed.
##
## Example:
## .. code-block:: nim
## type MyData = object
## a: int
## b: float
## c: string
## var myData = MyData(a: 1, b: 2)
## # Make copies of myData with some fields changed.
## let
## myAdjustment1 = myData.with(b = 5, c: "Adj1")
## myAdjustment2 = myData.with:
## b = 7
## c = "Adj2"
## assert myAdjustment1 == MyData(a: 1, b: 5, c: "Adj1")
## assert myAdjustment2 == MyData(a: 1, b: 7, c: "Adj2")
##
let res = genSym(nskVar, "newVal")
const
allowedNodes = [nnkAsgn, nnkExprEqExpr, nnkExprColonExpr]
unknownMsg = "Expected `field = value` or `field: value`, cannot process node of type "
duplicateMsg = "Duplicate assignment to field: "
var
doUpdates = newStmtList()
fieldsSet: seq[NimNode]
template addAssignment(n: NimNode) =
if n.kind notin allowedNodes: error unknownMsg & $n.kind
if n[0] in fieldsSet: error duplicateMsg & $n.toStrLit
doUpdates.add newAssignment(newDotExpr(res, n[0]), n[1])
fieldsSet.add n[0]
for node in updates:
if node.kind == nnkStmtList:
for asgn in node:
asgn.addAssignment
else:
node.addAssignment
result = quote do:
var `res` = `value`
`doUpdates`
`res`
Essentially, it outputs something like this:
# myVar.with(field1 = value, field2 = value) becomes:
var myVarTmp = myVar
myVarTmp.field1 = value
myVarTmp.field2 = value
myVarTmp # Returned to you
When put through your example:
type
Customer = object
name: string
age: int
address: string
var c1 = Customer(name: "wk", age: 20, address: "420")
var c2 = with(c1, name: "jw", address: "440")
var c3 = with(c2, age: 30)
echo c1 # (name: "wk", age: 20, address: "420")
echo c2 # (name: "jw", age: 20, address: "440")
echo c3 # (name: "jw", age: 30, address: "440")