Solution in moc 0.8.3: let else - "match" and "take" in motoko - do? for variants (was "when")

It would probably be clearer if R1 and R2 were named Ok1 and Ok2.

There’s no “marshaling”. R2 is the Ok type parameter of whatever action Is provided. That’s how in one function it’s Text and another it’s Bool.

1 Like

TIL Result.chain in Motoko - thanks Paul!

1 Like

Monads are a great abstraction for sequential computation with “short-circuiting” :slightly_smiling_face:

2 Likes

You are probably aware of this, and explicitly avoiding it, but the code can be shortened, at the cost of increased nesting, by push the happy path continuations into the case branches, instead of returning to the outermost level:

let v = switch (exp) {
      ... 
     case (?x) { x }
};
decs

can be written as:

switch (exp) {
   ... 
   case (?v) { decs }
}

which avoids introducing and returning temporary x.

In your example, a lot of the returns can now also be elided since the function is about to exit anyway (after each case) so there is no need to return early.

  public shared(msg) func query_gender(user_id : Nat, access_id : Nat) : async Result.Result<Text, Text> {
    switch(users.get(access_id)){
        case(null) { #err("access user not found")};
        case(?foundAccessUser) {
            switch(users.get(user_id)){
                case(null){ #err("user not found")};
                case(?foundUser){
                  foundUser.access_log.add((foundAccessUser.id, Time.now()));
                  return #ok(
                     switch(foundUser.gender){
                        case(#M){"male"};
                        case(#F){"female"};
                        case(#O(val)){val};
                   })
               }
            }
        }
    }
  };

Another trick is to exploit compound patterns, switching once on the result of several computations rather than switching separately on each, though that does involve doing the computations up front rather than on demand.

 // Say the given phase.
  public shared(msg) func is_female(user_id : Nat, access_id : Nat) : async Result.Result<Bool, Text> {
    switch(users.get(access_id), users.get(user_id)){
        case(null, _){ #err("access user not found") };
        case(_, null){ #err("user not found") };
        case(?foundAccessUser, ?foundUser) {
          foundUser.access_log.add((foundAccessUser.id, Time.now()));
          switch(foundUser.gender){
            case(#O(val)){ #err("nyi for custom gender")};
            case(#M){ #ok(false)};
            case(#F){ #ok(true)};
           };
        }
    };
  };
1 Like

Interestingly, to reduce the pain of match (switch),
Rust has if let pat = e {} <else {}> conditional let statements:

(does it have conditional let expressions too?)

Similarly for while loops: while let pat = e {}.

1 Like

In case you previously participated and contributed:

let ?x = getMyNat() else {return #err("I just could not do it")};

let y = x + x;

1 Like