Stable HashMap without need for an upgrade func

Happy new year, Guys.

I ‘d like to discuss about StableHashMap that simplify upgrade steps.

When we use HashMap lib in Motoko, we have to implement these code. Because class variables can not be stable.

    var hp = HashMap.HashMap<Text, Nat>(1, Text.equal, Text.hash);
    stable var entries : [(Text, Nat)] = [];

    system func preupgrade() {
        entries := Iter.toArray(hp.entries());
    };

    system func postupgrade() {
        hp := HashMap.fromIter<Text, Nat>(entries.vals(), 1, Text.equal, Text.hash);
        entries := [];
    };

This way is difficult to understand and tend to make mistakes.

So, I thought much simpler way to upgrade HashMap.

    var hp = HashMap.HashMap<Text, Nat>(1, Text.equal, Text.hash);
    stable var stableVars = hp.exportVars();

    system func preupgrade() {
        stableVars := hp.exportVars();
    };
    system func postupgrade() {
        hp := HashMap.HashMap<Text, Nat>(1, Text.equal, Text.hash);
        hp.importVars(stableVars);
    };


This is implemented by passing the address of table variable inside HashMap class object to the stable variable in main.

If it is possible to share stable name space, we don’t need upgrade methods anymore.
But now, stable variables are bound in compiler, not address. So they can’t pass stable as an argument either.

By changing Motoko’s syntax and allowing stable var namespaces to be shared, we will be released from the hassle of the upgrade method.

Here is a proof of concept.
// GitHub URL

Thanks.

6 Likes

I’m using this in my repo. Thank you. Do you still see this as a good approach?

Just use GitHub - ZhenyaUsenko/motoko-hash-map: Stable hash maps for Motoko and you’ll get 4-11x performance and a stable structure with no need to upgrade.

We’ve been using GitHub - ZhenyaUsenko/motoko-migrations: Sample project structure to implement migrations in motoko as an upgrade pattern and it is working well.

2 Likes

Thanks. To be clear: those are two separate ways of doing it, right? If I use the first repo I don’t need to look into the second one, correct?

No. You would use them in conjunction. You don’t need migrations, but if you ever change something in your hashmap, you need to upgrade all your objects to the new types, and migrations gives you a pattern to do that with.

Perfect. So, as long as the types the hashmap stores don’t change, I don’t need to worry about migrations, not even through updates. Correct?

Actually I’m getting errors trying to use that repo:

Not sure if it’s due to the way I imported it:

// From https://github.com/ZhenyaUsenko/motoko-hash-map/tree/master/src/Map
import Map "Map/optimized";

Also tried

// From https://github.com/ZhenyaUsenko/motoko-hash-map
import Map "motoko-hash-map/src/Map/Map";

with no errors at import but the same errors as above on use.

How do I integrate it to the project so that

import Map "mo:hashmap/Map";

works?

Did you able to make it work the import Map “mo:hashmap/Map”;? if yes, how did you do it?

Yes, I did

import Map "motoko-hash-map/src/Map/Map"; // From https://github.com/ZhenyaUsenko/motoko-hash-map

and just placed the complete directory there.

There’s probably a better way though, using vessel I think, but I’m using mops and they’re mutually incompatible.

1 Like

@ZhenyaUsenko Maybe we should add mops support to this library?

4 Likes

Thank you @blabagastered

I second that, please.

Actually with mops you can install packages from GitHub too How do I enable / install moc? - #8 by ZenVoich
But would love to see it published to mops)

2 Likes

Hello, I just installed the stable hashMap library, in order to use it we just define the let like this:

let map1 = Map.new<Nat, Message>();

Don’t we need to use the stable keyword in front? Thank you guys!

Also is it possible to use this type for a key without receiving the error “shared function has non-shared parameter type”

 type ChatRoom = {
    id: Nat;
    creator: Text;
    messages: Map.Map<Text, Message>;  //Here! is this possible?
  };

I am using a normal HashMap to store “ChatRooms” and each chatroom has its messages. I am having trouble to loop and delete from the Array of messages that every ChatRoom has, which would be the best data structure? Thank you!

 type ChatRoom = {
    id: Nat;
    creator: Text;
    messages: [Message];
  };

  type Message = {
    id: Nat;
    sender: Text;
    timestamp: Time.Time;
    text: Text;
  };

*Edit
I started using .filterEntries()

bufferedMessages.filterEntries(
      func(_, x) = x.id != messageId
    );

Even though I am not sure what is the role of the first parameter in the func, the underscore.

Designates wildcard pattern so that argument will match any type.

Did you ever get an answer to your question about stable keyword? I’m thinking of using this but have some doubt on whether it will behave properly when the map is initialized with Map.new() (whether existing values are preserved on upgrade)