Jump to content

Constrained genericity: Difference between revisions

m
formatting
(Omit Perl.)
m (formatting)
Line 9:
=={{header|Ada}}==
Ada allows various constraints to be specified in parameters of a generics. A formal type constrained to be derived from certain base is one of them:
<lang ada>with Ada.Containers.Indefinite_Vectors;
with Ada.Containers.Indefinite_Vectors;
 
package Nutrition is
Line 22 ⟶ 21:
);
subtype Food_Box is Food_Boxes.Vector;
end Nutrition;</lang>
</lang>
The package Nutrition defines an interface of an eatable object, that is, the procedure Eat. Then a container package is instantiated with the elements to be of the class Food. I.e. the elements can be only the members of the class Food. Example of use:
<lang ada>type Banana is new Food with null record;
<lang ada>
type Banana is new Food with null record;
overriding procedure Eat (Object : in out Banana) is null;
 
type Tomato is new Food with null record;
overriding procedure Eat (Object : in out Tomato) is null;</lang>
</lang>
We have declared Banana and Tomato as a Food.
<lang ada> Lunch_Box : Food_Box;
Lunch_Box : Food_Box;
begin
Lunch_Box.Append (Banana'(null record));
Lunch_Box.Append (Banana'(null record));
Lunch_Box.Append (Tomato'(null record));</lang>
</lang>
The lunch box contains two banana and one tomato.
 
=={{header|C++}}==
The current C++ standard doesn't support constrained genericity (however you can emulate it by having the container refer to the corresponding eat function without actually calling it). The next version will, however, allow it through concepts:
<lang cpp>#include <concepts>
#include <concepts>
#include <vector>
 
Line 59 ⟶ 52:
public:
std::vector<T> food;
};</lang>
</lang>
The only requirement to implement an Eatable type is, indeed, that a suitable function <tt>eat</tt> is defined for it (to put it in the FoodBox, in addition it has to be Moveable, since <tt>std::vector</tt> requires that; but that's ortogonal to the type being Eatable). A possible implementation of an eatable type could be:
<lang cpp>class Banana {};
classvoid eat(Banana const &) {};</lang>
void eat(Banana const &) {}
</lang>
Even a built-in type can be made eatable by defining a suitable <tt>eat</tt> function. The following makes <tt>double</tt> an eatable type:
<lang cpp>void eat(double) {}</lang>
void eat(double) {}
</lang>
 
Another way to make an existing type eatable is to use a concept map. Let's assume we have an abstract base class <tt>Food</tt> which looks like this;
<lang cpp>class Food
class Food
{
public:
virtual void munch() = 0;
virtual ~Food() {}
};</lang>
</lang>
Then we can make all classes derived from Food eatable using <tt>Food::munch()</tt> for <tt>eat</tt> with the following concept map template:
<lang cpp>template<std::DerivedFrom<Food> T>
template<std::DerivedFrom<Food> T>
concept_map Eatable<T>
{
void eat(T const& t) { t->munch(); }
}</lang>
}
</lang>
The difference to a global function <tt>void eat(Food const&)</tt> is that the function in the concept map is only visible to functions using that concept, thus reducing namespace polution. Functions directly operating on <tt>Food</tt> objects can use the interface provided by <tt>Food</tt> itself, e.g. <tt>apple.munch()</tt>, or explicitly invoke <tt>Eatable<Food>::eat(apple)</tt>. Of course, concept maps also work with built-in types:
<lang cpp>concept_map Eatable<int>
concept_map Eatable<int>
{
void eat(int) {}
}</lang ada>
}
</lang>
 
=={{header|D}}==
In D this can be done in two different ways.
 
<lang D>interface Edible { void eat(); }
<lang D>
interface Edible { void eat(); }
interface NonPoisonous {}
 
Line 107 ⟶ 88:
static assert (is (T : NonPoisonous), "don't eat that!");
T[] food;
}</lang>
}
</lang>
 
First one is similar to Java example. Trying to instantiate or create alias of ''FoodBox'' parametrized with
Line 116 ⟶ 96:
 
Usage:
<lang D>class Banana : Edible, NonPoisonous
<lang D>
class Banana : Edible, NonPoisonous
{
void eat() { Stdout("eating banana").newline; }
Line 137 ⟶ 116:
// will fail due to class template specialization
alias FoodBox!(Car) CarBox;
}</lang>
}
</lang>
 
=={{header|E}}==
Line 144 ⟶ 122:
It is surely arguable whether this constitutes an implementation of the above task:
 
