Hey, everyone!
Today I started writing a small library to perform computations with Complex Numbers. (ComplexNum, it is still very much unfinished). While working on that, I realized that I did not only wanted to be able to specify the real and imaginary parts as Integer
s or Float
s, but also Decimal
s, Ratio
nals or other numeric data types.
This made me realize that it was possible to create a more general ‘dispatching’ module, as long as each of the numeric data types followed the same API (i.e. the same Behaviour):
And thus Numbers
was born.
What does it do?
It allows you to call Numbers.add
, Numbers.sub
, Numbers.mult
, etc. regardless of what data types you are performing arithmetic with. As long as they are the same type (or one of them is a built-in data type such as an integer or float, in which case they will be attempted to automatically be converted to the custom data type), it will Just Work™.
Some Examples:
iex> alias Numbers, as: N
iex> N.add(1, 2)
3
iex> N.mul(3,5)
15
iex> N.mul(1.5, 100)
150.0
Using Decimals: (requires the Decimal
package)
iex> d = Decimal.new(2)
iex> N.div(d, 10)
#Decimal<0.2>
iex> small_number = N.div(d, 1234)
#Decimal<0.001620745542949756888168557536>
iex> N.pow(small_number, 100)
Using Rationals: (requires the Ratio
package)
iex> use Ratio, operators: false
iex> res = N.add(1 <|> 2, 3 <|> 5)
11 <|> 10
iex> N.mult(res, 10)
How does it work?
Simply put, the Numbers
module accepts for most functions two arguments that should be of the same struct type (optionally, one of them could be an integer or float which is automatically converted by Numbers to the struct of the other argument). It will extract the module name of the struct, and call the functions named add
, sub
, etc. on that module.
So Numeric
is a Behaviour, to follow. Numbers
dispatches that behaviour, which means that you can build functions and data types that wrap any kind of number, including custom-built ones.
Some libraries that now use Number
in practice, meaning that they can contain any kind of number and be able to perform mathematical operations on their contents, are Tensor and ComplexNum.
What data types are supported right now?
Right now, I know of the following data types that follow the behaviour:
- built-in Integers
- built-in Floats
- Ratio for rational numbers.
-
Decimal for decimal numbers. (Does not yet formally implement the Numeric behaviour using
@behaviour
, I’ll create a Pull Request on its repository adding just that shortly. In the meantime, as it already informally follows the behaviour, it already simply works!).
One of the things I like the most, is that the following structures both dispatch using Numbers internally as well as implement the Numeric Behaviour, meaning that they can be used as composite Numeric types, when the type they contain follow the Numeric Behaviour. (So you can do elementwise addition of multiple Vectors of Decimals, or even a Matrix filled with Vectors of Complex numbers of Floats). The possibilities are endless!
- ComplexNum for complex numbers. (as long as the data type used for the real/imaginary parts itself follows Numeric.
- Tensor for Vectors, Matrices and higher-order Tensors.
Tl;Dr: Numbers is a wrapper exposing the generic functionality all kinds of (custom or built-in) numeric datatypes have. So you can use it to make your code number-agnostic!
I look forward to any and all feedback from you!
Thanks,
~Wiebe-Marten/Qqwy