Constrained genericity: Difference between revisions

From Rosetta Code
Content added Content deleted
(Omit Perl.)
m (formatting)
Line 9: Line 9:
=={{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:
<lang ada>
<lang ada>with Ada.Containers.Indefinite_Vectors;
with Ada.Containers.Indefinite_Vectors;


package Nutrition is
package Nutrition is
Line 22: Line 21:
);
);
subtype Food_Box is Food_Boxes.Vector;
subtype Food_Box is Food_Boxes.Vector;
end Nutrition;
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:
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;
overriding procedure Eat (Object : in out Banana) is null;


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;</lang>
</lang>
We have declared Banana and Tomato as a Food.
We have declared Banana and Tomato as a Food.
<lang ada>
<lang ada> Lunch_Box : Food_Box;
Lunch_Box : Food_Box;
begin
begin
Lunch_Box.Append (Banana'(null record));
Lunch_Box.Append (Banana'(null record));
Lunch_Box.Append (Banana'(null record));
Lunch_Box.Append (Banana'(null record));
Lunch_Box.Append (Tomato'(null record));
Lunch_Box.Append (Tomato'(null record));</lang>
</lang>
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:
<lang cpp>
<lang cpp>#include <concepts>
#include <concepts>
#include <vector>
#include <vector>


Line 59: Line 52:
public:
public:
std::vector<T> food;
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:
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>
<lang cpp>class Banana {};
class Banana {};
void 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:
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>
<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;
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>
<lang cpp>class Food
class Food
{
{
public:
public:
virtual void munch() = 0;
virtual void munch() = 0;
virtual ~Food() {}
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:
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>
<lang cpp>template<std::DerivedFrom<Food> T>
template<std::DerivedFrom<Food> T>
concept_map Eatable<T>
concept_map Eatable<T>
{
{
void eat(T const& t) { t->munch(); }
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:
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>
<lang cpp>concept_map Eatable<int>
concept_map Eatable<int>
{
{
void eat(int) {}
void eat(int) {}
}</lang>
}
</lang>


=={{header|D}}==
=={{header|D}}==
In D this can be done in two different ways.
In D this can be done in two different ways.


<lang D>interface Edible { void eat(); }
<lang D>
interface Edible { void eat(); }
interface NonPoisonous {}
interface NonPoisonous {}


Line 107: Line 88:
static assert (is (T : NonPoisonous), "don't eat that!");
static assert (is (T : NonPoisonous), "don't eat that!");
T[] food;
T[] food;
}</lang>
}
</lang>


First one is similar to Java example. Trying to instantiate or create alias of ''FoodBox'' parametrized with
First one is similar to Java example. Trying to instantiate or create alias of ''FoodBox'' parametrized with
Line 116: Line 96:


Usage:
Usage:
<lang D>class Banana : Edible, NonPoisonous
<lang D>
class Banana : Edible, NonPoisonous
{
{
void eat() { Stdout("eating banana").newline; }
void eat() { Stdout("eating banana").newline; }
Line 137: Line 116:
// will fail due to class template specialization
// will fail due to class template specialization
alias FoodBox!(Car) CarBox;
alias FoodBox!(Car) CarBox;
}</lang>
}
</lang>


=={{header|E}}==
=={{header|E}}==
Line 144: Line 122:
It is surely arguable whether this constitutes an implementation of the above task:
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 {
def Eatable {
to coerce(specimen, ejector) {
to coerce(specimen, ejector) {
Line 162: Line 139:
=={{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:
<lang haskell>
<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).
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:
The <tt>FoodBox</tt> type could be implemented as follows:
<lang haskell>
<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.
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>.
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
instance Eatable Banana where
eat _ = "I'm eating a banana"
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:
We can declare existing types to be instances in the exact same way. The following makes <tt>Double</tt> an eatable type:
<lang haskell>
<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;
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>
<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:
Then we can make all instances of Food eatable using <tt>munch</tt> for <tt>eat</tt> with the following instance declaration:
<lang haskell>
<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}}==
=={{header|Java}}==
Line 221: Line 186:


A module type defines a set of operations that must be implemented by a module:
A module type defines a set of operations that must be implemented by a module:
<lang ocaml>
<lang ocaml>module type Eatable = sig
module type Eatable = sig
type t
type t
val eat : t -> unit
val eat : t -> unit
end
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.
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):
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>
<lang ocaml>module MakeFoodBox(A : Eatable) = struct
module MakeFoodBox(A : Eatable) = struct
type elt = A.t
type elt = A.t
type t = F of elt list
type t = F of elt list
let make_box_from_list xs = F xs
let make_box_from_list xs = F xs
end
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>.
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>
<lang ocaml>type banana = Foo (* a dummy type *)
type banana = Foo (* a dummy type *)


module Banana : Eatable with type t = banana = struct
module Banana : Eatable with type t = banana = struct
type t = banana
type t = banana
let eat _ = print_endline "I'm eating a banana"
let eat _ = print_endline "I'm eating a banana"
end
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:
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
type t = float
let eat f = Printf.printf "I'm eating %f\n%!" f
let eat f = Printf.printf "I'm eating %f\n%!" f
end
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:
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>
<lang ocaml>module BananaBox = MakeFoodBox (Banana)
module BananaBox = MakeFoodBox (Banana)
module FloatBox = MakeFoodBox (EatFloat)
module FloatBox = MakeFoodBox (EatFloat)


let my_box = BananaBox.make_box_from_list [Foo]
let my_box = BananaBox.make_box_from_list [Foo]
let your_box = FloatBox.make_box_from_list [2.3; 4.5]
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.
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.



Revision as of 12:22, 3 August 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.

This language feature only applies to statically-typed languages.

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;

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;</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; 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> We have declared Banana and Tomato as a Food. <lang ada> Lunch_Box : Food_Box; begin

  Lunch_Box.Append (Banana'(null record));
  Lunch_Box.Append (Banana'(null record));
  Lunch_Box.Append (Tomato'(null record));</lang>

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: <lang cpp>#include <concepts>

  1. 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;

};</lang> 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: <lang cpp>class Banana {}; void eat(Banana const &) {}</lang> Even a built-in type can be made eatable by defining a suitable eat function. The following makes double an eatable type: <lang cpp>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 Food which looks like this; <lang cpp>class Food { public:

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

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

concept_map Eatable<T>

{

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

}</lang> 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: <lang cpp>concept_map Eatable<int> {

 void eat(int) {}

}</lang>

D

In D this can be done in two different ways.

<lang D>interface Edible { void eat(); } interface NonPoisonous {}

class FoodBox(T : Edible) {

   static assert (is (T : NonPoisonous), "don't eat that!");
   T[] food;

}</lang>

First one is similar to Java example. Trying to instantiate or create alias of FoodBox parametrized with type that can't be implicitly converted to Edible result in compiler error.

The second one is using static assert and is-expression. (Static asserts are checked during compilation).

Usage: <lang D>class Banana : Edible, NonPoisonous {

   void eat() { Stdout("eating banana").newline; }

}

class AmanitaMuscaria : Edible {

   void eat() { Stdout("eating that red thinggy").newline; }

}

class Car { }

void main() {

   // is ok
   alias FoodBox!(Banana) BananaBox;
   // will fail due to static assert
   alias FoodBox!(AmanitaMuscaria) ShroomBox;
   // will fail due to class template specialization
   alias FoodBox!(Car) CarBox;

}</lang>

E

It is surely arguable whether this constitutes an implementation of the above task:

<lang e>/** Guard accepting only objects with an 'eat' method */ def Eatable {

   to coerce(specimen, ejector) {
       if (Ref.isNear(specimen) && specimen.__respondsTo("eat", 0)) {
           return specimen
       } else {
           throw.eject(ejector, `inedible: $specimen`)
       }
   }

}

def makeFoodBox() {

   return [].diverge(Eatable) # A guard-constrained list

}</lang>

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>

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: <lang haskell>data (Eatable a) => FoodBox a = F [a]</lang> 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. <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>

We can declare existing types to be instances in the exact same way. The following makes Double an eatable type: <lang haskell>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 Food which looks like this; <lang haskell>class Food a where

 munch :: a -> String</lang>

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

 eat x = munch x</lang>

Java

Works with: Java version 5

In Java type constraints are made on the type hierarchy, so here we make Eatable an interface, with an eat method. Types which are Eatable would have to implement the Eatable interface and provide an eat method.

<lang java>interface Eatable {

   void eat();

}</lang>

Type constraints in type parameters can be made via the extends keyword, indicating in this case that the type argument must be a type that is a subtype of Eatable. <lang java>import java.util.List;

class FoodBox<T extends Eatable> {

   public List<T> food;

}</lang>

Similarly a generic method can constrain its type parameters <lang java>public <T extends Eatable> void foo(T x) { }</lang>

OCaml

OCaml handles type constraints through modules and module types.

A module type defines a set of operations that must be implemented by a module: <lang ocaml>module type Eatable = sig

 type t
 val eat : t -> unit

end</lang> We just require that module instances of this module type describe a type t and implement a function eat which takes in the type and returns nothing.

The FoodBox 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

 type elt = A.t
 type t = F of elt list
 let make_box_from_list xs = F xs

end</lang>

We can create a module that is an instance of Eatable by specifying a type providing an implementation for the function eat. Here we define a module Banana, and make it an instance of Eatable. <lang ocaml>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>

We can also create modules that use an existing type as its t. The following module uses float as its type: <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>

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 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> 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. BananaBox, FloatBox). And the operations on that resulting type (i.e. make_box_from_list) are tied to each specific module.