Skip to main content

MoonBit Update

· 2 min read

IDE Update

  • AI Codelens now supports /generate and /fix commands.

The /generate command provides a generic interface for generating code.

generate.gif

The /fix command reads the current function's error information and suggests fixes.

fix.gif

Language Updates

  • Adjusted the precedence of infix expressions and if, match, loop, while, for, try control flow expressions. These control flow expressions can no longer directly appear in positions requiring infix expressions and now require an additional layer of parentheses when nested.

For example, the syntax for if and match is:

if <infix-expr> { <expression> } [else { <expression> }]
match <infix-expr> { <match-cases> }

Since if, match, loop, while, for, try are no longer considered infix expressions, code like if if expr {} {} is now invalid:

// invalid
if if cond {a} else {b} {v} else {d}
match match expr { ... } { ... }
let a = expr1 + expr2 + if a {b} else {c} + expr3
// valid
if (if cond {a} else {b}) {v} else {d}
match (match expr { ... }) { ... }
let a = expr1 + expr2 + (if a {b} else {c}) + expr3
  • JavaScript backend

    • Arrays now compile to native JS arrays, making interaction with JS more convenient.
  • Standard Library API

    • Added concat and from_array functions to the String package, deprecating Array::join.
    • Added rev_concat() to the immut/list package.
    • Buffer type now includes length and is_empty functions.
    • Improved the to_json function for the Option type.
  • Experimental Library API

    • x/fs package now supports the Wasm, Wasm-gc, and JS backends, including the following APIs:
      • write_string_to_file, write_bytes_to_file
      • read_file_to_string, read_file_to_bytes
      • path_exists
      • read_dir
      • create_dir
      • is_dir, is_file
      • remove_dir, remove_file

Build System Updates

  • moon test -p now supports fuzzy matching. For example, moon test -p moonbitlang/core/builtin can be shortened to moon test -p mcb or moon test -p builtin.

  • In moon.pkg.json, if the source field is an empty string "", it is equivalent to ".", representing the current directory.

Moondoc Update

  • The documentation generator now supports package-level README files. Any README.md in the same directory as moon.pkg.json will be displayed on the package’s documentation page.

weekly 2024-09-18

· 6 min read

Language Updates

  • Simplified Field Access for type

The type system now supports passing field access to internal types.

struct UnderlyingType {
field1: Int,
field2: Bool
}

type Newtype UnderlyingType

fn main {
let newtype = Newtype({ field1: 100, field2: true })
println(newtype.field1) // 100
println(newtype.field2) // true
}

Previously, to access the field1 of UnderlyingType within newtype, you had to use newtype._.field1. Now, you can access field1 directly via newtype.field1.

  • JSON serialization via derive

Supports custom types implementing ToJson and FromJson traits via derive.

derive(ToJson) automatically generates the necessary implementation for a type, and the resulting JSON format is compatible with the auto-generated FromJson.

struct Record {
field1: Int,
field2: Enum
} derive(Show, ToJson, FromJson)

enum Enum {
Constr1(Int, Bool?),
Constr2
} derive(Show, ToJson, FromJson)

fn main {
let record = { field1: 20, field2: Constr1(5, Some(true)) }
println(record.to_json().stringify())
// Output: {"field1":20,"field2":{"$tag":"Constr1","0":5,"1":true}}
let json = record.to_json()
try {
let record: Record = @json.from_json!(json)
println(record)
// Output: {field1: 20, field2: Constr1(5, Some(true))}
} catch {
@json.JsonDecodeError(err) => println(err)
}
}
  • Guard Statement Support

Two forms, guard and guard let, to enforce invariants and reduce indentation from pattern matching.

fn init {
guard invariant else { otherwise }
continue_part
}

The invariant in guard is a Bool expression. If true, continue_part executes; otherwise, otherwise runs, and the rest of the continue_part is skipped. The else { otherwise } part is optional; if omitted, the program terminates when invariant is false.

fn init {
guard let ok_pattern = expr1 else {
fail_pattern1 => expr2
fail_pattern2 => expr3
}
continue_part
}

guard let works similarly to guard, but it supports additional pattern matching. When expr1 matches the ok_pattern, the continue_part is executed; otherwise, it tries to match the branches inside the else block.

If the else block is omitted or no branch matches, the program terminates. The ok_pattern can introduce new bindings, which are available throughout the entire continue_part. Here’s an example:

fn f(map: Map[String, Int]) -> Int!Error {
guard let Some(x) = map["key1"] else {
None => fail!("key1 not found")
}
x + 1
}
  • moonfmt Adjustments

For if, match, loop, while, for, and try expressions used outside of a statement context, parentheses will automatically be added during formatting.

Next week, we will adjust the precedence of if, match, loop, while, for, try, and infix expressions, which is a breaking change. After this adjustment, these expressions can no longer appear directly in places where infix expressions are required by syntax. For example, the following code will be considered invalid in the future:

if if cond {a} else {b} {v} else {d}
match match expr { ... } { ... }
let a = expr1 + expr2 + if a {b} else {c} + expr3
guard if a {b} else {c} else { d }

After the adjustment, the original code will require additional parentheses:

if (if cond {a} else {b}) {v} else {d}
match (match expr { ... }) { ... }
let a = expr1 + expr2 + (if a {b} else {c}) + expr3
guard (if a {b} else {c}) else { d }

