Option and Either in Scala

I'm used to Ruby. In Ruby, you can use nil with abandon and just do something like if variable to check if it exists. Below is valid Ruby code (though forgive me if I'm now out of practice):

1
2
3
4
5
6
7
def displayUser(user)
  if user
    user.name
  else
    "No name"
  end
end

When I started on this project, I started treating Scala the same way. However, I found out that apparently you want to avoid using null in Scala. My first iteration prior to discovering this was the following:

1
2
3
4
5
6
7
def displayUser(user: User = null): String = {
  if(user != null) {
    return user.name
  } else {
    return "No name"
  }
}

This is not proper Scala. Unlike Ruby, Scala has Option. Option allows you to have Some or None. As you might expect, Some has a value, while None is the equivalent of null. Here's an example of how to do that same function properly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def displayUser(user: Option[User] = None): String = {
  if(user.isDefined) {
    return user.get.name
  } else {
    return "No name"
  }
}
// call it like this
user = User()
displayUser(Some[user]) //or
displayUser()

Either provides a similar function to Option but is better for returning error messages. The problem I was trying to solve was creating an organization user. For that to happen, there must be an organization and a user. Here's my inital way I did it, thinking as a rubyist:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def validateUserAndOrgExist(email: String, orgId: Long): (Organization, User, Boolean, String) = {
  val user = usersService.findByEmail(email).head
  val organization = organizationsService.find(orgId).head
  if (user == null) {
    return (null, null, false, "User does not exist. Please create user first.")
  } else if (organization == null) {
    return (null, null, false, "Organization does not exist. Please create organization first.")
  } else {
    return (organization, user, true, "both exist")
  }
}

However, the better option (hehe) is to use Either in this case. So here's the better way to do this same function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
trait ValidationError { val message: String}
case class IdentifierNotFound(message: String) extends ValidationError

def validateUserAndOrgExist(name: String, orgId: Long): Either[IdentifierNotFound, (Organization, User)] = {
  val user = usersService.findByEmail(name).headOption
  val organization = organizationsService.find(orgId).headOption
  if (user.isEmpty) {
    Left(IdentifierNotFound("User does not exist. Please create user first."))
  } else if (organization.isEmpty) {
    Left(IdentifierNotFound("Organization does not exist. Please create organization first."))
  } else {
    Right((organization.get, user.get))
  }
}
// then call it and use it like this:
val validationOfUserOrg = validateUserAndOrgExist("test", 123)
if(validationOfUserOrg.isRight){
  val (organization, user) = validationOfUserOrg.right.get
  print("User $user.name is in Organization $organization.id.")
} else {
  print(validationOfUserOrg.left.get.message)
}

And that's how you use Either and Option!