Is using Optional in Scala's case classes and

2019-04-06 10:49发布

There were already quite a few discussions on Stackoverflow about proper ways of using Optional in Java (Discussions like this one, or this)

As of now, using Optional for class members in Java is widely recognized as a code smell and even discouraged by fact that it deliberately does not implement Serializable interface. Also, we should avoid it in DTOs, constructors and methods' input parameters. From OOP point of view everything I have read so far about Optional appeals to my reason.

My question is, does FP side of Scala change something in a way we should use Optional ? Especially since implementation of Optional in Scala seems to be way richer. I have found plenty of articles describing how to use it in Scala, but not a single one that exhaust topic when should I use it and when should I not.

2条回答
ら.Afraid
2楼-- · 2019-04-06 11:17

Option of scala implements Serializable

Usage of Option in scala is highly recommended for nullable attributes. Option[T] is considered better than T because the former is more typesafe than than latter.

As of now, using Optional for class members in Java is widely recognized as a code smell

on the contrary presence of null in-place of Optional attribute in scala is considered a code-smell.

As much as Scala is a functional language it also a language that promotes type-safety. In an Ideal world a truly fully typesafe language will not have runtime exceptions like NullpointerException and Option plays a important role in Scala to avoid it.

The Option[T] explicits states that the attribute can be in the state of null (i.e. None) and forces the clients of the attribute to handle the null scenario. Thus Option adds more information to the type system and makes the code more typesafe.

With language feature such as such as pattern matching and Monad/Monoid the economics of using Optional datatypes in Scala is very cheap and user friendly in Scala compared to Java.

Pattern matching:

 optionalVariable match {
   case Some(x) => /* handle when variable has a value*/
   case None => /* handle when the variable doesn't have a value*/
 }

Option as Monad:

optionalVariable foreach { x => /* run code when the variable is defined*/ }
optionalVariable map { x => /* map optional type to another type */}

Edit:

Jubobs makes very good case where using Option can be replaced with custom types. But I think there are many more cases where Optional attributes make more sense. For eg: If the Account object has optional attributes such as emailId and phoneNo then Option[T] would be a better solution since creating custom types for each combination would be impractical and would lead to class explosion.

查看更多
爱情/是我丢掉的垃圾
3楼-- · 2019-04-06 11:24

Short answer

Option fields have use cases; they're not intrinsically bad. However, even though several well established libraries (e.g. ScalaTest) define classes with Option fields, the latter, IMO, tend to be a code smell, as they often try to do too much for their own good.

In many cases, a type containing optional fields can easily and advantageously be replaced by an algebraic data type.

An example

The domain

Consider a business domain dealing with accounts. An account starts its life one day as an open account, but may eventually be closed. Accounts, among other data, contains the dates on which they were open and closed, where applicable.

Using an Option field

Here is an implementation of an account, using an Option field:

final case class Account(openedOn: LocalDate, closedOn: Option[LocalDate], ...)

We also have an account service, which defines, among other things, a close method:

trait AccountService {
  // ...
  def close(account: Account): Account
}

This approach is problematic, for a number of reasons. One problem is that Account isn't particularly performant: because closedOn is a "boxed" type, you have one level of indirection too many, so to speak. Moreover, Account's memory footprint is less than ideal: a "closed account" contains a pretty uninteresting value (None), which is a waste of space.

Another, more serious, problem is that the close method cannot enforce, at the type level, that the parameter be an "open account" and the result be a "closed account". You would have to write tests to check that this business rule is enforced by your implementation.

Using a small ADT (and eschewing Option fields)

Consider the following alternative design:

sealed trait Account { ... }

final case class OpenAccount(openedOn: LocalDate, ...) extends Account

final case class ClosedAccount(openedOn: LocalDate, closedOn: LocalDate, ...) extends Account

This small ADT remedies the performance problem, but there is more... You can now encode the business rule at the type level! This is an example of making illegal states unrepresentable (a phrase attributed to Yaron Minsky). As a result, your service's API becomes more expressive and harder to misuse:

trait AccountService {
  // ...
  def close(account: OpenAccount): ClosedAccount
}

This example may be sufficient to convince you that the second approach is preferable, and that Option fields are best avoided (or, at least, used sparingly).

Resources

For more more about eliminating optional fields towards making illegal states unrepresentable, see

查看更多
登录 后发表回答