Refactor MoonBit code to be idiomatic: shrink public APIs, convert functions to methods, use pattern matching with views, add loop invariants, and ensure test coverage without regressions. Use when updating MoonBit packages or refactoring MoonBit APIs, modules, or tests.
Start broad, then refine locally:
moon doc, moon ide find-references).moon checkmoon testAvoid local cleanups (renaming, pattern matching) until the high-level structure is sound.
Treat files in MoonBit as organizational units; move code freely within a package as long as each file stays focused on one concept.
When spinning off package A into A and B:
Create the new package and re-export temporarily:
// In package B
using @A { ... } // re-export A's APIs
Ensure moon check passes before proceeding.
Find and update all call sites:
moon ide find-references <symbol>
Replace bare f with @B.f.
Remove the use statement once all call sites are updated.
Audit and remove newly-unused pub APIs from both packages.
internal/ package for helpers that shouldn't leak.pub from helpers; keep only required exports.internal/ packages to block external imports... for fluent, mutating chains when it reads clearly.Example:
// Before
fn reader_next(r : Reader) -> Char? { ... }
let ch = reader_next(r)
// After
fn Reader::next(self : Reader) -> Char? { ... }
let ch = r.next()
Example (chaining):
buf..write_string("#\\")..write_char(ch)
@pkg.fn instead of using when clarity matters.Example:
let n = @parser.parse_number(token)
When the expected type is known from context, you can omit the full package path for enum constructors:
Examples:
// Pattern matching - annotate the value being matched
let tree : @pkga.Tree = ...
match tree {
Leaf(x) => x
Node(left~, x, right~) => left.sum() + x + right.sum()
}
// Nested constructors - only outer needs full path
let x = @pkga.Tree::Node(left=Leaf(1), x=2, right=Leaf(3))
// Return type provides context
fn make_tree() -> @pkga.Tree {
Node(left=Leaf(1), x=2, right=Leaf(3))
}
// Collections - type annotation on the array
let trees : Array[@pkga.Tree] = [Leaf(1), Node(left=Leaf(2), x=3, right=Leaf(4))]
.. in the middle to match prefix and suffix at once.Array[Char].String/StringView indexing yields UInt16 code units. Use for ch in s for Unicode-aware iteration.For example,
match gen_results.get(0) {
Some(value) => Iter::singleton(value)
None => Iter::empty()
}
We can pattern match directly, it is more efficient and as readable:
match gen_results {
[value, ..] => Iter::singleton(value)
[] => Iter::empty()
}
MoonBit pattern matching is pretty expressive, here are some more examples:
match items {
[] => ()
[head, ..tail] => handle(head, tail)
[..prefix, mid, ..suffix] => handle_mid(prefix, mid, suffix)
}
match s {
"" => ()
[.."let", ..rest] => handle_let(rest)
_ => ()
}
Use char literal overloading for Char, UInt16, and Int; the examples below rely on it. This is handy when matching String indexing results (UInt16) against a char range.
test {
let a_int : Int = 'b'
if (a_int is 'a'..<'z') { () } else { () }
let a_u16 : UInt16 = 'b'
if (a_u16 is 'a'..<'z') { () } else { () }
let a_char : Char = 'b'
if (a_char is 'a'..<'z') { () } else { () }
}
isis patterns inside if/guard to keep branches concise.Example:
match token {
Some(Ident([.."@", ..rest])) if process(rest) is Some(x) => handle_at(rest)
Some(Ident(name)) => handle_ident(name)
None => ()
}
for i in start..<end { ... }, for i in start..<=end { ... }, for i in large>..small, or for i in large>=..small for simple index loops.for loops for algorithms that update state.Example:
// Before
for i = 0; i < len; {
items.push(fill)
continue i + 1
}
// After
for i in 0..<len {
items.push(fill)
}
for x in xs loops.Example:
for i = 0, acc = 0; i < xs.length(); {
acc = acc + xs[i]
i = i + 1
} else { acc }
where {
invariant: 0 <= i <= xs.length(),
reasoning: (
#| ... rigorous explanation ...
#| ...
)
}
*_test.mbt or *.mbt.md.mbt check for public APIs.Example:
///|
/// Return the last element of a non-empty array.
///
/// # Example
/// ```mbt check
/// test {
/// inspect(last([1, 2, 3]), content="3")
/// }
/// ```
pub fn last(xs : Array[Int]) -> Int { ... }
Commands:
moon coverage analyze -- -f summary
moon coverage analyze -- -f caret -F path/to/file.mbt
moon doc "<query>"
moon ide outline <dir|file>
moon ide find-references <symbol>
moon ide peek-def <symbol>
moon ide rename <symbol> -new-name <new_name>
moon check
moon test
moon info
Use these commands for reliable refactoring.
Example: spinning off package_b from package_a.
Temporary import in package_b:
using @package_a { a, type B }
Steps:
moon ide find-references <symbol> to find all call sites of a and B.@package_a.a and @package_a.B.using statement and run moon check.