【问题标题】:Optional boolean parameters in ScalaScala 中的可选布尔参数
【发布时间】:2014-04-24 07:04:36
【问题描述】:

我最近一直在研究基于 Apache POI 功能的 DSL 样式库包装器,并面临一个我似乎无法很好解决的挑战。

该库的目标之一是为用户提供将电子表格模型构建为不可变对象集合的能力,即

val headerStyle = CellStyle(fillPattern = CellFill.Solid, fillForegroundColor = Color.AquaMarine, font = Font(bold = true))

val italicStyle = CellStyle(font = Font(italic = true))

有以下假设:

  • 用户可以选择指定任何参数(也就是说,您可以创建不带参数的 CellStyle 以及明确指定参数的完整列表);
  • 如果用户未明确指定参数,则将其视为未定义并使用默认环境值(我们要转换为的格式的默认值);

第二点很重要,因为我想将此数据模型转换为多种格式,即 Excel 中的默认字体不必与 HTML 浏览器中的默认字体相同(如果用户未定义明确的字体系列我希望他使用这些默认值查看数据)。

为了满足要求,我使用了此处描述的空模式的变体:Pattern for optional-parameters in Scala using null 并在此处建议Scala default parameters and null(下面是一个简化示例)。

object ModelObject {
  def apply(modelParam : String = null) : ModelObject = ModelObject(
    modelParam = Option(modelParam)
  ) 
}
case class ModelObject private(modelParam : Option[String])

由于 null 仅在伴生对象内部使用并且非常本地化,为了解决方案的简单性,我决定接受 null 牺牲。该模式适用于所有参考类。

但是对于 Scala 原始类型包装器,不能指定 null。对于布尔值来说,这尤其是一个巨大的问题,我有效地考虑了 3 种状态(真、假和未定义)。想要提供接口,用户仍然可以编写bold = true,我决定使用接受空值的 Java 包装器。

object ModelObject {
  def apply(boolParam : java.lang.Boolean = null) : ModelObject = ModelObject(
    boolParam = Option(boolParam).map(_.booleanValue)
  )
}
case class ModelObject private(boolParam : Option[Boolean])

但这并不正确,我一直想知道是否有更好的方法来解决这个问题。我一直在考虑定义联合类型(带有表示未定义值的附加对象):How to define "type disjunction" (union types)?,但是由于不应该明确使用未定义状态,IDE 向用户公开的参数类型,它将非常令人困惑(理想情况下,我希望它是布尔值)。

有没有更好的方法来解决这个问题?

更多信息:

【问题讨论】:

    标签: scala


    【解决方案1】:

    您可以使用我在此处描述的模式的变体:How to provide helper methods to build a Map

    总而言之,您可以使用一些辅助泛型类来表示可选参数(很像Option)。

    abstract sealed class OptArg[+T] {
      def toOption: Option[T]
    }
    object OptArg{
      implicit def autoWrap[T]( value: T  ): OptArg[T] = SomeArg(value)
      implicit def toOption[T]( arg: OptArg[T] ): Option[T] = arg.toOption
    }
    case class SomeArg[+T]( value: T ) extends OptArg[T] {
      def toOption = Some( value )
    }
    case object NoArg extends OptArg[Nothing] {
      val toOption = None
    }
    

    您可以像这样简单地使用它:

    scala>case class ModelObject(boolParam: OptArg[Boolean] = NoArg)
    defined class ModelObject
    
    scala> ModelObject(true)
    res12: ModelObject = ModelObject(SomeArg(true))
    
    scala> ModelObject()
    res13: ModelObject = ModelObject(NoArg)    
    

    但是,您可以看到 OptArg 现在在 ModelObject 类本身中泄漏(boolParam 被键入为 OptArg[Boolean] 而不是 Option[Boolean]。 解决这个问题(如果它对您很重要)只需要像您自己那样定义一个单独的工厂:

    scala> :paste
    // Entering paste mode (ctrl-D to finish)
    case class ModelObject private(boolParam: Option[Boolean])
    object ModelObject {
      def apply(boolParam: OptArg[Boolean] = NoArg): ModelObject = new ModelObject(
        boolParam = boolParam.toOption
      )
    }
    // Exiting paste mode, now interpreting.
    defined class ModelObject
    defined module ModelObject
    
    scala> ModelObject(true)
    res22: ModelObject = ModelObject(Some(true))
    
    scala> ModelObject()
    res23: ModelObject = ModelObject(None)
    

    UPDATE 与@drexin 所示的简单定义几个重载的apply 方法相比,使用这种模式的优势在于,在后一种情况下,重载的数量随着参数的数量增长得非常快(2^N)。如果ModelObject 有 4 个参数,那就意味着要手动编写 16 个重载!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-07-19
      • 1970-01-01
      • 1970-01-01
      • 2012-05-26
      • 2011-08-23
      • 2016-01-28
      • 2019-01-06
      • 1970-01-01
      相关资源
      最近更新 更多