How do i update an existing Trie element with the new version of that element, if the key is not Nat32 but is a custom type?

I’ve been amending the ToDo and superhero examples, and ive been successfully able to call Trie.replace with a new element. However, i am now having issues trying to create a method which will find the existing element in the Trie, update that element, and then replace the element in the Trie.

I’ve tried quite a few different ways and have made changes based on the answer given How to work with type record - #4 by rbole here.

I’ve pasted the full script import List "mo:base/List";import Option "mo:base/Option";import Trie "mo:ba - Pastebin.com here.

But I think the relevant code is below:

  public type MarketId = Nat32;

  private stable var allMarkets : Trie.Trie<MarketId, MarketBook> = Trie.empty();
 
  public func make_trade(tradeSide : Text, tradeMarketId : MarketId, tradeRunner : Text, tradeAmount : Nat32) : async Bool  {
    let orderId = nextOrderId;
    nextOrderId += 1;
    let complete = true;
    let order : Order = { side = tradeSide; market = tradeMarketId; runner = tradeRunner; amount = tradeAmount};
    var market = Trie.find(allMarkets, key(tradeMarketId), Nat32.equal);
    switch (market) {
        case(null) {return false};
        case(?notNullMarket) {
          let updatedMarket = add_trade_market(notNullMarket, order);
          allMarkets := Trie.replace(
            allMarkets,
            key(tradeMarketId),
            Nat32.equal,
            ?updatedMarket,
            ).0;
          return true;
        };
    };
   return true;
  };


  public func add_trade_market(aMarket : MarketBook, anOrder : Order) : async MarketBook {
    var newMarket : MarketBook = {marketName = aMarket.marketName; 
                                status = "open_for_orders"; 
                                runner1 = aMarket.runner1; 
                                runner2 = aMarket.runner2; 
                                orders = List.push<Order>(anOrder, aMarket.orders); 
                                matched = aMarket.matched;  
      };
      return newMarket;
};

If i try and compile the above script i get the following error:

stdin:74.25-79.16: type error [M0096], expression of type
  Trie/42<Nat32, Any> = {
                          #branch : Branch/42<Nat32, Any>;
                          #empty;
                          #leaf : Leaf/42<Nat32, Any>
                        }
cannot produce expected type
  {
    #branch : Branch/42<MarketId/42, MarketBook/42>;
    #empty;
    #leaf : Leaf/42<MarketId/42, MarketBook/42>
  }

My experience with programming has only been with duck typed languages, but i think the above error is saying that i am trying to a replace key value pair Nat32, Any with MarketId, MarketBook. Is that correct?

I’ve tried quite a few different implementations, but they have always created errors.

As stated above, i would like to create a method which will find the existing element in the Trie, update that element, and then replace the element in the Trie.

Any help or advice would be appreciated.

One problem is that add_trade_market returns an async which you don’t await in the other code before adding to the trie.

Try making that function synchronous by removing the async return type and just returning the fresh record.

On mobile so hard to be more elaborate…

If the line numbers of the error match the full listing that you provided (thanks!), and I assume that they do, I think the issue is that the replace operation returns a pair, including what you expected (the new Trie, with the replaced item). It also returns the original value for the key, if it existed. That part is optional.

The type signature for the return type is detailed here:
Trie :: Internet Computer

If you do instead

let (allMarkets, _) = Trie.replace(...what you have there...)

then you’ll get the first projection of that pair, which is the updated Trie.

BTW, if you find this API cumbersome (I do at times), then you may also find the TrieMap object type useful: TrieMap :: Internet Computer

It wraps the Trie type in a more OO-like API, where replace side-effects the map, like the pattern in the code that I see here.

thanks @claudio ,

I was actually just looking at web-development/App.mo at main · DFINITY-Education/web-development · GitHub when you answered.

between that example and your answer i got it working.

I changed the method to a private synchronous method,

  func add_trade_market(aMarket : MarketBook, anOrder : Order) : (MarketBook) {
    {marketName = aMarket.marketName; 
                                status = "open_for_orders"; 
                                runner1 = aMarket.runner1; 
                                runner2 = aMarket.runner2; 
                                orders = List.push<Order>(anOrder, aMarket.orders); 
                                matched = aMarket.matched;  
      }
};

Thanks for your help!

Glad to hear that it’s sorted out.

I meant to say above (perhaps for other readers as well):

For lots of examples of using the TrieMap, check out the way it appears in the CanCan backend definitions and the main actor methods (if you search for .put, you’ll see more instances of the replace pattern that I think you’re going for here.)

Cheers!

thanks @matthewhammer,

now that it compiles, i’ll look into the issue you raised because i thought it just returned the updated Trie.

Yeah I am finding it very cumbersome to work with to be honest. But i have long term goals with Motoko so i am just trying to slowly progress from the tutorials. I hesitant to go much further than the examples too quickly, and become lost.

Once i get a few more methods working in my scripts ill probably reimplement the data structures and classes.

thanks again for your help

1 Like

Makes perfect sense. @claudio and I (and others) are happy to answer future questions as you go.

The OO style maps (like HashMap and TrieMap) may be closest to your expectations. I’d advocate using those in your simple examples, until you need something more.

As you’ll learn, a central challenge in designing canisters is choosing a stable memory representation that is in fact stable (you don’t need to modify it as you change other code ini your Motoko project). Unfortunately, the OO containers can’t serve that purpose, and are at best flexible containers that can be used between upgrades. Hence, we have List and Trie (and flat arrays) and containers with no OO wrapper around them. That’s a longer story that tries to explain why there are so many choices. With time, more best practices should emerge (from conversations like this). Thanks again!

1 Like