【问题标题】:Pattern for optional-parameters in Scala using nullScala中使用null的可选参数模式
【发布时间】:2014-01-16 11:15:54
【问题描述】:

我有一个带有可选参数的函数。但是,这些不是 Option[?] 而是可以设置或为空:

private def div(id: String = null, cssClass: String = null): JQuery = {
  val optId = Option(id)
  val optCssClass = Option(cssClass)
  ...
  // deal with optId and optCssClass using the Scala-way™
  ...
}

我正在使用“null”,我知道应该像瘟疫一样避免使用它。但是,它允许我编写这样的代码:

div(id = "someId") // no cssClass
div(id = "otherId", cssClass = "someClass")

在我看来,这比:

div(id = Some("someId")) // no cssClass
div(id = Some("otherId"), cssClass = Some("someClass"))

这是一种已知/可接受的 Scala 模式吗? (使用 null 作为默认参数值并转换为 Option)

或者它仍然是异端/不良做法?如果有,为什么?

【问题讨论】:

标签: scala coding-style null options optional-parameters


【解决方案1】:

为什么不用空字符串替换null

private def div(id: String = "", cssClass: String = ""): JQuery = {
  val optId = if(id.isEmpty) None else Some(id)
  val optCssClass = if(cssClass.isEmpty) None else Some(cssClass)
  ...
  // deal with optId and optCssClass using the Scala-way™
  ...
}

那么你可以这样做:

div(id = "someId") // no cssClass
div(id = "otherId", cssClass = "someClass")

【讨论】:

  • 为什么不用空字符串替换 null?因为它改变了语义......我将获得的 HTML 会将所有属性设置为空,而不是根本不生成这些属性。
【解决方案2】:

我可以建议的另一种方法是构建器模式

trait ElementBuilder {
  def identified(id: String): ElementBuilder
  def build: JQuery
}

case class DivElement(identifier: Option[String] = None) 
  extends ElementBuilder {
  def identified(id: String) = this.copy(identifier = Option(id))
  def build: JQuery = ??? // Smth like <div id={identifier}></div>
}

val builder = DivElement()
builder.identified("foo")
val element = builder.build

这种方法允许您显式设置参数,然后通过它们构建您的元素

【讨论】:

    【解决方案3】:

    这里的大多数答案都提出了“空对象”模式的一些变体,将一个空字符串表示为“未定义”(如val optId = if(id.isEmpty) None else Some(id)

    这里的问题是空字符串可能是有效值! any String 也是如此,尽管您可以通过使用一些非常离谱的东西(可能涉及不可打印的字符)来缓解问题。例如:

    val UndefinedString = "THIS-IS-A-REALLY-UNLIKELY-VALID-VALUE"
    
    private def div(
      id: String = UndefinedString,
      cssClass: String = UndefinedString
    ): JQuery = {
      val optId = Option(id) filter (_ != UndefinedString )
      val optCssClass = Option(cssClass) filter (_ != UndefinedString )
      ...
      // deal with optId and optCssClass using the Scala-way™
      ...
    }
    

    更好的是,您可以使用不同的类型来表示您的空对象。由于您不能继承 String,因此您必须将参数提升到类型层次结构并使其成为 CharSequences

    object NullCharSeq extends CharSequence {
      def charAt(idx: Int): Char = ???
      def length(): Int = 0
      def subSequence(start: Int, end: Int): CharSequence = this
      def toString(): String = ???
    }
    
    def charSeqToOptStr(cs: CharSequence): Option[String] = cs match {
      case NullCharSeq => None
      case x => Option(x) map (_.toString)
    }
    
    private def div(
      id: CharSequence = NullCharSeq,
      cssClass: CharSequence = NullCharSeq
    ): JQuery = {
      val optId = charSeqToOptStr(id)
      val optCssClass = charSeqToOptStr(cssClass)
      ...
      // deal with optId and optCssClass using the Scala-way™
      ...
    }
    

    它是一次性使用的重量级模式,但如果你经常使用它,成本很快就会摊销(NullCharSeqcharSeqToOptStr 只需要在代码库中定义一次)。

    错误地传递“未定义”字符串的风险也为零,就好像它是一个有效值一样。此外,您还可以直接接受 CharBuffer/StringBuffer/StringBuilder 作为参数。

    【讨论】:

    • 与我仅使用 null 并用 Option(param) 包装它的方法相比,除了不写那个可怕的关键字的情感满足之外,这有什么不同/更好? :-D 除此之外,非常聪明的解决方案。
    • 这绝对是情感上的满足 :) 更广泛地说,如果您在多个方法之间传递它们,像这样的 Null 对象会更好,并且如果正确实施,在它们上调用方法总是安全的 - 没有风险臭名昭著的NullPointerException。也就是说,我自己有时也会使用 nulls-as-defaults,通常是在创建 DSL 时
    【解决方案4】:

    如果这是一个合理的值,我也会选择一个特殊的字符串,比如@Jiafeng 的答案中的空字符串。你也可以定义一个字符串,比如

    val NoId = "?"
    
    def div(id: String = NoId) = id match {
      case NoId => None
      case x    => Some(x)
    }
    

    另一种方法是使用可以从字符串或缺失隐式创建的另一种类型。

    sealed trait MaybeId
    implicit class Id(val name: String) extends MaybeId
    case object NoId extends MaybeId
    
    def div(id: MaybeId = NoId) = id match { 
      case NoId  => None
      case x: Id => Some(x.name)
    }
    

    Here 是一种通用类型,其行为类似于 Option[A] 隐式转换 A =&gt; Some[A]

    【讨论】:

    • 如果我有 2 或 3 个这样的可选参数,匹配参数会变得很麻烦,不是吗?我喜欢从 T 隐式转换为 Option[T] ... 的建议
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-03
    • 2012-05-26
    • 1970-01-01
    相关资源
    最近更新 更多