Ref:
http://www.eff-lang.org/handlers-tutorial.pdf
https://www.microsoft.com/en-us/research/wp-content/uploads/2016/08/algeff-tr-2016-v2.pdf
https://github.com/matijapretnar/eff
I have searched a lot of links, but it seems that no one could explain it specifically. Could anyone give some code(use javaScript) to explain it?
What is an Algebraic Effect?
TL;DR: In short, Algebraic Effects are an exception mechanism which lets the throwing function continue its operation.
Try to think of Algebraic Effects as some sort of try / catch mechanism, where the catch handler does not just "handle the exception", but is able to provide some input to the function which threw the exception. The input from the catch handler is then used in the throwing function, which continues as if there was no exception.
Some sample pseudo code:
Let's consider a function which needs some data to perform its logic:
function throwingFunction() {
// we need some data, let's check if the data is here
if (data == null) {
data = throw "we need the data"
}
// do something with the data
}
Then we have the code that invokes this function:
function handlingFunction() {
try {
throwingFunction();
} catch ("we need the data") {
provide getData();
}
}
As you see, the throw statement is an expression evaluating to the data provided by the catch handler (I used the keyword provide here, which afaik does not exist in any programming language of today).
Why is this important?
Algebraic Effects are a very general and basic concept. This can be seen by the fact that many existing concepts can be expressed in Algebraic Effects.
try/catch
If we had Algebraic Effects but no Exceptions in our favorite programming language, we could just omit the provide keyword in the catch handler, and voilà, we would have an exception mechanism.
In other words, we would not need any Exceptions if we had Algebraic Effects.
async/await
Look again at the pseudo code above. Let's assume the data which we need has to be loaded over the network. If the data is not yet there, we would normally return a Promise and use async/await to handle it. This means that our function becomes an asynchronous function, which can only be called from asynchronous functions. However, Algebraic Effects are capable of that behavior too:
function handlingFunction() {
try {
throwingFunction();
} catch ("we need the data") {
fetch('data.source')
.then(data => provide data);
}
}
Who said that the provide keyword has to be used immediately?
In other words, had we had Algebraic Effects before async/await, there would be no need to clutter up the languages with them. Furthermore, Algebraic Effects would not render our functions colorful - our function does not become aynchronous from the language viewpoint.
Aspect-Oriented Programming
Let's say we want to have some log statements in our code, but we don't yet know which logging library it will be. We just want some general log statements (I replaced the keyword throw with the keyword effect here, to make it a bit more readable - note that effect is not a keyword in any language I know):
function myFunctionDeepDownTheCallstack() {
effect "info" "myFunctionDeepDownTheCallstack begins"
// do some stuff
if (warningCondition) {
effect "warn" "myFunctionDeepDownTheCallstack has a warningCondition"
}
// do some more stuff
effect "info" "myFunctionDeepDownTheCallstack exits"
}
And then we can connect whichever log framework in a few lines:
try {
doAllTheStuff();
}
catch ("info" with message) {
log.Info(message);
}
catch ("warn" with message) {
log.Warn(message);
}
This way, the log statement and the code that actually does the logging are separated.
As you can see, the throw keyword is not really suited in the context of the very general Algebraic Effects. More suitable keywords would be effect (as used here) or perform.
More examples
There are other existing language or library constructs that could be easily realized using Algebraic Effects:
Iterators with yield. A language with Algebraic Effects does not need the yield statement.
React Hooks (this is an example of a construct at the library level - the other examples here are language constructs).
Today's support
AFAIK there are not many languages with support for Algebraic Effects out of the box (please comment if you know examples that do). However, there are languages which allow the creation of algebraic effects libraries, one example being Javascript with its function* and yield keywords (i.e. generators). The library redux-saga uses Javascript generators to create some Algebraic Effects:
function* myRoutineWithEffects() {
// prepare data load
let data = yield put({ /* ... description of data to load */ });
// use the data
}
The put is an instruction that tells the calling function to execute the data load call described in the argument. put itself does not load anything, it just creates a description of what data to load. This description is passed by the yield keyword to the calling function, which initiates the data load.
While waiting for the results, the generator routine is paused. Then, the results are passed back to the routine and can then be used there after being assigned to the data variable. The routine then continues with the local stack plus the loaded data.
Note that in this scenario, only the calling function (or the code that has a reference to the generator) can "serve" the algebraic effect, e.g. do data loads and other things. So it is not an algebraic effect as described above, because it is not an exception mechanism that can jump up and down the call stack.
Its hard to gain a solid theoretical understanding of algebraic effects without a basis in category theory, so I'll try to explain its usage in layman terms, possibly sacrificing some accuracy.
An computational effect is any computation that includes an alteration of its environment. For example, things like total disk capacity, network connectivity are external effects, that play a role in operations like reading/writing files or accessing a database. Anything that a function produces, besides the value it computes, is a computational effect. From the perspective of that function, even another function that accesses the same memory as that function does, can be considered an effect.
That's the theoretical definition. Practically, its useful to think of an effect as any interaction between a sub expression and a central control which handles global resources in a program. Sometimes, a local expression may need to send messages to the central control while execution, along with enough information so that once the central control is done, it can resume the suspended execution.
Why do we do this? Because sometimes large libraries have very long chains of abstractions, which can get messy. Using "algebraic effects", gives us a sort of short cut to pass things between abstractions, without going through the whole chain.
As a practical JavaScript example, let's take a UI library like ReactJS. The idea is that UI can be written as a simple projection of data.
This for instance, would be the representation of a button.
function Button(name) {
return { buttonLabel: name, textColor: 'black' };
}
'John Smith' -> { buttonLabel: 'John Smith', textColor: 'black' }
Using this format, we can create a long chain of composable abstractions. Like so
function Button(name) {
return { buttonLabel: name, textColor: 'black' };
}
function UsernameButton(user) {
return {
backgroundColor: 'blue',
childContent: [
Button(user.name)
]
}
}
function UserList(users){
return users.map(eachUser => {
button: UsernameButton(eachUser.name),
listStyle: 'ordered'
})
}
function App(appUsers) {
return {
pageTheme: redTheme,
userList: UserList(appUsers)
}
}
This example has four layers of abstraction composed together.
App -> UserList -> UsernameButton -> Button
Now, let's assume that for any of these buttons, I need to inherit the color theme of whatever machine it runs on. Say, mobile phones have red text, while laptops have blue text.
The theme data is in the first abstraction (App). It needs to be implemented in the last abstraction (Button).
The annoying way, would be to pass on the theme data, from App to Button, modifying each and every abstraction along the way.
App passes theme data to UserList
UserList passes it to UserButton
UserButton passes it to Button
It becomes obvious that in large libraries with hundreds of layers of abstraction, this is a huge pain.
A possible solution is to pass on the effect, through a specific effect handler and let it continue when it needs to.
function PageThemeRequest() {
return THEME_EFFECT;
}
function App(appUsers) {
const themeHandler = raise new PageThemeRequest(continuation);
return {
pageTheme: themeHandler,
userList: UserList(appUsers)
}
}
// ...Abstractions in between...
function Button(name) {
try {
return { buttonLabel: name, textColor: 'black' };
} catch PageThemeRequest -> [, continuation] {
continuation();
}
}
This type of effect handling, where one abstraction in a chain can suspend what its doing (theme implementation), send the necessary data to the central control (App, which has access to external theming), and passes along the data needed for continuation, is an extremely simplistic example of handling effects algebraically.
Well as far as I understand the topic, algebraic effects are currently an academic/experimental concept that lets you alter certain computational elements (like function calls, print statements etc.) called "effects" by using a mechanism that resembles throw catch
The simplest example I can think of in a language like JavaScript is modifying the output message in lets say console.log. Supposed you want to add "Debug Message: " in front of all your console.log statements for whatever reason. This would be trouble in JavaScript. Basically you would need to call a function on every console.log like so:
function logTransform(msg) { return "Debug Message: " + msg; }
console.log(logTransform("Hello world"));
Now if you have many console.log statements every single one of them needs to be changed if you want to introduce the change in logging. Now the concept of algebraic effects would allow you to handle the "effect" of console.log on the system. Think of it like console.log throwing an exception before invocation and this exception (the effect) bubbles up and can be handled. The only difference is: If unhandled the execution will just continue like nothing happened. No what this lets you do is manipulate the behaviour of console.log in an arbitrary scope (global or just local) without manipulating the actual call to console.log. Could look something like this:
try
{
console.log("Hello world");
}
catch effect console.log(continuation, msg)
{
msg = "Debug message: " + msg;
continuation();
}
Note that this is not JavaScript, I'm just making the syntax up. As algebraic effects are an experimental construct they are not natively supported in any major programming language I know (there are however several experimental languages like eff https://www.eff-lang.org/learn/). I hope you get a rough understanding how my made-up code is intended to work. In the try catch block the effect that might be thrown by console.log can be handled. Continuation is a token-like construct that is needed to control when the normal workflow should continue. It's not necessary to have such a thing but it would allow you to make manipulations before and after console.log (for example you could add an extra log message after each console.log)
All in all algebraic effects are an interesting concept that helps with many real-world problems in coding but it can also introduce certain pitfalls if methods suddenly behave differently than expected. If you want to use algebraic effects right now in JavaScript you would have to write a framework for it yourself and you probably won't be able to apply algebraic effects to core functions such as console.log anyway. Basically all you can do right now is explore the concept on an abstract scale and think about it or learn one of the experimental languages. I think that's also the reason why many of the introductory papers are so abstract.
You can check out algebraic-effects. It is a library that implements a lot of the concepts of algebraic effects in javascript using generator functions, including multiple continuations. It's a lot easier to understand algebraic-effects in terms of try-catch (Exception effect) and generator functions.
Related
private def retrieveSongId(songName: String): Option[JsValue] = {
val geniusStringResponse = Http("https://api.genius.com/search?q=" + formattedSongName)
.param("access_token", apiKey)
.asString
.body
//Extra processing with geniusStringResponse
}
}
Will the above function be considered a side effect due to the HTTP request? If so, is Scala code like this appropriate?
Yes, calling this function has the side effect of making an HTTP request. Calling this function may affect the result of another function (e.g. getSearchCount), and this function may return different results given the same input values (e.g. the server is not available all the time).
However this does not mean it is not appropriate. Any usable Scala program is going to have side effects, but the trick is to keep them as constrained as possible. A well-written Scala program will have a rich set of side-effect free classes and functions, and a relatively light layer of non-functional code that calls them.
In this case, for example, you should have a simple function that does the HTTP request and a second function that processes the results. The result-processing function can be pure functional code, and can be effectively tested with mock data.
Consider the following lookup function that I wrote, which is using optionals and optional binding, reports a message if key is not found in the dictionary
func lookUp<T:Equatable>(key:T , dictionary:[T:T]) -> T? {
for i in dictionary.keys {
if i == key{
return dictionary[i]
}
}
return nil
}
let dict = ["JO":"Jordan",
"UAE":"United Arab Emirates",
"USA":"United States Of America"
]
if let a = lookUp( "JO",dictionary:dict ) {
print(a) // prints Jordan
} else {
print("cant find value")
}
I have rewritten the following code, but this time, using error handling, guard statement, removing -> T? and writing an enum which conforms to ErrorType:
enum lookUpErrors : ErrorType {
case noSuchKeyInDictionary
}
func lookUpThrows<T:Equatable>(key:T , dic:[T:T])throws {
for i in dic.keys{
guard i == key else {
throw lookUpErrors.noSuchKeyInDictionary
}
print(dic[i]!)
}
}
do {
try lookUpThrows("UAE" , dic:dict) // prints united arab emirates
}
catch lookUpErrors.noSuchKeyInDictionary{
print("cant find value")
}
Both functions work well but:
which function grants better performance
which function is "safer"
which function is recommended (based on pros and cons)
Performance
The two approaches should have comparable performance. Under the hood they are both doing very similar things: returning a value with a flag that is checked, and only if the flag shows the result is valid, proceeding. With optionals, that flag is the enum (.None vs .Some), with throws that flag is an implicit one that triggers the jump to the catch block.
It's worth noting your two functions don't do the same thing (one returns nil if no key matches, the other throws if the first key doesn’t match).
If performance is critical, then you can write this to run much faster by eliminating the unnecessary key-subscript lookup like so:
func lookUp<T:Equatable>(key:T , dictionary:[T:T]) -> T? {
for (k,v) in dictionary where k == key {
return v
}
return nil
}
and
func lookUpThrows<T:Equatable>(key:T , dictionary:[T:T]) throws -> T {
for (k,v) in dic where k == key {
return v
}
throw lookUpErrors.noSuchKeyInDictionary
}
If you benchmark both of these with valid values in a tight loop, they perform identically. If you benchmark them with invalid values, the optional version performs about twice the speed, so presumably actually throwing has a small bit of overhead. But probably not anything noticeable unless you really are calling this function in a very tight loop and anticipating a lot of failures.
Which is safer?
They're both identically safe. In neither case can you call the function and then accidentally use an invalid result. The compiler forces you to either unwrap the optional, or catch the error.
In both cases, you can bypass the safety checks:
// force-unwrap the optional
let name = lookUp( "JO", dictionary: dict)!
// force-ignore the throw
let name = try! lookUpThrows("JO" , dic:dict)
It really comes down to which style of forcing the caller to handle possible failure is preferable.
Which function is recommended?
While this is more subjective, I think the answer’s pretty clear. You should use the optional one and not the throwing one.
For language style guidance, we need only look at the standard library. Dictionary already has a key-based lookup (which this function duplicates), and it returns an optional.
The big reason optional is a better choice is that in this function, there is only one thing that can go wrong. When nil is returned, it is for one reason only and that is that the key is not present in the dictionary. There is no circumstance where the function needs to indicate which reason of several that it threw, and the reason nil is returned should be completely obvious to the caller.
If on the other hand there were multiple reasons, and maybe the function needs to return an explanation (for example, a function that does a network call, that might fail because of network failure, or because of corrupt data), then an error classifying the failure and maybe including some error text would be a better choice.
The other reason optional is better in this case is that failure might even be expected/common. Errors are more for unusual/unexpected failures. The benefit of returning an optional is it’s very easy to use other optional features to handle it - for example optional chaining (lookUp("JO", dic:dict)?.uppercaseString) or defaulting using nil-coalescing (lookUp("JO", dic:dict) ?? "Team not found"). By contrast, try/catch is a bit of a pain to set up and use, unless the caller really wants "exceptional" error handling i.e. is going to do a bunch of stuff, some of which can fail, but wants to collect that failure handling down at the bottom.
#AirspeedVelocity already has a great answer, but I think it's worth going a bit further into why to use optionals vs errors.
There are basically four ways for something to go wrong:
Simple error: it fails in only one way, so you don't need to care about why something went wrong. The problem may come either from programmer logic or user data, so you need to be able to handle it at run time and design around it when coding.
This is the case for things like initializing an Int from a String (either the string is parseable as an integer or it's not) or dictionary-style lookups (either there's a value for the key or there's not). Optionals work really well for this in Swift.
Logic error: this is the kind of error that (in theory) comes up only during development, as a result of Doing It Wrong — for example, indexing beyond the bounds of an array.
In ObjC, NSException covers these kinds of cases. In Swift, we have functions like fatalError. I'd assume that part of why NSException isn't surfaced in Swift is that once your program encounters a logic error, it's not really safe to assume anything about its further operation. Logic errors should either be caught during development or cause a (nicely debuggable) crash rather than letting the program continue in an undefined (and thus unsafe) state.
Universal error: there are loads of ways to fail, but they aren't very connected to programmer logic or user action. You might run out of memory to allocate, get a low-level interrupt, or (wait for it...) overflow the stack, but those can happen with almost anything you do and not really because of any specific thing you do.
You see universal errors getting surfaced as exceptions in some other languages, but that means that you have to code around the possibility of any and every call you make being able to fail. And at that point you're writing more error handling than you are actual code.
Recoverable error: This is for when there are lots of ways to go wrong, but not in ways that preclude further operation, and what a program does upon encountering an error might change depending on what kind of error it is. Filesystems and networking are the common examples here: if you can't load a file, it might be because the user got the name wrong (so you should tell the user that) or because the wifi momentarily dropped and will be back shortly (so you might forego the alert and just try again).
In Cocoa, historically, this is what NSError parameters are for. Swift's error handling makes this pattern part of the language.
So, when you're writing new API (for yourself or someone else to call) in Swift, or using new ObjC annotations to make an existing API easier to use from Swift, think about what kind of errors you're dealing with.
Is there only one clear way to fail that isn't a result of API misuse? Use an Optional return type.
Can something fail only if a client doesn't follow your API contract — say, if you're writing a container class that has a subscript and a count, or requiring that some specific sequence of calls be made? Don't burden every bit of code that uses your API with error handling or optional unwrapping — just fatalError or assert (or throw NSException if your Swift API is a front to ObjC code) and document what the right way is for people to use your API.
Okay, so your ObjC init method returns nil iff [super init] returns nil. So should you mark your initializer as failable for Swift or add an error out-parpameter? Think about when that really happens — if -[NSObject init] is returning nil, it's because you chained it off of an alloc call that returned nil. If alloc fails, it's already The End Times for your process, so it's not worth handling that case.
Do you have multiple failure cases, some or all of which might be worth reporting to a user? Or that a client calling your API might want to ignore some but not all of? Write a Swift function that throws and a corresponding set of ErrorType values, or an ObjC method that returns an NSError out-parameter.
If you are using Swift 2.0 you could use both versions. The second version uses try/catch (introduced with 2.0) and is thus not backwards compatible which might be a disadvantage to consider.
If there is any performance difference then it will be neglectable.
My personal favorite is the first one as it is straight forward and well readable. If I had to do maintenance of the second version I would ask myself why the author took a try/catch approach for such a simple case. So I would rather be confused...
If you had many complex conditions with many exit points (throws) then I would go for the second one. But as I said, this is not the case here.
i.e., by passing the error condition and not halting the entire Observable?
My Observable starts with a user-supplied list of package tracking numbers from common delivery services (FedEx, UPS, DHL, etc), looks up the expected delivery date online, then returns those dates in terms of number of days from today (i.e. "in 3 days" rather than "Jan 22"). The problem is that if any individual lookup results in an exception, the entire stream halts, and the rest of the codes won't be looked up. There's no ability to gracefully handle, say, UnknownTrackingCode Exception, and so the Observable can't guarantee that it will look up all the codes the user submitted.
public void getDaysTillDelivery(List<String> tracking_code_list) {
Observable o = Observable.from(tracking_code_list)
// LookupDeliveryDate performs network calls to UPS, FedEx, USPS web sites or APIs
// it might throw: UnknownTrackingCode Exception, NoResponse Exception, LostPackage Exception
.map(tracking_code -> LookupDeliveryDate(tracking_code))
.map(delivery_date -> CalculateDaysFromToday(delivery_date));
o.subscribe(mySubscriber); // will handle onNext, onError, onComplete
}
Halting the Observable stream as a result of one error is by design:
http://reactivex.io/documentation/operators/catch.html
Handling Exceptions in Reactive Extensions without stopping sequence
https://groups.google.com/forum/#!topic/rxjava/trm2n6S4FSc
The default behavior can be overcome, but only by eliminating many of the benefits of Rx in the first place:
I can wrap LookupDeliveryDate so it returns Dates in place of Exceptions (such as 1899-12-31 for UnknownTrackingCode Exception) but this prevents "loosely coupled code", because CalculateDaysFromToday would need to handle these special cases
I can surround each anonymous function with try/catch and blocks, but this essentially prevents me from using lambdas
I can use if/thens to direct the code path, but this will likely require maintaining some state and eliminating deterministic evaluation
Error handling of each step, obviously, prevents consolidating all error handling in the Subscriber
Writing my own error-handling operator is possible, but thinly documented
Is there a better way to handle this?
What exactly do you want to happen if there is an error? Do you just want to throw that entry away or do you want something downstream to do something with it?
If you want something downstream to take some action, then you are really turning the error into data (sort of like your example of returning a sentinel value of 1899-12-31 to represent the error). This strategy by definition means that everything downstream needs to understand that the data stream may contain errors instead of data and they must be modified to deal with it.
But rather than yielding a magic value, you can turn your Observable stream into a stream of Either values. Either the value is a date, or it is an error. Everything downstream receives this Either object and can ask it if it has a value or an error. If it has a value, they can produce a new Either object with the result of their calculation. If it has an error and they cannot do anything with it, they can yield an error Either themselves.
I don't know Java syntax, but this is what it might look like in c#:
Observable.From(tracking_code_list)
.Select(t =>
{
try { return Either.From(LookupDeliveryDate(t)); }
catch (Exception e)
{
return Either.FromError<Date>(e);
}
})
.Select(dateEither =>
{
return dateEither.HasValue ?
Either.From(CalculateDaysFromToday(dateEither.Value)) :
Either.FromError<int>(dateEither.Error);
})
.Subscribe(value =>
{
if (value.HasValue) mySubscriber.OnValue(value.Value);
else mySubscribe.OnError(value.Error);
});
Your other option is the "handle"/suppress the error when it occurs. This may be sufficient depending on your needs. In this case, just have LookupDeliveryDate return magic dates instead of exceptions and then add a .filter to filter out the magic dates before they get to CalculateDaysFromToay.
I am writing a web app where exceptions are used to handle error cases. Often, I find myself writing helpers like this:
def someHelper(...) : Boolean {...}
and then using it like this:
if (!someHelper(...)){
throw new SomeException()
}
These exceptions represent things like invalid parameters, and when handled they send a useful error message to the user, eg
try {
...
} catch {
case e: SomeException => "Bad user!"
}
Is this a reasonable approach? And how could I pass the exception into the helper function and have it thrown there? I have had trouble constructing a type for such a function.
I use Either most of the time, not exceptions. I generally use exceptions, as you have done or some similar way, when the control flow has to go way, way back to some distant point, and otherwise there's nothing sensible to do. However, when the exceptions can be handled fairly locally, I will instead
def myMethod(...): Either[String,ValidatedInputForm] = {
...
if (!someHelper(...)) Left("Agree button not checked")
else Right(whateverForm)
}
and then when I call this method, I can
myMethod(blah).fold({ err =>
doSomething(err)
saneReturnValue
}, { form =>
foo(form)
form.usefulField
})
or match on Left(err) vs Right(form), or various other things.
If I don't want to handle the error right there, but instead want to process the return value, I
myMethod(blah).right.map{ form =>
foo(form)
bar(form)
}
and I'll get an Either with the error message unchanged as a Left, if it was an error message, or with the result of { foo(form); bar(form) } as a Right if it was okay. You can also chain your error processing using flatMap, e.g. if you wanted to perform an additional check on so-far-correct values and reject some of them, you could
myMethod(blah).right.flatMap{ form =>
if (!checkSomething(form)) Left("Something didn't check out.")
else Right(form)
}
It's this sort of processing that makes using Either more convenient (and usually better-performing, if exceptions are common) than exceptions, which is why I use them.
(In fact, in very many cases I don't care why something went wrong, only that it went wrong, in which case I just use an Option.)
There's nothing special about passing an exception instance to some method:
def someMethod(e: SomeException) {
throw e
}
someMethod(new SomeException)
But I have to say that I get a very distinct feeling that your whole idea just smells. If you want to validate a user input just write validators, e.g. UserValidator which will have some method like isValid to test a user input and return a boolean, you can implement some messaging there too. Exceptions are really intended for different purposes.
The two most common ways to approach what you're trying to do is to either just have the helper create and throw an exception itself, or exactly what you're doing: have the calling code check the results, and throw a meaningful exception, if needed.
I've never seen a library where you pass in the exception you expect the helper to throw. As I said on another answer, there's a surprisingly substantial cost to simply instantiating an exception, and if you followed this pattern throughout your code you could see an overall performance problem. This could be mitigated through the use of by-name parameters, but if you just forget to put => in a few key functions, you've got a performance problem that's difficult to track down.
At the end of the day, if you want the helper to throw an exception, it makes sense that the helper itself already knows what sort of exception it wants to throw. If I had to choose between A and B:
def helperA(...) { if (stuff) throw new InvalidStuff() }
def helperB(..., onError: => Exception) { if (stuff) throw onError }
I would choose A every time.
Now, if I had to choose between A and what you have now, that's a toss up. It really depends on context, what you're trying to accomplish with the helpers, how else they may be used, etc.
On a final note, naming is very important in these sorts of situations. If your go the return-code-helper route, your helpers should have question names, such as isValid. If you have exception-throwing-helpers, they should have action names, such as validate. Maybe even give it emphasis, like validate_!.
For an alternative approach you could check out scalaz Validators, which give a lot of flexibility for this kind of case (e.g. should I crash on error, accumulate the errors and report at the end or ignore them completely?). A few
examples might help you decide if this is the right approach for you.
If you find it hard to find a way in to the library, this answer gives some pointers to some introductory material; or check out .
In the upcoming scala 2.8, a util.control package has been added which includes a break library and a construct for handling exceptions so that code which looks like:
type NFE = NumberFormatException
val arg = "1"
val maybeInt = try { Some(arg.toInt) } catch { case e: NFE => None }
Can be replaced with code like:
import util.control.Exception._
val maybeInt = catching(classOf[NFE]) opt arg.toInt
My question is why? What does this add to the language other than providing another (and radically different) way to express the same thing? Is there anything which can be expressed using the new control but not via try-catch? Is it a DSL supposed to make exception-handling in Scala look like some other language (and if so, which one)?
There are two ways to think about exceptions. One way is to think of them as flow control: an exception changes the flow of execution of the program, making the execution jump from one place to another. A second way is to think of them as data: an exception is an information about the execution of the program, which can then be used as input to other parts of the program.
The try/catch paradigm used in C++ and Java is very much of the first kind(*).
If, however, if you prefer to deal with exceptions as data, then you'll have to resort to code such as the one shown. For the simple case, that's rather easy. However, when it comes to the functional style where composition is king, things start to get complicated. You either have to duplicate code all around, or you roll your own library to deal with it.
Therefore, in a language which purports to support both functional and OO style, one shouldn't be surprised to see library support for treating exceptions as data.
And note that there is oh-so-many other possibilities provided by Exception to handle things. You can, for instance, chain catch handlers, much in the way that Lift chain partial functions to make it easy to delegate responsibility over the handling of web page requests.
Here is one example of what can be done, since automatic resource management is in vogue these days:
def arm[T <: java.io.Closeable,R](resource: T)(body: T => R)(handlers: Catch[R]):R = (
handlers
andFinally (ignoring(classOf[Any]) { resource.close() })
apply body(resource)
)
Which gives you a safe closing of the resource (note the use of ignoring), and still applies any catching logic you may want to use.
(*) Curiously, Forth's exception control, catch&throw, is a mix of them. The flow jumps from throw to catch, but then that information is treated as data.
EDIT
Ok, ok, I yield. I'll give an example. ONE example, and that's it! I hope this isn't too contrived, but there's no way around it. This kind of thing would be most useful in large frameworks, not in small samples.
At any rate, let's first define something to do with the resource. I decided on printing lines and returning the number of lines printed, and here is the code:
def linePrinter(lnr: java.io.LineNumberReader) = arm(lnr) { lnr =>
var lineNumber = 0
var lineText = lnr.readLine()
while (null != lineText) {
lineNumber += 1
println("%4d: %s" format (lineNumber, lineText))
lineText = lnr.readLine()
}
lineNumber
} _
Here is the type of this function:
linePrinter: (lnr: java.io.LineNumberReader)(util.control.Exception.Catch[Int]) => Int
So, arm received a generic Closeable, but I need a LineNumberReader, so when I call this function I need to pass that. What I return, however, is a function Catch[Int] => Int, which means I need to pass two parameters to linePrinter to get it to work. Let's come up with a Reader, now:
val functionText = """def linePrinter(lnr: java.io.LineNumberReader) = arm(lnr) { lnr =>
var lineNumber = 1
var lineText = lnr.readLine()
while (null != lineText) {
println("%4d: %s" format (lineNumber, lineText))
lineNumber += 1
lineText = lnr.readLine()
}
lineNumber
} _"""
val reader = new java.io.LineNumberReader(new java.io.StringReader(functionText))
So, now, let's use it. First, a simple example:
scala> linePrinter(new java.io.LineNumberReader(reader))(noCatch)
1: def linePrinter(lnr: java.io.LineNumberReader) = arm(lnr) { lnr =>
2: var lineNumber = 1
3: var lineText = lnr.readLine()
4: while (null != lineText) {
5: println("%4d: %s" format (lineNumber, lineText))
6: lineNumber += 1
7: lineText = lnr.readLine()
8: }
9: lineNumber
10: } _
res6: Int = 10
And if I try it again, I get this:
scala> linePrinter(new java.io.LineNumberReader(reader))(noCatch)
java.io.IOException: Stream closed
Now suppose I want to return 0 if any exception happens. I can do it like this:
linePrinter(new java.io.LineNumberReader(reader))(allCatch withApply (_ => 0))
What's interesting here is that I completely decoupled the exception handling (the catch part of try/catch) from the closing of the resource, which is done through finally. Also, the error handling is a value I can pass on to the function. At the very least, it makes mocking of try/catch/finally statements much easier. :-)
Also, I can combine multiple Catch using the or method, so that different layers of my code might choose to add different handlers for different exceptions. Which really is my main point, but I couldn't find an exception-rich interface (in the brief time I looked :).
I'll finish with a remark about the definition of arm I gave. It is not a good one. Particularly, I can't use Catch methods such as toEither or toOption to change the result from R to something else, which seriously decreases the value of using Catch in it. I'm not sure how to go about changing that, though.
As the guy who wrote it, the reasons are composition and encapsulation. The scala compiler (and I am betting most decent sized source bases) is littered with places which swallow all exceptions -- you can see these with -Ywarn-catches -- because the programmer is too lazy to enumerate the relevant ones, which is understandable because it's annoying. By making it possible to define, re-use, and compose catch and finally blocks independently of the try logic, I hoped to lower the barrier to writing sensible blocks.
And, it's not exactly finished, and I'm working on a million other areas too. If you look through the actors code you can see examples of huge cut-and-pasted multiply-nested try/catch/finally blocks. I was/am unwilling to settle for try { catch { try { catch { try { catch ...
My eventual plan is to have catch take an actual PartialFunction rather than require a literal list of case statements, which presents a whole new level of opportunity, i.e. try foo() catch getPF()
I'd say it's foremost a matter of style of expression.
Beyond being shorter than its equivalent, the new catching() method offers a more functional way to express the same behavior. Try ... catch statements are generally considered imperative style and exceptions are considered side-effects. Catching() puts a blanket on this imperative code to hide it from view.
More importantly, now that we have a function then it can be more easily composed with other things; it can be passed to other higher-order functions to create more sophisticated behavior. (You cannot pass a try .. catch statement with a parametrized exception type directly).
Another way to look at this is if Scala didn't offer this catching() function. Then most likely people would 're-invent' it independently and that would cause code duplication, and lead to more non-standard code. So I think the Scala designers believe this function is common enough to warrant including it in the standard Scala library. (And I agree)
alex