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.