After the adjustment, the original code will require additional parentheses:

We recommend using let x = y to introduce new bindings for intermediate results of if, match, and similar expressions to improve code readability without introducing extra overhead. For example:

// not suggested
match (match expr { ... }) + (if a {b} else {c}) + expr { ... }
// suggested
let r1 = match expr { ... }
let r2 = if a {b} else {c}
match r1 + r2 + expr {
...
}

Functions like .land(), lor(), shl(), and op_shr() will now use the infix operators &, |, <<, and >> after formatting.

IDE Updates

  • Global project-wide symbol search supported. signal.png
  • Fixed renaming bug that overwrote package names.
  • Optimized automatic execution of moon check during plugin use.
  • Added completion for keywords and Bool literals.
  • Adapted conditional compilation to the build system, while maintaining compatibility with the original method of distinguishing backends through file name suffixes (e.g., x.wasm.mbt, x.js.mbt).

Build System Updates

  • Added support for build graph visualization.

By passing --build-graph after moon check | build | test, a .dot file of the build graph will be generated in the corresponding build directory after compilation.

  • moon.pkg.json now includes a targets field for defining conditional compilation expressions at the file level.

These expressions support three logical operators: and, or, and not. The or operator can be omitted, so ["or", "wasm", "wasm-gc"] can be simplified to ["wasm", "wasm-gc"]. The conditions include backend types ("wasm", "wasm-gc", and "js") and optimization levels ("debug" and "release"). Nested conditions are also supported. If a file is not listed in the targets field, it will be compiled under all conditions by default.

sample:

    {
"targets": {
"only_js.mbt": ["js"],
"not_js.mbt": ["not", "js"],
"only_debug.mbt": ["and", "debug"],
"js_and_release.mbt": ["and", "js", "release"],
"js_only_test.mbt": ["js"],
"complex.mbt": ["or", ["and", "wasm", "release"], ["and", "js", "debug"]]
}
}
  • The moon.pkg.json file now includes a pre-build field for configuring pre-build commands. These commands will be executed before running moon check | build | test. The pre-build field is an array, where each element is an object containing three fields: input, output, and command.

a. input and output can be either strings or arrays of strings.

b. command is a string where you can use any command-line command, along with $input and $output variables representing input and output files (if they are arrays, they are joined with spaces).

A special built-in command :embed is available to convert files into MoonBit source code:

a. -text (default) embeds text files, while -binary embeds binary files.

b. -name specifies the generated variable name, with a default value of resource.

The commands are executed in the directory where the moon.pkg.json file resides.

Sample: moon.pkg.json

{
"pre-build": [
{
"input": "a.txt",
"output": "a.mbt",
"command": ":embed -i $input -o $output"
}
]
}

if a.txt is:

hello,
world

After executing moon build, the following a.mbt file is generated in the directory where moon.pkg.json is located:

let resource : String =
#|hello,
#|world
#|
  • moon test --target all now supports backend suffixes ([wasm], [js], etc.).
$ moon test --target all
Total tests: 0, passed: 0, failed: 0. [wasm]
Total tests: 0, passed: 0, failed: 0. [js]

Weekly 2024-09-03

· 5 min read

Language Updates

  • Breaking Change: String interpolation syntax changed from \() to \{} where more expression types within \{} are supported. For example:
fn add(i: Int) -> Int {
i + 1
}

fn main {
println("\{add(2)}") // cannot invoke function in \() before
}
  • New Optional Argument: Supported a new optional argument syntax where the compiler automatically inserts the Some constructor. Optional arguments of type Option are common, with a default value of None, and explicitly passed arguments use the Some constructor. The new syntax ~label? : T simplifies this process:
fn image(~width?: Int, ~height?: Int, ~src: String) -> Unit

Previously, we had to manually insert lots of Some constructors with the default value, which is quite troublesome.

image(width=Some(300), height=Some(300), src="img.png")

Therefore, MoonBit introduced a new optional argument syntax ~label? : T, which is quite similar to ? in map pattern:

fn image(~width?: Int, ~height?: Int, ~src: String) -> Unit

At this point, width and height are optional arguments with a default value of None, and their types in the function body are Int?. When calling image, if you want to provide values for width and height, you don't need to manually insert Some; the compiler will automatically do it for you:

image(width=300, height=300, src="img.png")

If you need to pass an Int? type value directly to image, you can use the following syntax:

fn image_with_fixed_height(~width? : Int, ~src : String) -> Unit {
// `~width?` is shorthand for `width?=width`
image(~width?, height=300, src="img.png")
}
  • Range Operators: Added support for range operators ..< (upper bound exclusive) and ..= (upper bound inclusive) to simplify for loops:
fn main {
for i in 1..<10 {
println(i)
}
}

Currently, the range operators only support the built-in types Int, UInt, Int64, and UInt64, and they can only be used within for .. in loops. In the future, these limitations may be relaxed.

  • expr._ replaces <expr>.0: Introduced expr._ syntax for accessing newtypes. The previous syntax <expr>.0 will be deprecated in the future, and currently, it will trigger a warning. The purpose of this change is to simplify the usage of newtype when wrapping a struct or tuple type by automatically forwarding field access to the underlying type, making it easier to use newtype.

  • Trait Implementation Consistency: Implementing a trait now includes consistency checks to ensure that all impl signatures match. For example:

