我如何在 nim 中创建不同过程的查找 table?

How can I create a lookup table of different procedures in nim?

在 python 中,函数是“第一个 class 公民”,可以作为参数传递给 functions/methods。

假设我想在 python 中开始编写一个基本的远程过程调用 (rpc) 库,我可能会首先创建一个将函数名称映射到实际函数对象的字典:

rpc = {}  # string: function


def register_rpc(name, function):
    """ Register a function as a RPC """
    rpc[name] = function


def sum_nums(a, b):
    """ Sum numbers """
    return a + b


register_rpc("sum_nums", sum_nums) # register sum_nums as RPC

print(rpc["sum_nums"](10, 15))

我可以在 Nim 中接近这个。问题是我必须在查找 Table 中明确定义 proc 的参数和参数类型,并且这也必须与 register_rpc 过程的定义相匹配。这是我的半等价 Nim 代码:​​

import tables


var rpc = initTable[string, proc(a, b: int): int]()  # explicitly defined procedure arguments/types


# Procedure arguments/types must match Table's proc definition or else I can't register a RPC
proc register_rpc(p: proc(a, b: int): int, n: string): bool =
    #[ Register a function as a RPC ]#
    rpc[n] = p
    true


# proc definition matches Table/register_rpc
proc sum_nums(a, b: int): int =
    #[ Sum numbers ]#
    a + b


discard register_rpc(sum_nums, "sum_nums")
echo rpc["sum_nums"](10, 15)

有什么方法可以创建一个 register_rpc 过程,而不必显式定义 proc 参数及其类型?我怎样才能让我的 Table 也匹配这个?我昨天问了一个似乎半相关的问题:

但是我无法将 untyped 类型用于 Table

我是否必须重载 register_rpc 过程以涵盖所有不同的 type 场景?如何在不必显式定义 proc arguments/types 的情况下创建查找 table?

nim 不支持这种动态处理方式。 procs 的类型必须在编译时知道,这是事实。虽然你当然可以做一些如此可怕和令人不快的事情,比如:

import tables

var table = initTable[string, pointer]()

table.add("add", cast[pointer](proc(a: int, b: int): int =
  a + b
))

table.add("negate", cast[pointer](proc(a: int): int =
  -a
))

assert cast[proc(a: int, b: int): int {.cdecl.}](table["add"])(10, 20) == 30

但老实说,如果你不小心施放了错误的过程,你就完蛋了。在这种情况下,我们需要安全性,一种区分指针的方法。这就是抽象的用武之地。

import macrocache, options

const counter = CacheCounter("counter")

func typeID(tp: type): int =
  const id = counter.value
  static: counter.inc
  id

{.pragma: dynProc, cdecl, noSideEffect, gcsafe, locks:0.}

type
  DynamicProc* = object
    id: int
    value: pointer

func initDynamicProc*[T](p: T): DynamicProc =
  DynamicProc(id: T.typeID, value: cast[pointer](p))

template ID*(d: DynamicProc): int = d.id

func get*(d: DynamicProc, tp: type): Option[tp] =
  if tp.typeID != d.id:
    none(tp)
  else:
    some(cast[tp](d.value))

var safeTable = initTable[string, DynamicProc]()

safeTable.add("add", initDynamicProc(proc (a, b: int): int {.dynProc.} =
  a + b
))

assert safeTable["add"].get(proc (a, b: int): int {.dynProc.}).get()(10, 20) == 30
assert safeTable["add"].get(proc (b: int) {.dynProc.}) == none(proc (b: int) {.dynProc.}) 

它仍然很冗长,但是这次你保证了它的安全。 dynProc 需要 pragma 来修复类型匹配,因为具有不同 pragma 的类型被认为是不同的类型。