如何在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
相关产品推荐
相关产品推荐

