Moving on to the functions:

def to_decimal(roman_string) do

..

end def to_decimal!(roman_string) do

..

end

The above block defines 2 functions. These can be referred to as Roman.to_decimal/1 and Roman.to_decimal!/1

Functions are scoped to the module in which they are defined and their names will reflect that: Roman. At the end of the string section of the function name ( to_decimal ) is its arity.

The arity of a function represents the number of arguments a function takes

This is the /1 in the definitions above. In this case, both expect a single string.

Additionally, one of the functions above has an exclamation mark at the end of the string part of its name to_decimal! This is a convention, rather than a specific requirement. For functions that can return ‘good’ and ‘bad’ values, the output is usually returned as a tuple. The ‘good’ output is returned as the tuple: {:ok, value} whereas the ‘bad’ output would be returned as {:error, message} . Note that these return values are also just a convention, but a rather common one. The output from such a function can be pattern-matched to extract the correct value, or the error message, by the calling function. For instance:

iex> {:ok, decimal_value} = Roman.to_decimal("MCM")

{:ok, 1900}

iex> decimal_value

1900 iex> {:error, error_message} = Roman.to_decimal("XFG")

{:error, "Invalid roman numeral"}

iex> error_message

""Invalid roman numeral"

However, in certain cases, it may be beneficial for the function to raise an Exception rather than return an error tuple. In such cases, a second function which is identical to the first with the one difference being an ! at the end of the string part of its name, could be defined. If the function is successful it would simple return the value required, otherwise it would raise an exception.

iex> decimal_value = Roman.to_decimal!("MCM")

1900 iex> decimal_value = Roman.to_decimal!("XFG")

Error: ...

This type of function pair will often show up in Elixir codebases.

As a final discussion on functions, let’s look at argument destructuring (essentially pattern-matching function arguments) and guards.

defp is_tuple_valid?({from, to}) do

..

end defp convert({atm, rest}, acc) when atm in [:v, :l, :d, :m] do

..

end defp convert({atm, rest}, acc) when atm in [:i, :x, :c] do

..

end

The private function (private due to the keyword defp ) called Roman.is_tuple_valid?/1 takes a single argument. However, it seems as though it receives 2. In actual fact, the function receives a single two-element tuple as an argument and matches the first element to the variable from and the second to the variable to .

If you needed access to the tuple itself as well (as opposed to just its contents) you could write:

defp is_tuple_valid?({from, to} = tp) do

In this case, in addition to the matched contents, the function also has access to the input tuple itself, in the variable tp The same pattern-matching is used in the subsequent Roman.convert/2 functions that take a tuple as the first parameter, and a number as the second.

Note that the two convert functions have exactly the same definition until the when keyword. This keyword allows for further differentiation between similar functions by changing which function gets called based on some filter applied to the function’s input. This filter is called a Function guard.

Function guards allow you to “match” functions to specific inputs