一个非常典型的用例:一个对象(或类)声明了几个相关类型的公共 val,并且它想声明一个访问器,返回一个包含所有这些的集合:
case class Ball(dia :Int)
object Balls {
val tennis = Ball(7)
val football = Ball(22)
val basketball = Ball(24)
val balls = Seq(tennis, football, basketball)
}
这个例子显然违反了 DRY 并且容易出错。使用可变状态可以很容易地解决它(例如向构造函数添加隐式Builder[Ball, Seq[Ball]]
参数Ball
。但是,该解决方案也不是没有问题。特别是当我们尝试概括解决方案和/或具有每个类都声明的类层次结构时一些值,我们应该从可变部分摘要切换到最终不可变值的时刻尚不清楚。
更多的是作为一种智力练习,出于好奇,我试图提出一个纯粹的功能变体,但没有取得多大成功。我想出的最好的是
object Balls {
import shapeless.{::, HNil}
val (balls @
tennis ::football::basketball::HNil
) =
Ball(7)::Ball(22)::Ball(24)::HNil
}
这是相当整洁的,但一旦球或它们的初始化器的数量不小,就会变得难以管理。一个可行的替代方法是将所有内容都更改为 HMap,但我通常会尽量避免公共 API 中的无形依赖。似乎它可能对 scala 延续是可行的,但我不知道如何使声明非本地到重置块。
编辑:我以前没有强调过,为什么scala.Enumeration
不为我做这项工作的原因是,在实际情况下,对象并不相同,但实际上是复合结构,构造函数需要几行或更多行. 因此,虽然最终类型可能相同(或者至少我对细节不感兴趣),但它不是一个简单的枚举,并且出于可读性原因,声明的成员/键的名称可以是很重要的很容易在视觉上与它的定义联系起来。所以我在这里的无形解决方案,以及基于 Seq 的无形解决方案,很容易受到一个错误的影响,即通过错误的真实标识符对错误的值进行修改。
当然,目前真实案例的实现方式与 scala.Enumeration 类似,通过维护继承的构造方法产生的一系列值。然而,它会遇到 Enumeration 会遇到的所有问题,并且会因在实际初始化程序之外调用构造object Balls
函数或丢弃条件块中的值而放大错误的可能性。此外,我对如何用纯函数式语言解决这个问题非常感兴趣。
有什么想法如何吃蛋糕吗?