I had a bout of insomnia last night, and I imagined a little programming exercise to write a function that would take an integer and translate it to the english words for that number. I felt that Elixir's pattern matching and macros would make the resulting code small and beautiful.
Here's what I wanted to achieve:
iex> IntegerToEnglish.integer_to_english(12)
"twelve"
iex> IntegerToEnglish.integer_to_english(-3034)
"negative three thousand and thirty four"
iex> IntegerToEnglish.integer_to_english(3823404)
"three million, eight hundred twenty three thousand, four hundred four"
This is the code I came up with:
defmodule IntegerToEnglish do
use IntegerToEnglish.Macros
def integer_to_english(i) when i < 0, do: "negative " <> integer_to_english(i * -1)
def integer_to_english(0), do: "zero"
def integer_to_english(1), do: "one"
def integer_to_english(2), do: "two"
def integer_to_english(3), do: "three"
def integer_to_english(4), do: "four"
def integer_to_english(5), do: "five"
def integer_to_english(6), do: "six"
def integer_to_english(7), do: "seven"
def integer_to_english(8), do: "eight"
def integer_to_english(9), do: "nine"
def integer_to_english(10), do: "ten"
def integer_to_english(11), do: "eleven"
def integer_to_english(12), do: "twelve"
def integer_to_english(13), do: "thirteen"
def integer_to_english(14), do: "fourteen"
def integer_to_english(15), do: "fifteen"
def integer_to_english(16), do: "sixteen"
def integer_to_english(17), do: "seventeen"
def integer_to_english(18), do: "eighteen"
def integer_to_english(19), do: "nineteen"
generate_clause("twenty", 20, 30)
generate_clause("thirty", 30, 40)
generate_clause("fourty", 40, 50)
generate_clause("fifty", 50, 60)
generate_clause("sixty", 60, 70)
generate_clause("seventy", 70, 80)
generate_clause("eighty", 80, 90)
generate_clause("ninety", 90, 100)
generate_clause("hundred", 100, 1000)
generate_clause("thousand", 1000, 1_000_000)
generate_clause("million", 1_000_000, 1_000_000_000)
generate_clause("billion", 1_000_000_000, 1_000_000_000_000)
end
defmodule IntegerToEnglish.Macros do
defmacro __using__(_opts) do
quote do
import IntegerToEnglish.Macros
defp prefix(i, dividend) when i >= 100 do
IntegerToEnglish.integer_to_english(dividend) <> " "
end
defp prefix(_i, _dividend), do: ""
defp remainder(_i, 0), do: ""
defp remainder(i, rem) when i >= 1000 and rem < 100,
do: " and " <> IntegerToEnglish.integer_to_english(rem)
defp remainder(i, rem) when i >= 1000, do: ", " <> IntegerToEnglish.integer_to_english(rem)
defp remainder(_i, rem), do: " " <> IntegerToEnglish.integer_to_english(rem)
end
end
defmacro generate_clause(name, min, max) do
quote do
def integer_to_english(i) when is_integer(i) and i >= unquote(min) and i < unquote(max) do
dividend = Integer.floor_div(i, unquote(min))
rem = i - dividend * unquote(min)
prefix(i, dividend) <>
unquote(name) <> remainder(i, rem)
end
end
end
end
I used the __using__
macro to define private helper functions for the
generate_clause
macro because macros need to be defined in a separate file
from the file that uses them, and if I used regular functions they would have to
be public for generate_clause
to access them.
I published the code on Hex and GitLab, including tests.
I think the code turned out fairly straightforward and readable, and I wonder how it might look in other languages.