<lang e>/** Guard accepting only objects with an 'eat' method */
<lang e>
/** Guard accepting only objects with an 'eat' method */
def Eatable {
to coerce(specimen, ejector) {
Line 162 ⟶ 139:
=={{header|Haskell}}==
A ''type class'' defines a set of operations that must be implemented by a type:
<lang haskell>class Eatable a where
eat :: a -> String</lang>
class Eatable a where
eat :: a -> String
</lang>
We just require that instances of this type class implement a function <tt>eat</tt> which takes in the type and returns a string (I arbitrarily decided).
 
The <tt>FoodBox</tt> type could be implemented as follows:
<lang haskell>data (Eatable a) => FoodBox a = F [a]</lang>
data (Eatable a) => FoodBox a = F [a]
</lang>
The stuff before the <tt>=></tt> specify what type classes the type variable <tt>a</tt> must belong to.
 
We can create an instance of <tt>Eatable</tt> at any time by providing an implementation for the function <tt>eat</tt>. Here we define a new type <tt>Banana</tt>, and make it an instance of <tt>Eatable</tt>.
<lang haskell>data Banana = Foo -- the implementation doesn't really matter in this case
<lang haskell>
data Banana = Foo -- the implementation doesn't really matter in this case
instance Eatable Banana where
eat _ = "I'm eating a banana"</lang>
</lang>
We can declare existing types to be instances in the exact same way. The following makes <tt>Double</tt> an eatable type:
<lang haskell>instance Eatable Double where
eat d = "I'm eating " ++ show d</lang>
instance Eatable Double where
eat d = "I'm eating " ++ show d
</lang>
 
Another way to make an existing type eatable is to declare all instances of another type class instances of this one. Let's assume we have another type class <tt>Food</tt> which looks like this;
<lang haskell>class Food a where
munch :: a -> String</lang>
class Food a where
munch :: a -> String
</lang>
Then we can make all instances of Food eatable using <tt>munch</tt> for <tt>eat</tt> with the following instance declaration:
<lang haskell>instance (Food a) => Eatable a where
eat x = munch x</lang>
instance (Food a) => Eatable a where
eat x = munch x
</lang>
 
=={{header|Java}}==
Line 221 ⟶ 186:
 
A module type defines a set of operations that must be implemented by a module:
<lang ocaml>module type Eatable = sig
module type Eatable = sig
type t
val eat : t -> unit
end</lang>
</lang>
We just require that module instances of this module type describe a type <tt>t</tt> and implement a function <tt>eat</tt> which takes in the type and returns nothing.
 
The <tt>FoodBox</tt> generic type could be implemented as a ''functor'' (something which takes a module as an argument and returns another module):
<lang ocaml>module MakeFoodBox(A : Eatable) = struct
module MakeFoodBox(A : Eatable) = struct
type elt = A.t
type t = F of elt list
let make_box_from_list xs = F xs
end</lang>
</lang>
 
We can create a module that is an instance of <tt>Eatable</tt> by specifying a type providing an implementation for the function <tt>eat</tt>. Here we define a module <tt>Banana</tt>, and make it an instance of <tt>Eatable</tt>.
<lang ocaml>type banana = Foo (* a dummy type *)
type banana = Foo (* a dummy type *)
 
module Banana : Eatable with type t = banana = struct
type t = banana
let eat _ = print_endline "I'm eating a banana"
end</lang>
</lang>
 
We can also create modules that use an existing type as its <code>t</code>. The following module uses <tt>float</tt> as its type:
<lang ocaml>module EatFloat : Eatable with type t = float = struct
<lang ocaml>
module EatFloat : Eatable with type t = float = struct
type t = float
let eat f = Printf.printf "I'm eating %f\n%!" f
end</lang>
</lang>
 
Then, to make a FoodBox out of one of these modules, we need to call the functor on the module that specifies the type parameter:
<lang ocaml>module BananaBox = MakeFoodBox (Banana)
module BananaBox = MakeFoodBox (Banana)
module FloatBox = MakeFoodBox (EatFloat)
 
let my_box = BananaBox.make_box_from_list [Foo]
let your_box = FloatBox.make_box_from_list [2.3; 4.5]</lang>
</lang>
Unfortunately, it is kind of cumbersome in that, for every type parameter we want to use for this generic type, we will have to explicitly create a module for the resulting type (i.e. <tt>BananaBox</tt>, <tt>FloatBox</tt>). And the operations on that resulting type (i.e. <tt>make_box_from_list</tt>) are tied to each specific module.
 
Anonymous user
Cookies help us deliver our services. By using our services, you agree to our use of cookies.