Exceptions in Erlang - Redux

Similar documents
Supplemental Handout: Exceptions CS 1070, Spring 2012 Thursday, 23 Feb 2012

CS 3 Introduction to Software Engineering. 3: Exceptions

An Introduction to Erlang. Richard Carlsson

ECE 122. Engineering Problem Solving with Java

An Introduction to Erlang

3 Nonlocal Exit. Quiz Program Revisited

The compiler is spewing error messages.

Erlang: An Overview. Part 2 Concurrency and Distribution. Thanks to Richard Carlsson for most of the slides in this part

Mastering Python Decorators

Introduction to Erlang. Franck Petit / Sebastien Tixeuil

CATCH Me if You Can Doug Hennig

HiPE Copyright Ericsson AB. All Rights Reserved. HiPE March 11, 2019

Lecture 20. Java Exceptional Event Handling. Dr. Martin O Connor CA166

Le L c e t c ur u e e 5 To T p o i p c i s c t o o b e b e co c v o e v r e ed e Exception Handling

Summary: Open Questions:

CS24: INTRODUCTION TO COMPUTING SYSTEMS. Spring 2018 Lecture 11

6.001 Notes: Section 17.5

GRAMMARS & PARSING. Lecture 7 CS2110 Fall 2013

BBM 102 Introduction to Programming II Spring Exceptions

Checked and Unchecked Exceptions in Java

BBM 102 Introduction to Programming II Spring 2017

CONTENTS: While loops Class (static) variables and constants Top Down Programming For loops Nested Loops

SML Style Guide. Last Revised: 31st August 2011

Chapter 13 Exception Handling

AP COMPUTER SCIENCE JAVA CONCEPTS IV: RESERVED WORDS

CS24: INTRODUCTION TO COMPUTING SYSTEMS. Spring 2015 Lecture 11

6.001 Notes: Section 8.1

EXCEPTION HANDLING. Summer 2018

QUIZ on Ch.5. Why is it sometimes not a good idea to place the private part of the interface in a header file?

COMP-202 Unit 4: Programming with Iterations

Closures. Mooly Sagiv. Michael Clarkson, Cornell CS 3110 Data Structures and Functional Programming

Static rules of variable scoping in Erlang

AHHHHHHH!!!! NOT TESTING! Anything but testing! Beat me, whip me, send me to Detroit, but don t make me write tests!

CS558 Programming Languages

Today. Instance Method Dispatch. Instance Method Dispatch. Instance Method Dispatch 11/29/11. today. last time

CMSC 201 Fall 2016 Lab 09 Advanced Debugging

Software Design and Analysis for Engineers

UNIT 3

Intro. Scheme Basics. scm> 5 5. scm>

Exception Safety. CS 311 Data Structures and Algorithms Lecture Slides Wednesday, October 28, Glenn G. Chappell. continued

CSE 341: Programming Languages

The Design Process. General Development Issues. C/C++ and OO Rules of Thumb. Home

CIS192 Python Programming

If you are going to form a group for A2, please do it before tomorrow (Friday) noon GRAMMARS & PARSING. Lecture 8 CS2110 Spring 2014

CIS192 Python Programming

엄현상 (Eom, Hyeonsang) School of Computer Science and Engineering Seoul National University COPYRIGHTS 2017 EOM, HYEONSANG ALL RIGHTS RESERVED

(Refer Slide Time: 4:00)

Lecture 14: Exceptions 10:00 AM, Feb 26, 2018

Internal Classes and Exceptions

Erlang. Functional Concurrent Distributed Soft real-time OTP (fault-tolerance, hot code update ) Open. Check the source code of generic behaviours

Question And Answer.


CSCI-1200 Data Structures Fall 2011 Lecture 24 Garbage Collection & Smart Pointers

Erlang 101. Google Doc

Notes on Non-Chronologic Backtracking, Implication Graphs, and Learning

Decision Management Community

Exceptions. Produced by. Introduction to the Java Programming Language. Eamonn de Leastar

QUIZ. What are 3 differences between C and C++ const variables?

6.001 Notes: Section 15.1

Module 10A Lecture - 20 What is a function? Why use functions Example: power (base, n)

Environments

Overview. finally and resource cleanup

QUIZ Friends class Y;

Closures. Mooly Sagiv. Michael Clarkson, Cornell CS 3110 Data Structures and Functional Programming

CS61A Notes Week 6: Scheme1, Data Directed Programming You Are Scheme and don t let anyone tell you otherwise

