Hi! I'm beginning to write some templates and I'm just not sure how to properly use them in the right way. Macros might be an option too, but they look much more complicated and I would have no idea how to solve it with macros.
Anyway - I want to have some "callback" function that can be called from somewhere else, maybe even from a C-library. This callback may take multiple mixed parameters, int and string. I could create and hard-code some wrappers to make it fit to the callback if possible, and throw an error if it doesn't fit, but I thought it might be easier if I use some kind of abstraction layer to reduce copy&paste code.
So - I tried to have some kind of template "runWithBody" that might already check specific lengths and may throw exceptions, if something doesn't fit - for example the length. But the parameters "values" aren't injected to the body somehow. Below is a simplified example that already shows my issue.
I'm probably doing something stupid here. I'm just not sure what would be smart in such a case :)
var callback: proc(args: varargs[int]): string # this line shouldn't change
template runWithBody(minLength: int, body: untyped) =
proc localFunc(values: openArray[int]): string =
if values.len >= minLength:
body
else:
raise newException(ServerException, "Called function contains less than " & $minLength & " arguments")
callback = proc(args: varargs[int]): string =
localFunc(args)
runWithBody(2):
result = $(values[0] + values[1]) # here is the problem: Error: undeclared identifier: 'values'
print callback(1, 2, 3) # this line also shouldn't change, if possible.
This is simply caused by template hygiene. In case values was defined in the scope you called runWithBody it would throw an error if values got redefined there. The solution is to mark values as injected, that was Nim makes it visible to the body port of your code:
var callback: proc(args: varargs[int]): string # this line shouldn't change
template runWithBody(minLength: int, body: untyped) =
proc localFunc(values {.inject.}: openArray[int]): string =
if values.len >= minLength:
body
else:
raise newException(ServerException, "Called function contains less than " & $minLength & " arguments")
callback = proc(args: varargs[int]): string =
localFunc(args)
runWithBody(2):
result = $(values[0] + values[1]) # here is the problem: Error: undeclared identifier: 'values'
print callback(1, 2, 3) # this line also shouldn't change, if possible.
The alternative is to mark your entire template as {.dirty.}, but that would make localFunc also visible and would cause an error if you ever called your template twice (because now you would redefine localFunc).
Of course you can also drop the proc localFunc part entirely and just do this (modified slightly so I could run it in the playground without errors):
var callback: proc(args: varargs[int]): string # this line shouldn't change
template runWithBody(minLength: int, body: untyped) =
callback = proc(values {.inject.}: varargs[int]): string =
if values.len >= minLength:
body
else:
raise newException(Exception, "Called function contains less than " & $minLength & " arguments")
runWithBody(2):
result = $(values[0] + values[1]) # here is the problem: Error: undeclared identifier: 'values'
echo callback(1, 2, 3) # this line also shouldn't change, if possible.
Small correction: functions are injected by default, so calling the first template twice will cause a redefinition error for localFunc, even without {.dirty.}.
Dropping the proc localFunc as in the second version is the easiest solution. Another posibility is to put the template body inside a block:, creating a new scope.
Thx! {.inject.} actually makes the sample code running. This helps a lot.
Not sure why it didn't work in my other program earlier... Seems that it could have been an issue as I was using generics too.
Avoiding the localFunc unfortunately isn't an option as I need to get values from JsonNode... I just didn't want to make my sample much more complicate.