diff --git a/.claude/upstream.md b/.claude/upstream.md index d9237af2..67792629 100644 --- a/.claude/upstream.md +++ b/.claude/upstream.md @@ -10,9 +10,9 @@ Upstream file paths and the tracking model (release-tag vs commit) are listed pe - **Upstream:** https://github.com/caffeinelabs/skills - **Tracking model:** commit-based (this repo has no releases/tags). Watch for changes to the skill folder between the pinned commit and `main`; the per-skill `version:` frontmatter field is a secondary signal. -- **Commit:** 9274f9bb5d34db77c29304ea32ec5ad7bdc3d6fc -- **Upstream version:** 0.1.8 (skill frontmatter `version:`) -- **Last synced:** 2026-08-24 +- **Commit:** 38a113689f275bb2efe5fa87e4b151c4a8899e8d +- **Upstream version:** 0.2.0 (skill frontmatter `version:`) +- **Last synced:** 2026-08-31 - **Upstream files:** - `skills/writing-motoko/SKILL.md` - `skills/writing-motoko/api-reference.md → references/api-reference.md` @@ -23,7 +23,7 @@ Upstream file paths and the tracking model (release-tag vs commit) are listed pe - `skills/writing-motoko/references/reserved-keywords.md → references/reserved-keywords.md` - `skills/writing-motoko/references/type-conversions.md → references/type-conversions.md` - **icskills-owned sections (do not overwrite from upstream):** - - **Frontmatter (entire block):** upstream ships `version:`, an object `compatibility:` (`toolchain.moc`/`mops.core`/`mops` CLI major), and `caffeineai-subscription:`, and no `metadata:` block. We replace it with our schema: owned `description` (tuned for repo-wide trigger evals), `license: Apache-2.0`, string `compatibility` (`moc >= 1.11.2, core >= 2.5.0, mops >= 3.0.0`), and `metadata.title`/`category`. + - **Frontmatter (entire block):** upstream ships `version:`, an object `compatibility:` (`toolchain.moc`/`mops.core`/`mops` CLI major), and `caffeineai-subscription:`, and no `metadata:` block. We replace it with our schema: owned `description` (tuned for repo-wide trigger evals), `license: Apache-2.0`, string `compatibility` (`moc >= 1.11.2, core >= 2.6.0, mops >= 3.0.0`), and `metadata.title`/`category`. - **mops docs link → `mops-cli` skill:** the body's `https://docs.mops.one/` reference is rewritten to "Load the `mops-cli` skill …". Do not restore the external link on sync. - **`## Additional Resources` → `## Additional References`** (section renamed), plus an extra `- **mops tooling**: Load \`mops-cli\` …` bullet not in upstream. - **Reference-file paths:** upstream keeps `api-reference.md`/`examples.md` at the skill root; icskills places all non-SKILL files under `references/`, so intra-skill links are rewritten to `references/…`. @@ -34,9 +34,9 @@ Upstream file paths and the tracking model (release-tag vs commit) are listed pe - **Upstream:** https://github.com/caffeinelabs/skills - **Tracking model:** commit-based (no releases/tags). Same as `writing-motoko`. -- **Commit:** 9274f9bb5d34db77c29304ea32ec5ad7bdc3d6fc +- **Commit:** 38a113689f275bb2efe5fa87e4b151c4a8899e8d - **Upstream version:** 0.2.2 (skill frontmatter `version:`, unchanged — no content changes in this sync) -- **Last synced:** 2026-08-24 +- **Last synced:** 2026-08-31 - **Upstream files:** - `skills/migrating-motoko-actors/SKILL.md` - `skills/migrating-motoko-actors/examples.md → references/examples.md` @@ -51,9 +51,9 @@ Upstream file paths and the tracking model (release-tag vs commit) are listed pe - **Upstream:** https://github.com/caffeinelabs/skills - **Tracking model:** commit-based (no releases/tags). Same as `writing-motoko`. -- **Commit:** 9274f9bb5d34db77c29304ea32ec5ad7bdc3d6fc +- **Commit:** 38a113689f275bb2efe5fa87e4b151c4a8899e8d - **Upstream version:** 0.1.3 (skill frontmatter `version:`, unchanged — no content changes in this sync) -- **Last synced:** 2026-08-24 +- **Last synced:** 2026-08-31 - **Upstream file:** `skills/troubleshooting-motoko-migrations/SKILL.md` - **icskills-owned sections (do not overwrite from upstream):** - **Frontmatter (entire block):** same transform as the other two. Body is otherwise 1:1 with upstream (the `## Related skills` heading is kept as-is). @@ -64,10 +64,9 @@ Upstream file paths and the tracking model (release-tag vs commit) are listed pe - **Upstream:** https://github.com/caffeinelabs/skills - **Tracking model:** commit-based (no releases/tags). Same as `writing-motoko`. -- **Commit:** 90a2433578d0ea53fdbce3f51fb1dd3b8145ac04 +- **Commit:** 38a113689f275bb2efe5fa87e4b151c4a8899e8d - **Upstream version:** 0.1.0 (skill frontmatter `version:`) -- **Last synced:** 2026-08-28 -- **Pin note:** added at `90a2433` while the other three `caffeinelabs/skills` entries were still pinned at `9274f9b` (the skill did not exist there). Pins realign on the next full `caffeinelabs/skills` sync — until then, a sync issue may re-show this skill's content as newly added; verify against the local file instead of re-applying. +- **Last synced:** 2026-08-31 - **Upstream files:** - `skills/reviewing-motoko/SKILL.md` - `skills/reviewing-motoko/references/state-and-persistence.md → references/state-and-persistence.md` diff --git a/evaluations/writing-motoko.json b/evaluations/writing-motoko.json index 9f13568f..e0d6058d 100644 --- a/evaluations/writing-motoko.json +++ b/evaluations/writing-motoko.json @@ -236,6 +236,37 @@ "Mentions `persistent actor { ... }` as the per-actor alternative if the flag can't be set", "Does NOT suggest adding a `stable` keyword to fix M0220" ] + }, + { + "name": "Arrays have contains for membership", + "prompt": "In Motoko with mo:core, write a single expression that checks whether the `[Text]` array `tags` contains the exact string \"urgent\". Just the expression, no surrounding function.", + "expected_behaviors": [ + "Uses `tags.contains(\"urgent\")` — `[T]` arrays DO have `contains` in mo:core", + "Passes only the element — `equal` is an implicit argument, so does NOT pass `Text.equal` explicitly", + "Does NOT claim arrays have no `contains`", + "Does NOT fall back to `tags.indexOf(\"urgent\") != null` or `tags.any(func t = t == \"urgent\")` as the primary answer" + ] + }, + { + "name": "Numeric conversion uses toX on the source value", + "prompt": "In Motoko with mo:core, `sum` and `count` are both `Nat`. Write the expression computing their average as a `Float`, plus the import it needs. No surrounding function, no deploy steps.", + "expected_behaviors": [ + "Uses receiver conversions on the source values: `sum.toFloat() / count.toFloat()`", + "Does NOT use deprecated `Module.fromX` calls such as `Float.fromInt(...)`, `Float.fromInt64(...)`, or `Nat64.fromNat(...)`", + "Does NOT chain through the sized numeric modules (`Nat64` / `Int64`) to reach `Float`", + "Imports `mo:core/Nat` (the module supplying `Nat.toFloat`), not `Float`/`Int64`/`Nat64` for the conversion" + ] + }, + { + "name": "Result error type is a variant, not Text", + "prompt": "Write the Motoko error type and the update-method signature for `book(roomId : Nat)`, which can fail because the room is unknown, the slot is already taken, or the caller is not authorized. Just the type and the signature — no body, no deploy steps.", + "expected_behaviors": [ + "Returns `async Result.Result` using `mo:core/Result`", + "Declares the error as a variant with one tag per failure (e.g. `#unknownRoom : Nat`, `#slotTaken`, `#notAuthorized`)", + "Does NOT use `Result` or any `Text` error as the discriminator", + "Does NOT propose trapping (`Runtime.trap`) for these caller-fixable failures", + "Uses `public shared ({ caller }) func` since authorization is one of the failure cases" + ] } ], "trigger_evals": { diff --git a/skills/writing-motoko/SKILL.md b/skills/writing-motoko/SKILL.md index 8df02753..207cdf66 100644 --- a/skills/writing-motoko/SKILL.md +++ b/skills/writing-motoko/SKILL.md @@ -2,7 +2,7 @@ name: writing-motoko description: "Motoko language pitfalls, modern syntax, and architecture patterns for the Internet Computer. Covers persistent actors, stable types, mo:core standard library, dot notation, mixins, and common compilation errors. Use when writing Motoko canister code, fixing Motoko compiler errors, or generating Motoko actors. Do NOT use for deployment, icp.yaml, or CLI commands; for design review or audit of existing Motoko code, load reviewing-motoko instead." license: Apache-2.0 -compatibility: "moc >= 1.11.2, core >= 2.5.0, mops >= 3.0.0" +compatibility: "moc >= 1.11.2, core >= 2.6.0, mops >= 3.0.0" metadata: title: Writing Motoko category: Motoko @@ -30,13 +30,13 @@ Motoko is an under-represented language for the Internet Computer Protocol, so y **ALWAYS use:** -- `mo:core` library version 2.5.0+ (compiler `moc` 1.11.2+) +- `mo:core` library version 2.6.0+ (compiler `moc` 1.11.2+) - Contextual dot notation -- `list.add(item)`, `map.get(key)` - Null coalesce `??` for unwrap-or-default and unwrap-or-trap (`opt ?? default`, `opt ?? Runtime.trap(...)`) -- prefer over a two-arm `switch` on `?T` (requires `moc >= 1.7.0`) - Plain `break` / `continue` to exit or skip a loop iteration -- they work inside `for`, `while`, and `loop` just like in other languages - Enhanced orthogonal persistence (state persists without `stable` keyword) - Principled Motoko Architecture -- `types.mo` (types), `lib/` (domain logic), `mixins/` (API endpoints), `main.mo` (composition root, NO public methods) -- **API reference for uncertain APIs**: Use [api-reference.md](references/api-reference.md) to verify exact method signatures when you are about to use an unfamiliar `mo:core` API or when a compile diagnostic points at an API mismatch. Do NOT guess API shapes — a targeted lookup of a symbol you are unsure about is always worth the step; skipping it to save steps ships hallucinated APIs and costs far more in compile repair. +- **API reference for uncertain APIs**: Use [api-reference.md](references/api-reference.md) to verify exact method signatures when you are about to use an unfamiliar `mo:core` API or when a compile diagnostic points at an API mismatch. It lists only non-deprecated APIs — a symbol that is not there should not be written. Do NOT guess API shapes — a targeted lookup of a symbol you are unsure about is always worth the step; skipping it to save steps ships hallucinated APIs and costs far more in compile repair. **When encountering compilation errors:** Re-check [api-reference.md](references/api-reference.md) for exact method signatures. @@ -65,21 +65,6 @@ All configuration is in `mops.toml`. Load the `mops-cli` skill for `mops.toml` c If `mops check --fix` fails: read stderr first. Do NOT call `moc` directly. Fix `.mo` source and rerun the check. -### Build feedback - -When building through Caffeine, enable complete subprocess output with `CAFFEINE_VERBOSE=1 caffeine build`; successful subprocess warnings are otherwise hidden. Hosted Caffeine sandboxes set this environment variable globally. - -- **`MOPS-WASM-COMPLEXITY`**: Focus on the canister, function, severity, limit usage, primary contributors, result, and suggested correction. Refactor the implicated function according to its largest contributors. This check is advisory; PocketIC is authoritative. -- **`MOPS-CHECK-DEPLOY-SKIPPED`**: Focus on the canister and compatibility diagnostic. The canister was not tested, so NEVER treat this as deployment success. -- **PocketIC failure**: Focus on the canister, descriptive message, semantic error code, measured values, and suggested correction. - - For `CanisterInvalidWasm`, use the descriptive message to identify the exact validation failure. - - For `CanisterWasmMemoryLimitExceeded`, compare peak usage with the configured limit and reduce or relocate the implicated allocations. - - For traps or Candid failures, inspect the trap details or generated constructor interface. - -**RULE:** For unfamiliar deployment failures, follow the documentation URL in the diagnostic before changing code. Do not guess when the descriptive message is unclear. - -Caffeine may repeat failed subprocess output. Deduplicate diagnostics, fix each distinct issue, and rerun the relevant check. - ## Modern Motoko Features ### Contextual Dot Notation @@ -110,7 +95,7 @@ Principal.equal(a, b) // OK ``` -**Prefer `equal` / `compare` over `==`.** `==` is compiler-generated structural equality and exists only for **shared** types, so one `var` field takes a record out of shared and `==` stops compiling (M0060). Use `==` only for the numeric primitives that have no receiver form: `Nat`, `Int`, `Float`, and the sized int types declare `equal(x, y)` without a `self` parameter, so `myNat.equal(other)` fails with M0070 and `a == b` is the right call. Other receiver methods on those types (`myNat.toText()`) are fine. +**`equal` / `compare` vs `==`.** Collections take `equal` and `compare` as implicit arguments, so those are the functions to write for your own records and variants. `==` is compiler-generated structural equality and exists only for **shared** types — one `var` field takes a record out of shared and `==` stops compiling (M0060) — so do not build record comparisons on it. Comparing primitives and shared fields directly with `==` is fine, and on `Nat`, `Int`, `Float`, and the sized int types it is the only form: those declare `equal(x, y)` without a `self` parameter, so `myNat.equal(other)` fails with M0070. Other receiver methods on those types (`myNat.toText()`) are fine. Your own records and variants get nothing derived — a record `compare` must be an explicit function, and custom variants need both `equal` and `compare` written out. See [references/equality.md](references/equality.md). @@ -257,6 +242,7 @@ note: Did you mean to import mo:core/Int or mo:core/Nat? Do **not** pass the comparator explicitly when it can be inferred; that is M0237, which `mops check --fix` removes: ```motoko +let ages = Map.empty(); // Text.compare resolved at add, from the imported Text ages.add("Alice", 30); // CORRECT ages.add(Text.compare, "Alice", 30); // WRONG (M0237) ``` @@ -264,6 +250,7 @@ ages.add(Text.compare, "Alice", 30); // WRONG (M0237) A custom key type works the same way — give its module a `compare` and it is inferred: ```motoko +type Point = { x : Nat; y : Nat }; module Point { public func compare(a : Point, b : Point) : Order.Order { ... }; }; @@ -382,7 +369,7 @@ Core collections (`List.List`, `Map.Map`, `Set.Set`, `Queue.Queue - After `let snapshot = list.toArray()`, only use array operations whose exact signatures are shown here or verified in the API reference — with receiver dot notation: `snapshot.filter(pred)`, `snapshot.map(mapper)`, `snapshot.sort(comparator)`, `snapshot.concat([item])`. Do not call those as module functions such as `Array.filter(snapshot, pred)` or `Array.append(snapshot, [item])`. - If a value is an array (`[T]`) or came from `.toArray()` / `.filter(...)`, then `.map(...)` already returns an array; do not append `.toArray()` to that array-map result. (`List.List.map(...)` returns a `List`, so it still needs `.toArray()` when the caller expects an array.) - Arrays DO support predicate search: `.find(predicate) : ?T`, `.findIndex(predicate) : ?Nat`, `.any(predicate)`, and `.all(predicate)` are all in `mo:core/Array` (see the API reference). The JS spellings `.some(...)` / `.every(...)` do not exist — use `.any` / `.all`. -- Arrays have NO `.contains(...)`. Test membership with `.indexOf(element) != null` or a predicate: +- Arrays DO have `.contains(element)` (`equal` is implicit, so pass only the element). Reach for `.indexOf(element)` when you need the position — it returns `?Nat`, so keep the option and use it; and for `.any(pred)` when membership is decided by a predicate rather than equality: ```motoko // Overlap between two tag arrays @@ -501,7 +488,7 @@ all.map(func todo = { ... }); - **`contains(element)`** -- equality check on `List`/`Set`/etc. Does NOT take a predicate. - **`find(predicate)`** -- predicate search on `List.List` and `[T]`. Returns `?T`. -- `[T]` arrays have no `contains` at all — use `.indexOf(element) != null` or `.any(func x = x == element)` for membership. +- Both `List.List` and `[T]` have `contains`. Use `.any(func x = ...)` when the test is a predicate, not equality. ```motoko numbers.contains(3); // equal inferred from the imported Nat @@ -634,6 +621,63 @@ switch (todos.find(func todo = todo.id == targetId)) { }; ``` +## Error Handling: `Result` + +Use `mo:core/Result` to return a failure a caller can act on. `Result` is `{ #ok : Ok; #err : Err }`, so it is a shared type and crosses the API boundary as Candid — no wrapper needed. + +**Pick the return type by what the failure means:** + +| The call can fail because… | Return | +| --------------------------------------------------- | ---------------------- | +| the caller did something the caller can fix | `Result` | +| the thing simply is not there, and that is normal | `?T` | +| an invariant this code is responsible for is broken | trap (`Runtime.trap`) | + +```motoko +import Result "mo:core/Result"; + +public type BookingError = { + #slotTaken : { until : Time.Time }; + #notAuthorized; + #unknownRoom : Nat; +}; + +public shared ({ caller }) func book(roomId : Nat, at : Time.Time) : async Result.Result { + // ... +}; +``` + +**Never launder an error into `Text`.** `Result` forces every caller — including the frontend — to string-match to tell "slot taken" from "not authorized". Make `Err` a variant; put the data each failure needs inside its own tag. A `Text` payload is fine *inside* a tag when it is a message for a human, not a discriminator. + +**Do not trap on caller error.** A trap rolls back the whole message and reaches the frontend as an opaque reject — the caller cannot branch on it and the user gets no actionable message. Reserve traps for "this cannot happen" (see `?? Runtime.trap(...)` above). + +**Chain, do not nest.** `mapOk`, `mapErr`, and `chain` take `self`, so they are dot notation like every other self-parameter API and a pipeline stays flat. `Result.fromOption` has no `self` — it is the module-call bridge from `?T` at the edge where absence becomes a caller-visible error. + +```motoko project=result-chain filepath=src/backend/main.mo +import Result "mo:core/Result"; +import Map "mo:core/Map"; +import Nat "mo:core/Nat"; + +actor { + type Room = { id : Nat; name : Text }; + type RoomView = { name : Text }; + type BookingError = { #unknownRoom : Nat; #notAuthorized }; + + let rooms : Map.Map; + + func toView(room : Room) : RoomView = { name = room.name }; + func reserve(room : Room) : Result.Result = #ok(room); + + public query func book(roomId : Nat) : async Result.Result { + Result.fromOption(rooms.get(roomId), #unknownRoom(roomId)) + .chain(func room = reserve(room)) + .mapOk(toView); + }; +}; +``` + +Use `switch` on `#ok` / `#err` when the arms do different work; do not write `isOk`/`isErr` followed by an unwrap — that discards the payload the type was carrying. + ## Common Patterns ### Module with Self Pattern @@ -718,19 +762,25 @@ transient let cache = Map.empty(); // derived; rebuilt after each ## Numeric Conversion Hygiene -Treat deprecation warnings as failures. Do not write `Float.fromInt(...)` in new code; `mops check --fix` reports it as deprecated. When averaging `Nat` totals into a `Float`, import the required namespaces and use the current conversion chain: +Treat deprecation warnings as failures. **Every conversion is spelled `to`, on the source value** — never a `Module.fromX` call, and never a chain through the sized numeric modules. A conversion is one receiver call: ```motoko -import Float "mo:core/Float"; -import Int64 "mo:core/Int64"; -import Nat64 "mo:core/Nat64"; +import Nat "mo:core/Nat"; -let numerator = Float.fromInt64(Int64.fromNat64(Nat64.fromNat(sum))); -let denominator = Float.fromInt64(Int64.fromNat64(Nat64.fromNat(count))); -numerator / denominator +let average = sum.toFloat() / count.toFloat(); // Nat.toFloat ``` -If a conversion differs from this pattern, verify the exact `mo:core` signature before writing it. Do not guess conversion names such as `Int.fromNat` or `Int64.fromNat`. +| Instead of | write | +| ------------------------- | ----------------- | +| `Float.fromInt(i)` | `i.toFloat()` | +| `Float.fromInt64(i)` | `i.toFloat()` | +| `Nat.fromNat64(n)` | `n.toNat()` | +| `Int.fromNat(n)` | `n.toInt()` | +| `Nat64.fromNat(n)` | `n.toNat64()` | +| `Blob.fromArray(bytes)` | `bytes.toBlob()` | +| `Iter.fromArray(a)` | `a.values()` | + +A conversion that needs several hops is a sign the wrong function was picked: verify the exact `mo:core` signature in [api-reference.md](references/api-reference.md), which lists only non-deprecated APIs. ## Security and Authorization @@ -750,7 +800,7 @@ Attaching cycles to an inter-canister call (`await (with cycles = ...) `) | `You can use the dot notation ... contains` | Wrong Text contains shape | `text.toLower().contains(#text term)` | | `operator may trap for inferred type Nat` | Potentially unsafe Nat math | Avoid `Nat` subtraction; use bounds/loops | | `Int cannot produce expected type Nat` | Int/Nat mismatch | `.toNat()` | -| `field fromInt is deprecated` | Deprecated Float conversion | `Float.fromInt64(Int64.fromNat64(Nat64.fromNat(n)))` | +| `field fromX is deprecated` | Deprecated `fromX` conversion | The `toX` counterpart on the source value: `n.toFloat()`, `bytes.toBlob()` | | `syntax error, unexpected token '.'` | Missing parens | `#text (searchTerm.toLower())` | | `syntax error, unexpected token ','` | Missing parens in for | `for ((key, value) in map.entries())` | | `Compatibility error [M0170]` | Missing migration | Load `migrating-motoko-actors` | @@ -775,7 +825,7 @@ Attaching cycles to an inter-canister call (`await (with cycles = ...) `) | `unexpected token ';'` in function call | Semicolon after func literal | Remove `;` before `)` | | `unbound variable X` | Missing import | `import X "mo:core/X"` | | `M0098` no best choice for type param | Generic needs explicit types | `list.map(...)` | -| `M0096` on `contains` callback | Predicate passed to contains | Use `find(pred) != null`; on `[T]`, `.any(pred)` or `.indexOf(e) != null` | +| `M0096` on `contains` callback | Predicate passed to contains | `contains` takes an element; for a predicate use `.any(pred)` for a Bool, `.find(pred)` for the element | | `M0009` import file does not exist | Wrong path | Relative, no `.mo` extension | | `M0244 variable ... is never reassigned` | Unneeded `var` binding | Use `let` unless reassigned with `:=` | diff --git a/skills/writing-motoko/references/api-reference.md b/skills/writing-motoko/references/api-reference.md index c73a34d0..33592990 100644 --- a/skills/writing-motoko/references/api-reference.md +++ b/skills/writing-motoko/references/api-reference.md @@ -4,6 +4,9 @@ Complete API signatures from `mo:core` library. Use this as a reference when using contextual dot notation. +Generated from motoko-core's API lock by `scripts/generate-motoko-api-reference.mjs`. +Deprecated APIs are omitted — if a symbol is not here, do not write it. + ## Array - `public func all(self : [T], predicate : T -> Bool) : Bool` @@ -11,6 +14,7 @@ Use this as a reference when using contextual dot notation. - `public func binarySearch(self : [T], compare : (implicit : (T, T) -> Order.Order), element : T) : { #found : Nat; #insertionIndex : Nat }` - `public func compare(self : [T], other : [T], compare : (implicit : (T, T) -> Order.Order)) : Order.Order` - `public func concat(self : [T], other : [T]) : [T]` +- `public func contains(self : [T], equal : (implicit : (T, T) -> Bool), element : T) : Bool` - `public func empty() : [T]` - `public func enumerate(self : [T]) : Types.Iter<(Nat, T)>` - `public func equal(self : [T], other : [T], equal : (implicit : (T, T) -> Bool)) : Bool` @@ -23,8 +27,6 @@ Use this as a reference when using contextual dot notation. - `public func foldLeft(self : [T], base : A, combine : (A, T) -> A) : A` - `public func foldRight(self : [T], base : A, combine : (T, A) -> A) : A` - `public func forEach(self : [T], f : T -> ())` -- `public func fromIter(iter : Types.Iter) : [T]` -- `public func fromVarArray(varArray : [var T]) : [T]` - `public func indexOf(self : [T], equal : (implicit : (T, T) -> Bool), element : T) : ?Nat` - `public func isEmpty(self : [T]) : Bool` - `public func isSorted(self : [T], compare : (implicit : (T, T) -> Order.Order)) : Bool` @@ -47,19 +49,121 @@ Use this as a reference when using contextual dot notation. - `public func toVarArray(self : [T]) : [var T]` - `public func values(self : [T]) : Types.Iter` - `public let tabulate : (size : Nat, generator : Nat -> T) -> [T]` +- `public let toBlob : (self : [Nat8]) -> Blob` + +## Blob + +- `public func compare(self : Blob, other : Blob) : Order.Order` +- `public func empty() : Blob` +- `public func equal(self : Blob, other : Blob) : Bool` +- `public func greater(self : Blob, other : Blob) : Bool` +- `public func greaterOrEqual(self : Blob, other : Blob) : Bool` +- `public func isEmpty(self : Blob) : Bool` +- `public func less(self : Blob, other : Blob) : Bool` +- `public func lessOrEqual(self : Blob, other : Blob) : Bool` +- `public func notEqual(self : Blob, other : Blob) : Bool` +- `public func size(self : Blob) : Nat` +- `public let hash : (self : Blob) -> Types.Hash` +- `public let toArray : (self : Blob) -> [Nat8]` +- `public let toVarArray : (self : Blob) -> [var Nat8]` +- `public type Blob` + +## Bool + +- `public func allValues() : Iter.Iter` +- `public func compare(self : Bool, other : Bool) : Order.Order` +- `public func equal(self : Bool, other : Bool) : Bool` +- `public func logicalAnd(self : Bool, other : Bool) : Bool` +- `public func logicalNot(self : Bool) : Bool` +- `public func logicalOr(self : Bool, other : Bool) : Bool` +- `public func logicalXor(self : Bool, other : Bool) : Bool` +- `public func toText(self : Bool) : Text` +- `public type Bool` + +## Char + +- `public func compare(self : Char, other : Char) : { #less; #equal; #greater }` +- `public func equal(self : Char, other : Char) : Bool` +- `public func greater(self : Char, other : Char) : Bool` +- `public func greaterOrEqual(self : Char, other : Char) : Bool` +- `public func isAlphabetic(self : Char) : Bool` +- `public func isDigit(self : Char) : Bool` +- `public func less(self : Char, other : Char) : Bool` +- `public func lessOrEqual(self : Char, other : Char) : Bool` +- `public func notEqual(self : Char, other : Char) : Bool` +- `public let isLower : (self : Char) -> Bool` +- `public let isUpper : (self : Char) -> Bool` +- `public let isWhitespace : (self : Char) -> Bool` +- `public let toNat32 : (self : Char) -> Nat32` +- `public let toText : (self : Char) -> Text` +- `public type Char` ## Debug - `public func todo() : None` - `public let print : (text : Text) -> ()` +## Error + +- `public func isCleanReject(self : Error) : Bool` +- `public func isRetryPossible(self : Error) : Bool` +- `public let code : (self : Error) -> ErrorCode` +- `public let message : (self : Error) -> Text` +- `public let reject : (message : Text) -> Error` +- `public type Error` +- `public type ErrorCode` + +## Float + +- `public func add(x : Float, y : Float) : Float` +- `public func compare(x : Float, y : Float) : Order.Order` +- `public func div(x : Float, y : Float) : Float` +- `public func equal(x : Float, y : Float, epsilon : Float) : Bool` +- `public func format(self : Float, fmt : { #fix : Nat8; #exp : Nat8; #gen : Nat8; #exact }) : Text` +- `public func greater(x : Float, y : Float) : Bool` +- `public func greaterOrEqual(x : Float, y : Float) : Bool` +- `public func isNaN(self : Float) : Bool` +- `public func less(x : Float, y : Float) : Bool` +- `public func lessOrEqual(x : Float, y : Float) : Bool` +- `public func mul(x : Float, y : Float) : Float` +- `public func neg(x : Float) : Float` +- `public func notEqual(x : Float, y : Float, epsilon : Float) : Bool` +- `public func pow(x : Float, y : Float) : Float` +- `public func rem(x : Float, y : Float) : Float` +- `public func sub(x : Float, y : Float) : Float` +- `public let abs : (x : Float) -> Float` +- `public let arccos : (x : Float) -> Float` +- `public let arcsin : (x : Float) -> Float` +- `public let arctan : (x : Float) -> Float` +- `public let arctan2 : (y : Float, x : Float) -> Float` +- `public let ceil : (x : Float) -> Float` +- `public let copySign : (x : Float, y : Float) -> Float` +- `public let cos : (x : Float) -> Float` +- `public let e : Float` +- `public let exp : (x : Float) -> Float` +- `public let floor : (x : Float) -> Float` +- `public let fromInt64 : (x : Int64) -> Float` +- `public let log : (x : Float) -> Float` +- `public let max : (x : Float, y : Float) -> Float` +- `public let min : (x : Float, y : Float) -> Float` +- `public let nearest : (x : Float) -> Float` +- `public let pi : Float` +- `public let sin : (x : Float) -> Float` +- `public let sqrt : (x : Float) -> Float` +- `public let tan : (x : Float) -> Float` +- `public let toFloat32 : (self : Float) -> Prim.Types.Float32` +- `public let toInt : (self : Float) -> Int` +- `public let toInt64 : (self : Float) -> Int64` +- `public let toText : (self : Float) -> Text` +- `public let trunc : (x : Float) -> Float` +- `public type Float` + ## Int - `public func add(x : Int, y : Int) : Int` - `public func compare(x : Int, y : Int) : Order.Order` - `public func div(x : Int, y : Int) : Int` - `public func equal(x : Int, y : Int) : Bool` -- `public func fromNat(nat : Nat) : Int` - `public func fromText(text : Text) : ?Int` - `public func greater(x : Int, y : Int) : Bool` - `public func greaterOrEqual(x : Int, y : Int) : Bool` @@ -81,10 +185,6 @@ Use this as a reference when using contextual dot notation. - `public func toNat(self : Int) : Nat` - `public func toText(self : Int) : Text` - `public let abs : (x : Int) -> Nat` -- `public let fromInt16 : (x : Int16) -> Int` -- `public let fromInt32 : (x : Int32) -> Int` -- `public let fromInt64 : (x : Int64) -> Int` -- `public let fromInt8 : (x : Int8) -> Int` - `public let toFloat : (self : Int) -> Float` - `public let toInt16 : (self : Int) -> Int16` - `public let toInt32 : (self : Int) -> Int32` @@ -111,8 +211,6 @@ Use this as a reference when using contextual dot notation. - `public func foldLeft(self : Iter, initial : R, combine : (R, T) -> R) : R` - `public func foldRight(self : Iter, initial : R, combine : (T, R) -> R) : R` - `public func forEach( self : Iter, f : (T) -> () )` -- `public func fromArray(array : [T]) : Iter` -- `public func fromVarArray(array : [var T]) : Iter` - `public func infinite(item : T) : Iter` - `public func map(self : Iter, f : T -> R) : Iter` - `public func max(self : Iter, compare : (implicit : (T, T) -> Order.Order)) : ?T` @@ -172,6 +270,7 @@ Use this as a reference when using contextual dot notation. - `public func fromArray(array : [T]) : List` - `public func fromIter(iter : Types.Iter) : List` - `public func fromVarArray(array : [var T]) : List` +- `public func get(self : List, index : Nat) : ?T` - `public func indexOf(self : List, equal : (implicit : (T, T) -> Bool), element : T) : ?Nat` - `public func isEmpty(self : List) : Bool` - `public func isSorted(self : List, compare : (implicit : (T, T) -> Types.Order)) : Bool` @@ -192,6 +291,7 @@ Use this as a reference when using contextual dot notation. - `public func reader(self : List, start : Nat) : () -> T` - `public func removeLast(self : List) : ?T` - `public func repeat(initValue : T, size : Nat) : List` +- `public func retain(self : List, predicate : T -> Bool)` - `public func reverse(self : List) : List` - `public func reverseEnumerate(self : List) : Types.Iter<(Nat, T)>` - `public func reverseForEach(self : List, f : T -> ())` @@ -241,10 +341,13 @@ Use this as a reference when using contextual dot notation. - `public func maxEntry(self : Map) : ?(K, V)` - `public func minEntry(self : Map) : ?(K, V)` - `public func remove(self : Map, compare : (implicit : (K, K) -> Order.Order), key : K)` +- `public func replace(self : Map, compare : (implicit : (K, K) -> Order.Order), key : K, value : V) : ?V` - `public func reverseEntries(self : Map) : Types.Iter<(K, V)>` - `public func reverseEntriesFrom( self : Map, compare : (implicit : (K, K) -> Order.Order), key : K ) : Types.Iter<(K, V)>` - `public func singleton(key : K, value : V) : Map` - `public func size(self : Map) : Nat` +- `public func swap(self : Map, compare : (implicit : (K, K) -> Order.Order), key : K, value : V) : ?V` +- `public func take(self : Map, compare : (implicit : (K, K) -> Order.Order), key : K) : ?V` - `public func toArray(self : Map) : [(K, V)]` - `public func toMap(self : Types.Iter<(K, V)>, compare : (implicit : (K, K) -> Order.Order)) : Map` - `public func toText(self : Map, keyFormat : (implicit : (toText : K -> Text)), valueFormat : (implicit : (toText : V -> Text))) : Text` @@ -278,10 +381,6 @@ Use this as a reference when using contextual dot notation. - `public func toInt(self : Nat) : Int` - `public let bitshiftLeft : (x : Nat, y : Nat32) -> Nat` - `public let bitshiftRight : (x : Nat, y : Nat32) -> Nat` -- `public let fromNat16 : Nat16 -> Nat` -- `public let fromNat32 : Nat32 -> Nat` -- `public let fromNat64 : Nat64 -> Nat` -- `public let fromNat8 : Nat8 -> Nat` - `public let toFloat : (self : Nat) -> Float` - `public let toNat : (self : Text) -> ?Nat` - `public let toNat16 : (self : Nat) -> Nat16` @@ -336,8 +435,9 @@ Use this as a reference when using contextual dot notation. - `public func notEqual(self : Principal, other : Principal) : Bool` - `public func toLedgerAccount(self : Principal, subAccount : ?Blob) : Blob` - `public func toText(self : Principal) : Text` -- `public let fromActor : (a : actor` -- `public let fromBlob : (self : Blob) -> Principal` +- `public let fromActor : (a : actor {}) -> Principal` +- `public let fromBlob : (blob : Blob) -> Principal` +- `public let toActor : (p : Principal) -> A` - `public let toBlob : (self : Principal) -> Blob` - `public type Principal` @@ -375,9 +475,32 @@ Use this as a reference when using contextual dot notation. - `public func values(self : Queue) : Iter.Iter` - `public type Queue` +## Result + +- `public func assertErr(self : Result)` +- `public func assertOk(self : Result)` +- `public func chain( self : Result, f : Ok1 -> Result ) : Result` +- `public func compare( self : Result, other : Result, compareOk : (implicit : (compare : (Ok, Ok) -> Order.Order)), compareErr : (implicit : (compare : (Err, Err) -> Order.Order)) ) : Order.Order` +- `public func equal( self : Result, other : Result, equalOk : (implicit : (equal : Ok, Ok) -> Bool), equalErr : (implicit : (equal : (Err, Err) -> Bool)) ) : Bool` +- `public func flatten( self : Result, Err> ) : Result` +- `public func forErr(self : Result, f : Err -> ())` +- `public func forOk(self : Result, f : Ok -> ())` +- `public func fromOption(x : ?Ok, err : Err) : Result` +- `public func fromUpper( result : { #Ok : Ok; #Err : Err } ) : Result` +- `public func isErr(self : Result) : Bool` +- `public func isOk(self : Result) : Bool` +- `public func mapErr( self : Result, f : Err1 -> Err2 ) : Result` +- `public func mapOk( self : Result, f : Ok1 -> Ok2 ) : Result` +- `public func toOption(self : Result) : ?Ok` +- `public func toUpper( self : Result ) : { #Ok : Ok; #Err : Err }` +- `public type Result` + ## Runtime -- `public func trap(message : Text)` +- `public func envVar(name : Text) : ?Text` +- `public func envVarNames() : [Text]` +- `public func trap(errorMessage : Text) : None` +- `public func unreachable() : None` ## Set @@ -389,6 +512,7 @@ Use this as a reference when using contextual dot notation. - `public func clone(self : Set) : Set` - `public func compare(self : Set, other : Set, compare : (implicit : (T, T) -> Order.Order)) : Order.Order` - `public func contains(self : Set, compare : (implicit : (T, T) -> Order.Order), element : T) : Bool` +- `public func deleteAll(self : Set, compare : (implicit : (T, T) -> Order.Order), iter : Types.Iter) : Bool` - `public func difference(self : Set, other : Set, compare : (implicit : (T, T) -> Order.Order)) : Set` - `public func empty() : Set` - `public func equal(self : Set, other : Set, compare : (implicit : (T, T) -> Types.Order)) : Bool` @@ -400,6 +524,7 @@ Use this as a reference when using contextual dot notation. - `public func forEach(self : Set, operation : T -> ())` - `public func fromArray(array : [T], compare : (implicit : (T, T) -> Order.Order)) : Set` - `public func fromIter(iter : Types.Iter, compare : (implicit : (T, T) -> Order.Order)) : Set` +- `public func insertAll(self : Set, compare : (implicit : (T, T) -> Order.Order), iter : Types.Iter) : Bool` - `public func intersection(self : Set, other : Set, compare : (implicit : (T, T) -> Order.Order)) : Set` - `public func isEmpty(self : Set) : Bool` - `public func isSubset(self : Set, other : Set, compare : (implicit : (T, T) -> Order.Order)) : Bool` @@ -408,6 +533,7 @@ Use this as a reference when using contextual dot notation. - `public func max(self : Set) : ?T` - `public func min(self : Set) : ?T` - `public func remove(self : Set, compare : (implicit : (T, T) -> Order.Order), element : T) : ()` +- `public func retainAll(self : Set, compare : (implicit : (T, T) -> Order.Order), predicate : T -> Bool) : Bool` - `public func reverseValues(self : Set) : Types.Iter` - `public func reverseValuesFrom( self : Set, compare : (implicit : (T, T) -> Order.Order), element : T ) : Types.Iter` - `public func singleton(element : T) : Set` @@ -494,7 +620,6 @@ Use this as a reference when using contextual dot notation. - `public func trimStart(self : Text, p : Pattern) : Text` - `public let decodeUtf8 : (self : Blob) -> ?Text` - `public let encodeUtf8 : (self : Text) -> Blob` -- `public let fromChar : (c : Char) -> Text` - `public let toLower : (self : Text) -> Text` - `public let toUpper : (self : Text) -> Text` - `public type Pattern` @@ -507,4 +632,12 @@ Use this as a reference when using contextual dot notation. - `public type Duration` - `public type Time` - `public type TimerId` - // No Time.compare -->> use Int.compare to compare times. + +> No `Time.compare` — use `Int.compare` to compare times. + +## Timer + +- `public func recurringTimer(duration : Time.Duration, job : () -> async ()) : TimerId` +- `public func setTimer(duration : Time.Duration, job : () -> async ()) : TimerId` +- `public let cancelTimer : TimerId -> ()` +- `public type TimerId` diff --git a/skills/writing-motoko/references/equality.md b/skills/writing-motoko/references/equality.md index 76497a51..ad61cb29 100644 --- a/skills/writing-motoko/references/equality.md +++ b/skills/writing-motoko/references/equality.md @@ -4,7 +4,7 @@ Load when choosing between `==`, `equal`, and `compare`, or when a `.equal(...)` ## `==` vs `equal` -**Default to `equal` / `compare`. Use `==` only for the primitives that have no receiver form** — `Nat`, `Int`, `Float`, and the sized int types, where `a == b` is shorter than `Nat.equal(a, b)` and needs no import. +**`==` compares two values; `equal` and `compare` are functions you can pass.** Collections need the function form, so write it for the records and variants you use as keys or elements. For a direct comparison of primitives or shared fields, `==` is fine — and on `Nat`, `Int`, `Float`, and the sized int types it is the only receiver form available, where `a == b` is shorter than `Nat.equal(a, b)` and needs no import. `==` is compiler-generated structural equality, and it only exists for **shared** types. That is the catch: a single `var` field takes a record out of shared, and `==` stops compiling: @@ -14,7 +14,7 @@ a == b // type error [M0060], operator is not defined for operand types ``` -Internal state records routinely have `var` fields, so code written around `==` breaks the moment a field becomes mutable. `equal`/`compare` functions do not have that failure mode, which is why they are the default even where `==` would work today. +Internal state records routinely have `var` fields, so code written around `==` breaks the moment a field becomes mutable. `equal`/`compare` functions do not have that failure mode, which is why record and variant comparisons belong in them rather than in `==`. `equal` and `compare` are also what **collections** take, as implicit arguments: `Map`, `Set`, `contains`, and the collection-level `equal`/`compare` helpers. The compiler infers them, so you rarely name them: @@ -71,6 +71,7 @@ type error [M0230], Cannot determine implicit argument `compare` **Record `compare` must be an explicit function.** There is no sensible default — which field dominates, and in what direction, is a decision only you can make. Write it out and be deliberate about the tie-breaking: ```motoko +type Point = { x : Nat; y : Nat }; module Point { public func compare(a : Point, b : Point) : Order.Order { switch (Nat.compare(a.x, b.x)) { diff --git a/skills/writing-motoko/references/type-conversions.md b/skills/writing-motoko/references/type-conversions.md index 91ec75cf..8e803514 100644 --- a/skills/writing-motoko/references/type-conversions.md +++ b/skills/writing-motoko/references/type-conversions.md @@ -2,6 +2,8 @@ Reference for Motoko numerical type conversions. Load when you need to convert between `Nat`, `Int`, and their sized variants. +**Every conversion is spelled `to`, on the source value.** Widening and narrowing are both receiver calls; `Module.fromX` is deprecated. Do not write `Nat8.fromNat64(n)` even where it still compiles — write `n.toNat8()`. + ## Nat to Int ```motoko @@ -18,11 +20,11 @@ let nat8 : Nat8 = 255; let nat16 = nat8.toNat16(); let nat32 = nat16.toNat32(); let nat64 = nat32.toNat64(); -let backToNat8 = Nat8.fromNat64(nat64); // Reverse +let backToNat8 = nat64.toNat8(); // Narrow — traps on overflow ``` -Conversion chain: `Nat8 → Nat16 → Nat32 → Nat64` (widen) or reverse with `fromNatXX` (narrow). +Conversion chain: `Nat8 → Nat16 → Nat32 → Nat64` (widen) and back (narrow). Both directions are `toXX` on the value. ## Int Size Conversions @@ -31,11 +33,11 @@ let int8 : Int8 = -128; let int16 = int8.toInt16(); let int32 = int16.toInt32(); let int64 = int32.toInt64(); -let backToInt8 = Int8.fromInt64(int64); // Reverse +let backToInt8 = int64.toInt8(); // Narrow — traps on overflow ``` -Conversion chain: `Int8 → Int16 → Int32 → Int64` (widen) or reverse with `fromIntXX` (narrow). +Conversion chain: `Int8 → Int16 → Int32 → Int64` (widen) and back (narrow). Both directions are `toXX` on the value. ## Common Conversion Patterns @@ -46,9 +48,9 @@ let text = myNat.toText(); // dot notation // Int to Text let text = myInt.toText(); // dot notation -// Text to Nat/Int (returns optional) -let maybeNat = Nat.fromText("42"); // ?Nat -let maybeInt = Int.fromText("-5"); // ?Int +// Text to Nat/Int (returns optional) — receiver form on the Text +let maybeNat = "42".toNat(); // ?Nat +let maybeInt = "-5".toInt(); // ?Int // Nat to Float let f = myNat.toFloat();