Constrained genericity: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎{{header|C++}}: slight mistake in the explanation)
m (<code>)
Line 2: Line 2:
''Constrained genericity'' means that a parametrized type or function (see [[Parametric Polymorphism]]) can only be instantiated on types fulfilling some conditions, even if those conditions are not used in that function.
''Constrained genericity'' means that a parametrized type or function (see [[Parametric Polymorphism]]) can only be instantiated on types fulfilling some conditions, even if those conditions are not used in that function.


Say a type is called "eatable" if you can call the function <code>eat</code> on it. Write a generic type <code>FoodBox</code> which contains a collection of objects of a type given as parameter, but can only be instantiated on eatable types. The FoodBox shall not use the function eat in any way (i.e. without the explicit restriction, it could be instantiated on any type). The specification of a type being eatable should be as generic as possible in your language (i.e. the restrictions on the implementation of eatable types should be as minimal as possible). Also explain the restrictions, if any, on the implementation of eatable types, and show at least one example of an eatable type.
Say a type is called "eatable" if you can call the function <tt>eat</tt> on it. Write a generic type <tt>FoodBox</tt> which contains a collection of objects of a type given as parameter, but can only be instantiated on eatable types. The FoodBox shall not use the function eat in any way (i.e. without the explicit restriction, it could be instantiated on any type). The specification of a type being eatable should be as generic as possible in your language (i.e. the restrictions on the implementation of eatable types should be as minimal as possible). Also explain the restrictions, if any, on the implementation of eatable types, and show at least one example of an eatable type.


