type · match · effect · !io

Dawn

一门小而优雅的函数式语言:不可变数据、代数数据类型与穷尽的模式匹配、把效果写进类型签名。编译器已自举,两个平级后端——JVM 字节码与 C——在同一份源码上给出同一个答案;这件事由门禁管着,不是一句承诺。

main.dawndawn run
type Shape =
  | Circle(r: Float)
  | Rect(w: Float, h: Float)

fn area(s: Shape) -> Float =
  match s {
    Circle(r) -> 3.14159 * r * r
    Rect(w, h) -> w * h
  }

pub fn main() -> Unit !io =
  [Circle(1.0), Rect(2.0, 3.0)]
  |> map(area)
  |> fold(0.0, (a, x) => a + x)
  |> t => println("total: $t")
total: 9.14159

核心特性

效果进类型

函数默认是纯的,碰 IO 必须在签名标 !io——看签名即知它碰不碰外界,纯函数测试零 mock。第二条轴是你自己声明的具名效果effect 声明操作、with handle 就地应答,标签随签名传播,只在 handle 这一个语法节点上被减掉。这一档有规范、两个后端都实现了、也有测试,但它还没有内部使用者:标准库和编译器都没有声明过具名效果。

effect Ask {
  fn ask() -> Int
}

# Pure. The signature says it asks; it does not say whom
fn total() -> Int !Ask = ask() + ask()

pub fn main() -> Unit !io = {
  with handle Ask { ask() => 21 }
  println("${total()}")
}
42

编译期求值:comptime

comptime { ... } 在编译期由解释器执行,结果直接烧进常量池。没有宏系统,也不需要——普通函数就能在编译期跑。

fn fib(n: Int) -> Int =
  if n < 2 { n } else { fib(n - 1) + fib(n - 2) }

# Computed at compile time, burned into the constant pool
const FIB10: Int = comptime { fib(10) }

pub fn main() -> Unit !io = println("fib(10) = $FIB10")
fib(10) = 55

两个后端,一个答案

JVM 字节码与 C(再交给 cc)是平级的两条路。最容易分叉的地方语言自己拥有:Float 渲染是纯 Dawn 的 Schubfach、Unicode 大小写表是编译器的、Map 迭代顺序按插入定死。整套差分语料每次 push 两边编两边跑,比 stdout、stderr、退出码——分歧会红灯。

use std/map
use std/str

# One source, two backends, the same bytes on both
pub fn main() -> Unit !io = {
  let m = map.from([(1, "a"), (2, "b")])
  println("${0.1 + 0.2}")
  println(str.to_upper("île"))
  println("${map.keys(m)}")
}
0.30000000000000004
ÎLE
[1, 2]

教程开始上手;语言细节的权威定义在规范示例都能直接 dawn run;标准库 API 参考见标准库;每个设计取舍的「为什么」写在设计笔记

本站每一页都有中英两版。规范与设计笔记是其中唯一先写中文再翻译的一对:它们是活文档,每次改语言都在中文里改,所以中文是正本、英文按它登记——两者一脱节 scripts/doc-check.py 就红。docs/ 其余部分是设计方案与计划,读者是作者本人,仍然只有一种语言。代码、编译器诊断与标准库文档注释则一律英文——标准库页上那些条目正文也在其内,它们是编译器自己的文本。