2
votes

I represented my data model as case classes typing values that may be null as Option.

case class Document(id: Long, title: String, subtitle: Option[String])

Now I try to instantiate the case class:

Document(123, "The Title", "Subtitle") // Doesn't work

But NOPE! This doesn't work, I have to wrap the optional value in a Some.

Document(123, "The Title", Some("Subtitle")) // Works

Scala is very clever about types in general, but why is it not self-evident that a hard coded literal, or (any string for that matter) is a different than null/None?

I was able to fix this and make Scala "more clever" by adding this implicit conversion

implicit def autoSome[T](any:T) = Some(any)
Document(123, "The Title", "Subtitle") // Now it works!

Question: Am I the only one that the language should provide implicit conversion T -> Some(T) out of the box? Or is there any gotchas that I'm not aware of about having so broad implicit everywhere by default?

3
You are the only one. Code with implicit conversions is generally incredibly difficult to understand. This implicit conversion is confusing in particular when you have Option[Any] of which Some(Some(x)) is a valid value, as is Some(x). You can overload Document.apply and Document.unapply instead. - user1804599
If you want your subtitle to be an empty string by default, you can also write case class Document(id: Long, title: String, subtitle:String = "") - Ashalynd
I might rephrase this question as "what's the right way to simplify turning literals into options" which seems like a reasonable request - wrschneider

3 Answers

6
votes

This can cause untold number of problems. The issue here isn't what you might think but what you don't think could happen. That is, if you make another implicit class that works on Option types you could wind up creating artificial results that you never intended to happen, i.e. operators for your overloaded types being present in your non-overloaded types.

 implicit class OptDouble(opt: Option[Double]) extends Any{
   def *(x: Double) = Some((opt getOrElse 0.0) * x)
   def ^(x: Double) = Some(Math.power(opt getOrElse 1.0, x))
 }

 val z = q^4.5

The type of z is Option[Double]. You wouldn't expect that to happen but first Scala did an implicit conversion to Option and then it used the implicit class to call the ^ operator. Now people looking at your code are going to be scratching their heads wondering why they have an Option. You might start seeing a few defensive x getOrElse 0.0 sprinkled around the code as people fight to keep Option away (and yes, this is from personal experience.)

That said, what you should do is use another apply on the object:

object Document{
  def apply(id: Long, title: String, subtitle: String) = new Document(id, title, Some(subtitle))
}

which will do everything you wanted it to do as long as you don't have a default defined for subtitle, i.e. subtitle: Option[String] = None.

4
votes

Most problems pointed out earlier can easily be fixed with a small change to the implicit: implict def autoOpt[T](x: T): Option[T] = Option(x)

I can't really think of a good reason scala does not provide this conversion as a part of the default library of implicit converters.

The fact that implicts make code harder to understand can be used as an argument against using any implicit, but not as one against using this particular one

1
votes

That's a pretty dangerous implementation:

scala> val s: String = null
s: String = null

scala> Document(123, "The Title", s)
res2: Document = Document(123,The Title,Some(null))