2

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

v.oddou
  • 6,476
  • 3
  • 32
  • 63

1 Answers1

5

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.

zah
  • 5,314
  • 1
  • 34
  • 31
  • isn't it possible to use `when bodyFinally is nil` or `when compiles(bodyFinally == nil)` ? – v.oddou Apr 03 '18 at 03:06
  • 1
    `nil` is not a type, so it can't be used with the `is` operator. You may find this counter-intuitive, but `compiles` is much more expensive for the compiler than `astToStr` (as it involves executing the entire semantic pass of the compiler, while `astToStr` is a simple traversal of an AST tree. – zah Apr 03 '18 at 08:47
  • very interesting. the `is` of python can do "is Null" or "is not Null" that's why I need to adapt my thinking to what `is` means in Nim. Also thanks for the `astToStr` trick then, this is golden advice. answer accepted – v.oddou Apr 03 '18 at 09:05