Skip to main content

2024-11-18

Β· 4 min read

Language Updates​

  • Trait now has abstract and pub(readonly) visibility.
  1. An abstract/readonly trait behaves the same as a fully public trait within the current package.
  2. Outside the current package, you can't call methods in an abstract trait directly, and you can't write new implementations of an abstract trait.
  3. A pub(readonly) trait cannot be implemented outside the current package, but methods can be called directly.
  4. The default visibility for a trait has been changed from private to abstract. To explicitly define a private trait, use the priv trait keyword.
  5. When implementing an abstract or readonly trait, at least one method must be implemented using the impl Trait for Type syntax to allow external usage of that implementation.

Example of abstract trait:

trait Number {
add(Self, Self) -> Self
}

pub fn add[N : Number](x : N, y : N) -> N {
Number::add(x, y)
}

impl Number for Int with add(x, y) { x + y }
impl Number for Double with add(x, y) { x + y}

Using the moon info command to generate a .mbti interface file, the exported interface is as follows:

trait Number
impl Number for Int
impl Number for Double

fn add[N : Number](N, N) -> N

Externally, the add function can only be called for Int and Double. The compiler ensures that the Number trait can only be implemented for Int and Double.

  • Visibility Adjustments for Types and Traits

To encourage the use of readonly types/traits to provide more robust APIs, we plan to change the default visibility for type and trait definitions to readonly in the future.

To declare fully public visibility:

  1. Types: Use pub(all) struct/enum/type.
  2. Traits: Use pub(open) trait to allow external implementations.

Currently:

  1. pub still means fully public visibility, but the compiler will issue a warning recommending migration to pub(all) or pub(open).
  2. The moon fmt formatter will automatically migrate existing pub to pub(all) or pub(open).
  • New Private Fields in struct Types:

Public structs can now have private fields by adding the priv keyword before specific fields.

  1. External Visibility: Private fields are completely hidden from the outside world. They cannot be read or modified externally.
  2. If a struct contains private fields, it cannot be directly constructed using literals from outside the module. ****However, the struct update syntax ({ ..old_struct, new_field: ... }) can be used to update public fields while maintaining the private field's state.
  • New suffix-style label argument syntax

The new syntax is equivalent to moving ~ after the identifier, and the ~ can be omitted in the case of forwarded options of the form ~label?

enum Foo {
Bar(label~ : Int, Bool)
}
fn f(a~ : Int, b~ : Int = 0, c? : Int) -> Unit {...}
fn main {
f(a=1, b=2, c=3)
f(a=1, c?=Some(3))// b Default values are declared and can be omitted. c Forwarding option Syntax as usual
let a = 1
let c = Some(3)
f(a~, c?) // Like at the declaration, punning takes the form of a suffix
}

The old syntax is deprecated:

enum Foo {
//deprecated
Bar(~label : Int, Bool)
}
//deprecated
fn f(~a : Int, ~b : Int = 0, ~c? : Int) -> Unit {}
fn main {
let a = 1
let c = Some(3)
f(~a, ~c?) //deprecated
}

You can migrate code to the new syntax with moon fmt.

  • Reserved Keywords

A series of reserved keywords have been added for future use by the compiler. Currently:

  1. Using these reserved keywords will produce a warning, advising users to migrate.
  2. These keywords may become language keywords in future versions.

Reserved keywords are:

module
move
ref
static
super
unsafe
use
where
async
await
dyn
abstract
do
override
typeof
virtual
yield
local
method
alias
  • Planned Changes to Bytes Type

The standard library's Bytes type will be changed to an immutable type. Therefore, functions that modify Bytes will be marked as deprecated.

Alternatives for mutable byte operations:

  1. Array[Byte]
  2. FixedArray[Byte]
  3. Buffer
  • Rename of @test.is

The @test.is function has been renamed to @test.same_object. @test.is is now deprecated. The is keyword will become a reserved word in the future.

IDE Updates​

  • Fixed an issue where moon fmt --block-style didn't handle document comments like ////| comment correctly.

  • IDE adapted suffix label argument syntax to support gotoref and rename.

  • Support code highlighting in documentation.

  • Fixed an issue in LSP with @ autocompletion when there are internal packages.

