如何在 Nim 中存储对类型的引用?

How to store references to types in Nim?

你能在 Nim 中存储一个对象的 type,类似于在 Java 中使用 Class 对象吗?
例如,我想实现这样的目标:

let myType = someObject.type

假设 .type 将 return 对象的 type。如果可能的话,我还想创建可以存储 HashSets 类型的对象,理想情况下使用泛型来限制可以使用的类型:

type System = object
    includes: HashSet[type[Component]]
    excludes: HashSet[type[Component]]

或者这在 Nim 目前是不可能的?

如果你想限制泛型接受的类型,你可以使用类型class。 https://nim-lang.org/docs/manual.html#generics-type-classes

例如:

# Generic type only takes string or int.
type
  Foo[T: string or int] = object
    x: T

var
  a: Foo[string]
  b: Foo[int]

#var c: Foo[float] # Compile error

# Generic proc takes any type of arguments excepts float and tuple.
proc bar[T: not (float or tuple)](x: T) = discard

bar(1)
bar("a")
#bar(1.1)     # Compile error
#bar((1, 1))  # Compile error

Nim 不像 java 那样支持反射。您只能在编译时使用类型进行操作。不过,您可以做的是在编译时将所有需要的类型转换为 id,然后改用 id。现在我们如何生成ID?实际上,nim 有一个使用全局宏数据的简单而巧妙的解决方案。

import macrocache

type TypeID* = uint16

const nextTypeID = CacheCounter("nextTypeID")

converter typeID*(T:typedesc): TypeID =
    const id = nextTypeID.value
    static:
        inc nextTypeID
    return id.TypeID

when isMainModule:
    assert float == 0
    assert float == 0
    assert int == 1
    assert float64 == 0
    assert string == 2
    assert (float, int) == 3

每次为类型编译 typeID 时,它都会将缓存的计数器状态存储在编译主体中的常量中。每次函数编译时,静态块都会 运行,因此每次传递不同的类型时,计数器都会增加。

存储类型

typedesc 类型在运行时不可用,因此无法按原样存储类型。

天真的解决方案是只采用以下类型的字符串表示形式:

let myType = $(myObject.type)

但如果您有类型别名,这可能无法按照您想要的方式工作

type
  A = seq[int]

此处 $A != "seq[int]" 尽管两者在其他方面相同且可互操作,类似地 float64float

https://github.com/yglukhov/variant 已经实现了这些边缘案例,所以让我们利用它:

nimble install variant,那么,大致是:

import variant
let myTypeId = someObject.getTypeId # the hash of a string representation
myTypeSet.incl myTypeId #put it in your hash set

此答案的功能部分到此结束,后面广泛涉及如何在尝试包含不需要的类型时出现静态错误。



限制可以包含的类型

如果您只对限制可继承类型感兴趣,这比您想要限制类型类要容易一些。

import variant,sets
type
  TypeSetBase = HashSet[TypeId]
  TypeSet*[T] = distinct TypeSetBase

proc initTypeSet*[T](): TypeSet[T] =
  TypeSetBase(result).init()

proc incl*[T](ts: var TypeSet[T], x: typedesc[T]) =
  TypeSetBase(result).incl getTypeId(x)

proc contains[T](ts: TypeSet[T],x: typedesc): bool =
  TypeSetBase(ts).contains getTypeId(x)

type
  Foo = object of RootObj
  Bar = object of Foo
  Baz = object of Foo
  Qux = object

var includes = initTypeSet[Foo]()

includes.incl Bar
includes.incl Baz

assert Bar in includes
assert Baz in includes
assert not(Foo in includes)
#includes.incl Qux #static error

对于一般情况,这更难。类型类不会让我们到达那里,因为不能实例化 TypeSet[int | float]

这是我的解决方案,使用宏为我们制作样板文件。这是独立的。

import macros,variant,sets
type TypeSetBase = HashSet[TypeId]

macro TypeSet*(name,cls,rhs:untyped) =
  let tynm = ident("TypeSet_" & cls.repr)
  let initnm = ident("init_" & cls.repr)
  
  result = quote do:
    when not declared(`tynm`):
      type `tynm` = distinct TypeSetBase
      proc `initnm`():`tynm` =
        TypeSetBase(result).init()
      proc incl*(ts: var `tynm`, x:typedesc[`cls`]) =
        TypeSetBase(ts).incl getTypeId(x)
      proc contains*(ts: `tynm`, x:typedesc):bool =
        TypeSetBase(ts).contains getTypeId(x)
    var `name` = `initnm`()
import sugar # just nicer for procs

var x{.TypeSet.}:SomeNumber | proc

x.incl float
x.incl (int)->int
x.incl ()->string

#x.incl string 
# static error:
# type mismatch: got <TypeSet_SomeNumber | proc, type string>

assert float in x
assert ((int)->int) in x
assert (proc():string) in x

这还没有让你找到你的 System 类型,但我现在没时间了。