I often find myself wanting to modify a local copy of an argument without modifying the value that was passed to the function. In python this is trivial. For example:
def myFunc(arg):
arg = 2*arg
print(arg)
x = 10
myFunc(x)
print(x)
Which prints: > 20 > 10
In nim I would do it this way:
proc myFunc(arg: int) =
let arg = 2*arg
echo arg
var x = 10
myFunc(x)
echo x
I know that I could just use a different name for the local copy but that is a bit annoying, especially in cases in which the input argument name is meaningful (and even exposed in the API as a named argument). Perhaps I am too used to the python way but IMHO using a different name can make the code less clear (not to mention that naming things is often considered one of the hardest problems in computer science).
So, my question is: is this the right way to do this? Is there any problem with shadowing the input argument in this way (other than perhaps style preference)?
Thanks!
I never really have this problem since I either operate on result or create local variables with names that represent my values more accurately.
For example, your code could be rewritten as:
proc myFunc(arg: int): int =
result = 2*arg
let x = 10
echo myFunc(x)
echo x
Actually, I have an additional question. Can that pattern result in an unnecessary copy when used with seqs, for example?:
proc myFunc(arg: seq[int]) =
let arg = arg
Will this result in a copy? This is obviously a super silly example, but imagine a slightly more complex case:
proc myFunc(arg: seq[int]) =
# Ensure that the minimum size of arg is 10
let arg = if arg.len >= 10:
arg
else:
arg & newSeq[int](10 - arg.len)
# Process arg...
Here I'd like to just use arg when its length is 10 or larger, and create a modified copy otherwise. Will this create a copy in all cases?
Sorry if this is obvious!
Yes, it will copy. The copy is subject to "Nim's cursor optimizer" so it can be eliminated. You can also ask for it explicitly:
let arg {.cursor.} = arg
However, in your more complex example not even that can work as a cursor doesn't own its data but your arg & newSeq[int](10 - arg.len) expression allocates memory and so should not be bound to a cursor as it causes a memory leak.
Your example can be written like so:
proc myFuncImpl(arg: seq[int]) = ...
proc myFunc(arg: seq[int]) =
# Ensure that the minimum size of arg is 10
if arg.len >= 10:
myFuncImpl arg
else:
myFuncImpl(arg & newSeq[int](10 - arg.len))
Thank you again Araq. That's clear now.
Is there some way to print the memory allocations that are introduced in some block of code at compilation time? I know about the --expandArc flag but is there something simpler? Or perhaps, could a macro be created that caused compilation to fail if any data allocations or if any data copies where done within a block of code?
I don't disagree with you, in almost all cases this should not matter. I'd just like to be able to tell whether I am accidentally introducing some allocation deep inside a nested loop or something like that, for example.
In a way it'd be more to learn how nim works than anything else. The syntax is so nice and so high level looking that I am often wondering if I am doing something silly by accident. I've heard some "horror stories" about the performance of sequtils for example, so being able to check if some innocuous looking code is introducing memory allocations seemed like a nice idea.