Build system update​

  • The -verbose option now outputs the currently running command.

MoonBit ProtoBuf​

2024-11-04

Β· 3 min read

Language Updates​

  • Compile-time Constants Support
    • Introduced support for constants declared with const C = ..., where names start with uppercase letters. Must be built-in numeric types or String. Constants can be used as regular values and for pattern matching. Currently, the values of constants can only be literals.
const MIN_INT = 0x1000_0000
const MAX_INT = 0x7fff_ffff

fn classify_int(x : Int) -> Unit {
match x {
MIN_INT => println("smallest int")
MAX_INT => println("largest int")
_ => println("other int")
}
}

fn main {
classify_int(MIN_INT) // smallest int
classify_int(MAX_INT) // largest int
classify_int(42) // other int
}
  • Improved Unused Warnings
  1. Added detection for unused parameters in enum:
enum E {
// Compiler warns if y is unused
C(~x : Int, ~y : Int)
}

fn f(x : E) -> Unit {
match x {
C(~x, ..) => println(x)
}
}

fn main {
f(C(x=1, y=2))
}
  1. Added detection for unused default parameter values (default off):
// The function `f` is private and that the caller always provides a value for `x` explicitly when calling it.
// If warning 32 is enabled (which is off by default), the compiler will notify the developer that the default value for `x` is unused.
fn f(~x : Int = 0) -> Unit {
println(x)
}

fn main {
f(x=1)
}
  • Direct Function Imports
    • Functions from other packages can now be directly imported without the @pkg. prefix. To set this up, you must declare the functions in the "value" field of the moon.pkg.json configuration file.
{
"import": [
{
"path": "moonbitlang/pkg",
"alias": "pkg",
"value": ["foo", "bar"]
}
]
}

