Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best Scala imitation of Groovy's safe-dereference operator (?.)?

Tags:

I would like to know what the best Scala imitation of Groovy's safe-dereference operator (?.), or at least some close alternatives are?

I've discussed it breifly on Daniel Spiewak's blog, but would like to open it up to StackOverFlow...

For the sake of everyone's time, here is Daniel's initial response, my counter, and his 2nd response:

@Antony

Actually, I looked at doing that one first. Or rather, I was trying to replicate Ragenwald’s andand “operator” from Ruby land. The problem is, this is a bit difficult to do without proxies. Consider the following expression (using Ruby’s andand, but it’s the same with Groovy’s operator):

test.andand().doSomething()

I could create an implicit conversion from Any => some type implementing the andand() method, but that’s where the magic stops. Regardless of whether the value is null or not, the doSomething() method will still execute. Since it has to execute on some target in a type-safe manner, that would require the implementation of a bytecode proxy, which would be flaky and weird (problems with annotations, final methods, constructors, etc).

A better alternative is to go back to the source of inspiration for both andand as well as Groovy’s safe dereference operator: the monadic map operation. The following is some Scala syntax which uses Option to implement the pattern:

val something: Option[String] = … // presumably could be either Some(…) or None

val length = something.map(_.length)

After this, length either be Some(str.length) (where str is the String object contained within the Option), or None. This is exactly how the safe-dereferencing operator works, except it uses null rather than a type-safe monad.

As pointed out above, we could define an implicit conversion from some type T => Option[T] and then map in that fashion, but some types already have map defined, so it wouldn’t be very useful. Alternatively, I could implement something similar to map but with a separate name, but any way it is implemented, it will rely upon a higher-order function rather than a simple chained call. It seems to be just the nature of statically typed languages (if anyone has a way around this, feel free to correct me).

Daniel Spiewak Monday, July 7, 2008 at 1:42 pm

My 2nd question:

Thanks for the response Daniel regarding ?. I think I missed it! I think I understand what you’re proposing, but what about something like this, assuming you don’t have control over the sources:

company?.getContactPerson?.getContactDetails?.getAddress?.getCity

Say it’s a java bean and you can’t go in and change the return values to Something[T] - what can we do there?

Antony Stubbs Tuesday, July 21, 2009 at 8:07 pm oh gosh - ok on re-read that’s where you’re proposing the implicit conversion from T to Option[T] right? But would you still be able to chain it together like that? You’d still need the map right? hmm….

var city = company.map(_.getContactPerson.map(_.getContactDetails.map(_.getAddress.map(_.getCity))))

?

Antony Stubbs Tuesday, July 21, 2009 at 8:10 pm

His 2nd response:

@Antony

We can’t really do much of anything in the case of company?.getContactPerson, etc… Even assuming this were valid Scala syntax, we would still need some way to prevent the later calls in the chain. This is not possible if we’re not using function values. Thus, something like map is really the only option.

An implicit conversion to Option wouldn’t be bad, but by making things implicit, we’re circumventing some of the protection of the type system. The best way to do this sort of thing is to use for-comprehensions in concert with Option. We can do map and flatMap, but it’s much nicer with magical syntax:

 for {
   c < - company
   person <- c.getContactPerson   
   details <- person.getContactDetails
   address <- details.getAddress 
  } yield address.getCity

Daniel Spiewak Tuesday, July 21, 2009 at 9:28 pm

P.s. if Daniel posts his original answers on his blog as answers, I will edit the question to remove them for the sake of the System.

like image 930
Antony Stubbs Avatar asked Jul 22 '09 06:07

Antony Stubbs


2 Answers

There are two things that need to be considered here.

First, there is the problem of the "nothing". How do you chain things when a part of the chain may not return anything? The answer is using Option and for comprehensions. For example:

scala> case class Address(city: Option[String] = None, street: Option[String] = None, number: Option[Int] = None)
defined class Address

scala> case class Contact(name: String, phone: Option[String] = None, address: Option[Address] = None)
defined class Contact

scala> case class ContactDetails(phone: Option[String] = None, address: Option[Address] = None)
defined class ContactDetails

scala> case class Contact(phone: Option[String] = None, address: Option[Address] = None)
defined class Contact

scala> case class Person(name: String, contactDetails: Option[Contact] = None)
defined class Person

scala> case class Company(name: String, contactPerson: Option[Person] = None)
defined class Company

scala> val p1 = Company("ABC", Some(Person("Dean", Some(Contact(None, Some(Address(city = Some("New England"))))))))
p1: Company = Company(ABC,Some(Person(Dean,Some(Contact(None,Some(Address(Some(New England),None,None)))))))

scala> val p2 = Company("Finnicky", Some(Person("Gimli", None)))
p2: Company = Company(Finnicky,Some(Person(Gimli,None)))

scala> for(company <- List(p1, p2);
     | contactPerson <- company.contactPerson;
     | contactDetails <- contactPerson.contactDetails;
     | address <- contactDetails.address;
     | city <- address.city) yield city
res28: List[String] = List(New England)

This is how you are supposed to write code which may return something or not in Scala.

The second problem, of course, is that sometimes you may not have access to the source code to do the proper convertion. In this case, there is some additional syntax overhead to be head, unless an implicit can be used. I'll give an example below, in which I use an "toOption" function -- there is such a thing on Scala 2.8, of which I'll talk about below.

scala> def toOption[T](t: T): Option[T] = if (t == null) None else Some(t)
toOption: [T](t: T)Option[T]

scala> case class Address(city: String = null, street: String = null, number: Int = 0)
defined class Address

scala> case class Contact(phone: String = null, address: Address = null)
defined class Contact

scala> case class Person(name: String, contactDetails: Contact = null)
defined class Person

scala> case class Company(name: String, contactPerson: Person = null)
defined class Company

scala> val p1 = Company("ABC", Person("Dean", Contact(null, Address(city = "New England"))))
p1: Company = Company(ABC,Person(Dean,Contact(null,Address(New England,null,0))))

scala> val p2 = Company("Finnicky", Person("Gimli"))
p2: Company = Company(Finnicky,Person(Gimli,null))

scala> for(company <- List(p1, p2);
     | contactPerson <- toOption(company.contactPerson);
     | contactDetails <- toOption(contactPerson.contactDetails);
     | address <- toOption(contactDetails.address);
     | city <- toOption(address.city)) yield city
res30: List[String] = List(New England)

Remember that you can be quite creative in naming a function. So, instead of "toOption", I might have named it "?", in which case I'd write things like "?(address.city)".

Thanks to nuttycom for reminding me, on Scala 2.8 there is an Option factory on the object Option, so I can just write Option(something). In effect, you can replace "toOption" above with "Option". And if you prefer using ?, you can just use import with rename.

like image 160
Daniel C. Sobral Avatar answered Sep 24 '22 08:09

Daniel C. Sobral


Create this implicit conversion.

class SafeDereference[A](obj: A) {
  def ?[B >: Null](function: A => B): B = if (obj == null) null else function(obj)
}

implicit def safeDereference[A](obj: A) = new SafeDereference(obj)

The usage isn't as pretty as Groovy, but it's not awful.

case class Address(state: String)
case class Person(first: String, last: String, address: Address)
val me = Person("Craig", "Motlin", null)

scala> me ? (_.first)
res1: String = Craig

scala> me ? (_.address)
res2: Address = null

scala> me ? (_.address) ? (_.state)
res3: String = null
like image 36
Craig P. Motlin Avatar answered Sep 25 '22 08:09

Craig P. Motlin