Search code examples
templatesnim-langdefault-arguments

default arguments to template's expressions


If I want to make a template that can accept 2 untyped arguments, and pass them through do notation, when I omit the second do I'd like to have a way to specify a fallback in the form of a parameter's default value. as such:

template tpl(x: bool, body: untyped, bodyFinally: untyped): void =
  if x: body
  else: bodyFinally

#call site:
var r: int
tpl(true) do:
  r = 2
do:
  raise newException(Exception, "")

This works, but that:

template tpl(x: bool, body: untyped, bodyFinally: untyped = discard): void =
  # same rest

Error: expression expected, but found 'keyword discard'

The default value is not accepted, and the message is weird, discard is an expression isn't it.

Attmpts to workaround:

template tpl(x: bool, body: untyped, bodyFinally: untyped = proc()): void =
  # same rest

Then we get:

Error: expression 'proc ()' is of type 'type proc (){.closure.}' and has to be discarded

I'm moderately ready to accept that, even though I find this discard requirement uselessly pedant, and a nuisance of the language, since it's forcing us into uncomfortable gymnastics in generic code like here.

let's edit again:

template tpl(x: bool, body: untyped, bodyFinally: untyped = proc()): void =
  if x: body
  else: discard bodyFinally

And now the result is:

Error: internal error: expr(nkProcTy); unknown node kind


Solution

  • It's true that there is no way to describe a block of code as a default parameter value. There are two possible work-arounds for this:

    1) You can simulate the default values through an overload:

    template tpl(x: bool, body: untyped, bodyFinally: untyped) =
      if x: body
      else: bodyFinally
    
    template tpl(x: bool, body: untyped): void =
      tpl(x) do:
        body
      do:
        discard
    

    Another shorter version of the second overload would be this:

    template tpl(x: bool, body: untyped): void =
      tpl(x, body, (discard))
    

    2) You can use a default value like nil that can be detected inside the template:

    import macros
    
    template tpl(x: bool, body: untyped, bodyFinally: untyped = nil) =
      if x:
        body
      else:
        when astToStr(bodyFinally) == "nil":
          discard
        else:
          bodyFinally
    

    Please note that I had to use astToStr, because it won't be possible to compare bodyFinally to nil when the user supplies a non-default value.