Skip to main content

weekly 2024-02-26

· 4 min read

MoonBit Update

1. Supports cloud-native debugging features.

Now, you can debug MoonBit programs directly in your browser using devtools by visiting try.moonbitlang.com, without installing any software. The steps are as follows:

2. MoonBit now supports functional loop control flow defined with the for keyword.

MoonBit now supports functional loop control flow defined with the for keyword, purely functional yet as efficient as C. For instance, the fib function can be written as follows:

fn fib( n : Int ) -> Int {
for i = 0, a = 1, b = 2
i < n
i = i + 1, a = b, b = a + b {
} else { b }
}

MoonBit's for loop can return a value as an expression. For example, in the program mentioned above, b is used as the value of the entire for loop after the loop ends. It's also possible to return early by using break within for loop body, like:

fn exists(haystack: Array[Int], needle: Int) -> Bool {
for i = 0; i < haystack.length(); i = i + 1 {
if haystack[i] == needle {
break true
}
} else {
false
}
}

Moreover, within the for loop, you can use continue to proceed to the next iteration, just as in traditional languages. MoonBit further offers a parameterized continue to specify the value of the loop variable in the next iteration of the loop, for example:

fn find_in_sorted[T](xs: Array[(Int, T)], i: Int) -> Option[T] {
for l = 0, r = xs.length() - 1; l < r; {
let mid = (l + r) / 2
let k = xs[mid].0
if k == i {
break Some(xs[mid].1)
} else if k > i {
continue l, mid
} else {
continue mid + 1, r
}
} else {
None
}
}

In cases where a return value is not required, the else branch can be omitted, like:

fn print_from_0_to(n: Int) {
for i = 0; i <= n; i = i + 1 {
println(i)
}
}

3. Inline Test Improvements

The return type of tests has been changed from Unit to Result[Unit, String] to represent the outcome of the test:

test "id" {
if (id(10) != 10) { return Err("The implementation of `id` is incorrect.") }
}

The compiler automatically wraps the block {...} in a test "id" {...} statement with Ok(). Therefore, when the block type is Unit and there is no early return, it indicates that the inline test has passed. Combined with the question mark operator, this makes testing more elegant:

fn id(x : Int) -> Int {
x + 1 // incorrect result
}

fn assert(x : Bool, failReason : String) -> Result[Unit,String] {
if x { Ok(()) } else { Err(failReason) }
}

test "id" {
assert(id(10) == 10, "The implementation of `id` is incorrect.")?
}

Running moon test, generates the following output:

➜  my-project moon test
running 1 tests in package username/hello/lib
test username/hello/lib::hello ... ok

test result: 1 passed; 0 failed

running 1 tests in package username/hello/main
test username/hello/main::id ... FAILED: The implementation of `id` is incorrect.

test result: 0 passed; 1 failed

Hello, world!

4. Improved function signature hints in the VS Code extension, now displaying parameter names:

5. Enhanced support for core package development in the VS Code extension.

6. moon new supports the rapid creation of new projects.

  • moon new hello creates an executable project named username/hello inside the hello folder.

  • moon new hello --lib creates a library named username/hello inside the hello folder.

weekly 2024-02-19

· 2 min read

MoonBit Update

  1. Added support for functional for loop control flow. Unlike traditional imperative for loops, the loop variable is immutable. Such a design will also be easier to extract for formal verification in the future.
fn init {
for i = 0; i < 5; i = i + 1 {
debug(i)
// i = i + 4 error: The variable i is not mutable.
}
}

Output:

01234

The functional for loop also supports multiple bindings. Setting it apart from other languages, x and y have semantics of being updated simultaneously in the third expression of the functional for loop:

fn init {
for x = 0, y = 0; x < 10; x = x + 1, y = x + 1 {
// ^~~ the value of x is pre-update
println("x: \(x), y: \(y)")
}
}

Output:

x: 0, y: 0
x: 1, y: 1
x: 2, y: 2
x: 3, y: 3
x: 4, y: 4
x: 5, y: 5
x: 6, y: 6
x: 7, y: 7
x: 8, y: 8
x: 9, y: 9

Functional for loop also supports the use of break and continue.

