Parametric polymorphism: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎{{header|Haskell}}: discuss fmap)
(Derived a Clean implementation based on the Haskell one)
Line 59: Line 59:
right->replace_all (new_value);
right->replace_all (new_value);
}</lang>
}</lang>

=={{header|Clean}}==

<lang clean>::Tree a = Empty | Node a (Tree a) (Tree a)

mapTree :: (a -> b) (Tree a) -> (Tree b)
mapTree f Empty = Empty
mapTree f (Node x l r) = Node (f x) (mapTree f l) (mapTree f r)</lang>

<blockquote><small>
A digression:

Note that for the most usefulness in practical programming, a map operation like this should not be defined with a separate name but rather as <code>fmap</code> in an ''instance'' of the <code>Functor</code> ''type class'':

<lang clean>instance Functor Tree where
fmap f Empty = Empty
fmap f (Node x l r) = Node (f x) (fmap f l) (fmap f r)</lang>

<code>fmap</code> can then be used exactly where <code>mapTree</code> can, but doing this also allows the use of <code>Tree</code>s with other components which are parametric over ''any type which is a Functor''. For example, this function will add 1 to any collection of any kind of number:

<lang clean>add1Everywhere :: (f a) -> (f a) | Functor f & Num a
add1Everywhere nums = fmap (\x = x + 1) nums</lang>

If we have a tree of integers, i.e. <var>f</var> is <code>Tree</code> and <var>a</var> is <code>Integer</code>, then the type of <code>add1Everywhere</code> is <code>Tree Integer -> Tree Integer</code>.
</small></blockquote>

=={{header|D}}==
=={{header|D}}==
D template can parametric by constant.
D template can parametric by constant.
Line 167: Line 193:
fmap f (Node x l r) = Node (f x) (fmap f l) (fmap f r)</lang>
fmap f (Node x l r) = Node (f x) (fmap f l) (fmap f r)</lang>


<code>fmap</code> can then be used exactly where <code>mapTree</code> can, but doing this also allows the use of <code>Tree</code>s with other components which are parametric over ''any type which is a Functor''. For example, this function will add 1 to any collection of any kind of number:
<code>fmap</code> can then be used exactly where <code>mapTree</code> can, but doing this also allows the use of <code>Tree</code>s with other components which are parametric over ''any type which is a Functor''. For example, this function will add 1 to any collection of any kind of number:


<lang haskell>add1Everywhere :: (Functor f, Num a) => f a -> f a
<lang haskell>add1Everywhere :: (Functor f, Num a) => f a -> f a

Revision as of 21:21, 29 July 2009

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

Parametric Polymorphism is a way to define types or functions that are generic over other types. The genericity can be expressed by using type variables for the parameter type, and by a mechanism to explicitly or implicitly replace the type variables with concrete types when necessary.

Write a small example for a type declaration that is parametric over another type, together with a short bit of code (and its type signature) that uses it. A good example is a container type, let's say a binary tree, together with some function that traverses the tree, say, a map-function that operates on every element of the tree.

This language feature only applies to statically-typed languages.

Ada

<lang ada>

generic
   type Element_Type is private;
package Container is
   type Tree is tagged private;
   procedure Replace_All(The_Tree : in out Tree; New_Value : Element_Type);
private
   type Node;
   type Node_Access is access Node;
   type Tree tagged record
      Value : Element_type;
      Left  : Node_Access := null;
      Right : Node_Access := null;
   end record;
end Container;

</lang> <lang ada>

package body Container is
   procedure Replace_All(The_Tree : in out Tree; New_Value : Element_Type) is
   begin
      The_Tree.Value := New_Value;
      If The_Tree.Left /= null then
         The_Tree.Left.all.Replace_All(New_Value);
      end if;
      if The_tree.Right /= null then
         The_Tree.Right.all.Replace_All(New_Value);
      end if;
   end Replace_All;
end Container;

</lang>

C++

<lang cpp> template<class T>

class tree
{
  T value;
  tree *left;
  tree *right;
public:
  void replace_all (T new_value);
};</lang>

For simplicity, we replace all values in the tree with a new value:

<lang cpp> template<class T>

void tree<T>::replace_all (T new_value)
{
  value = new_value;
  left->replace_all (new_value);
  right->replace_all (new_value);
}</lang>

Clean

<lang clean>::Tree a = Empty | Node a (Tree a) (Tree a)

mapTree :: (a -> b) (Tree a) -> (Tree b) mapTree f Empty = Empty mapTree f (Node x l r) = Node (f x) (mapTree f l) (mapTree f r)</lang>

A digression:

Note that for the most usefulness in practical programming, a map operation like this should not be defined with a separate name but rather as fmap in an instance of the Functor type class:

<lang clean>instance Functor Tree where fmap f Empty = Empty fmap f (Node x l r) = Node (f x) (fmap f l) (fmap f r)</lang>

fmap can then be used exactly where mapTree can, but doing this also allows the use of Trees with other components which are parametric over any type which is a Functor. For example, this function will add 1 to any collection of any kind of number:

<lang clean>add1Everywhere :: (f a) -> (f a) | Functor f & Num a add1Everywhere nums = fmap (\x = x + 1) nums</lang>