trait MyTrait {
f1(Self) -> Unit
f2(Self) -> Unit
}

// The signatures of these two `impl` blocks are inconsistent,
// with `f2` being more generic. However, because `impl` is used
// to implement a specific trait, this generality in `f2` is meaningless.
// All `impl` blocks for the same trait and type should have consistent signatures.
impl[X : Show] MyTrait for Array[X] with f1(self) { .. }
impl[X] MyTrait for Array[X] with f2(self) { ... }

Core Update

  • Deprecate xx_exn: Functions named xx_exn have been renamed to unsafe_xx (e.g., unsafe_pop, unsafe_nth, unsafe_peek).

  • Breaking Change: Converting floating-point numbers to strings now conforms to the ECMAScript standard.

  • Function Update: The op_as_view function type signature has been updated for compatibility with the new optional argument syntax.

Before:

fn op_as_view[T](self : Array[T], ~start : Int, ~end : Int) -> ArrayView[T]

Now:

fn op_as_view[T](self : Array[T], ~start : Int, ~end? : Int) -> ArrayView[T]

This allows the Iter type to implement the op_as_view method, enabling slice syntax:

fn main {
let it: Iter[Int] = [1, 2, 3, 4].iter()[1:2] // slice syntax on iter

for x in it {
println(x) // 2
}
}

As the new optional argument syntax ~end? : Int is backward compatible, all previous ways of calling op_as_view still work and maintain the same semantics.

  • Renaming: Int::to_uint and UInt::to_int have been renamed to Int::reinterpret_as_uint and UInt::reinterpret_as_int.

  • Removal and Fixes: The BigInt::lsr function was removed, and bug fixes and performance improvements were made to BigInt.

Toolchain Updates

  • Breaking Change: The Diagnostic information text for moon {build,check,test,run} (such as printed errors and warnings) has been moved from stdout to stderr to avoid pollution of stdout output with error and warning details when running moon {test,run}. If your tools rely on stdout text-format diagnostics, please update your code accordingly.

    JSON mode output is unaffected.

  • MoonBit AI: Supports batch generation of tests and documentation.

ai-package

  • New Feature: Snapshot testing is now supported, similar to inspect!, but results are written to a file. For example, when running moon test -u, the following test block will generate a file 001.txt in the __snapshot__ folder:
test (it : @test.T) {
it.write(".")
it.writeln("..")
it.snapshot!(filename="001.txt")
}

Note that snapshot testing ignores LF and CRLF differences.

  • Build Process Update: moon build now supports building projects even without is-main or link fields in moon.pkg.json. These packages will only generate a core file without linking to wasm/js.

  • Formatting Update: moon fmt now supports incremental formatting, initially formatting all .mbt files, then only formatting changed files in subsequent runs.

IDE Updates

  • Project-Level Goto References: The IDE now supports project-level goto references. For example, you can find all references to a function like inspect within the core. In the pic, all references calling inspect are found:

reference

  • Test Block Debugging: Test blocks allows for quick debugging through codelens.

codelens

weekly 2024-08-19

· 2 min read

Language Update

  • MoonBit Beta Preview

MoonBit Beta Preview achieved major features of modern generic system, precise error handling, and efficient iterators, and an AI-powered toolchain, offering real use cases in cloud and edge computing. Read our latest blog for details.

  • Error Type Printing Improvement

Error type printing now allows error types that implement Show to display more detailed information when used as Error. For example:

type! MyErr String derive(Show)
fn f() -> Unit! { raise MyErr("err") }
fn main {
println(f?()) // Output: Err(MyErr("err"))
}
  • Test Block Enhancement

Added support for parameters in test blocks. The parameter type must be @test.T.

test "hello" (it: @test.T) {
inspect!(it.name, content="hello")
}

Build System Update

  • Fixed an issue where moon info would write the wrong mbti file path when the source field was set in moon.mod.json.

Core Update

  • Added last function to array and fixedarray.

  • Frequently used functions in the test package have been moved to builtin, and the old functions have been deprecated.

  • Added last, head, and intersperse methods to iter.

IDE Update

  • MoonBit IDE now supports the VS Code Test Explorer.

test explorer

  • Online IDE Multi-Package Editing

The online IDE now supports multi-package editing. You can develop MoonBit modules with package dependencies just like in a local environment. In this example, the main package depends on the lib package.

IDE

Other Update

  • MoonBit docs domain is now: https://docs.moonbitlang.com.

  • As MoonBit's language features stabilize and reaches the beta preview, update announcements will be adjusted to once every two weeks.

weekly 2024-08-12

· 5 min read

Language Update

  • Added support for for .. in loops based on Iter and Iter2 types:

    fn main {
    for x in [1, 2, 3] {
    println(x)
    }
    for k, v in {"x": 1, "y": 2} {
    println("\{k} => \{v}")
    }
    }

    You can bind one or two variables between for and in to iterate over elements in Iter. A single-variable loop for x in expr1 iterates over expr1.iter() : Iter[_], while a two-variable loop for x, y in expr2 iterates over expr2.iter2() : Iter2[_, _]. Underscores can be used to ignore elements, but pattern matching is not allowed between for and in.

    The loop body in for .. in can use control flow statements like return/break/raise:

    test "for/in" {
    // `iter2` for arrays iterates over index + elements
    for i, x in [1, 2, 3] {
    assert_eq!(i + 1, x)
    }
    }

