Search code examples
typesnim-lang

How to store references to types in Nim?


Can you store the type of an object in Nim, similiar to using the Class object in Java?
For example, I would like to achieve something like this:

let myType = someObject.type

Assuming that .type would return the type of the object. If that is possible, I'd also like to create objects that can store HashSets of types, ideally using generics to limit the types that can be used:

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

Or is this something that is currently impossible in Nim?


Solution

  • Storing types

    the typedesc type is not available at runtime, so it's not possible to store a type as is.

    the naive solution would be to just take the string representation of the type:

    let myType = $(myObject.type)
    

    but this might not work the way you want if you have type aliases

    type
      A = seq[int]
    

    here $A != "seq[int]" even though the two are otherwise identical and interoperable, similarly float64 and float

    https://github.com/yglukhov/variant has already implemented these edge cases, so let's leverage that:

    nimble install variant, then, roughly:

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

    This concludes the functional portion of this answer, that which extensively follows deals with how to statically error when trying to include an unwanted type.



    Limiting which types may be included

    If you're only interested in limiting inheritable types this is a bit easier than if you want to limit with typeclasses.

    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
    

    For the general case this is harder. Typeclasses won't get us there, as one can't instantiate a TypeSet[int | float]

    Here's my solution, using a macro to do the boilerplate for us. this is self-contained.

    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
    

    this doesn't get you your System type yet but i'm out of time for the moment.