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