How to use multi-constraint generics to implement reusable numeric algorithms
Introduction
The goal of this tutorial is to show you how you can harness the power of multi-constraint generic type parameters to implement versatile numerical algorithms.
If you write algorithms in a generic way you can reuse them and create instances of the same algorithm parametrized with different types. This way the reuse of your code can be maximized. For numeric applications one obvious need is the ability to chose between REAL_32
and REAL_64
. If your algorithms become more abstract they might be applied to vectors, polynomials or to any other mathematical construct which satisfies the given constraints.
First trial
Our first attempt looks somehow like this
class EXAMPLE [G -> NUMERIC] create default_create feature -- Numeric algorithms compute_sum (n1, n2: G): G is -- Computes the sum of `n1' and `n2' do Result := n1 + n2 end end
This code works perfectly well with any descendant of NUMERIC
. As the class NUMERIC
defines a deferred infix `+' operation each descendant has to implement it accordingly.
example_feature is -- Example usage local l_exmpl: EXAMPLE [INTEGER_64] l_integer: INTEGER_64 do create l_exmpl l_integer := l_exmpl.compute_sum (11, 31) io.put_integer (l_integer) end
This is already pretty powerful and one can for example write VECTOR
classes in a generic way using NUMERIC
as a constraint.
More sophisticated example
If you want to program more sophisticated algorithms you soon run into problems because many algorithms require an order relation, meaning some kind of comparison function like smaller as (<).
The problem is that the class NUMERIC
has no feature to compare to numbers. Classes like INTEGER
and REAL
inherit this feature from the class COMPARABLE
.
Once again Eiffel provides a neat way to resolve the issue:
We simply add a second constraint to our generic type parameter G
.
class EXAMPLE [G -> {COMPARABLE, NUMERIC}] end
G is now called a multiple constrained formal type parameter.
This means that any valid type for G has to be conform to COMPARABLE and NUMERIC. This perfectly suits our purpose because all common used descendands of NUMERIC like INTEGER and REAL also inherit from COMPARABLE.
Now we are able to use the order relations defined in COMPARABLE:
class EXAMPLE [G -> {COMPARABLE, NUMERIC}] create default_create feature -- Numeric algorithms absolute_value (n1, n2: G): G is -- Example usage -- This is not the best way to compute the absolute value :) do if n1 > n2 then Result := n1 - n2 else Result := n2 - n1 end end end
Renaming for multi-constraint generics
But watch out! As some features are inherited twice, like is_equal
we have to resolve this name clash by a renaming in the following way:
class EXAMPLE [G -> {COMPARABLE, NUMERIC rename is_equal as is_equal_numeric end}] -- ... end
If you want to be able to create instances of G
you should also set a creation constraint:
class EXAMPLE [G -> { COMPARABLE rename default_create as default_create_comparable end, NUMERIC rename is_equal as is_equal_numeric end } create default_create end] end
Again we use a renaming to resolve the name conflict because both classes provide default_create
.
Inversion
If you need inversion the following hack can help you to achive your goal:
class EXAMPLE [G -> {COMPARABLE, NUMERIC}] feature inversion (a_g: G) -- Inverts `a_g'. do Result := a_g.one / a_g end end
We use the feature one
from NUMERIC
to achieve our goal. Consider storing this object to a local variable to speed up things a little. There is also a feature zero
available for usage.
Now you may ask: How can I multiply by three? Well, I leave that to your imagination how one could achieve that goal. I think it's ugly. I know that some more specialized algorithms might have the need for it. But unless we get a better abstracted mathematical class hierarchy we are stuck to ugly hacks. And by the way don't forget to check the precondition every time you divide something!
But apart from issues like that, you can build wonderful reusable algorithms at little or no additional cost!
To end our little tour (haha :-) let's have a look at a useful example:
class SUPER_DOOPER_EXAMPLE end