Skip to content

Commit

Permalink
improvement for tour.md (#269)
Browse files Browse the repository at this point in the history
  • Loading branch information
notch1p authored Jul 29, 2024
1 parent 51c404d commit 8fe1d8c
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 48 deletions.
90 changes: 63 additions & 27 deletions tour.md
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.

Expand Down Expand Up @@ -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`.
Expand All @@ -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
```

Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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`:

Expand All @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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] {
Expand Down Expand Up @@ -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 {
Expand All @@ -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.
66 changes: 45 additions & 21 deletions zh-docs/tour.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down Expand Up @@ -119,7 +119,7 @@ fn compose[S, T, U](f : (T) -> U, g : (S) -> T) -> (S) -> U {
现在的语言一般都有 Lambda 表达式。大部分语言是通过语法糖来实现这一特性的。
一个 Lambda 表达式不过是一个匿名的闭包,这一特性也体现在 MoonBit 的语法上:

> 闭包只捕捉其周围(即同一缩进等级的)的变量和自己的约束变量
> 闭包只捕捉其周围(即同一缩进等级的,假设代码已经格式化过)的变量和自己的约束变量
```moonbit
fn foo() -> Int {
Expand Down Expand Up @@ -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` 来创建一个用户列表:
Expand All @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -235,7 +259,7 @@ listOfUser.to_string()

上方的 `match` 代码可以表述为

> 如果 `self` 是通过 `Nil` 构造子(空链表)构造的,我们返回 `""`
> 如果 `self` 是通过 `Nil` 构造子(空链表)构造的,我们返回 `""`
> 否则如果 `self` 是通过 `Cons(x,xs)` 构造出来的(非空链表)
> 我们输出 `x` 和链表的剩余部分,其中 `x` 是链表的头, `xs` 是剩余部分。
Expand Down Expand Up @@ -284,7 +308,7 @@ fn greetUserAlt(self: User) -> String {

## 循环

最后来了解一下面向对象语言中十分重要的循环结构。虽然我们上面都使用递归的写法
最后来了解一下面向对象语言中十分重要的循环结构。虽然我们上面主要使用递归
MoonBit 本身是一个多范式语言,
因而其也保留了 C 风格命令式的的 `for` `while` 循环结构。

Expand All @@ -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) 的写法,
前者的可读性更佳,也保留了递归的风格,同时还具备和尾递归相同的性能。

## 结束语
Expand Down

0 comments on commit 8fe1d8c

Please sign in to comment.