0

一个非常典型的用例:一个对象(或类)声明了几个相关类型的公共 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函数或丢弃条件块中的值而放大错误的可能性。此外,我对如何用纯函数式语言解决这个问题非常感兴趣。

有什么想法如何吃蛋糕吗?

4

2 回答 2

1

不知道你在寻找什么,但根据你尝试使用 Shapeless 的方法,我相信你可以在没有它的情况下实现它,并且与你刚刚所做的非常相似:

case class Ball(dia :Int)
object Balls {
    val balls@Seq( tennis, football, basketball ): Seq[Ball] = Ball(7)::Ball(22)::Ball(24)::Nil
}

另一方面,正如已经回答的那样,这将是一种枚举,您可以在此处实际使用它。

编辑

您是否考虑过以更具描述性的方式对数据进行建模。让我们来看看:

sealed trait Balls {
    def dia: Int
}
case object Football { val dia: Int = 22 }
case object Tennis { val dia: Int = 7 }
case object Basketball { val dia: Int = 24 }

object Balls {
    val values: Seq[Ball] = Football :: Tennis :: Basketball :: Nil
}

这种方法的优点是使用模式匹配,您仍然可以对 Ball 进行模式匹配以提取直径,同时能够将模式匹配细化为子类型。

def kickBall( in: Ball ): Boolean = {
    in match {
        case f: Football => 
            true
        case b: Basketball =>
            // You shouldn't do this
            false
        case _ =>
            // Anything else
            false
    }
}

使用密封将强制您在同一个文件中定义所有类型,编译器会在您忘记模式匹配的情况时通知您。

仍然有一些样板,但它是一种以功能方式对解决方案建模的典型方法。

于 2016-10-05T08:57:50.083 回答
0

不是一个功能性的解决方案,所以不是我的问题的正确答案,但是可以通过将收集器移动到一个方法来解决由构造函数方法创建的“丢失”值的小问题,该方法也负责它们的收集unapply

class Collector[T] {
    private[this] var seq :Seq[T]=Nil
    def items = seq
    def unapply(item :T) = synchronized { seq = item+:seq; Some(item) }
}

class Ball private (val dia :Int)

object Ball {
    val Ball = new Collector[Ball]
    implicit private def ball(dia :Int) = new Ball(dia)

    val Ball(basket) = 24
    val Ball(tennis) = 7
    val Ball(football) = 22
}

虽然我在语法上更喜欢这种解决方案,但与最简单的工厂方法相比,我认为好处不足以抵消混淆因素。

于 2016-10-05T19:55:48.870 回答