I'm a relative Scala beginner and would like some advice on how to proceed on an implementation that seems like it can be done either with a function returning Option or with PartialFunction. I've read all the related posts I could find (see bottom of question), but these seem to involve the technical details of using PartialFunction or converting one to the other; I am looking for an answer of the type "if the circumstances are X,Y,Z, then use A else B, but also consider C".
My example use case is a path search between locations using a library of path finders. Say the locations are of type L
, a path was of type P
and the desired path search result would be an Iterable[P]
. The patch search result should be assembled by asking all the path finders (in something like Google maps these might be Bicycle, Car, Walk, Subway, etc.) for their path suggestions, which may or may not be defined for a particular start/end location pair.
There seem to be two ways to go about this:
(a) define a path finder as f: (L,L) => Option[P]
and then get the result via something like finders.map( _.apply(l1,l2) ).filter( _.isDefined ).map( _.get )
(b) define a path finder as f: PartialFunction[(L,L),P] and then get the result via something like
finders.filter( _.isDefined( (l1,l2) ) ).map( _.apply( (l1,l2)) )`
It seems like using a function returning Option[P]
would avoid double evaluation of results, so for an expensive computation this may be preferable unless one caches the results. It also seems like using Option
one can have an arbitrary input signature, whereas PartialFunction
expects a single argument. But I am particularly interested in hearing from someone with practical experience about less immediate, more "bigger picture" considerations, such as the interaction with the Scala library. Would using a PartialFunction
have significant benefits in making available certain methods of the collections API that might pay off in other ways? Would such code generally be more concise?
Related but different questions:
It feels like Option
might fit your use case better.
My interpretation is that Partial Functions work well to be combined over input ranges. So if f
is defined over (SanDiego,Irvine)
and g
is defined over (Paris,London)
then you can get a function that is defined over the combined input (SanDiego,Irvine)
and (Paris,London)
by doing f orElse g
.
But in your case it seems, things happen for a given (l1,l2)
location tuple and then you do some work...
If you find yourself writing a lot of {case (L,M) => ... case (P,Q) => ...}
then it may be the sign that partial functions are a better fit.
Otherwise options work well with the rest of the collections and can be used like this instead of your (a) proposal:
val processedPaths = for {
f <- finders
p <- f(l1, l2)
} yield process(p)
Within the for comprehension p
is lifted into an Traversable
, so you don't even have to call filter
, isDefined
or get
to skip the finders without results.