After working with Motoko for a while I figured I would just make a list of my pain points and potential changes that would exist in my perfect world. If anyone knows alternatives that exist for the following, let me know below
- Problem - Error Propagation
The number one issue that im running into is dealing with stopping code evaluation and returning an#error
or continuing on with eval. My code is full of this:
let value = switch(doSomething(...)) {
case (#error(e)) return #error(e);
case (#ok(v)) v;
}
Potential solution:
Have a built in Result<T, E> like Rust and handle propagation like null propagation with do ? {}
let result : Result<T, E> = do E {
let value1 : T = doSomething(...)*;
let value2 : T = doSomething(...)*;
value2;
}
...
public func doSomething(...) : Result<T, E> {
...
}
- Problem - Multi/nested inline function calls
A huge problem with readability and nesting is the need to perform multiple function calls on a value. Since motoko isnāt very object oriented, it requires many very verbose inline function calls
Example:
let value : [Text] = Iter.toArray(Iter.sort(Iter.fromArray(Array.map([....], func (c) = ...))));
And that doesnāt include all the generic type names and function
Usually I just split these up into their own variables/lines to make it readable
Potential solution: Pipeline operators
let value : [Text] = [....]
|> Array.map func(c) = ....
|> Iter.fromArray
|> Iter.sort
|> Iter.toArray;
- Subtyping with pattern matching
This is one I didnāt expect and is new to me with structural typing. I have run into this issue and I know others have as well.
Usually ill have a Supertype that adds additional functionality on type of an existing type. When this happens I want to handle the supertypes cases and the ALL of the subtypes. Given this example:
public type SubType = {
#one;
#two;
};
public type SuperType = {
#three;
};
switch (superType) {
case (#three) processThree();
case (#two) processSubType(#two);
case (#one) processSubType(#one);
};
public func processSubType(subType : SubType) {
...
};
I want it to be less redundant like:
switch (superType) {
case (#three) processThree();
// Remaining cases have to be the subtype
case (subType) processSubType(subType);
};
- Problem - Clunky string concat and value stringification(?)
Its just annoying to write out strings with#
and no interpolation
let value = "This is some " # someToTextFunc(v) # " text that im writing and took me " # Nat.toText(x) # " seconds to come up with";
Solution: ?
I donāt have a good solution to making stringification better because of structural typing makes it hard to know how to format the value. An option is to make anything that is not Text default to whatever debug_show
does, but I feel like that might be dangerous and make it easy to make mistakes or not understand how its formatting them. But at least with interpolation that seems more straight forward
let value = $"This is some {someToTextFunc(v)} text that im writing and took me {Nat.toText(x)} seconds to come up with"
- Problem - Inline functions arenāt quite there/Type inference problems
A lot of the time I just want to do something like a simple map like:
let v : [Nat] = [1, 2, 3];
Array.map(v, func(x) = x + 1);
But the code above doesnāt work because cannot infer type of variable
is an error. So either the param types/return types have to be defined in the function or on the Array.map. This becomes more and more of a problem with longer type names and more parameters. It seems like it should have enough information to infer, but it isnt able to.
Also it helped that i found the func(...) = ...
syntax vs a normal func but its not quite there.