Hello all,
(One week experience of learning Nim)
I would like to take advantages of the static typing that would look a bit like distinct types, but not exactly. I would like the sub-types to be distinct, but each sub-type to be fully equivalent to the base type. For example:
var v:uint32 = 1
type Type1 = uint32 var v1: Type1 = 1
type Type1 = uint32 var v2: Type2 = 1
(v1 == v2) # Currently false. That's good (v == v1) # Currently false. I want true.
Why do I want this? It would enable to use uint32 for different purposes. Distinct types do that well. However, one of my use of the sub-types is, amongst others, as index of arrays. I want to avoid casting/coercing.
Is that achievable?
I apologise. You are right. I completely inverted the results. They are all true.
All true or all false is not what I am looking for though.
Correct. I get the equivalent (if I am not wrong this time) of Haskell's newtype declaration.
I am looking for an half-way street. The benefit of static typing without the hard work of casting/coercion.... Basically I want the compiler to do everything for me without being a pain when I don't want it to....
I tried using 'template' to declare the v1 and v2 declaration but that didn't achieve what I want too.
I appreciate I am asking for something unusual, at least in the sense I have not seen that in any other language I know. If it is not possible in Nim, no worries.
All true or all false is not what I am looking for though.
Do you want to compare types or values?
You can compare values only, when the types are compatible, that is when types are equal or when there is a converter. In your initial post you compared values, that worked because your types were only aliases. But I guess you may want to compare the types?
Maybe some background will help.
To learn Nim, I decided to write an emulator. Obviously an over-ambitious project that will never be finished, but nevertheless seems to me a good use for Nim (if only judging by the Nimes emulator).
I want to index the memory map by a 'memory address' type. That would be a uint32. Obviously, some memory content would also be uint32 (as a value) for time to time, or an address (pointer) from time to time.
I want the type system to keep me on my toes (no mixing of addresses and values), but I want it out of the way when using addresses as an index in the array representing the memory.
I guess that will not work: When your "memory map" is just a plain array of uint32 types, then all entries are equal. That is because in Nim by design all elements of an array have the same type, and more importantly, unit32 is just 32 bit, which is used all for storing numbers, there is no bit free to encode type. In Nim you may use objects of different type, maybe object variants, or refs to various object, which you can store inside an array. But that will consume additional memory. Or you may decide to just use a few bits of your uint32 to indicate type, and remaining bits for the numbers. That is a low level hack.
You may know the keyword distinct, it can be used to create distinct types. But I think it does not help you, as an array can only contain one single type.
Is distinct not what you need?
You can define the needed operations from its base type using borrow pragma so you don't have to cast/coerce every time you use it
Also, I did what @cdome suggested, defining converter s and it works like charm. I have several subtype and using converters to convert back and forth between the base type and its subtype.
As you guessed, the content of the memory map will be variants. The memory will contain uint32 from time to time. But it will also be int32, float32, addresses, and other things. The hardware I am looking at emulating is the OpenGenera lisp machine where memory content was tagged. So it seems that what I was looking for is not possible in Nim (not that I had any expectations in Nim or any other language).
Thanks to all for taking the time to answer.
If the mapping is fixed, you can use subrange types.
Although the base type is same, you can define like
type
Row1 = range[ 0 .. 71 ] # assuming row has 72 bytes
Row2 = range[ 72 .. 143 ]
Maybe like that?
I looked into converters, which I didn't know about. I thought the following might work: make all the type distinct, but automatic conversion to uint32 to remove the 'distinction' between sub-type and base type.
type
I1 = distinct uint32
I2 = distinct uint32
converter toU32 (value: I1): uint32 = result = value.uint32
converter toI32 (value: I1): int32 = result = value.int32
converter toU32 (value: I2): uint32 = result = value.uint32
converter toI32 (value: I2): int32 = result = value.int32
var
v: uint32 = 1
v1: I1 = 1.I1
v2: I2 = 1.I2
if (v1 == v):
echo "1: true"
if (v2 == v):
echo "2: true"
if (v1 == v2):
echo "3: true"
Returns 3 trues. Still no cigar... If all you want is to prevent the equality operator (==), you can do:
type
I1 = distinct uint32
I2 = distinct uint32
converter toU32 (value: I1): uint32 = value.uint32
converter toU32 (value: I2): uint32 = value.uint32
proc `==`(i1: I1, i2: I2): bool {.error: "Thou shall not compare types I1 and I2".}
var
v: uint32 = 1
v1: I1 = 1.I1
v2: I2 = 1.I2
if (v1 == v): # works
echo "1: true"
if (v2 == v): # works
echo "2: true"
if (v1 == v2): # compile error!
echo "3: true"
In my emulator, the address is a distinct range and values are plain values: https://github.com/mratsim/glyph/blob/master/glyph/snes/datatypes.nim.
Also tagged unions are variants in Nim ;).