0

Would it be possible to automate writing the implementation for remote function?

If written by hand it would look like code below. Having both function declaration and implementation is important.

proc rcall*[A, B, R](fn: string, a: A, b: B, _: type[R]): R =
  echo (fn, a, b)

proc multiply(a, b: int): int

proc multiply(a, b: int): int =
  rcall("multiply", a, b, int)

I would like to automate it and write it as

proc rcall*[A, B, R](fn: string, a: A, b: B, _: type[R]): R =
  echo (fn, a, b)

proc multiply(a, b: int): int

remotefn multiply

And the remotefn macro should look at function definition and generate its implementation as rcall("multiply", a, b, int), is that possible?

Alex Craft
  • 13,598
  • 11
  • 69
  • 133

1 Answers1

3

Would it be possible to automate writing the implementation ...

Yes, it would be possible to automate implementation of just about anything using nim macros that take typed argument (and then getTypeImpl)

import std/macros

macro dumpImpl(arg: typed): untyped =
  echo arg.getTypeImpl().treeRepr()
  
  
proc rcall*[A, B, R](fn: string, a: A, b: B, _: type[R]): R =
  echo (fn, a, b)

proc multiply(a, b: int): int

proc multiply(a, b: int): int =
  rcall("multiply", a, b, int)
  
  
dumpImpl multiply

Shows that multiply type (function signature) has the following structure:

ProcTy
  FormalParams
    Sym "int"
    IdentDefs
      Sym "a"
      Sym "int"
      Empty
    IdentDefs
      Sym "b"
      Sym "int"
      Empty
  Empty

Although it is important to keep in mind that overloaded procedures cannot be easily resolved based only on name (because, well, there are many implementations). The most obvious choice would be to still use typed argument, but pass some parameter to disambiguate the function call

import std/macros

macro dumpImpl(arg: typed): untyped =
  echo arg.treeRepr()
  
  
proc overload(a: string) = discard
proc overload(a: int) = discard


dumpImpl overload
# ClosedSymChoice - lists all possible overloads with their respective symbols
#   Sym "overload"
#   Sym "overload"

dumpImpl overload("123")
#Call
#  Sym "overload"
#  StrLit "123"

dumpImpl overload(123)
#Call
#  Sym "overload"
#  IntLit 123

As a small (personal) side note - when you are talking about nim macros the question should mostly be not "if this is even possible?" but rather "what is the most optimal way to do this?". It might require some knowledge of macro tricks, but it is possible to implement almost anything.

EDIT1 (add implementation code example, reply to question in comments):

import std/[macros]

proc rcall*[A, B, R](fn: string, a: A, b: B, _: type[R]): R =
  echo (fn, a, b)

macro remotefn(fn: typed) =
  let fname = fn.str_val()
  # `quote do` generates hygienic identifiers - i.e. all new
  # variables/functions introduced by it are unique and not visible in
  # global. To fix this you need to explicitly create identifier for your
  # procedure using `ident`

  let
    multId = ident("multiply") # < Function name identifier

    # Same goes for variables - nim does have a name-based overloading, so
    # you need to make sure function arguments use the same identifiers as
    # the original one
    aId = ident("a")
    bId = ident("b")

  result = quote do:
    proc `multId`(`aId`, `bId`: int): int =
      rcall(`fname`, 1, 1, int)

  echo result.toStrLit()

proc multiply(a, b: int): int
remotefn multiply
haxscramper
  • 775
  • 7
  • 17
  • Thanks! As for overloaded functions, it would be ok to don't allow overloaded functions to be remote functions. Or maybe append arg types to its name in the string key. – Alex Craft Apr 18 '21 at 15:48
  • But can the macro generate the implementation itself when only the definition of the function available? I generated the implementation, but it still complains that proc doesn't have the implementation https://play.nim-lang.org/#ix=2Wx8 – Alex Craft Apr 18 '21 at 16:13
  • 1
    @AlexCraft updated my answer with implementation example - too small to fit into the comment section. – haxscramper Apr 20 '21 at 06:31