Hi,
Nim has a wonderful feature of the sideEffect tracking and I know one can annotate the proc with noSideEffect pragma to enforce no side effects constraint.
In my case proc is not annotated, but I still would like to know whether particular function has side effects or not. Mainly to know if it is safe to incrementalize the calculation and it includes memoization.
proc p1(a, b: int): int =
result = 2 * a + b
proc p2(a, b: int): int =
echo "AAAA"
result = b
macro hasSideEffect(procName: typed): bool =
... # not clear how to implement
hasSideEffect(p1) # returns compile time false
hasSideEffect(p2) # returns compile time true
Thanks Krux02,
Feels like I am getting close:
import macros
proc clean(a, b: int) : int =
result = 2 * a + b
macro hasNoSideEffects(p : typed) : bool =
var procDef = getImpl(p.symbol)
addPragma(procDef, newIdentNode("noSideEffect")) # commenting this line has no effect, compiles() will return false anyway
result = newCall(newIdentNode("compiles"), procDef)
echo treeRepr(result)
echo hasNoSideEffects(clean)
The following code returns false all the time, I can't make compiles() to work on proc definitions
Something like this?
import macros
proc clean(a, b: int) : int =
result = 2 * a + b
proc dirty(a,b:int): int =
echo "dirty"
3
macro hasNoSideEffects(p : typed) : untyped =
result = newCall("compiles", newNimNode(nnkProcDef))
let frm = p.symbol.getImpl
var i = 0
# frm has an additional result node at the end, this ugly loop
# is to construct our procDef without altering the original proc.
while i < frm.len-1:
if i == frm.len-1: break
result[1].add(frm[i])
inc i
addPragma(result[1], newIdentNode("noSideEffect"))
echo hasNoSideEffects(clean) # true
echo hasNoSideEffects(dirty) # false
As NimNodes are ref types, I don't think you should change the ones returned from getImpl directly ( I may be wrong though ).
( There are probably better ways to get all childs except the last, but I can't seem to find them. )
When dealing with macros, it's usually useful to compare the ast you generate with the ast printed by dumpTree, eg:
import macros
dumpTree:
proc clean(a, b: int) : int {.noSideEffect.} =
result = 2 * a + b
The following works:
macro hasNoSideEffects(p : typed) : bool =
var procDef = getImpl(p.symbol)
addPragma(procDef, newIdentNode("noSideEffect"))
result = newCall(newIdentNode("compiles"), parseStmt(repr(procDef)))
Still I think it would more convenient to have a standard function in macros module that will expose tags and effects of the procs.