4 ms·
An exception is simply a change in control flow and, if I'm not mistaken, this can be modeled easily in a purely functional system. I imagine a real system like
by danking00 15y ago
An exception is simply a change in control flow and, if I'm not mistaken, this can be modeled easily in a purely functional system. I imagine a real system like Scheme, Racket, etc. would use call/cc, but I can best explain this with Continuation Passing Style[1].
Suppose you have some function like:
(define (foo x y)
(+ (/ x y) 1))
And you call it:
> (* (foo 4 2) 3)
9
the previous expression is equivalent to: ((4 / 2) + 1) * 3.
Now, suppose instead you call it with a bogus argument:
> (* (foo 4 0) 3)
Error: / expects the second argument to be non-zero!
Obviously, an error must be thrown because there is no reasonable answer to the quotient of 4/0.
Now, let me introduce CPS. To evaluate (+ (/ 4 2) 1) we must first evaluate (/ 4 2), then we can add 1 to the result. Written in a Scheme/Lisp language this looks like:
(let ((result (/ 4 2)))
(+ result 1))
The body of this let form is the "continuation" of (/ 4 2), which is a fancy way of saying it's what we should do next. One of the neat things about Scheme/Lisp is that we can write let expressions as function applications, like this:
((lambda (result) (+ result 1)) (/ 4 2))
So now we have a function which represents what we should do when we're done with (/ 4 2).
k := (lambda (result) (+ result 1)) ; continuations are often called k
Now we are ready to add real power. Let us modify our call to foo as follows:
((lambda (result) (* result 3)) (foo 4 2))
But, why not let foo decide its own future by passing the continuation as an argument which foo can decide to use, or not!
(foo 4 2 (lambda (result) (* result 3)))
Now, foo can say "Hey, I'm all done, I'm gonna call my continuation and keep evaluating!", or foo can say "No way man, shit hit the fan, we cannot continue evaluating!"
This is how foo can be given that behavior:
(define (foo x y k)
(if (zero? y)
"Uh-oh, you gave me a divisor of zero. I don't know what happens in black holes!"
(k (+ (/ x y) 1))) ; I'll divide, then add, then give the result to my continuation
Furthermore, primitive operations like /, +, and * can be also be modified to accept continuations so the final program looks like:
(define (foo x y k)
(if (zero? y)
"Uh-oh, you gave me a divisor of zero. I don't know what happens in black holes!"
(/ x y (lambda (quotient)
(+ quotient 1 k)))))
> (foo 4 2 (lambda (result) (* result 3)))
9
> (foo 4 0 (lambda (result) (* result 3)))
"Uh-oh, you gave me a divisor of zero. I don't know what happens in black holes!"
I encourage you to experiment with turning simple arithmetic programs into CPS-style. It's really enlightening and let's you experiment with highly abnormal control flow (much more than just implementing exceptions). In Scheme, Racket, and languages like them, you can use call/cc and let/cc to have access to continuations without this laborious rewriting process. In fact, continuations are used to model the interactions of a web server written in Racket[2].
TL;DR:
Exceptions can be modeled in a completely functional way, and the manner in which you do this opens up a really cool world of control flow mucking.
[1] http://en.wikipedia.org/wiki/Continuation-passing_style http://en.wikipedia.org/wiki/Continuation-passing_style
[2] http://pre.racket-lang.org/docs/html/more/index.html#(part._.Continuations) http://pre.racket-lang.org/docs/html/more/index.html#(part._...
edit: added a TL;DR and a reference to Racket's example of applying continuations to web server interactions
edit2: formatting