In this example, the functions foo and bar from the package moonbitlang/pkg can be called directly without the prefix @pkg..

  • Native JavaScript BigInt Support

    • BigInt type compiles to native JavaScript BigInt, with efficient pattern matching using switch statements.
  • Experimental: JavaScript backend generates .d.ts for exported functions

    • JavaScript backend now generates .d.ts files based on exported functions specified in moon.pkg.json, enhancing TypeScript/JavaScript integration. (*This feature for exporting complex types is still under design, and for now, it generates TypeScript's any type.)

IDE Updates​

  • Block-line Support

    • Introduced special block-line markers (///|) in top-level comments (///) to enhance code readability and structure: block-line.jpg
    • Use moon fmt --block-style can automatically add these markers. In the future, incremental code parsing and type checking based on block-line markers will further enhance the responsiveness and usability of the Language Server Protocol (LSP), improving development efficiency.
  • MoonBit Online IDE can visit GitHub repos for instant review, edit, and test.

  1. Go to a MoonBit-based repo on GitHub.
  2. Replace github.com with try.moonbitlang.com.
  • Test Coverage Visualization

    • Added support for visualizing test coverage in the test explorer: coverage.jpg
  • AI Features

    • Added /doc-pub command for generating documentation for public functions.
    • Fixed issue where /doc command would overwrite pragmas.
    • Patch now verifies generated test cases: ai-test.jpg

Build System Updates​

  • Package Checking
    • moon check supports checking specified packages and their dependencies: moon check /path/to/pkg.

MoonBit Markdown Library​

  • Open Source
    • The MoonBit Markdown library is now open source, and available for download on MoonBit's package manager mooncakes.io.

2024-10-21

Β· 5 min read

Language Update​

  • MoonBit native backend support

  • Js-string-builtins proposal support for Wasm-gc backend

    When the -use-js-builtin-string compiler option is enabled, MoonBit strings will be represented with JavaScript's string type when targeting the wasm-gc backend. This requires importing string-related functions from the JavaScript host into the generated Wasm executable. This can be achieved using the following option in the JS glue code:

// glue.js
// read wasm file
let bytes = read_file_to_bytes(module_name);
// compile the wasm module with js-string-builtin on
let module = new WebAssembly.Module(bytes, { builtins: ['js-string'], importedStringConstants: "moonbit:constant_strings" });
// instantiate the wasm module
let instance = new WebAssembly.Instance(module, spectest);
  • Integer literal overloading for Byte type
let b : Byte = 65
println(b) // b'\x41'
  • Multiline string interpolation and escape sequence support

    Since multiline strings are sometimes used to store raw strings, which may contain sequences conflicting with escape sequences, MoonBit has extended the existing multiline string interpolation syntax. Users can now control whether to enable interpolation and escape sequences for each line with the following markers:

    $|: enables interpolation and escape

    #|: marks it as a raw string.

    For example:

let a = "string"
let b = 20
let c =
#| This is a multiline string
$| \ta is \{a},
$| \tb is \{b}
#| raw string \{not a interpolation}
println(c)

Print:

 This is a multiline string
a is string,
b is 20
raw string \{not a interpolation}
  • Syntax adjustment for labeled parameters

    The syntax f(~label=value) in function calls and Constr(~label=pattern) in pattern matching has been removed. The form without the ~ symbol is now the only valid syntax: f(label=value) and Constr(label=pattern). However, f(~value) and Constr(~name) remain unaffected.

IDE Update​

  • Fixed highlighting for string interpolation.

Core Update​

  • Introduced StringBuilder in the Builtin package

    StringBuilder has been optimized for string concatenation operations across different backends. For example, on the JS backend, using StringBuilder results in a fivefold performance improvement compared to the previous Buffer implementation. The Buffer in the Builtin package has been deprecated, and its related APIs have been moved to the moonbitlang/core/buffer package. Future updates will involve adjustments to the Bytes and Buffer APIs.

  • Bitwise operations adjustment

    The standard library’s left and right shift functions (lsr, asr, lsl, shr, shl) have been deprecated. Only op_shl and op_shr remain. For bitwise operations like lxor, lor, land, op_shr, and op_shl, infix operators are now recommended for use.

  • Breaking change

    The Last function in immut/List now returns Option[T].

Build System Update​

  • Initial support for the native backend

    • run | test | build | check now support --target native.
    • On the native backend, moon test compiles with tcc in debug mode (default) and cc in release mode (on Unix). Windows is not yet supported.
    • Panic tests are not yet supported.
  • Support for @json.inspectβ€”objects inspected must implement ToJson.

    Example:

enum Color {
Red
} derive(ToJson)

struct Point {
x : Int
y : Int
color : Color
} derive(ToJson)

test {
@json.inspect!({ x: 0, y: 0, color: Color::Red })
}

After running moon test -u, the test block is automatically updated:

test {
@json.inspect!({ x: 0, y: 0, color: Color::Red }, content={"x":0,"y":0,"color":{"$tag":"Red"}})
}

Compared to inspect, the results of @json.inspect can be formatted with code formatting tools:

test {
@json.inspect!(
{ x: 0, y: 0, color: Color::Red },
content={ "x": 0, "y": 0, "color": { "$tag": "Red" } },
)
}

Additionally, moon test automatically performs structured comparisons on the JSON within @json.inspect.

enum Color {
Red
Green
} derive(ToJson)

struct Point {
x : Int
y : Int
z : Int
color : Color
} derive(ToJson)

test {
@json.inspect!(
{ x: 0, y: 0, z: 0, color: Color::Green },
content={ "x": 0, "y": 0, "color": { "$tag": "Red" } },
)
}

The moon test output diff for the following code will look like this:

Diff:
{
+ z: 0
color: {
- $tag: "Red"
+ $tag: "Green"
}
}
  • moon.mod.json supports include and exclude fields. These fields are arrays of strings, with each string following the same format as lines in a .gitignore file. The rules are as follows:

    • If neither include nor exclude fields exist, only the .gitignore file is considered.
    • If the exclude field exists, both the exclude field and .gitignore file are considered.
    • If the include field exists, both exclude and .gitignore are ignored; only files listed in include will be packaged.
    • The moon.mod.json file is always packaged, regardless of the rules.
    • /target and /.mooncakes are always excluded from packaging.
  • Added the moon package command for packaging files without uploading.

    • moon package --list lists all files in the package.
  • Support for moon publish --dry-runβ€”the server will validate the package without updating the index data.

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.