语言漫游

一个程序,从上到下读完,每个小节几行代码就把事情说清楚。 Emo 的设计规则是:一切所见即所得 —— 调用有括号,返回写出来,可变性永远拼出来。 把各节拼成一个 main.emo,emo run main.emo 即可运行。

1绑定与字符串

const 不变,var 可变但出不了块。字符串永远双引号,插值只有一种形状,花括号里可以装任意表达式。

main.emo
// ---- 1 · 绑定与字符串 ----
const who = "世界"
var visits = 6
visits = visits * 7
println("hello, ${who} — six sevens are ${visits}")

// 插值可以装任何表达式, 中文就是普通的字符串数据
const item = "伞"
println("one ${item} costs ${19 + 9} yuan")

2类:不可变值类型

字段只在 init 里诞生,之后冻结;两个内容相同的实例 ==。谓词方法以 ? 结尾。

main.emo
// ---- 2 · 类: 不可变值类型 ----
class User {
  def init(name String, age Int64) {
    self.name = name
    self.age = age
  }

  def describe() String {
    return "${self.name},age ${self.age}"
  }

  def adult?() Bool {
    return self.age >= 18
  }
}

const ming = User.new(name: "王晓明", age: 28)
println(ming.describe())
println("adult? ${ming.adult?()}")
// 字段在 init 之后冻结, 相等比较的是内容:
println(ming == User.new(name: "王晓明", age: 28))

3接口:形状即契约

接口属于调用方。类不必声明实现任何接口 —— 形状对上,契约就成立。

main.emo
// ---- 3 · 接口: 形状即契约 ----
// 哪里都没有 implements: 形状匹配, 即满足接口。
interface Greeter {
  def greet() String
}

class Machine {
  def greet() String {
    return "[beep] 你好"
  }
}

class Friend {
  def greet() String {
    return "很高兴见到你"
  }
}

def welcome(g Greeter) String {
  if g.is(Machine) {
    return "a machine says: ${g.greet()}"
  }
  return "a friend says: ${g.greet()}"
}

println(welcome(Machine.new()))
println(welcome(Friend.new()))

4枚举与标签惯用法

枚举成员不携带数据。需要数据时,标签骑在元组里,case 按位置解构,还能带守卫。

main.emo
// ---- 4 · 枚举与标签惯用法 ----
// 枚举是封闭的有名值集合, 成员不携带数据。
// 需要携带数据时, 标签放进元组, case 按位置解构。
enum Outcome { ok, err }

def classify(age Int64) (Outcome, String) {
  if age >= 18 {
    return (Outcome.ok, "adult")
  }
  return (Outcome.err, "minor")
}

case classify(28) {
  (Outcome.ok, label) -> { println("28 -> ${label}") }
  (Outcome.err, label) -> { println("28 -> ${label}") }
}

// 分支可以带守卫:
case (Outcome.ok, 42) {
  (Outcome.ok, v) when v > 40 -> { println("a big answer: ${v}") }
  (Outcome.ok, v) -> { println("a small answer: ${v}") }
  (Outcome.err, label) -> { println("no answer: ${label}") }
}

5数组:定长、变换、永不就地修改

变换总是返回新数组;== 逐元素比较内容。

main.emo
// ---- 5 · 数组: 定长、变换、永不就地修改 ----
const nums = [3, 1, 2]
println(nums[0] + nums.length())
// 变换返回新数组, 原数组纹丝不动:
const more = nums.append(9)
println(more.length())
println(nums.length())
println([1, 2] == [1, 2])

6可变性,一层一层来

var 活不过它的块;长期状态只有 Box,只有 read 与 replace。

main.emo
// ---- 6 · 可变性, 一层一层来 ----
// var 显式且块作用域; Box 是一个进程拥有的唯一长期可变单元
// —— 只有 read 与 replace, 刻意没有别的。
var tally = 0
tally = tally + 1
tally = tally + 10
println(tally)

const total = Box.new(0)
total.replace(total.read() + 5)
total.replace(total.read() + 6)
println(total.read())

7代码块:唯一的函数记法

-> (x) { ... } 是匿名函数;def 是它的命名形态。尾随代码块让回调与迭代保持同一种记法。

main.emo
// ---- 7 · 代码块: 唯一的函数记法 ----
// 箭头块是匿名函数; 调用可以尾随一个代码块,
// 回调与迭代因此保持同一种记法。
const twice = -> (n Int64) { return n * 2 }
println(twice(21))

def walk(xs Array[Int64], i Int64, f Block) Int64 {
  if i == xs.length() {
    return 0
  }
  f(xs[i])
  return walk(xs, i + 1, f)
}

walk([10, 20, 30], 0) -> (v Int64) {
  println(v)
  return v
}

8进程:一条消息链

do 起进程,<- 发消息,receive 选择性接收。尾调用有保证,递归就是收信循环。

main.emo
// ---- 8 · 进程: 三个进程, 一条消息链 ----
def feed(wh Pid, i Int64) Int64 {
  if i > 3 {
    wh <- "done"
    return 0
  }
  wh <- (i, i)
  return feed(wh, i + 1)
}

const rep = do reporter.run(0, self_pid())
const wh = do warehouse.run(rep)
feed(wh, 1)

receive {
  (who, handled) -> { println("handled ${handled} orders") }
}