fn init {
let xs = [0,1,2,3,4,5,6,7,8]
let mut sum = 0
for i = 0, v = xs[0]; i < xs.length(); i = i + 1, v = xs[i + 1] {
if v % 2 == 0 { continue }
if v >= 7 { break }
sum = sum + v
}
debug(sum) //output: 9
}
  1. Improved the wizard for creating projects with moon new, now allowing the selection of creating a lib or an exec project using the arrow keys:

  1. The IDE now supports intelligent autocompletion for the pipeline operator. Functions whose first parameter type matches the type of the expression on the left side of the pipeline will be placed at the top of the completion list, while other completion options will still be displayed further down the list.

  1. Adjusted the pipe expression based on community feedback. Now, the right side of the pipeline operator supports function calls like Double::to_int.
fn init {
debug(3.14 |> Double::to_int) // output: 3
debug(4 |> Array::make('c')) // output: ['c', 'c', 'c', 'c']
}
  1. Fixed an issue in the IDE where inlay hints were incorrectly inserted for infix expressions.

weekly 2024-02-05

· One min read

MoonBit Update

  1. Introduced support for multi-line strings, requiring each line to commence with #|. Multi-line strings allow for breaks and comments between lines, but do not support escape sequences or string interpolation.

  1. Functional loop: A functional style loop. Here, continue is only allowed at the position of tail recursion calls, and within the loop, break can be used to return a value early.

  1. Added support for Trait::method calls: Enables calling trait methods in the form of Debug::debug_write(self, buf).

  1. Supported implicit conversion to trait objects. When a trait object is explicitly required in the context, as SomeTrait will be automatically inserted. For example, in the code below:

Now we can omit as Debug.

  1. Supported inlay hints for function parameters.

  1. Strings and character literals now support Unicode escapes, hexadecimal escapes, and octal escapes.

weekly 2024-01-29

· 2 min read

MoonBit Update

1.New feature ——Trait object

It can explicitly box values of different types but implement the same trait and represent them as the same type, achieving dynamic dispatch of functions.

fn get_show_list() -> List[Show] {
let a = 42 as Show
let b = "xxx" as Show
let c = 3.14 as Show
List::Cons(a, Cons(b, Cons(c, Nil)))
}

fn init {
fn print_show_list {
List::Cons(a, rest) => { println(a); print_show_list(rest) }
List::Nil => ()
}
print_show_list(get_show_list())
}

2. Introduction of the Pipe Operator

The Pipe Operator, provides a syntax similar to method chaining, which can string together multiple consecutive function calls, eliminating the need forlet name = ... code. For example, value |> func1(arg1,arg2) |> func2 is equivalent to:

let a = value
let b = func1(a, arg1, arg2)
func2(b)

Another example:

fn sub2(x : Int, y : Int) -> Int {
x - y
}

fn sum3(x : Int, y : Int, z : Int) -> Int {
x + y + z
}

fn init {
6 |> sub2(5) |> sum3(1,2) |> println()
}

3. Strings support hexadecimal escape using \xFF

fn init {
let data = "\x48\x65\x6c\x6c\x6f"
println(data) //output: Hello
}

4. Inline test change

Now test mode will also run fn init, and the order of execution is before inline test.

5. Moonfmt: Improved indentation of types and long array literals.

Original code:

Formatting before improvements:

Formatting after improvements:

weekly 2024-01-22

· 2 min read

MoonBit Update:

  • Added syntactic sugar for matrix functions to easily define local functions and anonymous functions with pattern matching:
fn init {
fn boolean_or { // local function with pattern matching
true, _ => true
_, true => true
_, _ => false
}
fn apply(f, x) {
f(x)
}
let _ = apply(fn { x => x + 1 }, 42) // anonymous function
let _ = apply(fn { // anonymous function with pattern match
0 => 0
1 => 1
_ => 2
}, 42)
}
  • Added syntax for constructing records using T::{ ... }. This syntax allows explicit record disambiguation and better completion for labels in the record:
struct T {
x: Int
y: Int
}

struct T2 {
x: Int
y: Int
}