-Introduced new string interpolation syntax \{}, deprecating the old \(). This allows embedding more complex expressions directly into strings. Future updates will relax syntax restrictions within string interpolations, such as supporting \{1 + 2} and \{x.f(y)}.

"hello, \(name)!" // warning: deprecated
"hello, \{name}!" // new syntax
  • Expanded numerical handling: Added a new built-in BigInt type for managing large values beyond the range of regular integers.
// BigInt literals end with N
let num = 100000000N

// Like Int literals, underscores are allowed between digits. Hexadecimal, octal, and binary formats are also supported.
let n2 = 0xFFFFFFFFN
let n3 = 0o77777777N
let n4 = 0b1111111100000000N
let n5 = 1234_4567_91011N

// If the type is explicitly BigInt, the N suffix is not required
let n6 : BigInt = 1000000000000000000

// Pattern matching also supports BigInt
match 10N {
1N => println(1)
10N => println(10)
100 => println(100)
}
  • Added support for declaring error types using enums. For example, errors in the Json package can be declared as follows:

    pub type! ParseError {
    InvalidChar(Position, Char)
    InvalidEof
    InvalidNumber(Position, String)
    InvalidIdentEscape(Position)
    } derive(Eq)

    You can also use labeled and mutable arguments within error types using enums, the same as how you would use them in regular enum types.

type! Error1 {
A
B(Int, ~x: String)
C(mut ~x: String, Char, ~y: Bool)
} derive(Show)

fn f() -> Unit!Error1 {
raise Error1::C('x', x="error2", y=false)
}

fn g() -> Unit!Error1 {
try f!() {
Error1::C(_) as c => {
c.x = "Rethrow Error2::C"
raise c
}
e => raise e
}
}

fn main {
println(g?()) // Err(C(x="Rethrow Error2::C", 'x', y=false))
}
  • Introduced catch! syntax to rethrow errors that are not handled within an error handler. By using catch!, you can more easily manage and propagate errors through your code, simplifying the process of error handling. For example, the function g above can be rewritten as:
fn g() -> Unit!Error1 {
try f!() catch! {
Error1::C(_) as c => {
c.x = "Rethrow Error2::C"
raise c
}
}
}
  • The generated JavaScript code no longer relies on the TextDecoder API. If Node.js support for TextDecoder improves in the future, we might consider adding it back.

IDE Update

  • Fixed an issue where the source code in the core library couldn't load in web-based VSCode plugin while debugging. Debugging features in MoonBit IDE are now functional.

  • MoonBit IDE now supports auto-completion for constructors in pattern matching based on type:

match (x : Option[Int]) {

// ^^^^ Auto-completes `None` and `Some`
}

Core Update

  • Added a new Iter2 type for iterating over collections with two elements, like Map, or iterating over arrays with an index:
fn main {
let map = {"x": 1, "y": 2}
map.iter2().each(fn (k, v) {
println("\{k} => \{v}")
})
}

Compared to Iter[(X, Y)], Iter2[X, Y] offers better performance and native support for for .. in loops.

  • Moved @json.JsonValue to the @builtin package and renamed it to Json. @json.JsonValue is now an alias for Json, so this change is backward compatible.

  • Added a ToJson interface in @builtin to represent types that can be converted to Json.

Build System Update

  • Added -C/--directory commands to moon check|build|test, equivalent to --source-dir, to specify the root directory of a MoonBit project, i.e., where moon.mod.json is located.

  • Updated the root-dir in moon.mod.json to source. This field specifies the source directory for modules, and the value of the source field can be a multi-level directory but must be a subdirectory of the directory containing moon.mod.json, e.g., "source": "a/b/c".

    This field is introduced because package names in MoonBit modules are related to file paths. For example, if the current module name is moonbitlang/example and a package is located at lib/moon.pkg.json, you would need to import the package using its full name moonbitlang/example/lib. Sometimes, to better organize the project structure, we may want to place the source code in the src directory, such as src/lib/moon.pkg.json. In this case, you would need to use moonbitlang/example/src/lib to import the package. However, generally, we do not want src to appear in the package path, so you can specify "source": "src" to ignore this directory level and still import the package as moonbitlang/example/lib.

Toolchain Update

  • MoonBit AI supported generating code explanations: In MoonBit IDE, click the MoonBit logo and select /explain to get code explanations which will appear on the right side. Don't forget to click 👍 or 👎 to give us your feedback.

ai explain

weekly 2024-08-05

· 5 min read

Language Update

  • JSON Literal Support for Array Spread:
let xs: Array[@json.JsonValue] = [1, 2, 3, 4]
let _: @json.JsonValue = [1, ..xs]
  • Type Alias Support: Added support for type aliases, primarily for gradual code refactoring and migration rather than just giving types short names. For example, if you need to rename @stack.Stack to @stack.T, doing it all at once would require modifying many places that use @stack.Stack, which could easily cause conflicts in large projects. If a third-party package uses @stack.Stack, it would result in a breaking change. With type alias, you can leave an alias for @stack.Stack after renaming it, so existing code won't break:
