Maybe I've just missed it in the documentation: Is there a way to update a (ref) object by doing something like person["name"] = "John" or set(person, "name", "John"), like with tables? I've quickly tested someting like this with a simple macro:
import macros
macro setKeyByString(ent: ref object | object, field: string, value: untyped): untyped =
var dotExpr = newNimNode(nnkDotExpr)
dotExpr.add(ent)
dotExpr.add(newIdentNode($field))
newAssignment(dotExpr, value)
But I'm kinda worried that I'm reinventing wheel here. Is something like this already in the standard library?
If your string is a compile time constant you're code is from a quick glance only missing one thing. You just need to change the type of field to static[string] and remove the stringification (just field instead of $field).
Though if the string is not known at compile time this is not that easy. In languages where this is possible all objects are just tables (hash maps) in disguise, which isn't the case for Nim.
You can use something like this (which could also be generated using a macro:
proc assign[T](person: var Person, field: string, val: T) =
when T is int:
case field
of "age": person.age = val
else: raiseAssert("cannot assign int field " & field)
elif T is string:
case field
of "name": person.name = val
of "job": person.job = val
else: raiseAssert("cannot assign string field " & field)
Otherwise what exactly do you need this for? There might be a another more idiomatic way for it.
Thanks for your feedback! In hindsight it makes perfect sense that the provided string has to be known at compile time. I think that does not pose a problem for my use case.
My question came up while trying to understand metaprogramming and macros better. In my example above I'm not so much interested in an assignment statement per se but rather if I could do it with other functions. Assignment was just my first test.
I'll give you some context on what I'm trying to achieve. Sorry it's kinda lenghty, feel free to skip over the next three code snippets, if you are in a hurry or not interested in the details.
Currently, I'm experimenting with a tweening library. (Tweening basically means transitioning values smoothly over a given duration.) I've got it working so far but now I'm testing macros to create a nicer API. Essentially, I want to implement a function that takes a ref object, a sequence of keys and a sequence of target values.
I managed to get it working for floats quite nicely, with a syntax close to the example below (inspired by the with function in Kotlin and the Nim macro by Github user zevy). Given a ref object with keys x, y: float, I've come up with a syntax close to this
tween(ent, duration = 0.5):
x >>> 0.5
y >>> 3.5
Here my custom >>> operator translates to a function that does the actual tweening, but for simplicity's sake, you can just think of an assignment that does a little bit extra (logging could be an example).
Next, I also want to support transitioning to other objects without having to implement custom functions for each type of object. For instance I want do do:
tween(ent, duration = 0.5):
pos >>> Vector2(x: 0.5, y: 3.5)
Here the key pos is also a Vector2 and this code is a more concise version of something like this.
tween(ent, duration = 0.5):
pos.x >>> 0.5
pos.y >>> 3.5
Coming back to my original question now. I've been looking at a way to access the keys of an object and transfer their values to another object (be it by plain assignment or another function). I had indeed looked at fieldPairs but only the one argument version. After Araq pointed it out, I looked again if I was missing anything. As it turns out, the two argument version of fieldPairs is sort of what I was trying to do (the calling syntax of the function in next example mirrors ES6 Object.assign):
proc assign[T, S](dst: var T, src: S) =
for name, v1, v2 in fieldPairs(src, dst):
v2 = v1
Instead of v2 = v1 I could theoretically plug in another function (e.g. a function that changes values gradually over time). But fieldPairs only works for tuples and (non ref) objects. With my set macro above from my first post however, I could also do it for ref objects like so:
import macros
# copied from above, modified as suggested by doofenstein
macro set(ent: ref object | object, field: static[string], value: untyped): untyped =
var dotExpr = newNimNode(nnkDotExpr)
dotExpr.add(ent)
dotExpr.add(newIdentNode(field))
newAssignment(dotExpr, value)
proc assign[S](dst: ref object, src: S) =
for key, val in src.fieldPairs:
set(dst, key, val)
So after some thinking and more studying I guess my question is why fieldPairs only works for tuples and plain objects. Was this a deliberate choice because of potential pitfalls that I am not seeing at the moment? And additionally: is there a more idiomatic way to achieve what I am doing in the last example?
You can deref with [] as in
type
Foo = ref object
a, b, c: int
var
x = Foo(a: 1, b: 2, c: 3)
y = new Foo
for name, v1, v2 in fieldPairs(y[], x[]):
v1 = v2
echo y[]
https://play.nim-lang.org/#ix=3z5H
Not sure why it doesn't work on ref Object