[BUG] HashMap assignable to TrieMap

I think this should throw an error:

    private var _registry : TrieMap.TrieMap<Types.TokenIndex, Types.AccountIdentifier> = HashMap.fromIter(state._registryState.vals(), 0, ExtCore.TokenIndex.equal, ExtCore.TokenIndex.hash);

@diegop @claudio

Due to structural subtyping, this is allowed if the structure of the two types are compatible.

Yes, it should. Do you have errors above this line that prevents the compiler from checking the error?

1 Like

No errors, everything compiles fine and I can deploy the canister as well.

Paul is right. Both Map happens to have exactly the same type…It is confusing that you might be thinking one data structure and the code is using another one. But from the typing perspective, it is correct.

Although there’s definitely room for improvement in this implementation, I think this might be one of the reasons why it’s preferable to use “exposed” data structures and functions that operate on those data structures instead of class wrappers.

For example, something like StableHashMap/FunctionalStableHashMap.mo at main · canscale/StableHashMap · GitHub

The StableHashMap type exposed here is different than the underlying data structure/size variables in the TrieMap, which were the TrieMap converted from a class to a record + collection of functions, you wouldn’t be able to assign these types interchangeably.

@chenyan it would be nice though if the instance variable var types within classes were compared, and not just the function signatures. Maybe a key word to denote instance variables, which may not be public, but that are part of the underlying class-related data stored within.

1 Like

TBH I find this very odd, even though it’s correct. I almost missed setting the variable to a TrieMap, as I’m heavily relying on the compiler as my best friend and saviour :smiley:

2 Likes

This is probably a good example of why tests are still necessary in typed languages :slightly_smiling_face:

Out of curiosity, how would you test for something like this?

I suppose in this case you can’t since whether it’s a HashMap or TrieMap appears to be an implementation detail.

I should perhaps capitalize on this opportunity to (yet again) shill variants :smile:

The “newtype” pattern exists for situations where the type checker sees types as the same but you’d like to attach some additional meaning to them and get some help from the type system.

For example, this isn’t going to help much:

type Kilometers = Nat;
type Miles = Nat;

With the above we can still easily provide a value representing kilometers somewhere we should be providing a value in miles.

However, we can do the following instead:

type Kilometers = { #km : Nat };
type Miles = { #miles : Nat };

Now we have to tag values with the desired units and the type checker will prevent us from providing incorrect values.

We can even do this:

type Distance = Kilometers or Miles;

I mention this in my talk from Motoko Bootcamp:

So, perhaps you’d prefer to do this for the various Map implementations. e.g.

type HashMap<K, V> = { #hashMap : HashMap<K, V> };
type TrieMap<K, V> = { #trieMap : TrieMap<K, V> };
1 Like

For the same reasons as above, it wouldn’t prevent programming mistakes like this though:

private var _registry
  : TrieMap<Types.TokenIndex, Types.AccountIdentifier>
  = #trieMap HashMap.fromIter(state._registryState.vals(), 0, ExtCore.TokenIndex.equal, ExtCore.TokenIndex.hash);
1 Like