fn init {
let x = T::{x: 1, y: 2}
debug(x.x + x.y) // 3
}
  • Officially removed the syntax var id = expr.

  • The new testing syntax test "name" {} is used to replace the original fn test_name(){}. Currently, only tests with top-level indentation are recognized as keywords, and in the future, test will no longer be supported as an identifier. Old syntax: fn test_name {} New syntax: test "name" {}

  • Supported the use of return statements within init or test code blocks.

fn init  {
if i > 0 {
return
}
...
}

test {
if i > 0 {
return
}
...
}

Plugin Update

  • Improved syntax highlighting:

Before:

After:

weekly 2024-01-15

· 3 min read

MoonBit Update

  • Relaxed the syntax on the right-hand side of match, allowing the appearance of single statements. The following syntax is now permitted:
match x {
A => return
B => return 1
C => i = i + 1
D => break
E => continue
F => while true {}
}
  • Fix various bugs in the formatter, and resolve issues with missing curly braces and parentheses in the code after formatting. For example:
Original Code Before Fix After Fix
fn init {
let mut a = 1
{
{
let a = 2
f(a)
}
let a = 3
f(a)
{
let a = 4
f(a)
}
}
f(a)
}
fn init {
let mut a = 1
let a = 2
f(a)
let a = 3
f(a)
let a = 4
f(a)
f(a)
}
fn init {
let mut a = 1
{
{
let a = 2
f(a)
}
let a = 3
f(a)
let a = 4
f(a)
}
f(a)
}
  • Added a new experimental inline testing mechanism. The declaration format is fn test_*. Inline tests need to be declared in regular .mbt files (instead of _test.mbt). They must not have any parameters or return types. For example, the following usage will result in an error:

Now, moon test will not only execute each test file ending with _test.mbt in every package, but also perform inline tests within each package.

Plugin Updates

  • Fixed bugs related to newtype goto definition and rename.

Build System Updates

  • moon new now adds default values for various options, allowing users to select default values by pressing Enter.
$ moon new
Enter the path to create the project (. for current directory) [default: myproject] >
Enter the create mode (exec or lib) [default: exec] >
Enter your username [default: username] >
Enter the package name [default: hello] >
Enter your license [default: Apache-2.0] >
Created my-project

  • moon.mod.json now includes license and repository fields. license indicates the license used by the mooncakes.io, which must comply with the SPDX standard.

  • Removed moon check --daemon.
  • moon publish now includes an upload size limit, the upload size must be less than 16 MiB.

Other

  • On the Windows platform, the installation path has been changed from ~/.moon to ~/.moon/bin, bringing it in line with other platforms.

weekly 2024-01-08

· 2 min read

MoonBit Update

  • Now we have removed the interface keyword officially, replacing it with trait.

  • Based on community feedback, we've introduced the syntax let mu t id = expr to replace var id = expr. Support for var id = expr will be removed next week.

  • Added Default implementation for the Array type:

fn init {
debug(Array::default()) // []
}
  • Added Default, Eq, and Debug implementations for the List type:
fn init {
let l1: List[_] = Cons(1, Cons(2, Cons(3, List::default())))
let l2: List[_] = Cons(1, Cons(2, Cons(4, Nil)))
debug(l1) // Cons(1, Cons(2, Cons(3, Nil)))
debug(l1 == l2) // false
debug(l2 == l2) // true
}
  • Fixed type checking for public function bodies. For example:
priv type T
pub fn f() {
let t: T = ... // public definition cannot depend on private type
...
}

This used to cause an error in T, it will no longer result in such an issue.

Plugin Update

  • We've integrated the MoonBit AI, now available at https://ai.moonbitlang.com, feel free to try it.

  • Resolved several issues to enhance the stability of the LSP, preventing unexpected crashes.

Build System Update

  • Fixed an issue where moon test would test packages in the .mooncakes folder.

  • Deprecated moon check --daemon.

  • Improved error messages for incorrect format or content in moon.pkg.json.

weekly 2024-01-02

· 6 min read

MooBit Update

1. Supported recursive newtype

Supported recursive newtype, which allows the implementation of a type-safe Y combinator in MoonBit.

type R[X] (R[X]) -> X

