Loading...
Loading...
Guide for writing, refactoring, and testing MoonBit projects. Use when working in MoonBit modules or packages, organizing MoonBit files, using moon tooling (build/check/run/test/doc/ide etc.), or following MoonBit-specific layout, documentation, and testing conventions.
npx skill4agent add moonbitlang/skills moonbit-agent-guidemoon.mod.jsonmoon.pkgmoon ide docmoon ide outlinemoon ide peek-defmoon ide find-referencesmoon ide rename--loc filename:line:col#alias(old_api, deprecated)///|moon check--warn-list +unnecessary_annotation--warn-list +73moon test [dirname|filename] --filter 'glob'moon test --updatemoon fmtmoon infopkg.generated.mbtimoon ide outlinemoon ide peek-defmoon ide find-referencesmoon checkmoon test [dirname|filename] --filter 'glob'moon fmtmoon infopkg.generated.mbtimoon ide renamemoon ide find-referencesmoon ide peek-defmoon ide rename <symbol> <new_name> --loc filename:line:colmoon checkmoon test [dirname|filename]moon fmtmoon infomoon ide doc///|moon checkmoon test [dirname|filename]--updatemoon fmtmoon infopkg.generated.mbti.mbt.mbtimoon.mod.jsonmoon.pkgmoon.mod.jsonmy_module
├── moon.mod.json # Module metadata, source field (optional) specifies the source directory of the module
├── moon.pkg # Package metadata (each directory is a package like Golang)
├── README.mbt.md # Markdown with tested code blocks (`test "..." { ... }`)
├── README.md -> README.mbt.md
├── cmd # Command line directory
│ └── main
│ ├── main.mbt
│ └── moon.pkg # executable package with `options("is-main": true)`
├── liba/ # Library packages
│ └── moon.pkg # Referenced by other packages as `@username/my_module/liba`
│ └── libb/ # Library packages
│ └── moon.pkg # Referenced by other packages as `@username/my_module/liba/libb`
├── user_pkg.mbt # Root packages, referenced by other packages as `@username/my_module`
├── user_pkg_wbtest.mbt # White-box tests (only needed for testing internal private members, similar to Golang's package mypackage)
└── user_pkg_test.mbt # Black-box tests
└── ... # More package files, symbols visible to current package (like Golang)moon.mod.jsonmoon.pkgmoonmoon.mod.jsonnamemoon.mod.json.mbt///|*_test.mbt*_test.mbt*.mbt.mdmbt checkREADME.mbt.mdmbt checkREADME.mbt.mdREADME.mdpkg.generated.mbtipkg.generated.mbtimoon infopkg.generated.mbtipkg.generated.mbtimoon idemutmutmutreturnget()i = i + 1i += 1tryfunction_name!(...)function_name(...)?forfor i in 0..<(n-1) {...}for j in 0..=6 {...}awaitasync[pub] async fn ...async test ...moonmoon new my_projectmoon run cmd/mainmoon run - < hello.mbtmoon run -e "code snippet"cat hello.mbt | moon run -moon run - <<'EOF'
fn main {
println("Hello, MoonBit!")
}
EOFmoon run -e 'fn main { println("Hello, MoonBit!") }'moon buildmoon runmoon build--targetmoon checkmoon check--targetmoon infombtimoon info--targetmoon check --target alljqmoon check --output-json 2>&1 | jq -R 'fromjson? | select(.message |
contains("unused"))'moon run -e--target nativewasm-gcasync fn main@stdio.stdin<<'EOF'$EOFundefinedGet the diagnostics with "unused" in the message, which can be used to find unused code.
- `moon explain` - Show built-in documentation for compiler diagnostics.
- `moon explain --diagnostics` lists warning mnemonics and IDs.
- `moon explain --diagnostics 31` explains warning 31 (`unused_optional_argument`).
- `moon explain --diagnostics unused_optional_argument` explains the same warning by mnemonic.
- `moon add package` - Add dependency
- `moon remove package` - Remove dependency
- `moon fmt` - Format code - should be run periodically - note that the files may be rewritten
Note you can also use `moon -C dir check` to run commands in a specific directory.
### Test Commands
- `moon test` - Run all tests
(`moon test` also supports `--target`)
- `moon test --update` - Update snapshots
- `moon test -v` - Verbose output with test names
- `moon test [dirname|filename]` - Test specific directory or file
- `moon coverage analyze` - Analyze coverage
- `moon test [dirname|filename] --filter 'glob'` - Run tests matching filter
```
moon test float/float_test.mbt --filter "Float::*"
moon test float -F "Float::*" // shortcut syntax
```
## `README.mbt.md` Generation Guide
- Output `README.mbt.md` in the package directory.
`*.mbt.md` file and docstring contents treats `mbt check` specially.
`mbt check` block will be included directly as code and also run by `moon check` and `moon test`. If you don't want the code snippets to be checked, explicit `mbt nocheck` is preferred.
If you are only referencing types from the package, you should use `mbt nocheck` which will only be syntax highlighted.
Symlink `README.mbt.md` to `README.md` to adapt to systems that expect `README.md`.
## Testing Guide
Use snapshot tests as it is easy to update when behavior changes.
- **Snapshot Tests**: `inspect(value, content="...")`. If unknown, write `inspect(value)` and run `moon test --update` (or `moon test -u`).
- Use regular `inspect()` for simple values (uses `Show` trait)
- Use `@json.inspect()` for complex nested structures (uses `ToJson` trait, produces more readable output)
- It is encouraged to `inspect` or `@json.inspect` the whole return value of a function if
the whole return value is not huge, this makes the test simple. You need `impl (Show|ToJson) for YourType` or `derive (Show, ToJson)`.
- **Update workflow**: After changing code that affects output, run `moon test --update` to regenerate snapshots, then review the diffs in your test files (the `content=` parameter will be updated automatically).
- **Validation order**: Follow the canonical sequence in `Agent Workflow` and `Fast Task Playbooks`.
- Black-box by default: Call only public APIs via `@package.fn`. Use white-box tests only when private members matter.
- Grouping: Combine related checks in one `test "..." { ... }` block for speed and clarity.
- Panics: Name tests with prefix `test "panic ..." {...}`; if the call returns a value, wrap it with `ignore(...)` to silence warnings.
- Errors: Use `try? f()` to get `Result[...]` and `inspect` it when a function may raise.
### Docstring tests
Public APIs are encouraged to have docstring tests.
````mbt check
///|
/// Get the largest element of a non-empty `Array`.
///
/// # Example
/// ```mbt check
/// test {
/// inspect(sum_array([1, 2, 3, 4, 5, 6]), content="21")
/// }
/// ```
///
/// # Panics
/// Panics if the `xs` is empty.
pub fn sum_array(xs : Array[Int]) -> Int {
xs.fold(init=0, (a, b) => a + b)
}moon test --updatembt checktestasync testspec.mbt///|
declare pub type Yaml
///|
declare pub fn Yaml::to_string(y : Yaml) -> String raise
///|
declare pub impl Eq for Yaml
///|
declare pub fn parse_yaml(s : String) -> Yaml raisespec_easy_test.mbtspec_difficult_test.mbtmoon checkdeclaremoon testdeclarepub type Yamlmoon ide [doc|peek-def|outline|find-references|hover|rename|analyze]moon ide doc <query>moon ide docgrep_searchmoon ide outline .moon ide find-references <symbol>moon ide peek-defmoon ide hover sym --loc filename:line:colmoon ide rename <symbol> <new_name> [--loc filename:line:col]--locmoon ide analyze [path]grepmoon ide docmoon ide docmoon ide doc ''moon ide doc "[@pkg.]value_or_function_name"moon ide doc "[@pkg.]Type_name"moon ide doc "[@pkg.]Type_name::method_or_field_name"moon ide doc "@pkg"pkgmoon ide doc "@json"@jsonmoon ide doc "@encoding/utf8"moon ide doc "query1" "query2" ...moon ide doc "String" "Array" "@json"*moon ide doc "String::*rev*"moon ide doc# search for String methods in standard library:
$ moon ide doc "String"
type String
pub fn String::add(String, String) -> String
# ... more methods omitted ...
$ moon ide doc "@buffer" # list all symbols in package buffer:
moonbitlang/core/buffer
fn from_array(ArrayView[Byte]) -> Buffer
# ... omitted ...
$ moon ide doc "@buffer.new" # list the specific function in a package:
package "moonbitlang/core/buffer"
pub fn new(size_hint? : Int) -> Buffer
Creates ... omitted ...
$ moon ide doc "String::*rev*" # globbing
package "moonbitlang/core/string"
pub fn String::rev(String) -> String
Returns ... omitted ...
# ... more
pub fn String::rev_find(String, StringView) -> Int?
Returns ... omitted ...Agent Workflowmoon ide rename sym new_name [--loc filename:line:col]compute_sumcalculate_sum$ moon ide rename compute_sum calculate_sum --loc math_utils.mbt:2
*** Begin Patch
*** Update File: cmd/main/main.mbt
@@
///|
fn main {
- println(@math_utils.compute_sum(1, 2))
+ println(@math_utils.calculate_sum(1, 2))
}
*** Update File: math_utils.mbt
@@
///|
-pub fn compute_sum(a: Int, b: Int) -> Int {
+pub fn calculate_sum(a: Int, b: Int) -> Int {
a + b
}
*** Update File: math_utils_test.mbt
@@
///|
test {
- inspect(@math_utils.compute_sum(1, 2))
+ inspect(@math_utils.calculate_sum(1, 2))
}
*** End Patchmoon ide hover sym --loc filename:line:colfilter$ moon ide hover filter --loc hover.mbt:14
test {
let a: Array[Int] = [1]
inspect(a.filter((x) => {x > 1}))
^^^^^^
```moonbit
fn[T] Array::filter(self : Array[T], f : (T) -> Bool raise?) -> Array[T] raise?
```
---
Creates a new array containing all elements from the input array that satisfy
... omitted ...
}moon ide peek-def sym [--loc filename:line:col]Parser::read_u32_leb128moon ide peek-def Parser::read_u32_leb128grepL45:|///|
L46:|fn Parser::read_u32_leb128(self : Parser) -> UInt raise ParseError {
L47:| ...
...:| }Parser$ moon ide peek-def Parser --loc src/parse.mbt:46:4
Definition found at file src/parse.mbt
| ///|
2 | priv struct Parser {
| ^^^^^^
| bytes : Bytes
| mut pos : Int
| }
|--locParser$ moon ide peek-def String::rev
Found 1 symbols matching 'String::rev':
`pub fn String::rev` in package moonbitlang/core/builtin at /Users/usrname/.moon/lib/core/builtin/string_methods.mbt:1039-1044
1039 | ///|
| /// Returns a new string with the characters in reverse order. It respects
| /// Unicode characters and surrogate pairs but not grapheme clusters.
| pub fn String::rev(self : String) -> String {
| self[:].rev()
| }moon ide outline [dir|file]moon ide find-references <sym>moon ide outlinemoon ide outline dirmoon ide outline parser.mbtpeek-defmoon ide find-references TranslationUnit$ moon ide outline .
spec.mbt:
L003 | pub(all) enum CStandard {
...
L013 | pub(all) struct Position {
...$ moon ide find-references TranslationUnitmoon add moonbitlang/x # Add latest version
moon add moonbitlang/x@0.4.6 # Add specific versionmoon update # Update package indexmoon fetchmoon fetch <author>/<module>[@<version>].repos/<author>/<module>/<version>/.mbtimoon.mod.jsonmoon add.repos/.gitignoremoon fetch moonbitlang/async@0.18.1 # browse source/examples without taking a dependencymoon.mod.json{
"name": "username/hello", // Required format for published modules
"version": "0.1.0",
"source": ".", // Source directory(optional, default: ".")
"repository": "", // Git repository URL
"keywords": [], // Search keywords
"description": "...", // Module description
"deps": {
// Dependencies from mooncakes.io, using`moon add` to add dependencies
"moonbitlang/x": "0.4.6"
}
}moon.pkgimport {
"username/hello/liba",
"moonbitlang/x/encoding" @libb
}
import {...} for "test"
import {...} for "wbtest"
options("is-main" : true) // other optionsmoon.pkg"module_name/package_path"@alias.function()libausername/hello/liba@packagename"username/hello/liba"@liba.function()import { "moonbitlang/x/encoding" @enc}@enc.function()_test.mbt_wbtest.mbt///|
/// In main.mbt after importing "username/hello/liba" in `moon.pkg`
fn main {
println(@liba.hello()) // Calls hello() from liba package
}moonbitlang/core/strconvmoon.pkgfib../fib/./fib/moon.pkg.mbt import {
"username/hello/fib",
}conditional compilationlink configurationwarning controlpre-build commandsreferences/advanced-moonbit-build.mdmoonbitlang/asyncmoon add moonbitlang/async@<version>moon ide doc "@async"@async@async/aqueue@async/fs, , , ..etc. Each must be imported separately in moon.mod.json{
"deps": { "moonbitlang/async": "0.18.1" },
"preferred-target": "native"
}moon.pkgis-mainimport {
"moonbitlang/async",
"moonbitlang/async/stdio",
}
supported_targets = "+native"
options("is-main": true)async fn mainfn mainwith_task_group///|
async fn main {
@async.with_task_group(group => {
group.spawn_bg(() => {
@async.sleep(50)
@stdio.stdout.write("A\n")
})
group.spawn_bg(() => {
@async.sleep(20)
@stdio.stdout.write("B\n")
})
})
}with_task_groupwith_task_groupallow_failure=truewith_task_groupspawn_bgspawn() => { ... }async fn() { ... }fn() { ... }Warning [0027] deprecated_syntaxfnasyncasync () => ...fn() async { ... }fn(args) async { ... }asyncfnasync testmoonbitlang/asyncfor "test"import {
"moonbitlang/async",
"moonbitlang/async/stdio",
} for "test"///|
async test "sleep completes" {
@async.sleep(1)
inspect("done", content="done")
}awaitasync testmoon test --target nativemoon.mod.json"preferred-target": "native"moon test -vREADME.mbt.mdmbt checkasync testmoonbitlang/asyncifmatchRef[T]///|fnpubpub(all)import@alias.fnmoon.pkg...let mutErrorsuberrorraisetry { } catch { }try!try?Result[...]///|
/// Declare error types with 'suberror'
suberror ValueError {
ValueError(String)
}
///|
/// Tuple struct to hold position info
struct Position(Int, Int) derive(ToJson, Debug, Eq)
///|
/// ParseError is subtype of Error
pub(all) suberror ParseError {
InvalidChar(pos~ : Position, Char) // pos is labeled
InvalidEof(pos~ : Position)
InvalidNumber(pos~ : Position, String)
InvalidIdentEscape(pos~ : Position)
} derive(Eq, ToJson, Debug)
///|
/// Functions declare what they can throw
fn parse_int(s : String, position~ : Position) -> Int raise ParseError {
// 'raise' throws an error
if s is "" {
raise ParseError::InvalidEof(pos=position)
}
... // parsing logic
}
///|
/// Just declare `raise` to not track specific error types
fn div(x : Int, y : Int) -> Int raise {
if y is 0 {
fail("Division by zero")
}
x / y
}
///|
test "inspect raise function" {
let result : Result[Int, Error] = try? div(1, 0)
guard result is Err(Failure::Failure(msg)) && msg.contains("Division by zero") else {
fail("Expected error")
}
}
// Three ways to handle errors:
///|
/// Propagate automatically
fn use_parse(s : String, position~ : Position) -> Int raise ParseError {
let x = parse_int(s, position~) // label punning, equivalent to position=position
// Error auto-propagates by default.
// Unlike Swift, you do not need to mark `try` for functions that can raise
// errors; the compiler infers it automatically. This keeps error handling
// explicit but concise.
x * 2
}
///|
/// Use try! to abort if it raises, no raise in the signature
fn use_parse2(position~ : Position) -> Int {
let x = try! parse_int("123", position~) // label punning
x * 2
}
///|
/// Handle with try-catch
fn handle_parse(s : String, position~ : Position) -> Int {
parse_int(s, position~) catch {
ParseError::InvalidEof(pos=_) => {
println("Parse failed: InvalidEof")
-1 // Default value
}
_ => 2
}
}asyncByteInt16IntUInt16UIntInt64UInt64///|
test "integer and char literal overloading disambiguation via type in the current context" {
let (int, uint, uint16, int64, byte) : (Int, UInt, UInt16, Int64, Byte) = (
1, 1, 1, 1, 1,
)
// The literal `1` is overloaded based on the expected type in the current context.
// compile time error if the literal cannot be represented in the target type,
// e.g. let a7 : Byte = 256 // ❌ won't compile, 256 exceeds Byte max value 255
assert_eq(int, uint16.to_int())
let (a1, a2, a3) : (Int, Char, UInt16) = ('b', 'b', 'b')
// char literal overloading, `a1` will be the unicode value of 'b',
// compile time error when the literal cannot be represented in the target type
// e.g, let a6 : UInt16 = '𐍈' // ❌ won't compile, '𐍈' is U+10348, which exceeds UInt16 max value 0xffff
let a4 : Byte = b'b' // Byte literal
}///|
test "bytes literals" {
let b0 : Bytes = b"abcd"
let b1 : Bytes = [0xff, 0x00, 0x01] // Array literal overloading
guard b0 is [b'a', ..] && b0[1] is b'b' else {
// Bytes can be pattern matched as BytesView and indexed
fail("unexpected bytes content")
}
}///|
test "array literals overloading: disambiguation via type in the current context" {
let (a0, a1, a2, a3) : (
Array[Int],
FixedArray[Int],
ReadOnlyArray[Int],
ArrayView[Int],
) = ([1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3])
// The literal `[1, 2, 3]` is overloaded based on the expected type in the current context.
// Defaults to Array[_]
}s[i]s.get_char(i)Char?///|
test "string indexing and utf8 encode/decode" {
let s = "hello world"
let b0 : UInt16 = s[0]
guard b0 is ('\n' | 'h' | 'b' | 'a'..='z') && s is [.. "hello", .. rest] else {
fail("unexpected string content")
}
guard rest is " world" // otherwise will crash (guard without else)
// In check mode (expression with explicit type), ('\n' : UInt16) is valid.
// Using get_char for Option handling
let b1 : Char? = s.get_char(0)
assert_true(b1 is Some('a'..='z'))
// ⚠️ Important: Variables won't work with direct indexing
let eq_char : Char = '='
// s[0] == eq_char // ❌ Won't compile - eq_char is not a literal, lhs is UInt while rhs is Char
// Use: s[0] == '=' or s.get_char(0) == Some(eq_char)
let bytes = @utf8.encode("中文") // utf8 encode package is in stdlib
assert_true(bytes is [0xe4, 0xb8, 0xad, 0xe6, 0x96, 0x87])
let s2 : String = @utf8.decode(bytes) // decode utf8 bytes back to String
assert_true(s2 is "中文")
for c in "中文" {
let _ : Char = c // unicode safe iteration
println("char: \{c}") // iterate over chars
}
}\{}Show///|
test "string interpolation basics" {
let name : String = "Moon"
let config = { "cache": 123 }
let version = 1.0
println("Hello \{name} v\{version}") // "Hello Moon v1"
// ❌ Wrong - quotes inside interpolation not allowed:
// println(" - Checking if 'cache' section exists: \{config["cache"]}")
// ✅ Correct - extract to variable first:
let has_key = config["cache"] // `"` not allowed in interpolation
println(" - Checking if 'cache' section exists: \{has_key}")
let sb = StringBuilder()
sb.write_char('[')
sb.write_view([ for x in [1, 2, 3] => "\{x}" ].join(","))
sb.write_char(']')
inspect(sb, content="[1,2,3]")
let x = 42
let streamed = StringBuilder()
streamed <+ "hello \{x}"
inspect(streamed, content="hello 42")
}\{}LoggerStringBuilder<+writer <+ "hello \{x}"writer.write_string("hello ")
writer.write(x)write_stringwritewriterwrite_stringwritewrite_stringwrite///|
test "multi-line string literals" {
let multi_line_string : String =
#|Hello "world"
#|World
#|
let multi_line_string_with_interp : String =
$|Line 1 ""
$|Line 2 \{1+2}
$|
// no escape in `#|`,
// only escape '\{..}` in `$|`
assert_eq(multi_line_string, "Hello \"world\"\nWorld\n")
assert_eq(multi_line_string_with_interp, "Line 1 \"\"\nLine 2 3\n")
}///|
test "map literals and common operations" {
// Map literal syntax
let map : Map[String, Int] = { "a": 1, "b": 2, "c": 3 }
let empty : Map[String, Int] = {} // Empty map, preferred
let also_empty : Map[String, Int] = Map([])
// From array of pairs
let from_pairs : Map[String, Int] = Map::from_array([("x", 1), ("y", 2)])
// Set/update value
map["new-key"] = 3
map["a"] = 10 // Updates existing key
// Get value - returns Option[T]
guard map is { "new-key": 3, "missing"? : None, .. } else {
fail("unexpected map contents")
}
// Direct access (panics if key missing)
let value : Int = map["a"] // value = 10
// Iteration preserves insertion order
for k, v in map {
println("\{k}: \{v}") // Prints: a: 10, b: 2, c: 3, new-key: 3
}
// Other common operations
map.remove("b")
guard map is { "a": 10, "c": 3, "new-key": 3, .. } && map.length() == 3 else {
// "b" is gone, only 3 elements left
fail("unexpected map contents after removal")
}
}StringViewBytesViewArrayView[T][:]StringBytesArray*ViewStringStringViews[:]s[start:end]s[start:]s[:end]BytesBytesViewb[:]b[start:end]Array[T]FixedArray[T]ReadOnlyArray[T] → viaors[a:b]try! s[a:b][first, .. rest].to_string().to_bytes().to_array()moon ide doc StringViewenumstruct///|
enum Tree[T] {
Leaf(T) // Unlike Rust, no comma here
Node(left~ : Tree[T], T, right~ : Tree[T]) // enum can use labels
} derive(Debug, ToJson) // derive traits for Tree
///|
pub fn Tree::sum(tree : Tree[Int]) -> Int {
match tree {
Leaf(x) => x
// we don't need to write Tree::Leaf, when `tree` has a known type
Node(left~, x, right~) => left.sum() + x + right.sum() // method invoked in dot notation
}
}
///|
struct Point {
x : Int
y : Int
} derive(Debug, ToJson) // derive traits for Point
///|
pub fn Point::Point(x~ : Int, y~ : Int) -> Point {
{ x, y }
}
///|
test "user defined types: enum and struct" {
json_inspect(Point(x=10, y=20), content={ "x": 10, "y": 20 })
debug_inspect(
Point(x=10, y=20),
content=(
#|{ x: 10, y: 20 }
),
)
}for///|
pub fn binary_search(arr : ArrayView[Int], value : Int) -> Result[Int, Int] {
let len = arr.length()
// functional for loop:
// initial state ; [predicate] ; [post-update] {
// loop body with `continue` to update state
//} nobreak { // exit block
// }
// predicate and post-update are optional
for i = 0, j = len; i < j; {
// post-update is omitted, we use `continue` to update state
let h = i + (j - i) / 2
if arr[h] < value {
continue h + 1, j // functional update of loop state
} else {
continue i, h // functional update of loop state
}
} nobreak { // exit of for loop
if i < len && arr[i] == value {
Ok(i)
} else {
Err(i)
}
} where {
proof_invariant: 0 <= i && i <= j && j <= len,
proof_invariant: i == 0 || arr[i - 1] < value,
proof_invariant: j == len || arr[j] >= value,
proof_reasoning: (
#|For a sorted array, the boundary invariants are witnesses:
#| - `arr[i-1] < value` implies all arr[0..i) < value (by sortedness)
#| - `arr[j] >= value` implies all arr[j..len) >= value (by sortedness)
#|
#|Preservation proof:
#| - When arr[h] < value: new_i = h+1, and arr[new_i - 1] = arr[h] < value ✓
#| - When arr[h] >= value: new_j = h, and arr[new_j] = arr[h] >= value ✓
#|
#|Termination: j - i decreases each iteration (h is strictly between i and j)
#|
#|Correctness at exit (i == j):
#| - By invariants: arr[0..i) < value and arr[i..len) >= value
#| - So if value exists, it can only be at index i
#| - If arr[i] != value, then value is absent and i is the insertion point
#|
),
}
}
///|
test "functional for loop control flow" {
let arr : Array[Int] = [1, 3, 5, 7, 9]
debug_inspect(binary_search(arr, 5), content="Ok(2)") // Array to ArrayView implicit conversion when passing as arguments
debug_inspect(binary_search(arr, 6), content="Err(3)")
// for iteration is supported too
for i, v in arr {
println("\{i}: \{v}") // `i` is index, `v` is value
}
}forwherewhereforfor .. infor ... {
...
} where {
invariant : <boolean_expr>, // checked at runtime in debug builds
invariant : <boolean_expr>, // multiple invariants allowed
reasoning : <string> // documentation for proof sketch
}arr[i-1] < valuearr[0..i) < value||i == 0 || arr[i-1] < valuecontinue///|
fn g(
positional : Int,
required~ : Int,
optional? : Int, // no default => Option
optional_with_default? : Int = 42, // default => plain Int
) -> String {
// These are the inferred types inside the function body.
let _ : Int = positional
let _ : Int = required
let _ : Int? = optional
let _ : Int = optional_with_default
// `to_repr` (from the prelude `Debug` trait) renders Option via the
// non-deprecated `Show for Repr`, avoiding the deprecated `Show for Option`.
"\{positional},\{required},\{to_repr(optional)},\{optional_with_default}"
}
///|
test {
inspect(g(1, required=2), content="1,2,None,42")
inspect(g(1, required=2, optional=3), content="1,2,Some(3),42")
inspect(g(1, required=4, optional_with_default=100), content="1,4,None,100")
}arg : Type?NoneSome(...)///|
fn with_config(a : Int?, b : Int?, c : Int) -> String {
"\{to_repr(a)},\{to_repr(b)},\{c}"
}
///|
test {
inspect(with_config(None, None, 1), content="None,None,1")
inspect(with_config(Some(5), Some(5), 1), content="Some(5),Some(5),1")
}arg? : Type?b? : Int = 1b? : Int? = Some(1)///|
fn f_misuse(a? : Int?, b? : Int = 1) -> Unit {
let _ : Int?? = a // rarely intended
let _ : Int = b
}
// How to fix: declare `(a? : Int, b? : Int = 1)` directly.
///|
fn f_correct(a? : Int, b? : Int = 1) -> Unit {
let _ : Int? = a
let _ : Int = b
}
///|
test {
f_misuse(b=3)
f_misuse(a=Some(5), b=2) // works but confusing
f_correct(b=2)
f_correct(a=5)
}arg : APIOptions///|
/// Do not use struct to group options.
struct APIOptions {
width : Int?
height : Int?
}
///|
fn not_idiomatic(opts : APIOptions, arg : Int) -> Unit {
}
///|
test {
// Hard to use in call site
not_idiomatic({ width: Some(5), height: None }, 10)
not_idiomatic({ width: None, height: None }, 10)
}references/moonbit-language-fundamentals.mbt.md