Cangjie:一门新的开源编译型语言,原生支持效应处理器和代数数据类型

来源: InfoQ - 后端

#🏗️ 架构设计
原文

Dan Ghica教授是华为爱丁堡研究中心编程语言实验室负责人,他最近介绍了Cangjie语言(CJ)。这是一门新的应用开发语言,具备代数数据类型和效应处理器(effect handler)特性。这门已开源的语言被定位为Java、Kotlin或Swift的替代选择。目前中国已有80多所大学教授Cangjie。

Ghica表示,Cangjie是一门通用型、高层次且表达力很强的语言,设计目标是安全和高效。和任何新语言一样,CJ吸收了前辈语言的经验,并希望在编程语言(PL)领域中占据一个明确位置。CJ会编译为原生机器码,并提供多个后端,使其能够运行在Linux、macOS、Windows、Android、iOS和HarmonyOS之上。

它的核心特性包括静态类型、模式匹配、并发垃圾回收、代数数据类型(algebraic data type,ADT),以及诸如宏和注解之类的元编程能力。下面是一个Cangjie中模式匹配的示例:

enum TimeUnit {
    | Year(UInt64)
    | Month(UInt64)
}

enum Command {
    | SetTimeUnit(TimeUnit)
    | GetTimeUnit
    | Quit
}

main() {
    let command = SetTimeUnit(Year(2022))
    match (command) {
        case SetTimeUnit(Year(year)) => println("Set year ${year}")
        case SetTimeUnit(Month(month)) => println("Set month ${month}")
        case _ => ()
    }
}

不过,将Cangjie带入主流学术领域方面的最重要特性,也许是它对效应处理器的原生支持。CJ对效应处理器的实现对异常机制做了泛化,并声称能够简化动态绑定。CJ中的效应处理器引入了新的perform和resume关键字,标准的try/catch/finally代码块也变成了try/catch/handle/finally。

class FileNotFound <: Command ) {
        resume r with &quot;/etc/default.txt&quot; // (2): control jumps back to (1), returning a value
    }
} {
    public FileNotFound(let filename: String) {}
}

func readFile(name: String): String {
    var actualName = name
    if !fileExists(name) {
        actualName = perform FileNotFound(name) // (1): control jumps to (2)
    }
    return File(actualName).read()
}

main() {
    try {
        let str: String = readFile(&quot;foo.txt&quot;)
        println(str)
    } handle (e: FileNotFound, r: Resumption

效应处理器可用于很多场景,包括非确定性与回溯、调度、增量计算、依赖注入与配置(例如,reader effect)、mock,以及前面展示过的异常处理。下面是一个用于缓存与记忆化的CJ效应处理器示例:

func withCache) {
        let result = match (cache.get(cmd)) {
            case None =&gt;
                let result = perform cmd
                cache.put(cmd, result)
                result
            case Some(cached) =&gt;
                cached
        }
        resume next with result
    }
}
()
    try {
        fn()
    } handle (cmd: Cmd, next: Resumption
{
    let cache = HashMap &amp; Command(fn: () -&gt; Return): Return
    where Cmd &lt;: Hashable &amp; Equatable

Ghica强调,CJ的效应处理器还原生支持动态绑定,从而允许代码与其调用上下文交互。Ghica举了一个日志库的例子,它会根据运行程序的设备来定义日志记录方式:

设想一下,你正在为Oniro这样的框架编写一个库。这个库可能运行在笔记本电脑、手机、手表上,也可能运行在某种没有屏幕、没有硬盘、也没有控制台的IoT设备上。跨所有平台并不存在一种标准的日志记录方式。那么这时你该怎么办?

在这种情况下,你就需要使用动态绑定。每当你的代码需要记录某些内容时,你需要通知上下文,说这里有日志需要处理,而上下文会知道该如何处理这些日志。但与此同时,它又不同于异常,因为你还需要能够返回原来的执行流程。你并不希望只是抛出一个异常然后终止执行。你希望以一种由上下文控制的方式完成日志记录,然后继续恢复计算。

例如,在桌面环境里,我可以直接把日志打印到控制台。但如果是在移动设备上,手机并没有控制台。所以,如果想做日志记录,也许你会决定弹出某种提示框来展示日志,也可以把它通过电子邮件发送出去,或者干脆忽略掉。你可以按需做任何处理,具体方式由上下文自行决定。对于日志来说,事情本质上就是这样。你只用三行代码就能完成日志功能。现在试着用一门不具备效应处理器的语言做同样的事情,你会发现复杂度会高出很多。

虽然已经有一些利用效应处理器的框架以第三方组件形式可用到Cangjie中,但效应处理器目前仍被视为这门语言中一个正在积极开发中的实验性部分。

Ghica的演讲于4月在布鲁塞尔举行的OCX(Open Community Experience)2026上发表。OCX是Eclipse基金会的旗舰开源大会,为开发者、研究人员、行业领袖和政策制定者提供为期三天的交流活动。

查看英文原文:Cangjie, a New Open-Source Compiled Language with Native Effect Handlers and Algebraic Data Types