TUTORIAL. Commandability in BACnet. David Fisher. 13-Aug-2016

CSE : Python Programming. Homework 5 and Projects. Announcements. Course project: Overview. Course Project: Grading criteria

11Debugging and Handling. C# Programming: From Problem Analysis to Program Design 2nd Edition. David McDonald, Ph.D. Director of Emerging Technologies

Exceptions. Why Exception Handling?

CIS192 Python Programming

Topic 6: Exceptions. Exceptions are a Java mechanism for dealing with errors & unusual situations

To figure this out we need a more precise understanding of how ML works

Internationalization and Error Handling

Organization of Programming Languages CS3200/5200N. Lecture 11

CPS506 - Comparative Programming Languages Elixir

Concepts of programming languages

INF 102 CONCEPTS OF PROG. LANGS FUNCTIONAL COMPOSITION. Instructors: James Jones Copyright Instructors.

Assoc. Prof. Marenglen Biba. (C) 2010 Pearson Education, Inc. All rights reserved.

Programming Languages Third Edition. Chapter 9 Control I Expressions and Statements

CS 4240: Compilers and Interpreters Project Phase 1: Scanner and Parser Due Date: October 4 th 2015 (11:59 pm) (via T-square)

Exceptions in Java

Intro. Speed V Growth

Compositional Cutpoint Verification

Exception-Handling Overview

tuprolog with exceptions

CS61A Discussion Notes: Week 11: The Metacircular Evaluator By Greg Krimer, with slight modifications by Phoebus Chen (using notes from Todd Segal)

CS107 Handout 08 Spring 2007 April 9, 2007 The Ins and Outs of C Arrays


1: Introduction to Object (1)

Erlang: An Overview. Part 1 Sequential Erlang. Thanks to Richard Carlsson for the original version of many slides in this part

What does IRET do, anyway?... Exam #1 Feb. 27, 2004

Exceptional Actors. Implementing Exception Handling for Encore. Sahand Shamal Taher

Unit Testing as Hypothesis Testing

Unit Testing as Hypothesis Testing

Advanced topics, part 2

Object Oriented Programming

CS558 Programming Languages

Debugging and Profiling

Functions and Procedures

Compiler Design Prof. Y. N. Srikant Department of Computer Science and Automation Indian Institute of Science, Bangalore

Transcription:

Exceptions in Erlang - Redux Richard Carlsson Uppsala University Björn Gustavsson Patrik Nyblom Ericsson

What's the big deal? Exceptions in Erlang are simple, right? Raising exceptions: throw(term) exit(term) run-time failures: foo = bar => {badmatch,bar} 1 + foo => badarith no matching clause => case_clause etc.

Evaluation and catches catch <Expr> Evaluates <Expr> If it completes normally with result R, the result of the whole thing is R. Otherwise, the evaluation completed abnormally with an exception. The result then depends on the cause: throw(t) => T exit(t) => {'EXIT', T} 1 + foo => {'EXIT', badarith}

Purposes of exit and throw throw(term) is for nonlocal returns, escaping from deep recursion. exit(term) is for terminating the current process. special case: exit(normal) Faking exits with throw: catch (...throw({'exit',badarg})...) => {'EXIT',badarg}

What happened? R = catch (case X of 1 -> 1 + foo; 2 -> exit(badarith); 3 -> throw({'exit,badarith'}); 4 -> {'EXIT',badarith}; 5 -> throw(ok); 6 -> ok end), case R of {'EXIT',badarith} -> 1-4 ; ok -> 5-6 end

Sometimes, this is a problem % XXX: We hope nobody inserts 'not_found' % in the table! lookup(x, F, Default) -> case catch F(X) of {'EXIT',Reason} -> handle(reason) not_found -> Default Value -> Value end

...with a known workaround % XXX: We hope nobody throws '{ok, Value}' % from function F. lookup(x, F, Default) -> case catch {ok,f(x)} of {ok,value} -> Value {'EXIT',Reason} -> exit(reason) not_found -> Default Term -> throw(term) end

Throw/catch not widely used catch always catches every exception, so the programmer must write extra code to rethrow uninteresting ones. (Partly) because of these difficulties, throw/catch is not commonly used in Erlang for signalling/handling errors. Much more common: return either {ok,result} or {error,reason}.

I can't believe it's not C! This kind of code quickly gets tedious: A = case a(...) of {ok, A1} -> A1; {error, R1} -> error_in_a(r1) end,... E = case e(...) of {ok, E1} -> E1; {error, R5} -> error_in_e(r5) end, foo(a,b,c,d,e)

