Using HashMap as a key/value store for Nat/CustomType

Hello! Just trying out Motoko for the first time, and I’m a little stuck on how to implement a HashMap properly as a key/value store. I tried to use an example from a different HashMap post in this forum but I don’t think it’s quite applicable to my use case. I’d like to index all Topics I’m trying to persist by their “primary key”, a Nat, like so:

import H "mo:base/HashMap";
import Hash "mo:base/Hash";
import Prim "mo:prim";
import types "types";
import utils "utils";

type Topic = types.Topic;

actor Board {
    let eq: (Nat,Nat) ->Bool = func(x, y) { x == y };
    let keyHash: (Nat) -> Hash.Hash = func(x) { Prim.natToWord32 x }
    var topics = H.HashMap<Nat, Topic>(8, eq, keyHash);
    var nextId : Nat = 1;

    ...

    public func addTopic (title : Text, description : Text) : async () {
        utils.add(topics, nextId, title, description);
        nextId += 1;
        topics
    };
};

where a Topic just looks like:

type Topic = {
  id: Nat;
  title: Text;
  description: Text;
}

I think I’m just not quite grokking how to use the type system properly or what interface I need to implement on Topic or otherwise to make this work… Thanks in advance!

Welcome! Just to start, returning topics from addTopic there is probably (hopefully) giving a compiler error? So you can remove that line. You likely don’t need topics to be var either.

If this is still giving you issues, maybe try just using topics.put() directly in the addTopic method for now and go from there…

Also you can just replace keyHash with the Hash module’s Hash.hash function too since it has the same signature, I was being a bit explicit in the example you found. So:

let topics = H.HashMap<Nat, Topic>(8, eq, Hash.hash);
2 Likes

Thanks for this! I will give it a try and see how it works

Hah yeah on returning topics out of that function, you’re definitely right that I have the wrong output signature :man_facepalming: I’m sure that compiler error would have popped up if it hadn’t triggered sooner on my construction of the HashMap–should have vetted my example code a little more before posting :stuck_out_tongue:

1 Like

Hmmm…I’m still missing something with what the compiler is trying to tell me…

import H "mo:base/HashMap";
import Hash "mo:base/Hash";
import types "types";

type Topic = types.Topic;

actor Board {
    let eq: (Nat, Nat) -> Bool = func(x, y) { x == y };
    let topics: H.HashMap<Nat, Topic> = H.HashMap<Nat, Topic>(8, eq, Hash.hash);
    var nextId : Nat = 1;

    public query func getTopics () : async H.HashMap<Nat, Topic> {
        topics
    };
    
    ...

and im getting the error:
(line 14 is the getTopics function signature)

src/message_board/main.mo:14.38-14.65: type error, shared function has non-shared return type
  HashMap<Nat, Topic/1> = {delete : Nat -> (); entries : () -> Iter/1<(Nat, Topic/1)>; get : Nat -> ?Topic/1; put : (Nat, Topic/1) -> (); remove : Nat -> ?Topic/1; replace : (Nat, Topic/1) -> ?Topic/1; size : () -> Nat}
type
  HashMap<Nat, Topic/1> = {delete : Nat -> (); entries : () -> Iter/1<(Nat, Topic/1)>; get : Nat -> ?Topic/1; put : (Nat, Topic/1) -> (); remove : Nat -> ?Topic/1; replace : (Nat, Topic/1) -> ?Topic/1; size : () -> Nat}
is or contains non-shared type
  Nat -> ()

I’m confused what it means by “is or contains non-shared type”…where does it contain something that takes a Nat and returns nothing?

Any public methods in this actor can be called externally by other canisters and you can’t pass mutable types between canisters. The topics HashMap works with mutable types internally.

If you want to return all topics then you could have getTopics create an array of type [Topic] by iterating through the hashmap (HashMap has an entries() method for this, eg. for (entry in topics.entries())... ) and return this array instead.

1 Like

Ah! This makes a lot of sense! I knew I was missing something, just didn’t realize it was the programming paradigm, not just the type system :man_facepalming: Thanks for taking the time to explain :pray:

FWIW I was trying to go down the road of returning a key/value structure to my FE canister b/c with a large dataset that would be better than looping over an array to find a specific record…is there a better, immutable key/value-like object that would fit this use case? At this point totally theoretical, just looking to better understand what good patterns look like in this programming paradigm :slight_smile:

No prob. You could take a look at Paul Liu’s reversi game https://github.com/ninegua/reversi and see how he uses types to create immutable views on the data, then freezes his data structures to them for returning. This is a good approach for more complex types; it would inherently use iteration again but this is de rigour really.

1 Like

Thanks! Yeah I think I was trying to be too fancy–good to know that just standard iteration over an array or similar pattern is the best practice. I’m all in on convention and consistency, so iteration it is :+1:

3 Likes