重新思考句法:邻近绑定
Rethinking Syntax: Binding by Adjacency

原始链接: https://github.com/manifold-systems/manifold/blob/master/docs/articles/binding_exprs.md

## 绑定表达式:一种新的Java DSL方法 绑定表达式,通过Manifold编译器插件探索,引入了一种在Java中直接定义领域特定语言(DSL)的新方法。其核心思想是将表达式之间的邻接关系视为二元运算符,允许它们基于静态类型“绑定”。 不同于修改基本类型或使用宏,绑定表达式利用类型上定义的`prefixBind`和`postfixBind`方法来解析相邻表达式。例如,`2025 July 19`可以通过链式绑定编译成`LocalDate`。编译器系统地探索可能的简化,从失败的尝试中“退回”,直到形成有效的、类型安全的表达式。 这种方法能够创建直观的单位系统语法(`299.8M m/s`),类似自然语言的DSL,以及自定义字面量语法——所有这些都具有静态类型安全性和零运行时开销。Manifold已经将其用于科学库(单位)、范围和向量数学。 虽然功能强大,但绑定表达式会引入解析复杂性以及语法-语义边界的转变。它们可能还需要开发者学习。然而,它们提供了一种灵活且非侵入性的方式来扩展Java的表达能力。

## 重新思考语法:基于“邻接绑定” 一个由owlstuffing探索并详细记录在GitHub上的新型编译器插件([https://github.com/manifold-systems/manifold](https://github.com/manifold-systems/manifold)),提出了一种新的语法,其中表达式基于它们的*邻接关系*和静态类型进行绑定。 该系统不依赖于预定义的运算符,而是旨在直接将诸如“2025年7月19日”解释为`LocalDate`,或将“300M米/秒”解释为`Velocity`。这种“邻接绑定”允许更自然和简洁的代码。 这个想法建立在Mathematica的后缀表达式等概念之上,但旨在更广泛地应用。讨论中的一个关键挑战是管理解析的复杂性和潜在的歧义,类似于自然语言处理中遇到的问题(歧义句)。该项目探讨了类型引导解析如何能够在Java等语言中创建类型安全的表达式。
相关文章

原文

What if this were a real, compile time type-safe expression:

2025 July 19   // → LocalDate 

That’s the idea behind binding expressions -- a compiler plugin for Java that explores what it would be like if adjacency were a binary operator. In a nutshell, it lets adjacent expressions bind based on their static types, to form a new expression.


Type-directed expression binding

With binding expressions, adjacency is used as a syntactic trigger for a process called expression binding, where adjacent expressions are resolved through methods defined on their types.

Here are some examples of binding expressions in Java with the Manifold compiler plugin:

2025 July 19        // → LocalDate
299.8M m/s          // → Velocity
1 to 10             // → Range<Integer>
Meet Alice Tuesday at 3pm  // → CalendarEvent

A pair of adjacent expressions is a candidate for binding. If the LHS type defines:

<R> LR prefixBind(R right);

...or the RHS type defines:

<L> RL postfixBind(L left);

...then the compiler applies the appropriate binding. These bindings nest and compose, and the compiler attempts to reduce the entire series of expressions into a single, type-safe expression.


Example: LocalDates as composable expressions

Consider the expression:

LocalDate date = 2025 July 19;

The compiler reduces this expression by evaluating adjacent pairs. Let’s say July is an enum:

public enum Month {
  January, February, March, /* ... */

  public LocalMonthDay prefixBind(Integer day) {
    return new LocalMonthDay(this, day);
  }

  public LocalYearMonth postfixBind(Integer year) {
    return new LocalYearMonth(this, year);
  }
}

Now suppose LocalMonthDay defines:

public LocalDate postfixBind(Integer year) {
  return LocalDate.of(year, this.month, this.day);
}

The expression reduces like this:

2025 July 19July.postfixBind(2025) // → LocalYearMonth
⇒ [retreat]              // → error: No binding with `19`July.prefixBind(19)    // → LocalMonthDay
⇒ .postfixBind(2025)     // → LocalDate

Although the reduction algorithm favors left-to-right binding, it systematically retreats from failed paths and continues exploring alternative reductions until a valid one is found. This isn’t parser-style backtracking — instead, it's a structured search that reduces adjacent operand pairs using available binding methods. In this case, the initial attempt to bind 2025 July succeeds, but the resulting intermediate expression cannot bind with 19, forcing the algorithm to retreat and try a different reduction. Binding July 19 succeeds, yielding a LocalMonthDay, which can then bind with 2025 to produce a LocalDate.


Binding expressions give you a type-safe and non-invasive way to define DSLs or literal grammars directly in Java, without modifying base types or introducing macros.

Going back to the date example:

LocalDate date = 2025 July 19;

The Integer type (2025) doesn’t need to know anything about LocalMonthDay or LocalDate. Instead, the logic lives in the Month and LocalMonthDay types via pre/postfixBind methods. This keeps your core types clean and allows you to add domain-specific semantics via adjacent types.

You can build:

  • Unit systems (e.g., 299.8M m/s)
  • Natural-language DSLs
  • Domain-specific literal syntax (e.g., currencies, time spans, ranges)

All of these are possible with static type safety and zero runtime magic.


The Manifold project makes interesting use of binding expressions. Here are some examples:

  • Science: The manifold-science library implements units using binding expressions and arithmetic & relational operators across the full spectrum of SI quantities, providing strong type safety, clearer code, and prevention of unit-related errors.

  • Ranges: The Range API uses binding expressions with binding constants like to, enabling more natural representations of ranges and sequences.

  • Vectors: Experimental vector classes in the manifold.science.vector package support vector math directly within expressions, e.g., 1.2m E + 5.7m NW.

Tooling note: The IntelliJ plugin for Manifold supports binding expressions natively, with live feedback and resolution as you type.


Binding expressions are powerful and flexible, but there are trade-offs to consider:

  • Parsing complexity: Adjacency is a two-stage parsing problem. The initial, untyped stage parses with static precedence rules. Because binding is type-directed, expression grouping isn't fully resolved until attribution. The algorithm for solving a binding series is nontrivial.

  • Flexibility vs. discipline: Allowing types to define how adjacent values compose shifts the boundary between syntax and semantics in a way that may feel a little unsafe. The key distinction here is that binding expressions are grounded in static types -- the compiler decides what can bind based on concrete, declared rules. But yes, in the wrong hands, it could get a bit sporty.

  • Cognitive overhead: While binding expressions can produce more natural, readable syntax, combining them with a conventional programming language can initially cause confusion -- much like when lambdas were first introduced to Java. They challenged familiar patterns, but eventually settled in.


Binding expressions have been part of Manifold for several years, but they remain somewhat experimental. There’s still room to grow. For example, compile-time formatting rules could verify compile-time constant expressions, such as validating that July 19 is a real date in 2025. Future improvements might include support for separators and punctuation, binding statements, specialization of the reduction algorithm, and more.

If you're curious, you can explore the implementation in the Manifold repo.

联系我们 contact @ memedata.com