Algebraic data types: Difference between revisions
Content added Content deleted
(Added Oz example.) |
(Added Scala) |
||
Line 200: | Line 200: | ||
insert(X,S,t(b,A,Y,B)) :- ins(X,S,t(_,A,Y,B)). |
insert(X,S,t(b,A,Y,B)) :- ins(X,S,t(_,A,Y,B)). |
||
</pre> |
|||
=={{header|Scala}}== |
|||
{{trans|Haskell}} |
|||
Algebraic data types are implemented in Scala through the combination of a number of |
|||
different features, to ensure principles of Object Oriented Programming. |
|||
The main type |
|||
is usually defined as a <code>sealed abstract class</code>, which ensures it can't be |
|||
instantiated, and guarantees that it can't be expanded outside the file it was defined |
|||
at. This last feature is used so the compiler can verify that the pattern matching is |
|||
complete, or warn when there are missing cases. It can be ommitted if preferred. |
|||
Each subtype is defined either as a <code>case object</code>, for non-paremeterized types, |
|||
or <code>case class</code>, for parameterized types, all extending the main type. The |
|||
<code>case</code> keyword is not required, but, when used, it provides a number of default |
|||
methods which ensure they can be used without any further definitions. |
|||
The most important of those default methods for the purpose of algebraic data types is the |
|||
''extractor'', a method called either <code>unapply</code> or <code>unapplySeq</code>, and |
|||
which returns an <code>Option</code> containing the deconstructed parameters, or <code>None</code> |
|||
if the passed object can't be deconstructed by this method. Scala uses the extractors to |
|||
implement pattern matching without exposing the internal representation of the data. |
|||
This specific task is made much harder than necessary because Scala doesn't have a variant |
|||
ordering class. Given that limitation, one has to either give up on a singleton object representing |
|||
the empty tree, or give up on parameterizing the tree itself. |
|||
The solution below, uses the latter approach. The algebraic data types are members of a <code>RedBlackTree</code> |
|||
class, which, itself, receives a type parameter for the keys of the tree, and an implicit |
|||
parameter for an <code>Ordering</code> for that type. To use the tree it is thus necessary |
|||
to instantiate an object of type <code>RedBlackTree</code>, and then reference the members |
|||
of that object. |
|||
<lang scala>class RedBlackTree[A](implicit ord: Ordering[A]) { |
|||
sealed abstract class Color |
|||
case object R extends Color |
|||
case object B extends Color |
|||
sealed abstract class Tree { |
|||
def insert(x: A): Tree = ins(x) match { |
|||
case T(_, a, y, b) => T(B, a, y, b) |
|||
case E => E |
|||
} |
|||
def ins(x: A): Tree |
|||
} |
|||
case object E extends Tree { |
|||
override def ins(x: A): Tree = T(R, E, x, E) |
|||
} |
|||
case class T(c: Color, left: Tree, a: A, right: Tree) extends Tree { |
|||
private def balance: Tree = (c, left, a, right) match { |
|||
case (B, T(R, T(R, a, x, b), y, c), z, d ) => T(R, T(B, a, x, b), y, T(B, c, z, d)) |
|||
case (B, T(R, a, x, T(R, b, y, c)), z, d ) => T(R, T(B, a, x, b), y, T(B, c, z, d)) |
|||
case (B, a, x, T(R, T(R, b, y, c), z, d )) => T(R, T(B, a, x, b), y, T(B, c, z, d)) |
|||
case (B, a, x, T(R, b, y, T(R, c, z, d))) => T(R, T(B, a, x, b), y, T(B, c, z, d)) |
|||
case _ => this |
|||
} |
|||
override def ins(x: A): Tree = ord.compare(x, a) match { |
|||
case -1 => T(c, left ins x, a, right ).balance |
|||
case 1 => T(c, left, a, right ins x).balance |
|||
case 0 => this |
|||
} |
|||
} |
|||
}</lang> |
|||
Usage example: |
|||
<pre> |
|||
scala> val rbt = new RedBlackTree[Int] |
|||
rbt: RedBlackTree[Int] = RedBlackTree@17dfcf1 |
|||
scala> import rbt._ |
|||
import rbt._ |
|||
scala> List.range(1, 17).foldLeft(E: Tree)(_ insert _) |
|||
res5: rbt.Tree = T(B,T(B,T(B,T(B,E,1,E),2,T(B,E,3,E)),4,T(B,T(B,E,5,E),6,T(B,E,7,E))),8,T(B,T(B,T(B,E,9,E),10,T(B,E,11,E |
|||
)),12,T(B,T(B,E,13,E),14,T(B,E,15,T(R,E,16,E))))) |
|||
</pre> |
</pre> |
||