You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Kotlin中实现支持定义“展开”的互递归ADT?

问题描述

我正在学习语言语法与解析器相关知识,并用Kotlin做实践。以下是我用Backus–Naur形式(BNF)定义的类型规则:

<type>           ::= <base-type>
                   | <array-type>
                   | <pair-type>

<base-type>      ::= 'int'
                   | 'bool'
                   | 'char'
                   | 'string'

<array-type>     ::= <type> '[' ']'

<pair-type>      ::= 'pair' '(' <pair-elem-type> ',' <pair-elem-type> ')'

<pair-elem-type> ::= <base-type>
                   | <array-type>
                   | 'pair' 

我认为这是一种互递归的代数数据类型(ADT),其中定义存在“展开”特性:比如下面的例子

<a> ::= 'red'
      | 'green'

<b> ::= <a>
      | 'blue'

<a>的定义会在<b>中展开,<b>可取值为red、green或blue。

我尝试用Kotlin密封类实现该结构:

sealed class Type {
    sealed class BaseType: Type() {
        data object Int: BaseType()
        data object Bool: BaseType()
        data object Char: BaseType()
        data object Str: BaseType()
    }
    class ArrayType(type: Type): Type()
    class PairType(fst: PairElementType, snd: PairElementType): Type()
    sealed class PairElementType {
        data object Pair: PairElementType()
        class BaseTypeElement(type: BaseType): PairElementType()
        class ArrayTypeElement(type: ArrayType): PairElementType()
    }
}

但没法模拟这种“展开”特性,目前只能通过包装类实现。如果后续有大量这类互关联的定义,会产生过多包装类。请问有没有更优的方式在Kotlin中表示这类定义?


优化方案

核心思路是让PairElementType直接兼容Type的合法子集,避免冗余包装类。下面提供两种贴合BNF语义的实现方式:

方案1:接口+密封类继承(紧凑型)

通过定义PairElementType接口,让BaseType和ArrayType直接实现该接口,同时单独定义Pair标记对象,完全匹配BNF的“展开”逻辑:

interface PairElementType

sealed class Type {
    sealed class BaseType : Type(), PairElementType {
        data object Int : BaseType()
        data object Bool : BaseType()
        data object Char : BaseType()
        data object Str : BaseType()
    }

    class ArrayType(val innerType: Type) : Type(), PairElementType

    class PairType(val fst: PairElementType, val snd: PairElementType) : Type()

    // 对应BNF中的'pair'选项
    data object Pair : PairElementType
}
  • BaseType和ArrayType直接成为PairElementType的合法成员,无需包装类
  • Type.Pair作为独立对象,对应BNF中pair-elem-type的单独选项
  • 保持Type的核心层级,结构紧凑,适合常规解析场景

方案2:统一父类+类型别名(语义直观型)

用一个统一的密封父类承载所有类型,再通过类型别名区分Type和PairElementType的语义边界,完全贴合BNF的“展开”特性:

sealed class TypeOrPairElem

// 类型别名明确区分两个语义集合
typealias Type = TypeOrPairElem
typealias PairElementType = TypeOrPairElem

// 基础类型(同时属于Type和PairElementType)
sealed class BaseType : TypeOrPairElem {
    data object Int : BaseType()
    data object Bool : BaseType()
    data object Char : BaseType()
    data object Str : BaseType()
}

// 数组类型(同时属于Type和PairElementType)
class ArrayType(val innerType: Type) : TypeOrPairElem

// Pair类型(仅属于Type)
class PairType(val fst: PairElementType, val snd: PairElementType) : TypeOrPairElem

// Pair标记(仅属于PairElementType)
data object PairMarker : TypeOrPairElem
  • TypeOrPairElem是所有类型的父类,Type和PairElementType作为它的别名,对应BNF中“子集展开”的关系
  • 清晰区分了仅属于Type或仅属于PairElementType的成员,语义完全匹配BNF规则
  • 无任何包装类,扩展新类型时只需新增密封子类即可

内容的提问来源于stack exchange,提问作者the thinker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 06:08:11