/// @alert deprecated "Use `T` instead"
pub typealias Stack[X] = T[X]

Then, you can gradually migrate the usage of @stack.Stack, giving third-party users time to adapt to the new name. Once the migration is complete, you can remove the type alias. Besides type renaming, typealias can also be used for migrating type definitions between packages, etc.

  • Support for Defining New Methods on Trait Objects:
trait Logger {
write_string(Self, String) -> Unit
}

trait CanLog {
output(Self, Logger) -> Unit
}

// Define a new method `write_object` for the trait object type `Logger`
fn write_object[Obj : CanLog](self : Logger, obj : Obj) -> Unit {
obj.output(self)
}

impl[K : CanLog, V : CanLog] CanLog for Map[K, V] with output(self, logger) {
logger.write_string("Map::of([")
self.each(fn (k, v) {
// Use `Logger::write_object` method for simplification
logger
..write_string("(")
..write_object(k)
..write_string(", ")
..write_object(v)
.write_string(")")
})
logger.write_string("])")
}
  • [Breaking Change] Error Type Constraint: In the return type T!E that may return errors, the error type E must be a concrete error type declared with the type! keyword. Currently, two declaration methods are supported:
type! E1 Int   // error type E1 has one constructor E1 with an Integer payload
type! E2 // error type E2 has one constructor E2 with no payload

In function declarations, you can use these concrete error types for annotations and return specific errors using raise, for example:

fn f1() -> Unit!E1 { raise E1(-1) }
fn f2() -> Unit!E2 { raise E2 }
  • Default Error Type: Added a built-in Error type as the default error type. Functions can use the following equivalent declarations to indicate they may return an Error type error:
fn f1!() -> Unit { .. }
fn f2() -> Unit! { .. }
fn f3() -> Unit!Error { .. }

For anonymous functions and matrix functions, you can use fn! to indicate the function may return an Error type error, for example:

fn apply(f: (Int) -> Int!, x: Int) -> Int! { f!(x) }

fn main {
try apply!(fn! { x => .. }) { _ => println("err") } // matrix function
try apply!(fn! (x) => { .. }) { _ => println("err") } // anonymous function
}

Errors returned using raise and f!(x) can be cast up to the Error type, for example:

type! E1 Int
type! E2
fn g1(f1: () -> Unit!E1) -> Unit!Error {
f1!() // error of type E1 is cast to Error
raise E2 // error of type E2 is cast to Error
}

Error types can be pattern matched. When the matched type is Error, pattern matching completeness checks require adding a branch using the _ pattern, whereas this is not needed for specific error types, for example:

type! E1 Int
fn f1() -> Unit!E1 { .. }
fn f2() -> Unit!Error { .. }
fn main {
try f1!() { E1(errno) => println(errno) } // this error handling is complete
try f2!() {
E1(errno) => println(errno)
_ => println("unknown error")
}
}

In addition, if different kinds of error types are used in a try expression, the entire try expression will be handled as returning the Error type, for example:

type! E1 Int
type! E2
fn f1() -> Unit!E1 { .. }
fn f2() -> Unit!E2 { .. }
fn main {
try {
f1!()
f2!()
} catch {
E1(errno) => println(errno)
E2 => println("E2")
_ => println("unknown error") // currently needed to ensure completeness
}
}

We will improve this in future versions to make completeness checks more precise.

  • Error Bound: Added Error bound to constrain generic parameters in generic functions, allowing them to appear as error types in function signatures, for example:
fn unwrap_or_error[T, E: Error](r: Result[T, E]) -> T!E {
match r {
Ok(v) => v
Err(e) => raise e
}
}

Core Update

  • Bigint: Changed Bigint to a built-in type.

Build System Update

  • Debug Single .mbt File: Added support for debugging a single .mbt file.

  • Parallel Package-Level Testing: moon test now supports parallel testing at the package level.

  • root-dir Field in moon.mod.json: Added root-dir field to specify the source directory of the module. Only supports specifying a single-level folder, not multi-level folders. moon new will default to setting root-dir to src. The default directory structure for exec and lib modes is now:

exec
├── LICENSE
├── README.md
├── moon.mod.json
└── src
├── lib
│ ├── hello.mbt
│ ├── hello_test.mbt
│ └── moon.pkg.json
└── main
├── main.mbt
└── moon.pkg.json

lib
├── LICENSE
├── README.md
├── moon.mod.json
└── src
├── lib
│ ├── hello.mbt
│ ├── hello_test.mbt
│ └── moon.pkg.json
├── moon.pkg.json
└── top.mbt

Toolchain Update

  • MoonBit AI: Now supports generating documentation.

ai file

weekly 2024-07-29

· 3 min read

Language Updates

  • Simplified Error Handling Syntax: The syntax for capturing potential errors in function calls has been updated from f(x)!! to f?(x), which returns a Result type.
fn div(x: Int, y: Int) -> Int!String {
if y == 0 {
raise "division by zero"
}
x / y
}

fn main {
let a = div?(10, 2)
println(a) // Ok(5)
}
  • JSON Literal Support: Added support for JSON literals. Numbers, strings, arrays, and dictionary literals are now overloaded to provide a JSON type, which can be used for constructing/matching JSON when the type is expected.