One good failure deserves another Often, you can't handle the error anyway, so you might as well just cause a badmatch! {ok, A} = a(...),... {ok, E} = e(...), foo(a,b,c,d,e) This can make the real cause of the error harder to find, but at least the code gets shorter...

Functional programming? Sometimes, these wrappers are extra annoying: {ok, F} = f(...), {ok, E} = e(f), {ok, G} = g(g), {result, G} In a better world, it could have been: {result, g(e(f(...)))} if errors were signalled through exceptions.

Don't do this A misguided attempt at error handling: case f(...) of {ok, Value} -> Value; {error, Reason} -> exit(reason) end The term Reason is often completely incomprehensible outside the context of the function f. Even {ok,value}=f(...) might be better.

Processes and signals 101 Erlang processes can be linked. When a process terminates, its linked processes will receive a signal containing the exit term. On normal termination (return from the process' initial function call), the exit term is the atom 'normal'. On termination due to exit(term), the exit term is simply Term.

Processes and signals 101 (continued) On termination due to runtime errors the exit term is the corresponding error term (badarg, badarith, etc.). On termination due to throw(term), the exit term is {nocatch,term}. Throws are supposed to be caught before they reach the top of the process' call stack; if not, it's considered an error.

Small white lies Everything said so far is according to The Erlang Book (Armstrong et al., 1996). Things have changed: Symbolic stack traces Error logger (system service) Abnormal termination of any process is logged; normal termination is not. Return from top-level call is normal. exit(term) counts as normal termination, regardless of Term. throw(term) causes abnormal termination.

Symbolic stack traces Example code: f(x) -> "1" ++ g(x). g(x) -> "2" ++ h(x). h(x) -> X ++ ".". Evaluating f(foo) yields this error term: {badarg,[{erlang,'++',[foo,"."]}, {foo,h,1}, {foo,g,1}, {foo,f,1}]} Does not happen for calls to exit or throw!

There is more to exceptions than meets the eye At least two pieces of information are needed to describe an exception: The Erlang term which will be returned by a catch, or included in an exit signal. A flag that shows whether or not the exception was caused by throw(term). The term must be wrapped in {nocatch,...} if a throw-exception terminates the process. The {'EXIT',...} wrapper cannot be added at the point of the exception. (And exit cannot be completely faked by throw.) Exception: <term, thrown>.

When is the stack trace added? If throw(term) terminates the process, the exit term will be {{nocatch,term},[...]}. But if the exception is caught by catch, the result is only Term, without a stack trace. Cannot add stack trace before we know where the exception will end up! Exception: <term, thrown, trace>. The trace part is null if and only if the exception was caused by exit.

Semantics of catch Expr If evaluation of Expr completes normally with result R, the result of the catch is R otherwise, we got <term, thrown, trace> if thrown is true, the result is just term else, if trace is null, the result is {'EXIT',term} otherwise, the result is {'EXIT', {term,trace}}

Semantics of process termination If evaluation of the initial call completes normally, the exit term is 'normal' otherwise, we got <term, thrown, trace> if thrown is true, the exit term is {{nocatch,term},trace} else, if trace is null, the exit term is term otherwise, the exit term is {term,trace}

Re-throwing kills information The catch operator catches all exceptions: case catch {ok,...} of {ok, Value} ->...; {'EXIT', Reason} -> exit(reason); not_found ->...; Term -> throw(term) end throw(term) will set a new stack trace, hiding where the first exception occurred. exit(reason) changes logged errors into non-logged exits.

The function formerly known as erlang:fault/1 Analogous to exit(term) and throw(term). Raises the kind of exception caused by runtime errors such as foo=bar or 1+foo. Mostly used in some standard library functions for raising runtime errors. Now also known as erlang:error/1. Use this to generate errors not exit/1! (Does not solve the re-throwing problem, because it also sets a new stack trace.)

Interlude Are you sufficiently confused?

So tell me what you want,... Strict separation between normal completion of evaluation and exceptions. Strict separation between throw and other exceptions, and also between exit and runtime errors (error/fault). No change at all to existing code the old catch operator must work exactly as before. Use pattern matching to select which exceptions will be handled.

...what you really really want! Automatic re-throw of unhandled exceptions as if they had not been caught at all. Easy to rewrite most uses of catch in existing code to the new construct. Simple to write code that guarantees execution on the way out, such as cleanup code for freeing allocated resources, no matter how we leave the protected code.

try according to Barklund Suggested in the Standard Erlang Specification draft by J. Barklund, ca 1998: try Expressions catch Pattern_1 -> Body_1... Pattern_N -> Body_N end Patterns matched against {'EXIT',Term} or {'THROW',Term}. Nonmatching exceptions are re-thrown.

Little did they know... The Standard draft did not recognize the difference between exits and runtime errors. The logging of errors was not mentioned. In fact, no existing description of the Erlang language was consistent with the de facto behaviour of exceptions in Erlang/OTP. Most of this was not realized until we tried to implement the suggested try construct.

try is easier said than done Need to separate three types of exceptions, rather than two. Must make the stack trace accessible somehow. Forcing users to switch on patterns like {'THROW',{not_found,X},Stack} will either make them not use try unless they have to, or, make them catch more than they should by writing patterns like {_,{not_found,x},_} Most of the time, users don't want to look at the stacktrace. (Mainly useful in top loops, etc.)

try this without a catch This kind of code is very common: case catch f(x) of {'EXIT',Reason} -> handle(reason); Pattern_1 -> Body_1;... Pattern_N -> Body_N end How can we replace this with an equivalent try...catch...end?

mix and match % Can't be fooled by throw({ok,...}): R = try {ok, f(x)} catch Exception -> Exception end, case R of {ok,pattern_1} -> Body_1;... {ok,pattern_n} -> Body_N; {'EXIT',Reason} -> handle(reason); {'THROW',Term} -> throw(term) end

What's missing in this picture? try... catch catch... end next

A better try Allow user to hook into the success case: try Expressions of Pattern_1 -> Body_1... Pattern_N -> Body_N catch Exception_1 -> Handler_1... Exception_M -> Handler_M end

Now we control all exits try... catch of... catch catch... end next

A simple equivalence We define try Expressions catch... end to be syntactic sugar for try Expressions of X -> X catch... end

A bit of refactoring We generalize the thrown flag to class. Exception: <class, term, trace>. exit(term) => <exit, term, trace> throw(term) => <throw, term, trace> error(term) => <error, term, trace> No null value for trace it is always defined. Easy to rewrite the semantics for catch etc. to use this representation instead.

No more 'EXIT' or 'THROW' 'EXIT' was chosen for the old catch to be different from typical runtime values. Of course, there was never any guarantee. We don't need this for try! We introduce a new form of pattern for matching on exceptions: Class:Term where Class is an atom (exit, error or throw) or a variable (possibly bound).

The class can be left out If the Class: part is left out, the class defaults to throw. Typically, programmers should not catch exit or error exceptions, unless they really know what they are doing! Makes it more obvious when somebody actually tries to catch exits or errors. Lazy programmers don't catch exceptions by mistake. (At least not as many.) try/throw becomes a straightforward error handling mechanism for function calls.

Getting the stacktrace We have a new built-in function erlang:get_stacktrace(). Returns the symbolic stack trace (a list of terms) of the latest occurred exception. Yields an empty list if no exception has occurred so far. No need to build the symbolic form of the stack trace (expensive) until it is required; when an exception occurs, a quick-save is made of the necessary data.

Example: catch as try try Expression catch throw:term -> Term; exit:term -> {'EXIT',Term}; error:term -> Trace = erlang:get_stacktrace(), {'EXIT',{Term,Trace}} end

Cleaning up Very common pattern: Allocate a resource Do some stuff (if allocation succeeded) Deallocate the resource Want to guarantee that the resource is always deallocated regardless of exit path. Possible with try as described, but verbose and clumsy.

Re-using old keywords We define a new form of try: try Expressions after Cleanup end Guarantees execution of Cleanup. Preserves the result of Expressions (both for normal completion and for exceptions). Exceptions in Cleanup take precedence.

The full Monty... try Expressions of Pattern_1 -> Body_1... Pattern_N -> Body_N catch Exception_1 -> Handler_1... Exception_M -> Handler_M after Cleanup end

...is actually equivalent to try tryexpressions of Pattern_1 -> Body_1... catch Exception_1 -> Handler_1... end after % Note that handlers run before cleanup. Cleanup end

Rolling your own Easy to nest manually for other behaviour: try tryexpressions after Cleanup end of... catch... end Allows tail calls in handlers.

A final example read_file(filename) -> try open(filename, [read]) of FileHandle -> try read_opened_file(filehandle) after close(filehandle) end catch {file_error, Reason} -> print_file_error(reason), throw(io_error) end.

The End