I’m struggling on how to handle possibly undefined data types. The function getIssue uses Array.find to lookup the desired Issue by comparing issue.id == issueId and returns the object if it exists.
In the function addDebate, I want to append a new type of Debate only in case the Issue given by the id issueId exists.
I’ve tried different approaches, but it looks like I’m missing some fundamental understanding on how to handle ?OptionalTypes in motoko. Any hint is highly appreciated.
actor IssueAssistant {
var issues : [Issue] = [];
var issueId : Nat = 1;
public query func getIssue (issueId : Nat) : async ?Issue {
Array.find<Issue>(issues, func x { x.id == issueId });
};
public func addDebate (issueId : Nat, newThesis : Text) : async () {
var issue = await getIssue(issueId);
let debate = {
id = 1;
thesis = newThesis;
};
switch (issue) {
case null return;
case Issue Array.append<Debate>(issue.debate, [debate]);
// ^^^^^ expected object type, but expression produces type ?Issue/219Motoko
}
};
};
When you’re pattern matching an optional you handle 2 cases. One is null and the other is ?(value) where value now is the non-optional value. So we’d need to change your
switch (issue) {
case null return;
case Issue Array.append<Debate>(issue.debate, [debate]);
}
into
switch (issue) {
case null return;
case (?actualIssue) Array.append<Debate>(actualIssue.debate, [debate]);
}
The second issue is that Array.append returns an Array. It doesn’t modify the array in place, so what you’d need to instead is reassign the value to your issues variable:
switch (issue) {
case null return;
case (?actualIssue) {
issues := Array.append<Debate>(actualIssue.debate, [debate]);
}
}
Now this is a somewhat common pattern. Performing some side-effect based on whether an optional is present or not so the base library has the iterate function to capture that. The final version I’d suggest would be:
You’re trying to reassign actualIssue.debate, but Issue.debate is defined as an immutable variable.
Intuitively one might change Issue.debate to be mutable, but then it’d fail to compile as getIssue above must return an immutable data structure.
Bunch of approaches one could take here. One could create a new Issue and replace the existing one in the issues array.
Alternatively one could make Issue.debate mutable, and make getIssue return an immutable copy thereof.
You could - but then you’d have to make sure that getIssue returns an immutable version thereof. Motoko enforces that any data leaving an actor by means of its public methods must be immutable.
Generally I’d probably go about it a slightly different way - but do take it with a grain of salt, it’s not as if I had years of experience with Motoko.
Leave the issue type immutable as-is
Use a mutable hash map instead of an array for storing issues. This:
Makes the data structure mirror the business logic of the issue’s ID (I assume!) being unique
Provides constant-time lookups of an issue by its ID - as opposed to an array’s O(n)
When adding a debate, create a new issue containing said debate (based on the existing issue), and replace the old issue in the hash map
Edit: Bit unsure about that snippet you just posted. Specifically:
issues := Array.filter<Issue>(issues, func (i : Issue) {i.id == issueId}
This will replace issues with an array containing only the issue you are currently adding a debate for, surely? Ie drop all other issues?
Your example code has me wondering about the bigger design of the data model (involving the types Issue and Debate)? How do these entities relate? (one to one, one to many, many to many? something else?)
I have an ongoing experiment in Motoko where I’m trying to accomodate any answer to the questions above, even if they change over time, as the application evolves. To be as flexible as possible, I’m advocating a general model based on graphs (nodes and edges for data and its relationships):
For your example, I’d use a graph node to represent each Issue and each Debate, and use an edge to relate them, as needed.
Issue and debate should have one to many relationship. The Debate should have two one to many relations to itself to form pros and cons which are opening a next level of debate. The idea is basically to mimic the functionality of https://www.kialo.com/the-existence-of-god-2629
motoko-graph looks very nice. Was thinking a lot about the requirement to build a graphql implementation to make data querying more convenient. Feels very painful at the moment for more complex domain models.