Created
July 14, 2019 08:34
-
-
Save imaman/a74d33c55fc313561d59f40863007b47 to your computer and use it in GitHub Desktop.
exceptions are referentially transparent
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
So a common conception (perhaps I should say “misconception”) about Exceptions in Scala is that they should be avoided due to lack of referential transparency (see “do not throw exceptions” and “referential transparency”). I think this topic is a bit subtler that one might initially think. | |
The argument goes as follows: “you should be able to inline/extract variables without changing the program’s behavior. But Exceptions are not like that.” | |
The text-book example goes as follows: | |
def f1(n: Int): Int = | |
if (n == 0) throw new RuntimeException("zero") else n + 1 | |
def f2(n: Int): Int = { | |
val x = throw new RuntimeException("zero"); | |
if (n == 0) x else n + 1 | |
} | |
The only difference between these two functions is that the throw expression was extracted into a variable. However, the two functions are not equivalent (f1(4) succeeds but f2(4) fails), hence … “exceptions should be avoided”. | |
Let’s try a slight variation on this example and see were it takes us. | |
def f3(n: Int): Int = | |
if (n == 0) 1 else f3(n - 1) * n; | |
def f4(n: Int): Int = { | |
val x = f4(n - 1); | |
if (n == 0) 1 else x * n; | |
} | |
The only difference between these two functions is that one expression (specifically, the recursive call) was extracted into a variable. However, the two functions are not equivalent (f3(4) succeeds but f4(4) fails), hence … “recursion should be avoided” ??? | |
Of course not. Scala has lazy evaluation: | |
def f4Fixed(n: Int): Int = { | |
lazy val x = f4Fixed(n - 1); | |
if (n == 0) 1 else x * n; | |
} | |
f4Fixed(4) now returns the same output as f3(4). lazy saved the day! | |
Now let’s take it one step further. What if we were to use lazy in f2() ? | |
def f2Fixed(n: Int): Int = { | |
lazy val x = throw new RuntimeException("zero"); | |
if (n == 0) x else n + 1 | |
} | |
So now f2Fixed(4) succeeds and returns the same output as f1(4). In other words, exceptions are referential transparent to the same extent as recursion. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment