BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News Cangjie, a New Open-Source Compiled Language with Native Effect Handlers and Algebraic Data Types

Cangjie, a New Open-Source Compiled Language with Native Effect Handlers and Algebraic Data Types

Listen to this article -  0:00

Prof. Dan Ghica, who leads the Programming Languages Lab at Huawei’s Edinburgh Research Centre, recently presented Cangjie (CJ), a new application development language that features algebraic data types and effect handlers. The open-sourced language is positioned as a counterpart to Java, Kotlin, or Swift. Cangjie is taught by 80+ universities in China.

According to Ghica, Cangjie is a general-purpose, high-level, and expressive language designed to be safe and efficient. As with any new language, CJ learned from its predecessors and seeks to carve a specific position in the programming language (PL) space. CJ compiles to raw machine code, offering multiple backends that allow it to run on Linux, macOS, Windows, Android, iOS, and HarmonyOS.

Core features include static typing, pattern matching, concurrent garbage collection, algebraic data types (ADTs), and metaprogramming facilities such as macros and annotations. An example of pattern matching in Cangjie is as follows:

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 _ => ()
    }
}

However, the most academically significant feature brought to the mainstream by Cangjie is arguably its native support for effect handlers. CJ’s implementation of effect handlers generalizes exceptions and claims to simplify dynamic binding. Effect handlers in CJ introduce new perform and resume keywords. The standard try/catch/finally block becomes try/catch/handle/finally.

class FileNotFound <: Command<String> {
    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("foo.txt")
        println(str)
    } handle (e: FileNotFound, r: Resumption<String, Unit> ) {
        resume r with "/etc/default.txt" // (2): control jumps back to (1), returning a value
    }
}

Effect handlers can be used for many purposes, including nondeterminism and backtracking, scheduling, incremental computing, dependency injection and configuration (e.g., reader effect), mocking, and as shown before, exceptions. The following is an example of a CJ effect handler used for caching and memoization:

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

Ghica emphasizes that CJ’s effect handlers also provide native support for dynamic binding, allowing code to interact with its calling context. Ghica takes the example of a logging library which defines its logging method according to the device running the program:

So imagine that you write a library for a framework such as Oniro. That library can run on a laptop, on a mobile phone, on a watch, on some kind of IoT device that doesn’t have a screen, or a hard drive, or a console. There’s no standard way to do logging across all platforms. So then what do you do?

Well, in that case you need to use dynamic binding. Whenever your code needs to log something, you need to inform the context, saying some logging needs to be done, and the context will know how to handle the logging. But also unlike an exception, you need to be able to come back. You don’t want to just throw an exception and quit your execution. You want to perform that logging in a way controlled by the context and then resume the computation.

So for example on a desktop, I can just print to the console. Whereas if I’m on a mobile, the mobile doesn’t have a console. So if you want to do the logging, maybe you decide to open some kind of alert that presents the log, but also you could send it in an email, or just ignore it. You can do whatever you want. The context can decide as appropriate. And that really is all there is to it for logging. You can do logging in three lines of code. Now try to do the same thing with a language without effect handlers, and you will see that it is significantly more complicated.

While several frameworks utilizing effect handlers are available as third-party components for Cangjie, effect handlers are still considered an actively developed, experimental part of the language.

Ghica’s talk took place at OCX (Open Community Experience) 2026 in April in Brussels. OCX is the Eclipse Foundation’s flagship open-source conference, gathering developers, researchers, industry leaders, and policymakers for three days.

About the Author

Rate this Article

Adoption
Style

BT