fn json_process(x: @json.JsonValue) -> Double {
match x {
{
"outer": {
"middle": {
"inner": [
{ "x": Number(x) },
{ "y": Number(y) }
]
}
}
} => x + y
_ => 0
}
}

fn main {
let x: @json.JsonValue = {
"outer": { "middle": { "inner": [{ "x": 42 }, { "y": 24 }] } },
"another_field": "string value",
}
json_process(x) |> println
}
  • Derive Hash Support: Added derive(Hash) to automatically generate implementations of the Hash trait for types.
enum MyType {
C1
C2(Int)
} derive(Eq, Hash)

fn main {
let m = {}..set(C1, 1)..set(C2(1), 2)..set(C2(2), 3)
println(m[C1]) // Some(1)
println(m[C2(1)]) // Some(2)
println(m[C2(3)]) // None
}
  • JavaScript Backend Update: Removed dependency on TextDecoder in generated code.

Core Updates

  • Function Signature Changes:

    • Array::new_with_index -> Array::makei
    • FixedArray::new / FixedArray::new_with_index -> FixedArray::makei
    • FixedArray::empty -> FixedArray::new
  • Removed Debug Trait

Build System Updates

  • Single .mbt File Support: Now supports running a single .mbt file (not within a package). Usage: moon run xx.mbt (links to the standard library by default, requires a main function in xx.mbt).

  • Shell Completion: Moon now supports shell completion.

    Example Usage: Zsh

    Zsh automatically reads all scripts under $FPATH on startup. You can place the completion script in any path under $FPATH.

    To manage more conveniently, create a new path for completions $ZSH/completions and add it to $FPATH.

    mkdir $ZSH/completions
    echo "FPATH+=$ZSH/completions" >> ~/.zshrc
    moon shell-completion > $ZSH/completions/_moon
    . ~/.zshrc # Reload to use, or omz reload

    To maintain portability, you can directly add the following line to .zshrc:

    eval "$(moon shell-completion --shell=zsh)"

    shell-completion.gif

    For the effect shown in the pic, you also need to install the zsh-autocomplete and zsh-autosuggestions plugins.

Toolchain Updates

  • moonfmt Fixes:

    Fixed an issue with array spread formatting adding braces.

    Adjusted parenthesis inference for pattern matching to avoid using wildcards.

  • VS Code Auto-test Support: Now supports automatic test generation for functions, including black-box tests (ending in _test.mbt) and white-box tests (unit tests).

  • Run | Debug Buttons for .mbt: Added support for single .mbt file Run | Debug buttons.

weekly 2024-07-22

· 6 min read

Language Updates

Breaking Changes

  • Error Handling Syntax: The syntax for error handling has changed from f(x)! to f!(x). This change facilitates IDE auto-completion, allowing the function name and ! to be completed together and enabling special rendering for f!.

  • Local Functions: If a local function can return an error type, it must be annotated. For example:

fn toplevel() -> Unit!String {
fn local1() -> Unit!String { raise "err" } // local function
fn local2() -> _!_ { raise "err" } // local function
apply!(fn (_x) -> Int!String { raise "err" }, 42) |> ignore // anonymous function
}

fn apply(f: (Int) -> Int!String, x: Int) -> Int!String {
f!(x)
}

Additionally, the fn! and fn local!() syntax can be used to indicate that a function might return an error and to let the compiler infer the error type. The former is used for lambda and anonymous functions, and the latter for regular local functions. For example:

fn toplevel() -> Unit!String {
fn local!() { raise "err" } // local function
apply!(fn! (_x) { raise "err" }, 42) |> ignore // anonymous function
apply!(fn! { _ => raise "err" }, 42) |> ignore // lambda function
}

Try Expression Enhancements

The try expression has been improved. The complete usage of the try expression is as follows:

fn test_try() -> Unit {
fn f() -> Int!String {
raise "err"
}
try {
println("this is try body")
f!()
} except {
err => println(err)
} else {
val => println(val)
}
}
  • The except keyword is used to handle errors returned by the try body.
  • The else keyword is used to handle normal returns from the try body.

Simplified cases for the try expression:

  • The else branch can be omitted if no handling of the return value is needed.
  • The try body can be a simple expression.
  • The except keyword can be omitted.

For example:

fn test_try() -> Result[Int, String] {
fn f() -> Int!String {
raise "err"
}
try Ok(f!()) { err => Err(err) }
}

Integer Literal Overloading in Pattern Matching

Integer literal overloading has been added to pattern matching:

fn is_42(x : Double) -> Bool {
match x {
42 => true
_ => false
}
}

Upcoming Breaking Changes

  • Map Pattern Matching: The semantics of { "key" : value } in map patterns will be changed this week.

    Previously, in a map pattern, value was of type Option[_] and would match regardless of whether "key" existed.

    In the future, { "key": value } will match only if "key" exists, and value will directly match the actual content of "key".

    To match both the presence and absence of "key", use the new syntax { "key"? : value_or_none }, where value_or_none is of type Option[_].

  • Trait Implementation Syntax: The old syntax for explicit trait implementation fn Trait::method(...) will be removed this week. Existing code should use the impl Trait for SelfType with method(...) syntax. The fn Trait::method(...) syntax will be repurposed for defining methods on trait object types in the future.

