-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
108 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,7 @@ | ||
# A Tour of MoonBit for Beginners | ||
|
||
This guide intended for newcomers, and it's not meant to be a 5-minute quick tour. This article tries to be a succinct yet easy to understand guide | ||
for those who haven't programmed in a way that MoonBit enables them to do, | ||
that is, in a more modern, functional way. | ||
This guide is intended for newcomers, and it's not meant to be a 5-minute quick tour. This article tries to be a succinct yet easy to understand guide | ||
for those who haven't programmed in a way that MoonBit enables them to, that is, in a more modern, functional way. | ||
|
||
See [the General Introduction](./README.md) if you want to straight delve into the language. | ||
|
||
|
@@ -30,7 +29,8 @@ For Windows users, powershell is used: | |
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser; irm https://cli.moonbitlang.com/install/powershell.ps1 | iex | ||
``` | ||
|
||
This automatically installs MoonBit in `$HOME/.moon` and adds it to your `PATH` | ||
This automatically installs MoonBit in `$HOME/.moon` and adds it to your `PATH`. | ||
|
||
Do notice that MoonBit is not production-ready at the moment, it's under active development. To update MoonBit, just run the commands above again. | ||
|
||
Running `moon help` gives us a bunch of subcommands. But right now the only commands we need are `build` `run` and `new`. | ||
|
@@ -41,12 +41,12 @@ To create a project (or module, more formally), run `moon new`. You will be gree | |
my-project | ||
├── README.md | ||
├── lib | ||
│ ├── hello.mbt | ||
│ ├── hello_test.mbt | ||
│ └── moon.pkg.json | ||
│ ├── hello.mbt | ||
│ ├── hello_test.mbt | ||
│ └── moon.pkg.json | ||
├── main | ||
│ ├── main.mbt | ||
│ └── moon.pkg.json | ||
│ ├── main.mbt | ||
│ └── moon.pkg.json | ||
└── moon.mod.json | ||
``` | ||
|
||
|
@@ -118,9 +118,9 @@ fn compose[S, T, U](f : (T) -> U, g : (S) -> T) -> (S) -> U { | |
} | ||
``` | ||
|
||
Languages nowadays have something called _lambda expression_. Most language implement it as a mere syntactic sugar. A lambda expression is really just a anonymous closure, this, is resembled in our MoonBit's syntax: | ||
Languages nowadays have something called _lambda expression_. Most languages implement it as a mere syntactic sugar. A lambda expression is really just a anonymous closure, this, is resembled in our MoonBit's syntax: | ||
|
||
> a closure only captures variables in its surroundings, together with its bound variable, that is, having the same indentation level. | ||
> a closure only captures variables in its surroundings, together with its bound variable, that is, having the same indentation level (suppose we've formatted the code already). | ||
```moonbit | ||
fn foo() -> Int { | ||
|
@@ -142,7 +142,7 @@ Now we've learned the very basic, let's learn the rest by coding. | |
|
||
A linked list is a series of node whose right cell is a reference to its successor node. Sounds recursive? Because it is. Let's define it that way using MoonBit: | ||
|
||
```moonbit live | ||
```moonbit | ||
enum List[T] { | ||
Nil // base case: empty list | ||
Cons(T, List[T]) // an recursive definition | ||
|
@@ -153,7 +153,7 @@ The `enum` type works like any `enum` from traditional OO languages. However, le | |
|
||
> the type `List[T]` can be constructed from the constructor `Nil` or `Cons`, the former represents an empty list; the latter carries some data of type `T` and the rest of the list. | ||
The square bracket used here is a _polymorphic_ (generic) definition, meaning a list of something of type `T`. Should we _instantiate_ `T` with a concrete type like `Int`, we define a list containing integers. | ||
The square bracket used here denotes a _polymorphic_ (generic) definition, meaning a list of something of type `T`. Should we _instantiate_ `T` with a concrete type like `Int`, we define a list containing integers. | ||
|
||
Another datatype frequently used in MoonBit is our good old `Struct`, which works like you would expect. Let's create a list of `User` using the definition above and `Struct`: | ||
|
||
|
@@ -164,10 +164,12 @@ struct User { | |
// by default the properties/fields of a struct is immutable. | ||
// the `mut` keyword works exactly the way we've mentioned before. | ||
mut email: String | ||
} derive(Debug) | ||
} derive(Show) | ||
// a method of User is defined by passing a object of type User as self first. | ||
// just like what you would do in Python. | ||
// Note that methods may only be defined within the same package the type is in. | ||
// We may not define methods for foreign types directly | ||
fn greetUser(self: User) -> String{ // a method of struct/type/class `User` | ||
let id = self.id | ||
let name = self.name | ||
|
@@ -180,21 +182,47 @@ let evan: User = {id:0,name:"Evan",email:"[email protected]"} | |
let listOfUser: List[User] = Cons(evan, Cons({..evan, email: "[email protected]"}, Nil)) | ||
``` | ||
|
||
`enum`, `struct` and `newtype` are the 3 ways to define a datatype. There isn't `class` in MoonBit, nor does it need that. | ||
Another datatype is `type`, a specific case of `enum` type. `type` can be thought as a wrapper | ||
around an existing type, keeping the methods of `String` but allows additional methods to be defined. | ||
Through this we extends the method definition of a foreign type without actually | ||
modifying it. Consider the type of `name` in `User`, | ||
we may define it as | ||
|
||
```moonbit no-check | ||
type UserName String // a newtype `UserName` based on `String` | ||
// defining a method for UserName is allowed but not String. | ||
fn is_blank(self : UserName) -> Bool { | ||
// use `.0` to access its basetype String | ||
// iter() creates a *internal iterator* | ||
// which provides a functional way to iterate on sequences. | ||
// find_first short circuits on the first `true` i.e. non-blank character | ||
let res = self.0.iter().find_first( | ||
fn(c) { if c == ' ' { false } else { true } }, | ||
) | ||
match res { | ||
Some(_) => false | ||
// found NO non-blank character, thus it's a blank string. | ||
None => true | ||
} | ||
} | ||
``` | ||
|
||
`enum`, `struct` and `newtype` are the 3 ways to define a datatype. | ||
There isn't `class` in MoonBit, nor does it need that. | ||
|
||
the `derive` keyword is like Java's `extends` and `implements`. Here `Debug` is a _trait_, | ||
indicates a type can be printed for debugging. So what is a trait? | ||
the `derive` keyword is like Java's `implements`. Here `Show` is a _trait_ which | ||
indicates a type is printable. So what is a trait? | ||
|
||
### Trait | ||
|
||
A trait (or type trait) is what we would call an `interface` in traditional OO-languages. | ||
`debug(evan)` would print `{id: 0, name: "Evan", email: "[email protected]"}`. As `User` consists | ||
of builtin types `Int` `String`, we do not need to implement it explicitly. Let's implement the | ||
`println(evan)` would print `{id: 0, name: "Evan", email: "[email protected]"}`. As `User` consists | ||
of builtin types `Int` `String`, which already implements `Show`. | ||
Therefore we do not need to implement it explicitly. Let's implement our own | ||
trait `Printable` by implementing `to_string()`: | ||
|
||
```moonbit | ||
// actually we have `Show` that's builtin and does the same thing. | ||
// but we'll use our own version of it -- `Printable`. | ||
trait Printable { | ||
to_string(Self) -> String | ||
} | ||
|
@@ -223,7 +251,7 @@ listOfUser.to_string() | |
// => [(0, Evan, [email protected]) (0, Evan, [email protected])] | ||
``` | ||
|
||
We use `<T extends Printable>` in Java to constrain the type of list element to make sure object of type | ||
We use `<T extends Printable>` in Java to constrain the type of list element to make sure objects of type | ||
`T` can be printed, similarly, in MoonBit we would write `[T: Printable]`. | ||
|
||
### Pattern Matching | ||
|
@@ -234,12 +262,13 @@ to _destructure_ (to strip the encapsulation of) a structure. | |
|
||
We may express the above `match` code as | ||
|
||
> if `self` is constructed with `Nil` (an empty list), we return `""`, | ||
> if `self` is constructed with `Nil` (an empty list), we return `""`; | ||
> otherwise if `self` is constructed with `Cons(x,xs)` (a non-empty list) | ||
> we print `x` and rest of the list. | ||
> Where `x` is the head of the `self` and `xs` being the rest. | ||
Intuitively, we extract `x` and `xs` (they are bound in situ) from `self` using pattern matching. Let's implement typical list operations such as `map` `reduce` `zip`: | ||
Intuitively, we extract `x` and `xs` (they are bound in situ) from `self` using pattern matching. | ||
Let's implement typical list operations such as `map` `reduce` `zip`: | ||
|
||
```moonbit | ||
fn map[S, T](self : List[S], f : (S) -> T) -> List[T] { | ||
|
@@ -283,9 +312,13 @@ fn greetUserAlt(self: User) -> String { | |
|
||
## Iteration | ||
|
||
Finally, let's talk about the major point of every OO-language: looping. Although we've been using recursion all along, MoonBit is designed to be multi-paradigm, thus it retains C-style imperative `for` `while` loop. | ||
Finally, let's talk about the major point of every OO-language: looping. | ||
Although we've been using recursion most of the times, | ||
MoonBit is designed to be multi-paradigm, | ||
thus it retains C-style imperative `for` `while` loop. | ||
|
||
Additionally, MoonBit provides a more interesting loop construct, the functional loop. For example the Fibonacci number can be calculated by | ||
Additionally, MoonBit provides a more interesting loop construct, the functional loop. | ||
For example the Fibonacci number can be calculated by | ||
|
||
```moonbit | ||
fn fib(n: Int) -> Int { | ||
|
@@ -308,4 +341,7 @@ better readability, preserving recursive flavor and same performance without wri | |
|
||
## Closing | ||
|
||
At this point, we've learned about the very basic and most not-so-trivial features of MoonBit, yet MoonBit is a feature-rich, multi-paradigm programming language. After making sure that you are comfortable with the basics of MoonBit, we suggest that you look into some [interesting examples](https://www.moonbitlang.com/docs/category/examples) to get a better hold of MoonBit. | ||
At this point, we've learned about the very basic and most not-so-trivial features of MoonBit, | ||
yet MoonBit is a feature-rich, multi-paradigm programming language. | ||
After making sure that you are comfortable with the basics of MoonBit, | ||
we suggest that you look into some [interesting examples](https://www.moonbitlang.com/docs/category/examples) to get a better hold of MoonBit. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,12 +40,12 @@ MoonBit 目前处于活跃开发的阶段,尚不满足生产环境的需求。 | |
my-project | ||
├── README.md | ||
├── lib | ||
│ ├── hello.mbt | ||
│ ├── hello_test.mbt | ||
│ └── moon.pkg.json | ||
│ ├── hello.mbt | ||
│ ├── hello_test.mbt | ||
│ └── moon.pkg.json | ||
├── main | ||
│ ├── main.mbt | ||
│ └── moon.pkg.json | ||
│ ├── main.mbt | ||
│ └── moon.pkg.json | ||
└── moon.mod.json | ||
``` | ||
|
||
|
@@ -119,7 +119,7 @@ fn compose[S, T, U](f : (T) -> U, g : (S) -> T) -> (S) -> U { | |
现在的语言一般都有 Lambda 表达式。大部分语言是通过语法糖来实现这一特性的。 | ||
一个 Lambda 表达式不过是一个匿名的闭包,这一特性也体现在 MoonBit 的语法上: | ||
|
||
> 闭包只捕捉其周围(即同一缩进等级的)的变量和自己的约束变量 | ||
> 闭包只捕捉其周围(即同一缩进等级的,假设代码已经格式化过)的变量和自己的约束变量 | ||
```moonbit | ||
fn foo() -> Int { | ||
|
@@ -150,9 +150,8 @@ enum List[T] { | |
因而上述代码可以读作 | ||
|
||
> 类型 `List[T]` 可以由 `Nil` `Cons` 构造子构造而来,前者表示一个空链表,后者能够容纳一些类型为 `T` 的数据和链表的剩余部分。 | ||
这里的方括号告诉我们这是一个多态(polymorphism)定义(泛型,generics),即一个元素类型为 `T` 的链表(此处 `T` 可以是任何类型,因此称多态)。 | ||
如果用 `Int` 实例化这个 `T`,我们就定义了一个整数的链表。 | ||
> 这里的方括号告诉我们这是一个多态/泛型(polymorphic/generic)函数,即一个元素类型为 `T` 的链表(此处 `T` 可以是任何类型,因此称多态)。 | ||
> 如果用 `Int` 实例化这个 `T`,我们就定义了一个整数的链表。 | ||
另外一个常见的数据类型是我们的老熟人 `Struct`,其工作方式和类 C 语言中的同名结构相似。我们用上面定义的 `List` | ||
和下方将要定义的 `User` 来创建一个用户列表: | ||
|
@@ -164,10 +163,11 @@ struct User { | |
// 默认情况下 Struct 的属性/字段是不可变的 | ||
// `mut` 关键字就和我们之前说的一样 | ||
mut email: String | ||
} derive(Debug) | ||
} derive(Show) | ||
// 我们通过把函数第一个参数定义为 `self: User` 来给该 Struct 定义一个 method | ||
// 写法和 Python 类似 | ||
// 注意:只有类型所在的包能为其定义方法。 不能直接为外部类型定义方法。 | ||
fn greetUser(self: User) -> String{ // `User` 的一个方法 | ||
let id = self.id | ||
let name = self.name | ||
|
@@ -181,21 +181,45 @@ let evan: User = {id:0,name:"Evan",email:"[email protected]"} | |
let listOfUser: List[User] = Cons(evan, Cons({..evan, email: "[email protected]"}, Nil)) | ||
``` | ||
|
||
除了这两种数据类型之外,还有一种较为特殊的枚举类型:`type`. 可以看作其将已存在的类型包装起来, | ||
成为一个新的类型。这样能在保持原有类型特性的基础上继续新增方法, | ||
为外部类型定义方法的同时不需修改其本身。 | ||
例如 `User` 中的 `name` 的类型,我们还可以定义为 | ||
|
||
```moonbit no-check | ||
type UserName String // 一个新类型 UserName,基于 String | ||
// 可以为 UserName 定义方法,String 则不行。 | ||
fn is_blank(self : UserName) -> Bool { | ||
// 通过 `.0` 访问其内部类型(String) | ||
// iter() 创建一个内部迭代器(internal iterator) | ||
// 并借此以函数式的风格在某个序列结构上迭代 | ||
// find_first 遇到第一个 true (即非空字符)就短路 | ||
let res = self.0.iter().find_first( | ||
fn(c) { if c == ' ' { false } else { true } }, | ||
) | ||
match res { | ||
Some(_) => false | ||
// 找不到非空字符,所以是只由空格组成的字符串 | ||
None => true | ||
} | ||
} | ||
``` | ||
|
||
`enum` `struct` `newtype` 是定义数据类型的三种方式,MoonBit 中没有 `class`,且也不需要。 | ||
|
||
`derive` 关键字就和 Java 的 `extends` 和 `implements` 相似。这里 `Debug` 是一个类型类(type trait/class), | ||
它指出一个类型的内容可以为了 debug 的需要被打印出来。那么什么是类型类? | ||
`derive` 关键字就和 Java 的 `implements` 相似。这里 `Show` 是一个类型类(type trait/class), | ||
它指出一个类型的内容可以被打印出来。那么什么是类型类? | ||
|
||
### 类型类 | ||
|
||
类型类(trait)就是我们在传统面向对象编程中熟悉的接口(interface)。 | ||
`debug(evan)` 会输出 `{id: 0, name: "Evan", email: "[email protected]"}`, | ||
这是因为 `User` 由内置类型 `Int` `String` 组成,所以我们不需要手动实现这个 trait。 | ||
不妨试试通过实现 `to_string()` 实现我们自己定义的 `Printable` trait: | ||
`println(evan)` 会输出 `{id: 0, name: "Evan", email: "[email protected]"}`, | ||
这是因为 `User` 由内置类型 `Int` `String` 组成,后者已经实现该 trait, | ||
所以我们不需要手动实现。 | ||
不妨通过实现 `to_string()` 来实现我们自己定义的 `Printable` trait: | ||
|
||
```moonbit | ||
// 实际上有内置 `Show` trait,功能上一样 | ||
// 但我们仍用自己的 trait -- `Printable`. | ||
trait Printable { | ||
to_string(Self) -> String | ||
} | ||
|
@@ -235,7 +259,7 @@ listOfUser.to_string() | |
|
||
上方的 `match` 代码可以表述为 | ||
|
||
> 如果 `self` 是通过 `Nil` 构造子(空链表)构造的,我们返回 `""` | ||
> 如果 `self` 是通过 `Nil` 构造子(空链表)构造的,我们返回 `""`; | ||
> 否则如果 `self` 是通过 `Cons(x,xs)` 构造出来的(非空链表) | ||
> 我们输出 `x` 和链表的剩余部分,其中 `x` 是链表的头, `xs` 是剩余部分。 | ||
|
@@ -284,7 +308,7 @@ fn greetUserAlt(self: User) -> String { | |
|
||
## 循环 | ||
|
||
最后来了解一下面向对象语言中十分重要的循环结构。虽然我们上面都使用递归的写法, | ||
最后来了解一下面向对象语言中十分重要的循环结构。虽然我们上面主要使用递归, | ||
MoonBit 本身是一个多范式语言, | ||
因而其也保留了 C 风格命令式的的 `for` `while` 循环结构。 | ||
|
||
|
@@ -305,8 +329,8 @@ fn fib(n: Int) -> Int { | |
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(fib) // => [1,1,2,3,5,8,13,21,34,55] | ||
``` | ||
|
||
从语义上讲, 函数式循环更强调循环过程中每个状态的转换, | ||
相比 [tail-recursion](https://en.wikipedia.org/wiki/Tail_call) 的写法, | ||
从语义上讲, 函数式循环更强调迭代过程中每个状态的转换, | ||
相比 [tail-recursive](https://en.wikipedia.org/wiki/Tail_call) 的写法, | ||
前者的可读性更佳,也保留了递归的风格,同时还具备和尾递归相同的性能。 | ||
|
||
## 结束语 | ||
|