You're definitely on the right track here. The effects solution is definitely 
cool, and I'd love to see what we can do with effects once they are no longer 
experimental. In the meantime however we can try to track things in a more 
manual way.

What I typically do when writing Nim macros like this is that I work backwards. 
I start with a question of "how do I want to write my code" which you've 
already done with the `withTransaction` block. Then I think to myself, what 
extra code do I need to generate to make this code work as-is. In your case 
with limdb you would need some way of making a transaction track whether it 
wrote anything or not. Now that we have these pieces we can see that `[]` and 
`[]=` needs to be overloaded in our `withTransaction` block somehow. The 
easiest way to achieve such an overload is of course with a distinct type. So 
if we write a small distinct type around a transaction, along with templates 
for `[]` and `[]=` on this distinct type that writes to a `writeCheck` 
compile-time variable, decasts the distinct, and calls the original procedure 
we should now have some sort of write tracking. Of course the user can still do 
the decasting themselves and run unsafe code, but at that point they can only 
blame themselves if things don't work properly. If you want to give the user 
this flexibility however you can offer procedures to convert the underlying 
transaction object but which requires them to specify if they need it for 
reading or writing purposes and set `writeCheck` accordingly.

As for your code it is simply a matter of type checking. If you change the 
argument to your template to `untyped` it actually works just fine. This is 
because the `static` parts of the code is actually run as the type checking is 
done, so by the time your template is invoked `called` is already set to true. 
If you either change it to `untyped` or if you simply move your `called = 
false` to after your check (to reset it for the next caller) it should work 
fine. Of course you might not want a global static variable so you might end up 
with something like this:
    
    
    template wasItCalled(body: untyped): untyped =
      block:
        var called {.compileTime.} = false
        template foo() {.used.} =
          static:
            called = true
          foo()
        body
        static:
          when called:
            echo "called"
          else:
            echo "not called"
    
    proc foo() =
      echo "Foo"
    
    wasItCalled:
      discard
      # This should echo 'not called'
    
    wasItCalled:
      foo()
      # This sould echo 'called'
    
    
    Run

Or you can do a similar thing with a macro:
    
    
    import macros
    
    macro wasItCalled(body: untyped): untyped =
      let calledSym = genSym(nskVar)
      quote do:
        block:
          var `calledSym` {.compileTime.} = false
          template foo() =
            static: `calledSym` = true
            foo()
          `body`
          when `calledSym`:
            echo "called"
          else:
            echo "not called"
    
    
    proc foo() =
      echo "Foo"
    
    wasItCalled:
      discard
      # This should echo 'not called'
    
    wasItCalled:
      foo()
      # This sould echo 'called'
    
    
    Run

Reply via email to