If we have a tree of integers, i.e. f is Tree and a is Integer, then the type of add1Everywhere is Tree Integer -> Tree Integer.

D

D template can parametric by constant. <lang d>module parapoly ;

class ArrayTree(T, int LEN) {

 // template instantiation in alias
 alias ArrayTree!(T, LEN) ANode ;  
 const int length = LEN ;
 T[LEN] array ;
 ANode LHS, RHS ;
 this (T initvalue) { array[] = initvalue ; }
 void map(void delegate(T[]) dg) { 
   dg(array) ;
   if(LHS) LHS.map(dg) ; 
   if(RHS) RHS.map(dg) ; 
 } 

}</lang> This is a using example. <lang d> module polytest ; import std.stdio ; import parapoly ;

//Instantiating an ArrayTree class of real(T) array, whose length(LEN) is 3. alias ArrayTree!(real, 3) VecT ;

void main() {

 VecT v = new VecT(0.01);
 // init an array tree.
 v.LHS = new VecT(1.11) ; v.LHS.LHS = new VecT(1.11) ; v.LHS.RHS = new VecT(1.12) ;
 v.RHS = new VecT(1.21) ; v.RHS.LHS = new VecT(1.21) ; v.RHS.RHS = new VecT(1.22) ;
 // do something start from root v
 v.map((real[] x){writefln(x) ;} ) ;
 real new_value = 0.9L ; 
 v.map((real[] x){x[] = new_value ;} ) ;
 writefln() ;
 v.map((real[] x){writefln(x) ;} ) ;

}</lang>

E

While E itself does not do static (before evaluation) type checking, E does have guards which form a runtime type system, and has typed collections in the standard library. Here, we implement a typed tree, and a guard which accepts trees of a specific type.

(Note: Like some other examples here, this is an incomplete program in that the tree provides no way to insert or delete nodes.)

(Note: The guard definition is arguably messy boilerplate; future versions of E may provide a scheme where the interface expression can itself be used to describe parametricity, and message signatures using the type parameter, but this has not been implemented or fully designed yet. Currently, this example is more of “you can do it if you need to” than something worth doing for every data structure in your program.)

<lang e>interface TreeAny guards TreeStamp {} def Tree {

   to get(Value) {
       def Tree1 {
           to coerce(specimen, ejector) {
               def tree := TreeAny.coerce(specimen, ejector)
               if (tree.valueType() != Value) {
                   throw.eject(ejector, "Tree value type mismatch")
               }
               return tree
           }
       }
       return Tree1
   }

}

def makeTree(T, var value :T, left :nullOk[Tree[T]], right :nullOk[Tree[T]]) {

   def tree implements TreeStamp {
       to valueType() { return T }
       to map(f) {
           value := f(value)  # the declaration of value causes this to be checked
           if (left != null) {
               left.map(f)
           }
           if (right != null) {
               right.map(f)
           }
       }
   }
   return tree

}</lang>

<lang e>? def t := makeTree(int, 0, null, null)

  1. value: <tree>

? t :Tree[String]

  1. problem: Tree value type mismatch

? t :Tree[Int]

  1. problem: Failed: Undefined variable: Int

? t :Tree[int]

  1. value: <tree></lang>

Haskell

<lang haskell>data Tree a = Empty | Node a (Tree a) (Tree a)

mapTree :: (a -> b) -> Tree a -> Tree b mapTree f Empty = Empty mapTree f (Node x l r) = Node (f x) (mapTree f l) (mapTree f r)</lang>

A digression:

Note that for the most usefulness in practical programming, a map operation like this should not be defined with a separate name but rather as fmap in an instance of the Functor type class:

<lang haskell>instance Functor Tree where fmap f Empty = Empty fmap f (Node x l r) = Node (f x) (fmap f l) (fmap f r)</lang>

fmap can then be used exactly where mapTree can, but doing this also allows the use of Trees with other components which are parametric over any type which is a Functor. For example, this function will add 1 to any collection of any kind of number:

<lang haskell>add1Everywhere :: (Functor f, Num a) => f a -> f a add1Everywhere nums = fmap (\x -> x + 1) nums</lang>

If we have a tree of integers, i.e. f is Tree and a is Integer, then the type of add1Everywhere is Tree Integer -> Tree Integer.

Java

Following the C++ example: <lang java> public class Tree<T>{

	private T value;
	private Tree<T> left;
	private Tree<T> right;

	public void replaceAll(T value){
		this.value = value;
		if(left != null)
			left.replaceAll(value);
		if(right != null)
			right.replaceAll(value);
	}
}</lang>

OCaml

<lang ocaml> type 'a tree = Empty | Node of 'a * 'a tree * 'a tree

(** val map_tree : ('a -> 'b) -> 'a tree -> 'b tree *)
let rec map_tree f = function
  | Empty        -> Empty
  | Node (x,l,r) -> Node (f x, map_tree f l, map_tree f r)</lang>

Standard ML

<lang sml> datatype 'a tree = Empty | Node of 'a * 'a tree * 'a tree

(** val map_tree = fn : ('a -> 'b) -> 'a tree -> 'b tree *)
fun map_tree f Empty = Empty
  | map_tree f (Node (x,l,r)) = Node (f x, map_tree f l, map_tree f r)</lang>