=={{header|Ada}}==
=={{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:
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:
<ada>
<code ada>
with Ada.Containers.Indefinite_Vectors;
with Ada.Containers.Indefinite_Vectors;


Line 20: Line 20:
subtype Food_Box is Food_Boxes.Vector;
subtype Food_Box is Food_Boxes.Vector;
end Nutrition;
end Nutrition;
</ada>
</code>
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:
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:
<ada>
<code ada>
type Banana is new Food with null record;
type Banana is new Food with null record;
overriding procedure Eat (Object : in out Banana) is null;
overriding procedure Eat (Object : in out Banana) is null;
Line 28: Line 28:
type Tomato is new Food with null record;
type Tomato is new Food with null record;
overriding procedure Eat (Object : in out Tomato) is null;
overriding procedure Eat (Object : in out Tomato) is null;
</ada>
</code>
We have declared Banana and Tomato as a Food.
We have declared Banana and Tomato as a Food.
<ada>
<code ada>
Lunch_Box : Food_Box;
Lunch_Box : Food_Box;
begin
begin
Line 36: Line 36:
Lunch_Box.Append (Banana'(null record));
Lunch_Box.Append (Banana'(null record));
Lunch_Box.Append (Tomato'(null record));
Lunch_Box.Append (Tomato'(null record));
</ada>
</code>
The lunch box contains two banana and one tomato.
The lunch box contains two banana and one tomato.


=={{header|C++}}==
=={{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:
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:
<cpp>
<code cpp>
#include <concepts>
#include <concepts>
#include <vector>
#include <vector>
Line 57: Line 57:
std::vector<T> food;
std::vector<T> food;
};
};
</cpp>
</code>
The only requirement to implement an Eatable type is, indeed, that a suitable function <code>eat</code> is defined for it (to put it in the FoodBox, in addition it has to be Moveable, since <code>std::vector</code> requires that; but that's ortogonal to the type being Eatable). A possible implementation of an eatable type could be:
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:
<cpp>
<code cpp>
class Banana {};
class Banana {};
void eat(Banana const &) {}
void eat(Banana const &) {}
</cpp>
</code>
Even a built-in type can be made eatable by defining a suitable <code>eat</code> function. The following makes <code>double</code> an eatable type:
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:
<cpp>
<code cpp>
void eat(double) {}
void eat(double) {}
</cpp>
</code>


Another way to make an existing type eatable is to use a concept map. Let's assume we have an abstract base class <code>Food</code> which looks like this;
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;
<cpp>
<code cpp>
class Food
class Food
{
{
Line 76: Line 76:
virtual ~Food() {}
virtual ~Food() {}
};
};
</cpp>
</code>
Then we can make all classes derived from Food eatable using <code>Food::munch()</code> for <code>eat</code> with the following concept map template:
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:
<cpp>
<code cpp>
template<std::DerivedFrom<Food> T>
template<std::DerivedFrom<Food> T>
concept_map Eatable<T>
concept_map Eatable<T>
Line 84: Line 84:
void eat(T const& t) { t->munch(); }
void eat(T const& t) { t->munch(); }
}
}
</cpp>
</code>
The difference to a global function <code>void eat(Food const&)</code> is that the function in the concept map is only visible to functions using that concept, thus reducing namespace polution. Functions directly operating on <code>Food</code> objects can use the interface provided by <code>Food</code> itself, e.g. <code>apple.munch()</code>, or explicitly invoke <code>Eatable<Food>::eat(apple)</code>. Of course, concept maps also work with built-in types:
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:
<cpp>
<code cpp>
concept_map Eatable<int>
concept_map Eatable<int>
{
{
void eat(int) {}
void eat(int) {}
}
}
</cpp>
</code>


=={{header|Haskell}}==
=={{header|Haskell}}==
A type class defines a set of operations that must be implemented by a type:
A type class defines a set of operations that must be implemented by a type:
<code haskell>
<pre>
class Eatable a where
class Eatable a where
eat :: a -> String
eat :: a -> String
</pre>
</code>
We just require that instances of this type class implement a function <code>eat</code> which takes in the type and returns a string (I arbitrarily decided).
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 <code>FoodBox</code> type could be implemented as follows:
The <tt>FoodBox</tt> type could be implemented as follows:
<code haskell>
<pre>
data (Eatable a) => FoodBox a = F [a]
data (Eatable a) => FoodBox a = F [a]
</pre>
</code>
The stuff before the <code>=></code> specify what type classes the type variable <code>a</code> must belong to.
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 <code>Eatable</code> at any time by providing an implementation for the function <code>eat</code>. Here we define a new type <code>Banana</code>, and make it an instance of <code>Eatable</code>.
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>.
<code haskell>
<pre>
data Banana = Foo -- the implementation doesn't really matter in this case
data Banana = Foo -- the implementation doesn't really matter in this case
instance Eatable Banana where
instance Eatable Banana where
eat _ = "I'm eating a banana"
eat _ = "I'm eating a banana"
</pre>
</code>
We can declare existing types to be instances in the exact same way. The following makes <code>Double</code> an eatable type:
We can declare existing types to be instances in the exact same way. The following makes <tt>Double</tt> an eatable type:
<code haskell>
<pre>
instance Eatable Double where
instance Eatable Double where
eat d = "I'm eating " ++ show d
eat d = "I'm eating " ++ show d
</pre>
</code>


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 <code>Food</code> which looks like this;
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;
<code haskell>
<pre>
class Food a where
class Food a where
munch :: a -> String
munch :: a -> String
</pre>
</code>
Then we can make all instances of Food eatable using <code>munch</code> for <code>eat</code> with the following instance declaration:
Then we can make all instances of Food eatable using <tt>munch</tt> for <tt>eat</tt> with the following instance declaration:
<code haskell>
<pre>
instance (Food a) => Eatable a where
instance (Food a) => Eatable a where
eat x = munch x
eat x = munch x
</pre>
</code>

Revision as of 18:12, 27 January 2009

Task
Constrained genericity
You are encouraged to solve this task according to the task description, using any language you may know.

Constrained genericity means that a parametrized type or function (see Parametric Polymorphism) can only be instantiated on types fulfilling some conditions, even if those conditions are not used in that function.

Say a type is called "eatable" if you can call the function eat on it. Write a generic type FoodBox which contains a collection of objects of a type given as parameter, but can only be instantiated on eatable types. The FoodBox shall not use the function eat in any way (i.e. without the explicit restriction, it could be instantiated on any type). The specification of a type being eatable should be as generic as possible in your language (i.e. the restrictions on the implementation of eatable types should be as minimal as possible). Also explain the restrictions, if any, on the implementation of eatable types, and show at least one example of an eatable type.

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: with Ada.Containers.Indefinite_Vectors;

package Nutrition is

  type Food is interface;
  procedure Eat (Object : in out Food) is abstract;
  package Food_Boxes is
     new Ada.Containers.Indefinite_Vectors
         (  Index_Type   => Positive,
            Element_Type => Food'Class
         );
  subtype Food_Box is Food_Boxes.Vector;

end Nutrition; 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: 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; We have declared Banana and Tomato as a Food.

  Lunch_Box : Food_Box;

begin

  Lunch_Box.Append (Banana'(null record));
  Lunch_Box.Append (Banana'(null record));
  Lunch_Box.Append (Tomato'(null record));

The lunch box contains two banana and one tomato.

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:

  1. include <concepts>
  2. include <vector>

auto concept Eatable<typename T> // auto makes it apply automatically {

 void eat(T);

};

template<std::Moveable T>

requires Eatable<T>

class FoodBox { public:

 std::vector<T> food;

}; The only requirement to implement an Eatable type is, indeed, that a suitable function eat is defined for it (to put it in the FoodBox, in addition it has to be Moveable, since std::vector requires that; but that's ortogonal to the type being Eatable). A possible implementation of an eatable type could be: class Banana {}; void eat(Banana const &) {} Even a built-in type can be made eatable by defining a suitable eat function. The following makes double an eatable type: void eat(double) {}

Another way to make an existing type eatable is to use a concept map. Let's assume we have an abstract base class Food which looks like this; class Food { public:

 virtual void munch() = 0;
 virtual ~Food() {}

}; Then we can make all classes derived from Food eatable using Food::munch() for eat with the following concept map template: template<std::DerivedFrom<Food> T>

concept_map Eatable<T>

{

 void eat(T const& t) { t->munch(); }

} The difference to a global function void eat(Food const&) is that the function in the concept map is only visible to functions using that concept, thus reducing namespace polution. Functions directly operating on Food objects can use the interface provided by Food itself, e.g. apple.munch(), or explicitly invoke Eatable<Food>::eat(apple). Of course, concept maps also work with built-in types: concept_map Eatable<int> {

 void eat(int) {}

}

Haskell

A type class defines a set of operations that must be implemented by a type: class Eatable a where

 eat :: a -> String

We just require that instances of this type class implement a function eat which takes in the type and returns a string (I arbitrarily decided).

The FoodBox type could be implemented as follows: data (Eatable a) => FoodBox a = F [a] The stuff before the => specify what type classes the type variable a must belong to.

We can create an instance of Eatable at any time by providing an implementation for the function eat. Here we define a new type Banana, and make it an instance of Eatable. data Banana = Foo -- the implementation doesn't really matter in this case instance Eatable Banana where

 eat _ = "I'm eating a banana"

We can declare existing types to be instances in the exact same way. The following makes Double an eatable type: instance Eatable Double where

 eat d = "I'm eating " ++ show d

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 Food which looks like this; class Food a where

 munch :: a -> String

Then we can make all instances of Food eatable using munch for eat with the following instance declaration: instance (Food a) => Eatable a where

 eat x = munch x