Soteric
Soteric

Reputation: 3120

Compare instances of Option[T] avoiding None == None

Yesterday I was suddenly enlighted and understood how and why people use 'map' method with Option to compare values. Yes, I'm a bit slow, sorry :)

I revised these very nice links and came to the question I would like to ask.

http://twitter.github.com/effectivescala

http://blog.tmorris.net/posts/scalaoption-cheat-sheet

In my Lift webapp I have some Option[User] and Option[Server] variables. I'm trying to find out if this User is admin of this Server by the following check

if(user.map(_.id) == server.map(_.adminId))

But I noticed that in case of 'user' is None and 'server' is also None this check succeeds which is not good for me (if any of them is None I'd like this check to fail). I could add user.isDefined condition but I feel there is more correct way to do it. Could you tell how to accomplish it in Scala way?

Upvotes: 18

Views: 22217

Answers (4)

Xavier Guihot
Xavier Guihot

Reputation: 61656

We can make use of Option#zip to work with an Option of the tuple user/server:

user zip server exists { case (user, server) => user.id == server.adminId }

where the behavior of Option#zip is:

Some(User(id = "hello")) zip Some(Server(adminId = "world"))
// Some((User("hello"), Server("world")))
Some(User(id = "hello")) zip None        // None
None zip Some(Server(adminId = "world")) // None
None zip None                            // None

and where Option#exists applies a predicate on the optional tuple produced by zip.

Upvotes: 3

Suma
Suma

Reputation: 34393

I got used to the combination of exists / contains for this purpose.

When comparing two options of the same type:

o1.exists(o2.contains)

In your case this can be applied using map:

user.map(_.id).exists(server.map(_.adminId).contains)

Upvotes: 25

pagoda_5b
pagoda_5b

Reputation: 7373

you can use a for comprehension

def isAdmin(server: Option[Server])(user: Option[User]): Boolean = (for {
    s <- server
    u <- user
  } yield (u.id == s.adminId)
).getOrElse(false)

The comprehension results in a Option[Boolean] from which you get the value or false if there's no value (the case where any of the options is None, as you requested)

Why curried?

I made the method curried, so you can define you function for a specific server, and then reuse that to check many users

def isMyServerAdmin = isAdmin(Some(myServer)) _

isMyServerAdmin(Some(user1)) = true
isMyServerAdmin(Some(user2)) = false
isMyServerAdmin(None) = false

Upvotes: 3

R&#233;gis Jean-Gilles
R&#233;gis Jean-Gilles

Reputation: 32719

You could do it with pattern matching (which in this case is probably the clearest way):

(user, server) match {
  case (Some(user), Some(server)) if user.id == server.adminId =>
    // both ids are matching, handle this case here
  case _ =>
    // no match, handle this case here
}

You could also try as a one-liner but here I don't advise it as it's pretty obfuscated:

if ( user.flatMap{ user => server.map(_.adminId == user.id) }.getOrElse( false ) ) {
  // both ids are matching, handle this case here
}
else {
  // no match, handle this case here
}

Finally, if you only have to handle the case where the ids match (and would just do nothing if there is not), using a for comprehension is not too bad of an option (no pun intended):

for ( user <- user; server <- server if user.id == server.adminId ) {
  // both ids are matching, handle this case here
}

Upvotes: 29

Related Questions