exercism/elixir/stack-underflow/README.md

110 lines
4.2 KiB
Markdown

# Stack Underflow
Welcome to Stack Underflow on Exercism's Elixir Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
## Introduction
## Exceptions
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:
- The module's name defines the error's name.
- The module defines an error-struct.
- The struct will have a `:message` field.
- The module can be used with `raise/1` and `raise/2` to raise the intended error
The _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`
### Defining an exception
To define an exception from an error module, we use the `defexception` macro:
```elixir
# 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
```
### Using exceptions
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.
## Instructions
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_.
## 1. Error for Division by Zero
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"`
```elixir
raise DivisionByZeroError
# => ** (DivisionByZeroError) division by zero occurred
```
## 2. Error when encountering stack underflow
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
```elixir
raise StackUnderflowError
# => ** (StackUnderflowError) stack underflow occurred
raise StackUnderflowError, "when dividing"
# => ** (StackUnderflowError) stack underflow occurred, context: when dividing
```
## 3. Write a dividing function
Implement the `divide/1` function which takes a stack _(a list of two numbers)_ and:
- raises _stack underflow_ when the stack does not contain enough numbers
- raises _division by zero_ when the divisor is 0 (note the stack of numbers is stored in the reverse order)
- performs the division when no errors are raised
```elixir
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!
## Source
### Created by
- @neenjaw
### Contributed to by
- @angelikatyborska