IDE Updates

  • Run | Debug Codelens for the main function: The main function will now have a Run | Debug Codelens for quick execution and debugging. Currently, quick debugging only supports the JavaScript backend.

ide

  • AI-Generated Tests for Top-Level Functions: Top-level functions now support generating tests through AI via Codelens. * Please note that this feature is still under development and may not be stable.

    1. Click the "Gen test" button.

    step1

    1. A new window will appear on the right side, displaying the generated test.

    step2

    1. Click "Reject" to remove unwanted tests, or "Insert" to add the test into the source file.

Core Updates

  • Show Trait Redefinition: The Show trait has been redefined for more accurate behavior. The new definition of the Show trait is as follows:
pub trait Show {
// The `output` function is for implementing `Show` for composite types.
// It writes a string representation of `Self` to a `Logger`.
output(Self, Logger) -> Unit
// The `to_string` method is for `Show` users.
// It can be used for string interpolation and `println`.
// `to_string` has a default implementation using `output`.
// Some types, like `String`, will override `to_string` to modify its behavior.
to_string(Self) -> String
}

The new Show resolves the incorrect behavior of String. Previously, if a composite type contained a String, the Show implementation would not escape this String, leading to incorrect results. Conversely, String used directly in string interpolation and println should not be escaped. The new Show definition and its core implementation address both needs.

For Show implementers, please keep in mind:

  • Implement the output method, not the to_string method.

  • When recursively calling the Show implementation of substructures (e.g., struct fields), use Show::output(..., logger) instead of to_string.

  • The default implementation of Show::to_string is not a method, so it cannot be called with a dot. To support .to_string() syntax, add a helper definition like this:

pub fn to_string(self: ...) -> String {
Show::to_string(self)
}
  • Upcoming Breaking Change: Deprecation of Debug Trait: Following the redefinition of the Show trait, the functionality of the Debug trait is now completely covered by Show. Therefore, the Debug trait is deprecated and will be removed soon.

  • Data Structure Type Renaming: Data structure type names have been changed to T. For example:

// old
// let m : @hashmap.HashMap[Int, Int] = @hashmap.new()

// new
let m : @hashmap.T[Int, Int] = @hashmap.new()

The following data structures have been renamed to T:

  • deque
  • hashmap
  • hashset
  • immut/array
  • immut/hashmap
  • immut/hashset
  • immut/list
  • immut/priority_queue
  • immut/sorted_map
  • immut/sorted_set
  • queue
  • priorityqueue
  • rational
  • sorted_map
  • sorted_set

Build System Updates

  • Build Output Adjustment: The build process output has been adjusted to only print a command if that command fails.

  • Target Specification for moon check|build|test|bundle: Previously, the --target option only supported three choices: wasm|wasm-gc|js. Now it supports specifying multiple backends to run in parallel. The supported options have changed to wasm|wasm-gc|js|all, and they can be combined with commas. The --serial option changes the parallel execution of different backends to serial execution, but the same backend will still execute concurrently. For example:

    • moon test --target all runs tests for all backends.
    • moon test --target js,wasm runs tests for js and wasm backends.
    • moon test --target js,wasm --serial is equivalent to running moon test --target js; moon test --target wasm sequentially.
  • Memory Configuration in moon.pkg.json: Added heap-start-address and import-memory configuration for ["link"]["wasm"|"wasm-gc"] in moon.pkg.json:

{ 
"link": {
"wasm": {
"heap-start-address": 65536,
"import-memory": {
"module": "xxx",
"name": "yyy"
}
}
}
}
  • Default Protocol for moon new: The default protocol for moon new has been changed to Apache-2.0.

weekly 2024-07-15

· 4 min read

Language Update

  • New syntax: Cascade Operator

MoonBit introduces the ".." operator, allowing for chaining of mutable API calls while keeping the mutable API signatures clean (still returning Unit).

As illustrated, to avoid repetitive typing of array and to distinguish between immutable and mutable operations, we apply the cascade operator. For all methods within a type that return Unit, you can use .. to chain these method calls without needing to change the return type of these methods. array..push(5)..sort() is equivalent to sequentially calling the mutable operations array.push(5) and array.sort(), returning array.

cascade.png

  • The @package.Type::[...] syntax has been removed. It is recommended to use @package.of([...]) as a replacement.

  • Sourcemap Improvement: Debug mode sourcemaps generated with --target js now support the names table. This means that when debugging with devtools or VSCode, local variables will be displayed with their original names and can be accessed with their original names in the Debug console. This greatly improves the debugging experience.

Before: names_before.png

After: names_after.png

  • Spread Syntax for Arrays: Arrays now support spread syntax, which allows any object expression that supports the iter() method to be expanded into a set of Iter[_] objects at the syntax level during array construction.

Array Expression:

let u1 = [1, 2, 3]
let u2 = [5, 6, 7]

fn main {
let v = [..u1, 4, ..u2, 8]
println(v)
}

// [1, 2, 3, 4, 5, 6, 7, 8]

Map Expression (returns key-value pairs):

  let map : @sorted_map.T[Int,String] = @sorted_map.of([(3, "c"), (2, "b"), (1, "a")])

fn main {
let v = [..map, (4, "d")]
println(v)
}

