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 the initial 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
!