【发布时间】:2019-11-25 16:17:46
【问题描述】:
对于冗长的介绍,我很抱歉,我想不出办法让它更短。
问题
我们正在构建一个简单的应用程序,该应用程序通过应用模式转换来操作代数表达式 - 目标是能够说“采用2*x+2 并应用'分解'”。最终目标是能够确定两个表达式在一组模式转换下是否等价(即是 2*(x+1)^2 'reachable' from 2x^2+4x+2)。
我们创建了一组对域进行建模的类并定义了“规范”形式,这基本上意味着构造(符号上)相同的表达式只有一种方式:x+y+z 将始终表示为 @ 987654325@) 而不是Plus(y, x, z) 或Plus(x, Plus(y, z)),即使它是通过调用Plus(Plus(x, z), y) 创建的。
许多规范转换是相应运算符的代数属性的直接结果 - 关联性允许上面看到的“扁平化”等。这些属性也对模式操作有直接影响 - 例如,Power(\_, 2) 是不可交换,因此它只匹配以2 作为第二个子项(参数)的表达式,但Plus(_, 2) 是可交换的,因此它可以匹配以2 作为其任何子项的表达式。由于这些原因,我们希望将这些代数属性表示为类上的显式数据,然后在应用程序的各个部分进行解析。
棘手的部分是我们需要这些数据既可以静态使用(我们在构造表达式以应用适当的规范转换时需要它,但由于尚未构造表达式,我们无法动态访问它)和动态(在模式匹配期间,我们必须能够做到expr.hasAttribute(Associative))。由于这些属性对于特定的表达式类型(类)是固定的,因此我们也试图避免直接在类上定义它,例如
class Plus(children: List<Expression>): Expression(children) {
val attributes = setOf(Associative, Commutative, IdentityElement(Number(0.0)))
...
}
这是因为我们不希望每次创建表达式时都创建新的 Set - 我们希望在类声明期间创建一次 Set,因为这是唯一一次需要创建。
解决方案
我们决定解决这个问题的方法是创建一个全局 HashMap<KClass<out Expr>, Set<Attribute>> 来保存属性,然后创建一个 Builder 对象,该对象负责解析属性并在创建表达式时应用适当的规范转换。
我将包含我们正在使用的代码的简化版本。一些高级 cmets:
-
HashMap在Expression.Companion上定义 - 所有表达式构造函数都受到保护,而是通过
companion object上定义的invoke函数创建实例。这是因为规范转换可能会导致与正在创建的对象不同,例如Plus(Symbol(x))创建Symbol(x)。 - 构建器主要构建一个运算符(函数)列表,然后将其应用于正在构建的表达式的子级 - 在本例中为展平和排序。
我的问题有两个:
- 您会采用这种方式吗?我们尝试了其他几种方法,但所有这些方法都需要大量重复代码(实际上速度更慢)。
- 你能给我一些关于如何使这个更清洁的代码审查技巧吗?我对 Kotlin 还很陌生。如您所见,我最终创建了一种半构建器语义来与
Builder中的Attributes一起使用。这实际上不是我的目标,它只是因为试图使代码更干净。而且我确信可以做得更多。
谢谢!
import kotlin.reflect.KClass
/**
* ATTRIBUTES
*/
abstract class Attribute
object Associative: Attribute()
object Commutative: Attribute()
class Identity(val element: Expr) : Attribute()
/**
* END ATTRIBUTES
*/
/**
* BUILDER
*/
// So there are basically two things we can do in canonical form -> we can operate on children (flattening, canonical ordering)
// or we can operate on the final expression as a whole (transforming Plus(x) to x, for example)
// So, every attribute may or may not contribute a functional operator that transforms either the children or the expression
// as a whole. These operators must be composed in a certain order (e.g. flattening must occur before ordering) and then
// applied to the result.
// The operators that change the expressions type short-circuit the function (because there is nothing else to do)
object Builder {
inline fun<reified A: Attribute> Set<Attribute>.withAttribute(block: A.() -> Any?) =
this.filterIsInstance<A>().firstOrNull()?.run(block)
inline fun<reified E: Expr> withAttributes(block: Set<Attribute>.() -> Any?)
= Expr.attributes[E::class]?.run(block)
inline fun<reified E: Expr> canonizeByAttributes(crossinline constructor: (List<Expr>) -> E, children: List<Expr>): Expr {
val childrenOperators = mutableListOf<(List<Expr>) -> List<Expr>>()
withAttributes<E> {
withAttribute<Identity> {
when(children.size) {
0 -> return element
1 -> return children.first()
else -> {}
}
}
withAttribute<Associative> {
childrenOperators.add {
children -> children.flatMap { if(it is E) it.children else listOf(it) }
}
}
withAttribute<Commutative> {
childrenOperators.add {
children -> children.sortedBy { it.toString() }
}
}
}
return constructor(childrenOperators.fold(children) { children, op -> op(children)})
}
}
/**
* END BUILDER
*/
/**
* EXPRESSIONS
*/
abstract class Expr protected constructor(val children: List<Expr>) {
val attributes: Set<Attribute>? get() = Companion.attributes[this::class]
override fun toString(): String = "${this::class.simpleName}(${children.joinToString(", ")})"
companion object {
val attributes: HashMap<KClass<out Expr>, Set<Attribute>> = hashMapOf()
}
}
Expr.attributes[Plus::class] = setOf(Identity(Symbol("0")), Commutative, Associative)
class Plus private constructor(children: List<Expr>): Expr(children) {
companion object {
operator fun invoke(children: List<Expr>) = Builder.canonizeByAttributes(::Plus, children)
operator fun invoke(vararg children: Expr) = invoke(children.toList())
}
}
class Symbol(val name: String): Expr(listOf()) {
override fun toString(): String = "Symbol($name)"
}
/**
* END EXPRESSIONS
*/
/**
* EVALUATION, TESTS
*/
Plus(Symbol("x")).attributes
(Plus(Symbol("x")) as Symbol).name == "x"
(Plus(Symbol("x"), Plus(Symbol("x"), Symbol("y"))))
(Plus() as Symbol).name == "0"
(Plus(Plus()) as Symbol).name == "0"
Plus(Symbol("2"), Symbol("y"), Plus(Symbol("x"), Symbol("1")))
/**
* END EVALUATION, TESTS
*/
【问题讨论】:
标签: generics kotlin dynamic static expression