In Nim, how can I define a procedure with an unspecified collection of generic parameters

78 Views Asked by At

I am trying to write a template to define a three procedures which are identical except in the number of generic arguments they take and the number of generic arguments of the types. Is there a way to do this without having to write the same procedure three times?

Specifically it would be nice if the generic parameters could be passed as an argument to the template. For example to generate the code:

proc Test*(self: var Test_Type_Zero) = 
  echo $self.Name

proc Test*[A](self: var Test_Type_One[A]) = 
  echo $self.Name

proc Test*[A, B](self: var Test_Type_Two[A, B]) = 
  echo $self.Name

I would like to be able to write something like the following:

template Test_Template*(vType: untyped, vGen: untyped) =
  proc Test*`vGen`(self: var vType`vGen`) = 
    echo $self.Name

Test_Template(Test_Type_Zero, void)
Test_Template(Test_Type_One, [A])
Test_Template(Test_Type_Two, [A, B])

instead of having to write something like:

template Test_Template_Zero*(vType: untyped) =
  proc Test*(self: var vType) = 
    echo $self.Name

template Test_Template_One*(vType: untyped) =
  proc Test*[A](self: var vType[A]) = 
    echo $self.Name

template Test_Template_Two*(vType: untyped) =
  proc Test*[A, B](self: var vType[A, B]) = 
    echo $self.Name

Test_Template_Zero(Test_Type_Zero)
Test_Template_One(Test_Type_One)
Test_Template_Two(Test_Type_Two)
2

There are 2 best solutions below

2
PMunch On BEST ANSWER

This unfortunately seems to be right at the edge of what is possible with templates. Mostly because of the special case of having no generic arguments. However a macro is able to deal with it easily enough:

import macros

type
  Test_Type_Zero = object
    Name: string
  Test_Type_One[A] = object
    Name: string
  Test_Type_Two[A, B] = object
    Name: string

macro Test_Template*(vType: untyped, vGen: varargs[untyped]): untyped =
  if vGen.len == 0:
    quote do:
      proc Test*(self: var `vType`) = 
        echo $self.Name
  else:
    quote do:
      proc Test*[`vGen`](self: var `vType`[`vGen`]) = 
        echo $self.Name

Test_Template(Test_Type_Zero)
Test_Template(Test_Type_One, A)
Test_Template(Test_Type_Two, A, B)

Note that I switched the syntax for Test_Template to just list the arguments instead of requiring them to be in a bracket. This just makes the macro a bit easier, but you could certainly make vGen take an untyped argument and parse out the elements you need from the bracket element you would get with your initial syntax.

1
shirleyquirk On

To resolve the issue you mentioned in that comment, i first had to echo result.treeRepr in that macro to see what the macro was outputting. quote do was interpreting vType[vGen] as a typed proc call to [], not as a generic type.

To fix that, you need to manually construct the untyped ast you want, which is BracketExpr( ident"vType", ident"A",...)

macro Test_Template*(vType: untyped, vGen: varargs[untyped]): untyped =
  result = if vGen.len == 0:
    quote do:
      proc Test*(self: var `vType`) = 
        echo $self.Name
      proc Test2*(self: var `vType`) =
        echo $self.Name, "2"
  else:
    let typ = nnkBracketExpr.newTree(vType)
    for t in vGen:
      typ.add t
    quote do:
      proc Test*[`vGen`](self: var `typ`) = 
        echo $self.Name
      proc Test2*[`vGen`](self: var `typ`) =
        echo $self.Name, "2"
  #echo result.treeRepr # for debugging