moonbitlang / core

MoonBit's Core library
https://moonbitlang.com/
Apache License 2.0
586 stars 73 forks source link

[lang design] [proposal] support expressions in string interpolation by `.with` postfix operator #442

Open shamiao opened 4 months ago

shamiao commented 4 months ago

TL;DR

Introducing a postfix operator .with{} to string literals to add ad-hoc variable bindings for string interpolation.

Problem

String interpolation in current MoonBit only recognizes bounded variables in current scope, doesn't support expressions.

It's good to support only variable bindings, in my opinion. It's not a wise decision to support expressions in string interpolation without carefully considered rules, since it causes a lot of problems to solve when expressions get complicated. (character escaping, multiline expressions, cooperating with format specifiers ...)

But it's also obvious that using ad-hoc expressions to do simply tasks (formatting or simple combining) in string interpolation is a legitimate need.

Solution

Adding a postfix operator (likes rust's await) .with { ... }, which introduces ad-hoc variable bindings to the preceding string literal. The { ... } parts follows the syntax of struct literal. For example:

let (lastname, firstname, place) = ("Himalaya", "Qomolangma", "Shangri-la")
let message = "I am \(fullname) from \(place)".with { fullname: "\(firstname) \(lastname)" }
// I am Qomolangma Himalaya from Shangri-la

The .with operator is just a syntactic sugar. This example will be desugared to:

let (lastname, firstname, place) = ("Himalaya", "Qomolangma", "Shangri-la")
let message = {
    let fullname = "\(firstname) \(lastname)"
    "I am \(fullname) from \(place)"    // noticed that `place` still uses the outer `place` variable
}

This also solves the formatting of floats very well:

let v = 42.112
let message = "Float with precisions: \(v)".with {
    v: v.format().precisions(2).finialize()    // due to variable shadowing, duplicate identifier `v` is ok to have
}
@assertion.assert_eq(message, "Float with precisions: 42.11")

Formatting like this avoids the burden of remembering format specifiers like {:2}. It's really hard to remember specifiers in various programming languages.