fn y[X, Y](f : ((X) -> Y) -> (X) -> Y) -> (X) -> Y {
fn ff (x: R[(X) -> Y]) -> (X) -> Y {
fn(a) { f(x.0(x))(a) }
}
ff(R::R(fn(x) { fn (a) { f(x.0(x))(a) } }))
}

fn factx(f: ((Int) -> Int)) -> (Int) -> Int {
fn(n: Int) -> Int {
if n <= 1 { 1 } else { n * f(n-1)}
}
}

fn init {
let fact = y(factx)
let n = fact(10)
println(n)
}

2. Add a new built-in function sqrt, used to calculate square roots.

fn init {
// The type of sqrt is Double -> Double
println(sqrt(4.0)) // 2.0
}

3. Add a new operator === to determine if two values are referentially equal.

fn init {
let x = [1, 3]
let y = [1, 3]
let z = x
if x === y {
println("x === y")
} else if x === z {
println("x === z")
}
// Output: x === z
}

4. method/trait system update:

In the past few weeks, we have made many design adjustments to MoonBit's method/trait system to make it more robust and well-behaved. The current behavior of MoonBit's method system is as follows:

  • methods are functions associated with a type. Methods can be defined as follows:
fn T::method(...) -> ... { ... }

// for example
type MyInt Int
fn MyInt::default() -> MyInt { MyInt(0) }

enum MyList[X] {
Nil
Cons(X, MyList[X])
}

fn MyList::map2[X, Y, R](
f: (X, Y) -> R,
xs: MyList[X],
ys: MyList[Y]
) -> MyList[R] {
...
}

As a convenient syntax to define methods, when the first parameter of a function is named self, MoonBit will automatically define the function as a method for the type of self:

fn add(self: MyInt, other: MyInt) -> MyInt { ... }
// equivalent to
fn MyInt::add(x: MyInt, y: MyInt) -> MyInt { ... }
  • Methods are just special functions owned by a type. So when there is no ambiguity, methods can be called with regular function call syntax directly:
enum MyList[X] { ... }
fn MyList::length[X](xs: MyList[X]) -> Int {
...
}

fn init {
let xs: MyList[_] = ...
debug(length(xs)) // called directly
}

When there is ambiguity (i.e. there are multiple methods of the same name in scope), methods can still be called explicitly by T::method(...):

struct Type1 { ... } derive(Debug)
fn Type1::default() -> Type1 { ... }

struct Type2 { ... } derive(Debug)
fn Type2::default() -> Type2 { ... }

fn init {
// debug(default()): ambiguity!
debug(Type1::default()) // ok
debug(Type2::default()) // ok
}
  • Whe the type of the first parameter of a method happens to be the owner type of that method, you can call the method conveniently via the dot syntax x.method(...) . Calling methods using dot syntax does not require writing out the package name, even when the method comes from a foreign package. MoonBit will automatically find the correct method based on the type of x:
// a package named @list
pub enum List[X] { ... }
pub fn map[X](self: List[X], f: (X) -> Y) -> List[Y] {
...
}

// using @list in another package
fn init {
let xs: @list.List[_] = ...
// the following three lines are equivalent
xs.map(...)
@list.map(xs, ...) // when there is no ambiguity
@list.List::map(xs, ...)
}
  • Only the package that defines a type can define methods for that type. This makes MoonBit's trait system coherent, and prevents third-party packages from modifying the behavior of an existing type.

Behavior change on MoonBit's trait system is as follows:

  • Method declarations in trait definition no longer need a Self:: prefix in all circumstances. Whether the first parameter of a method has type Self is no longer significant.

  • Types can implement a trait automatically and implicitly using its methods. However, if a type does not implement a trait, or if the corresponding method does not have desired behavior, this type can still be extended (outside its package) by defining special extension methods:

// implement method [op_equal] of trait [Eq] for type [T]
fn Eq::op_equal(x: T, other: T) -> { ... }

These extension methods can only be used to implement the specified trait. For example, the extension method Eq::op_equal above can only be used to implement Eq. It cannot be called via T::op_equal or t.op_equal(...). When searching for the implementation of a trait, extension methods have a higher priority than ordinary methods.

  • Only the package of the type or the package of the trait can define extension methods. So the implementations provided by some type to implement a trait is always globally unique. This makes MoonBit's trait system coherent, and ensures that third party packages cannot modify behavior of existing program by accident.

