All errors in Elixir implement the Exception Behaviour. Just like the Access Behaviour, the Exception Behaviour defines callback functions that a module must implement to fulfill the software contract of the behaviour. Once an error is defined, it has the following properties:
:message
field.raise/1
and raise/2
to raise the intended errorThe Exception Behaviour also specifies two callbacks: message/1
and exception/1
. If unimplemented, default implementations will be used. message/1
transforms the error-struct to a readable message when called with raise
. exception/1
allows additional context to be added to the message when it is called with raise/2
To define an exception from an error module, we use the defexception
macro:
# Defines a minimal error, with the name `MyError`
defmodule MyError do
defexception message: "error"
end
# Defines an error with a customized exception/1 function
defmodule MyCustomizedError do
defexception message: "custom error"
@impl true
def exception(value) do
case value do
[] ->
%MyCustomizedError{}
_ ->
%MyCustomizedError{message: "Alert: " <> value}
end
end
end
Defined errors may be used like a built in error using either raise/1
or raise/2
.
raise/1
raises a specific error by its module name, or, if the argument is a string, it will raise a RuntimeError
with the string as the message.raise/2
raises a specific error by its module name, and accepts an attributes argument which is used to obtain the error with the appropriate message.While continuing your work at Instruments of Texas, there is progress being made on the Elixir implementation of the RPN calculator. Your team would like to be able to raise errors that are more specific than the generic errors provided by the standard library. You are doing some research, but you have decided to implement two new errors which implement the Exception Behaviour.
Dividing a number by zero produces an undefined result, which the team decides is best represented by an error.
Implement the DivisionByZeroError
module to have the error message: "division by zero occurred"
raise DivisionByZeroError
# => ** (DivisionByZeroError) division by zero occurred
RPN calculators use a stack to keep track of numbers before they are added. The team represents this stack with a list of numbers (integer and floating-point), e.g.: [3, 4.0]
. Each operation needs a specific number of numbers on the stack in order to perform its calculation. When there are not enough numbers on the stack, this is called a stack underflow error. Implement the StackUnderflowError
exception which provides a default message, and optional extra context
raise StackUnderflowError
# => ** (StackUnderflowError) stack underflow occurred
raise StackUnderflowError, "when dividing"
# => ** (StackUnderflowError) stack underflow occurred, context: when dividing
Implement the divide/1
function which takes a stack (a list of two numbers) and:
RPNCalculator.Exception.divide([])
# => ** (StackUnderflowError) stack underflow occurred, context: when dividing
RPNCalculator.Exception.divide([0, 100])
# => ** (DivisionByZeroError) division by zero occurred
RPNCalculator.Exception.divide([4, 16])
# => 4
Note the order of the list is reversed!
Sign up to Exercism to learn and master Elixir with 57 concepts, 159 exercises, and real human mentoring, all for free.