12

So I'm learning functional Scala, and the book says exception breaks referential transparency, and thus Option should be used instead, like so:

def pattern(s: String): Option[Pattern] = {
  try {
    Some(Pattern.compile(s))
  } catch {
    case e: PatternSyntaxException => None
  }
}

This seems pretty bad; I mean it seems equivalent to:

catch(Exception e){
    return null;
}

Save for the fact that we can distinguish "null for error" from "null as genuine value". It seems it should at least return something that contains the error information like:

catch {
    case e: Exception => Fail(e)
}

What am I missing?

user2864740
  • 60,010
  • 15
  • 145
  • 220
BasilTomato
  • 1,071
  • 1
  • 8
  • 14
  • Which book? I don't see how exceptions would break referential transparency. In Scala, `Nothing` is a type (albeit special). If your function consistently throws an exception upon a given constant argument, that would be transparent in my understanding. – 0__ Jul 06 '14 at 12:03
  • @0__: The book is http://www.manning.com/bjarnason/ "throw and catch means we can no longer reason [...] by substituting terms with their definitions if we replace substitute x in x + y with throw new Exception("fail!") + y, our program has a different result" – BasilTomato Jul 06 '14 at 12:12
  • Well, I think he comes from the school that hates sub-typing. In Scala, `Nothing` is a bottom type, so I wouldn't call it "a different result". In any case, if you want to deal with errors directly on the call-side, then using a different mechanism like `Try` or `Either` might be more useful. – 0__ Jul 06 '14 at 12:22
  • 2
    There are several algebraic alternatives to the weird control flow thing that exception throwing does, and `Option` is just the one you use when you're not interested in the error details. The book probably introduced this one first because it's the simplest. – Chris Martin Jul 06 '14 at 19:01

5 Answers5

11

At this specific section, Option is used mostly as an example because the operation used (calculating the mean) is a partial function, it doesn't produce a value for all possible values (the collection could be empty, thus there's no way to calculate the mean) and Option could be a valid case here. If you can't calculate the mean because the collection is empty just return a None.

But there are many other ways to solve this problem, you could use Either[L,R], with the Left being the error result and a Right as being the good result, you could still throw an exception and wrap it inside a Try object (which seems more common nowadays due to it's use in Promise and Future computations), you could use ScalaZ Validation if the error was actually a validation issue.

The main concept you should take a way from this part is that the error should be part of the return type of the function and not some magic operation (the exception) that can't be reasonably declared by the types.

And as a shameless plug, I did blog about Either and Try here.

Maurício Linhares
  • 39,901
  • 14
  • 121
  • 158
  • 1
    Section 4.4 of the book talks about `Either`, and the exercises hint at `Validation` (which is also mentioned much later in the book.) – DNA Jul 06 '14 at 20:28
8

It would be easier to answer this question if you weren't asking "why is Option better than exceptions?" and "why is Option better than null?" and "why is Option better than Try?" all at the same time.

The answer to the first of these questions is that using exceptions in situations that aren't truly exceptional muddles the control flow of your program. This is where referential transparency comes in—it's much easier for me (or you) to reason about your code if I can think in terms of values and don't have to keep track of where exceptions are being thrown and caught.

The answer to the second question (why not null?) is something like "Have you ever had to deal with NullPointerException in Java?".

For the third question, in general you're right—it's better to use a type like Either[Throwable, A] or Try[A] to represent computations that can fail, since they allow you to pass along more detailed information about the failure. In some cases, though, when a function can only fail in a single obvious way, it makes sense to use Option. For example, if I'm performing a lookup in a map, I probably don't really need or want something like an Either[NoSuchElementException, A], where the error is so abstract that I'd probably end up wrapping it in something more domain-specific anyway. So get on a map just returns an Option[A].

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • 1
    "using exceptions in situations that aren't truly exceptional muddles the control flow of your program": Very good point that is often overlooked by exception fans. +1 – Giorgio Jul 06 '14 at 19:33
  • 1
    +1 for the clear separation of the three implied questions. Maybe I would add: Staying within the idiomatic framework also makes sense when considering the bigger picture. In contrast to plain exceptions, `Option` is a monad. This gives you the full power of monadic expressions, i.e., computations that are defined as a sequences of steps. In the end this allows to use monadic syntax concepts like `flatMap` and Scala's `for`, which overall results in a more homogeneous syntax. – bluenote10 Jul 07 '14 at 11:42
3

You should use util.Try:

scala> import java.util.regex.Pattern
import java.util.regex.Pattern

scala> def pattern(s: String): util.Try[Pattern] = util.Try(Pattern.compile(s))
pattern: (s: String)scala.util.Try[java.util.regex.Pattern]


scala> pattern("<?++")
res0: scala.util.Try[java.util.regex.Pattern] =
Failure(java.util.regex.PatternSyntaxException: Dangling meta character '+' near index 3
<?++
   ^)

scala> pattern("[.*]")
res1: scala.util.Try[java.util.regex.Pattern] = Success([.*])
Eastsun
  • 18,526
  • 6
  • 57
  • 81
2

The naive example

def pattern(s: String): Pattern = {
  Pattern.compile(s)
}

has a sideeffect, it can influence the programm that uses it by other means than its result(it can cause a exception). This is discouraged in functional programming, because it increases the code complexity.
The code

def pattern(s: String): Option[Pattern] = {
  try {
    Some(Pattern.compile(s))
  } catch {
    case e: PatternSyntaxException => None
  }
}

encapsulates the side effect producing part of the programm. The information why the Pattern failed is lost, but sometimes it only matters whether or not it fails. If it matters why the method failed one can use Try(http://www.scala-lang.org/files/archive/nightly/docs/library/index.html#scala.util.Try):

def pattern(s: String): Try[Pattern] = {
   Try(Pattern.compile(s))
}
Siphor
  • 2,522
  • 2
  • 13
  • 10
0

I think the other two answers give you good suggestions about how to proceed. I would still argue that throwing an exception is well represented in Scala's type system, using the bottom type Nothing. So it is well-typed, and I wouldn't exactly called it "magic operation".

However... if your method can quite commonly result in an invalid value, that is if your call side quite reasonably wants to handle such an invalid value straight away, then using Option, Either or Try is a good approach. In a scenario, where your call site doesn't really know what to do with such an invalid value, especially if it is an exceptional condition and not the common case, then you should use exceptions IMO.

The problem of exception is precisely not that they are not well working with functional programming, but that they can be difficult to reason about when you have side effects. Because then your call site must ensure to undo the side effects in the case of an exception. If your call site is purely functional, passing on an exception doesn't do any damage.

If any functions that does anything with integers would declare its return type a Try because of division-by-zero or overflow possibilities, this might totally clutter your code. Another very good reason to use exceptions is invalid argument ranges, or requirements. If you expect an argument to be an integer between 0 and x, you may well throw an IllegalArgumentException if it does not meet that property; conveniently in Scala: require(a >= 0 && a < x).

0__
  • 66,707
  • 21
  • 171
  • 266
  • "exception is well represented in Scala's type system": I have to disagree here. You seem to only consider the case where the method **always** throws (such as `sys.error`), but most methods will throw an exception only in, well, exceptional cases, in which case the return type cannot be `Nothing`. So the fact that method may throw is entirely lost in the signature, aka (black) "magic is happening". This contrasts with using `Try` or `Either`, where this is explicit. Basically, the argument is a generalisation of the reasons why `Option` is better than using `null`s. – Régis Jean-Gilles Jul 07 '14 at 11:46