`Gene` is unboxed. Any `int` behaves like `Gene`. An `array` of `Gene` is 
tightly packed. Any `proc` of `Gene` will instantiate at compile time to the 
concrete types of `Gene`. The `seq[Gene]` or the `table` must contain one 
concrete type. There is zero runtime overhead. You have zero type safety.

`Gene2` is boxed. Any `x` of type `int` will have to be wrapped 
`Gene2(kind:GeneInt, num:x)`. An `array` of `Gene2(kind:GeneInt,...)` is much 
larger than an `array` of `int`. Any `proc` of `Gene2` will need to check the 
`kind` at runtime. The `seq` or the `table` can contain mixed types, `int`, 
`string`, `seq`, or `table` wrapped under `Gene2`. That contributes runtime 
overhead. You have type safety.

There is also one middle ground. You get type safety, without heterogeneity.
    
    
     Nim
    type
      GeneInt = distinct int
      GeneString = distinct string
      GeneSeq = seq[Gene3]
      GeneMap = Table[string, Gene3]
      Gene3 = GeneInt | GeneString | GeneSeq | GeneMap
    
    
    Run

I have the feeling, possibly biased, that you don't really understand what you 
are asking for. It will be really useful if you start with concrete types, and 
write all the `proc` for each type separately. You can design the generic 
interface later by calling concrete `proc`.

TLDR: If you only want a uniform interface, but not type safety, go with 
`Gene`. If, in addition to a uniform interface, you want type safety, go with 
`Gene3`. If you really want heterogeneous arrays and don't care about the 
runtime overhead, go with `Gene2`.

Reply via email to