satgo1546’s ocean

Any sufficiently primitive magic is indistinguishable from technology.

Roc语言云简评

如果可以得到TypeScript一般的类型检查,但不需要标注任何类型;得到超过Go的性能,但无需涉足可变数据——

折纸之于七巧板

Roc是一门函数式编程语言。我最初在Strange Loop 2021上的演讲Outperforming Imperative with Pure Functional Languages中见到Roc,当时它的网站还很简陋。因为只有代码库而没有文档,设计目标又描述得比较抽象,此后我便没有再关注这门语言。前几天在Hacker News上读到Roc编译器准备由Rust重写成Zig,我发现网站已焕然一新,有教程和FAQ了,就看了看。

Roc的设计深受Elm的影响。Elm的图标是七巧板,Roc的图标是折纸大鹏。

高性能

在演讲中,Richard Feldman介绍了各种优化函数式程序的技巧,使从命令式翻译来的函数式程序取得接近C++的性能。优化方法主要有:编译到LLVM;减少指针用量(unbox);用引用计数代替垃圾回收;编译期引用计数;优化没有必要的复制为原地写入。虽然都是常规手段,但做完也就干掉所有主流GC语言了。

Roc的运行时性能定位在紧随C++和Rust这些T0语言之后的T1。FAQ指出,Roc编译器没有自举的计划,理由是性能不足。

另一方面,Roc的编译速度则比C++快得多。

复合数据结构:tag和record

Roc发扬光大了Elm特色record类型,彻底抛弃了ML风格的数据类型定义。复合数据结构有tag和record,对标tagged union和struct。特点是,这些类型都是依结构判定的(structural typing)(tag和字段按名称匹配),因此无需定义即可使用,通过:定义的类型也只是别名。另一方面,Roc保留了newtype的能力(称为不透明类型(opaque type)),通过:=定义。

# Roc
User a : {
    email : Str
    first_name : Str
    last_name : Str
}a
is_valid : User * -> Bool
user_from_email : Str -> User {}
capitalize_names : User a -> User a

tag_union1: [Foo Str, Bar Bool]* -> Bool
tag_union2 : [Foo Str, Bar Bool] -> Bool
tag_union3 : [Foo Str, Bar Bool]a -> [Foo Str, Bar Bool]a
// TypeScript
type User<T> = {
  email: string,
  firstName: string,
  lastName: string,
} & T
// TS中,参数位为逆变,因此User *对应User<{}>
declare function isValid(user: User<{}>): boolean
declare function userFromEmail(email: string): User<{}>
declare function capitalizeNames<T>(user: User<T>): User<T>

declare function tagUnion2(x:
  | { TAG: 'foo', 0: string }
  | { TAG: 'bar', 0: boolean }
): boolean
// tag_union1和tag_union3不能准确描述

注意record的组合语义是交,而tag的组合语义是并,但在语法上没有区别。

因为数据结构无需定义,又支持完全类型推断,所以写起来可以通篇不触及类型。官方教程的前半段就在无视类型的情况下进行。能像写动态类型语言一样,却有完整的编译期类型检查,是个很有意思的语言特性组合。

因为数据结构无需定义,所以返回异常很方便。因为太过方便,标准库里没有Maybe(泛用可空)而只有Result(带异常类型),传统用Maybe的地方都用Result代替了。

语法和其他特性

Roc的设计目标之一是用户友好,这一点始终未变,因此语言的其他特性设计得非常中规中矩。

不像其他ML系语言(如Elm),Roc中定义函数的语法与定义匿名函数一致,所以不能在函数定义处当场多分支模式匹配,而必须在函数体内写when语句。

FAQ有一整段介绍为什么没有隐式柯里化,主要是为了容易理解。不过说实话这个设计反倒让我很难理解|>运算符:|>实际上是一种universal function call syntax,而不是独立的函数调用运算符。

# Roc
"Hello " |> Str.concat "World!"
== Str.concat "Hello " "World!"
# Nim
"Hello ".echo("World!")
echo("Hello ", "World!")

虽然没有了隐式柯里化,但函数调用还是省略括号,用空格分隔参数,蛮怪的。

副作用的处理方式简单粗暴:函数分为纯函数和有副作用的函数,纯函数只能调用纯函数。标准库里只有纯函数,所以副作用只可能来自平台模块(platform module)。此处平台的概念不同于一般意义上的操作系统,而有些像其他语言中的框架库:例如,HTTP服务器(basic-webserver)是一个官方提供的平台,其封装程度类似于express。一个应用只能在一个平台上运行。

typeclass(称为能力(ability))被认为是高级语言特性,在教程中未提及,而是单开了一篇文档介绍。