Florian Schrofner
Florian Schrofner

Reputation: 193

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?

Upvotes: 3

Views: 1111

Answers (3)

shirleyquirk
shirleyquirk

Reputation: 1598

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.

Upvotes: 1

Jakub D&#243;ka
Jakub D&#243;ka

Reputation: 2625

Nim does not support reflection like java does. You can manipulate with types only on compile time. What you can do though is convert all needed types to ids at compile times and use ids instead. Now how do we generate ids? Actually nim has wery simple and neat solution for this that uses global macro data.

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

Each time typeID is compiled for a type it stores cached counter state in a constant within the compiled body. Static block will run each time function compiles so each time you pass a different type, counter increases.

Upvotes: 2

demotomohiro
demotomohiro

Reputation: 11

If you want to limit types generics takes, you can use type class. https://nim-lang.org/docs/manual.html#generics-type-classes

For example:

# 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

Upvotes: 1

Related Questions