Pattern for optional-parameters in Scala using null

拈花ヽ惹草 提交于 2019-12-11 03:19:39

问题


I have a function which takes optional parameters. However, these are not Option[?] but can be either set or null:

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™
  ...
}

I am using "null", which I know should be avoided like the plague. However, it allows me to write code like this:

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

which to my eyes looks nicer than:

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

Is this a known / acceptable Scala pattern? (using null as default parameter values and converting to Option)

Or is it still heressy / bad-practice? If so, why?


回答1:


why not replace null with empty string ?

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™
  ...
}

then you can do:

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



回答2:


One more approach I can suggest is a Builder Pattern

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

This approach allows you explicitly set parameters and then build you element by them




回答3:


Most of the answers here are proposing some variant of the "null object" pattern, by denoting an empty String to mean "undefined" (as in val optId = if(id.isEmpty) None else Some(id))

The catch here is that an empty string might be a valid value! This is true of any String, though you can mitigate the problem by using something really outrageous, possibly involving non-printable characters. e.g:

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™
  ...
}

Better still, you could use a different type to denote your null object. As you can't subclass String you'll have to bump your params up the type hierarchy and make them 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™
  ...
}

It's a heavyweight pattern for one-shot usage, but the cost is quickly amortized if you use it a lot (NullCharSeq and charSeqToOptStr only need to be defined once in the codebase).

There's also zero risk of mistakenly passing your "undefined" String as though it were a valid value. Plus, you gain the ability to directly accept CharBuffer/StringBuffer/StringBuilder as your arguments.




回答4:


I would also go for a special string like the empty string in @Jiafeng's answer, if this is a sensible value. You could also define a string, like

val NoId = "?"

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

Another approach would be to use another type which can be implicitly created from string or absence.

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 is a general type that behaves like Option[A] with implicit conversion A => Some[A].



来源:https://stackoverflow.com/questions/20824813/pattern-for-optional-parameters-in-scala-using-null

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!