genericsnim-lang

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


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)

Solution

  • 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.