// [(1, a), (2, b), (3, c), (4, d)]
  • C-like Bitwise Operators Support: MoonBit now supports C-like bitwise operators (^, &, |, <<, >>) with the same precedence as in C. To avoid ambiguity, when expressions contain several operators with unclear precedence, the formatter will insert additional parentheses to improve readability.

  • Integer Literal Overloading: For known types, literals for types other than Int can omit the special markers like U, L, etc:

fn main {
let uint : UInt = 42
let int64 : Int64 = 42
let double : Double = 42
}

Core Update

  • The Hash Trait will be updated to the following form.
trait Hash {
hash_iter(Self, Hasher) -> Unit
hash(Self) -> Int
}

impl Hash with hash(self) {
let hasher = Hasher::new()
hasher.hash_iter(self)
hasher.finalize()
}

Build System Update

Testing Mechanism Adjustment

Moon now automatically wraps source code files ending in _bbtest.mbt as black-box tests. Moon will automatically include the package as a dependency when compiling *_bbtest.mbt. We'll continue to adjust MoonBit's testing mechanism for a better developing and testing experience.

Background

Currently, a MoonBit project can have three types of tests: white-box tests, black-box tests, and inline tests.

  • White-box tests: Written in *_test.mbt, the build system packages and compiles the current package's *.mbt and *_test.mbt together. Therefore, *_test.mbt can access private members of the current package. These tests can use dependencies specified in the import and test-import fields in moon.pkg.json. test-import is only used in white-box tests and will not be packaged into the final build product.

  • Black-box tests: Written in *_bbtest.mbt, the build system automatically includes the package as a dependency when compiling *_bbtest.mbt. *_bbtest.mbt can only access public members of its package, simulating the perspective of an external user using the package. These tests can use dependencies specified in the *_bbtest.mbt field in moon.pkg.json (as well as the package itself, which does not need to be explicitly listed in the bbtest-import field). bbtest-import is only used in black-box tests and will not be packaged into the final build product.

  • Inline tests: Can be written directly in *.mbt(note that *.mbt here excludes the above-mentioned *_test.mbtand *_bbtest.mbt) and can access private members of the current package. These tests only use dependencies specified in the import field in moon.pkg.json.

Future Test Naming Change

In the future, the current *_test.mbt (white-box tests) will be renamed to *_wbtest.mbt, and *_bbtest.mbt (black-box tests) will be renamed to *_test.mbt to encourage black-box tests.

Toolchain Update

  • moonfmt improvements:

    • When there are syntax errors in the source code, formatting will ignore the relevant code, allowing code formatting to still be executed.

    • When expressions contain several operators with unclear precedence, the formatter will insert additional parentheses to improve readability.

  • MoonBit IDE added support for black-box tests (*_bbtest.mbt).

weekly 2024-07-08

· 3 min read

Language Update

  • [Breaking change] Modified array slice syntax from arr[start..end] to arr[start:end] similar to Python. This change is made to avoid syntax conflicts with the upcoming cascade method call x..f(). The old syntax will be deprecated soon.

  • [Breaking change on wasm backend] Code in fn init is now compiled into the start section. Previous versions compiled code from both fn init and fn main into a special function exported as "_start", therefore, host environments needed to call "_start" for initialization and execution of the main function. The new version uses the wasm standard's start section to execute fn init during module loading, eliminating the need to call "_start" for fn init. "_start" is only necessary when executing main.

  • [Breaking change] test block no longer returns Result type results. Error handling mechanisms are now used within test blocks to handle test failures. The standard library's inspect function and helpers like @test.eq from the @test package are encouraged for writing tests, for example:

test "unwrap should return value on Some" {
let some : Int? = Some(42)
inspect(some, content="Some(42)")!
@test.eq(some.unwrap(), 42)!
}
  • Supports using @pkg.C to access constructors across packages. For example, if @pkgA contains the following declaration:
// pkgA
pub enum E1 { C1 }

Now, in another package, E1's constructor C1 can be directly used, for instance:

// pkgB
fn main {
debug(@pkgA.C1)
}

In the same package, encountering duplicate public constructors will result in an error, for example:

pub enum E1 {
C1
}

pub enum E2 {
C1
^^ ------ There can be at most one public constructor with name C1.
}

Core Update

  • Migrated to a new error handling mechanism.

  • Migrated to unsigned integers, removed old compare_u, div_u, mod_u functions for Int and Int64 types; API adjustments include:

    • Int32.trunc_double_u, Int64.trunc_double_u changed to UInt.trunc_double, UInt64.trunc_double.
    • Int64::extend_i32_u changed to UInt64::extend_uint.
    • Double::convert_i32_u, Double::convert_i64_u changed to Double::convert_uint, Double::convert_uint64.

Build System Update

  • moon version --all displays moonrun's version information:
$ moon version --all
moon 0.1.20240705 (0e8c10e 2024-07-05) ~/.moon/bin/moon
moonc v0.1.20240705+7fdd4a042 ~/.moon/bin/moonc
moonrun 0.1.20240703 (52ecf2a 2024-07-03) ~/.moon/bin/moonrun
  • Modified moon new project creation to default the license field to empty.

  • Diagnostics information rendering is now enabled by default. render

Toolchain Update

  • VSCode plugin defaulted to installing the corresponding plugin version's toolchain, changed from always installing the latest version.