The biggest breaking change of the new behavior is: users can no longer define methods for foreign and built-in types. However, the functionality of foreign and built-in types can still be extended by implementing new traits using extension methods.

Build System Update

1. The import field in moon.pkg.json now includes array representation Each item in the array is either a string or an object { "path": ..., "alias": ...}, for example:

{
"is_main": true,
"import": [
{ "path": "moonbitlang/import004/lib", "alias": "lib" },
"moonbitlang/import004/lib2", // Use the default alias: "lib2"
{ "path": "moonbitlang/import004/lib3", "alias": "lib3" },
{ "path": "moonbitlang/import004/lib4", "alias": "" } // Use the default alias: "lib4"
]
}

2. moon new now supports creating projects through an interactive method.

  • Create an executable project.
$ moon new
Enter the path to create the project (. for current directory) > myproject
Enter the create mode (exec or lib) > exec
Enter your username > moonbitlang
Enter the package name > hello

The above command is equivalent to:

 moon new --path myproject --user moonbitlang --name hello

This will create a project named moonbitlang/hello in the folder ./myproject, with the following directory structure:

.
├── lib
│ ├── hello.mbt
│ ├── hello_test.mbt
│ └── moon.pkg.json
├── main
│ ├── main.mbt
│ └── moon.pkg.json
└── moon.mod.json
  • Create a package.
$ moon new
Enter the path to create the project (. for current directory) > mylib
Enter the create mode (exec or lib) > lib
Enter your username > moonbitlang
Enter the package name > hello

The above command is equivalent to:

 moon new --path mylib --lib --user moonbitlang --name hello

This will create a package named moonbitlang/hello in the folder ./mylib, with the following directory structure:

.
├── lib
│ ├── hello.mbt
│ ├── hello_test.mbt
│ └── moon.pkg.json
├── moon.mod.json
├── moon.pkg.json
└── top.mbt

weekly 2023-12-25

· 2 min read

MoonBit Update

01. Added built-in type Result

enum Result[T, E] {
Ok(T)
Err(E)
}

02. Added the question mark operator

A new question mark operator has been introduced to simplify error handling:

fn may_fail() -> Option[Int] { ... }

fn compose_may_fail() -> Option[String] {
let x = may_fail()?
let y = may_fail()?.lsr(3)
Some ((x + y).to_string())
}

The semantics of the question mark operator are: if t in t? results in None, then t? is equivalent to return None (exiting the current function). If t? results in Some(x), then t? is equivalent to x. Besides Option type, the question mark operator can also be used with Result type:

fn may_error() -> Result[Int, String] { ... }

fn compose_may_error() -> Result[Int, String] {
let x = may_error()?
let y = may_error()?
if y == 0 {
return Err("divide by zero")
}
Ok (x / y)
}

03. The keyword interface has been changed to trait

Based on community feedback, the keyword interface has been changed to trait, and the interface keyword is temporarily retained. trait|486x340

04. Modified the logic for dead code elimination

Top-level let statements not used are always considered removable, regardless of side effects. For example,

let a = 1       // will be removed
let b: T = f(a) // will be removed
fn init {
.. // a and b are not used
}

If the function f has side-effects, move it to fn init { .. } to prevent it from being removed.

05. Fixed issues where the code formatting tool did not correctly handle Chinese comments.

06. Fixed issues with handling Chinese global identifiers.

IDE Update

01. Added an option to control whether moon check starts automatically.

VSCode users can use moonbit.autoMoonCheck.enable in settings.json to control whether moon check starts automatically. Or, you can search "moonbit:" in the settings.

02. Fixed a bug where derive(Show) incorrectly reported errors.

Before the fix:

After the fix:

Build System Update

01. Added the moon doc command for generating and previewing documentation.

moon doc --serve will create documentation based on markdown comments in the code and launch a web server locally. Accessing the provided link allows you to view the results.

weekly 2023-12-18

· 2 min read

In December 2023, MoonBit underwent Alpha testing. This post aims to introduce recent updates to MoonBit’s language and toolchain, along with applications developed using MoonBit.

cover