{"id":276,"date":"2007-01-16T20:36:52","date_gmt":"2007-01-16T20:36:52","guid":{"rendered":"http:\/\/scientopia.org\/blogs\/goodmath\/2007\/01\/16\/haskell-the-basics-of-type-classes\/"},"modified":"2007-01-16T20:36:52","modified_gmt":"2007-01-16T20:36:52","slug":"haskell-the-basics-of-type-classes","status":"publish","type":"post","link":"http:\/\/www.goodmath.org\/blog\/2007\/01\/16\/haskell-the-basics-of-type-classes\/","title":{"rendered":"Haskell: the Basics of Type Classes"},"content":{"rendered":"<p> One thing that we&#8217;ve seen already in Haskell programs is <em>type<br \/>\nclasses<\/em>. Today, we&#8217;re going to try to take our first look real look<br \/>\nat them in detail &#8211; both how to <em>use<\/em> them, and how to <em>define<\/em> them. This still isn&#8217;t the entire picture around type-classes; we&#8217;ll come back for another look at them later. But this is a beginning &#8211; enough to<br \/>\nreally understand how to use them in basic Haskell programs, and enough to give us the background we&#8217;ll need to attack Monads, which are the next big topic.<\/p>\n<p> Type classes are Haskell&#8217;s mechanism for managing parametric polymorphism. Parametric polymorphism<br \/>\nis a big fancy term for code that works with type parameters. The idea of type classes is to provide a<br \/>\nmechanism for building <em>constrained<\/em> polymorphic functions: that is, functions whose type involves a type parameter, but which needs some constraint to <em>limit<\/em> the types that can be used to instantiate it, to specify what properties its type parameter must have. In essence, it&#8217;s doing very much the same thing that parameter type declarations let us do for the code. Type declarations let us say &#8220;the value of this parameter can&#8217;t be just any value &#8211; it must be a value which is a member of this particular type&#8221;; type-class declarations let us say &#8220;the type parameter for instantiating this function can&#8217;t be just any type &#8211; it must be a type which is a member of this type-class.&#8221;<\/p>\n<p><!--more--><\/p>\n<p> The type declarations in Haskell let us do this in a very flexible way, derived from something called the Curry-Howard isomorphism, which shows that type systems for lambda calculus are isomorphic to inference systems in some logic. The simply-typed lambda calculus has a type system related to simple propositional logic; Haskell, with its type variables, has a type system derived from first order predicate logic. <\/p>\n<p> In fact, in Haskell, when you declare a type with a type variable, in the logic of the type<br \/>\nsystem, you&#8217;ve implicitly quantified the type with a universal quantifier. So a type &#8220;a -&gt; b&#8221; in<br \/>\nHaskell is actually implicitly understood to mean: (&forall; a,b : a  &rarr; b). <\/p>\n<p> If you think back to predicate logic, you might recall that &#8220;such that&#8221; constructions like &#8220;&forall; x such that x &isin; S: T(x) &rarr; U(x)&#8221; are not really primitives in the logic &#8211; they&#8217;re just a shorthand for a logical implication: &forall; x : (x &isin; T) &rarr; (T(x) &rarr; U(x)). That&#8217;s exactly what Haskell does with type-classes &#8211; it writes that as predicate (e.g., &#8220;<code>Num(x)<\/code>) with an implication symbol (&#8220;<code>=&gt;<\/code>&#8220;). The reason for the separate implication symbol is<br \/>\nbasically just to visually distinguish the part of the type that is a constraint on the type variables. So when you write something like &#8220;<code>(Eq a) =&gt; a -&gt; a -&gt; Bool<\/code>&#8220;, what your really saying is &#8220;For all types a such that a is in Eq, this function has type &#8220;a -&gt; a -&gt; Bool&#8221;.<\/p>\n<p> Enough theory &#8211; let&#8217;s look at how we really use these things. Suppose we wanted to write a function<br \/>\nthat added a list of values. Without using type classes, you might try to write it as:<\/p>\n<pre>\naddValues :: [a] -&gt; a\naddValues v:vs = v + (addValues vs)\naddValues v:[] = v\n<\/pre>\n<p> If you were to try to compile that function, you&#8217;d get a compile error: it uses the &#8220;+&#8221; operation on values of type a, but the type declaration says that it works on <em>any<\/em> type a. What we need to do is specify a type constraint that supports addition &#8211; like <code>Num<\/code>:<\/p>\n<pre>\n&gt;addValues :: Num a =&gt; [a] -&gt; a\n&gt;addValues (v:[]) = v\n&gt;addValues (v:vs) = v + (addValues vs)\n<\/pre>\n<p> What we&#8217;ve done is chosen the appropriate type predicate for selecting types that we can use in the function. And there&#8217;s something important about that: the Haskell prelude and standard libraries define a <em>lot<\/em> of type classes. If you&#8217;re not defining a type-class for something very specific to your application, there&#8217;s a good chance that there&#8217;s already a suitable type-class in the libraries, and you should use the library one, rather than defining your own. Your code will be <em>much<\/em> more portable and reusable if you take advantage of the type classes in the standard libraries.<\/p>\n<p> Whether you&#8217;re only going to use library classes, or define your own, you&#8217;ll still need to be able to read type-class declarations, and read and write instances. <\/p>\n<p><p> A type-class declaration consists of two basic parts: the first is a declaration of what operations (methods) an instance of the class must support; and the second is a set of default implementations of those methods.<\/p>\n<p> For example here&#8217;s the declaration for the type-class <code>Ord<\/code>, which is the class of all<br \/>\nordered types:<\/p>\n<pre>\nclass (Eq a) =&gt; Ord a where\ncompare              :: a -&gt; a -&gt; Ordering\n(&lt;), (&lt;=), (&gt;=), (&gt;) :: a -&gt; a -&gt; Bool\nmax, min             :: a -&gt; a -&gt; a\ncompare x y | x == y    = EQ\n| x &lt;= y    = LT\n| otherwise = GT\nx &lt;= y  = compare x y \/= GT\nx &lt;  y  = compare x y == LT\nx &gt;= y  = compare x y \/= LT\nx &gt;  y  = compare x y == GT\n-- Note that (min x y, max x y) = (x,y) or (y,x)\nmax x y | x &lt;= y    =  y\n| otherwise =  x\nmin x y | x &lt;= y    =  x\n| otherwise =  y\n<\/pre>\n<p> The first line declares the name of the class being declared, and any constraints on what types can be instances of this class. The constraints are other type classes that a type must be an instance of before it can be made an instance of the new type. In the case of <code>Ord<\/code>, it says<br \/>\n<code>\"(Eq a) =&gt; Ord a\"<\/code>, which means before a type can be an instance of <code>Ord<\/code>,<br \/>\nit must also be an instance of <code>Eq<\/code>. The type variable <code>a<\/code> will be used throughout the class definition to refer to an instance of type-class <code>Ord<\/code>. <\/p>\n<p> After the declaration line, there are a list of function-type declarations. These declare the<br \/>\n<em>methods<\/em> that must be implemented in order to be an instance of the class. (We&#8217;ll get to how<br \/>\nfunctions are made into type-class methods when we look at the instance declarations.)<\/p>\n<p> What this says is: for a type to be an instance of <code>Ord<\/code>, it must first be an instance of <code>Eq<\/code>; and to be an instance of <code>Ord<\/code>, it needs to provide implementations<br \/>\nof methods for <code>compare<\/code>, <code>&lt;<\/code>, <code>&lt;=<\/code>, <code>&gt;<\/code>, <code>&gt;=<\/code>, <code>max<\/code>, and <code>min<\/code>. It then provides some default implementations &#8211; if an instance declaration doesn&#8217;t specify an implementation of a method for its type, then the default from the class declaration will be used. There&#8217;s one interesting thing to note about the default methods: a default method can use any operation specified on the type-class, or required by the class constraint. The default methods <em>do not<\/em> have to make sense together. If you were to use <code>Ord<\/code> without providing an implementation of either <code>&lt;=<\/code> or <code>compare<\/code>, you&#8217;d wind up the <code>Ord<\/code> operations all being infinite loops! The default implementation of <code>compare<\/code> is written in terms of <code>&lt;=<\/code>; and the default implementation of <code>&lt;=<\/code> is written in terms of <code>compare<\/code>.<\/p>\n<p> So what would a type-instance of class <code>Ord<\/code>  look like? Let&#8217;s try putting together the beginnings of an implementation of <a href=\"http:\/\/scientopia.org\/blogs\/goodmath\/2006\/08\/introducing-the-surreal-numbers\">surreal numbers.<\/a>.<\/p>\n<pre>\n&gt;-- A surreal number is a number with two sets of surreals:\n&gt;--    a left set which contains elements less than it, and\n&gt;--    a right set which contains elements greater than it.\n&gt;-- By definition, zero is the surreal with both sets empty.\n&gt;\n&gt;data Surreal = Surreal [Surreal] [Surreal] deriving (Eq, Show)\n&gt;srZero = Surreal [] []\n&gt;srPlusOne = Surreal [srZero] []\n&gt;srMinusOne = Surreal [] [srZero]\n&gt;srPlusOneHalf = Surreal [srZero] [srPlusOne]\n<\/pre>\n<p> Then, we implement the basic ordering operations on surreal numbers. We don&#8217;t do anything special &#8211; just implement them as perfectly normal functions. As a reminder, here&#8217;s the definition of &le; for surreal numbers:<\/p>\n<p>Given two surreal numbers x = {X<sub>L<\/sub>|X<sub>R<\/sub>} and y = {Y<sub>L<\/sub>|Y<sub>R<\/sub>}, x &le; y if and only if:<\/p>\n<ol>\n<li> &not;&exist;x<sub>i<\/sub>&isin;X<sub>L<\/sub> : y &le; x<sub>i<\/sub>, and<\/li>\n<li> &not;&exist;y<sub>i<\/sub>&isin;Y<sub>R<\/sub> : y<sub>i<\/sub> &le; x<\/li>\n<\/ol>\n<pre>\n&gt;srleq :: Surreal -&gt; Surreal -&gt; Bool\n&gt;srleq x@(Surreal [] []) y@(Surreal [] []) = True\n&gt;srleq x@(Surreal xl xr) y@(Surreal yl yr) =\n&gt;      (all (yi -&gt; not (srleq yi x))  yr) &amp;&amp;\n&gt;      (all (xi -&gt; not (srleq y xi)) xl)\n<\/pre>\n<p> Now, we can bind these functions into the type-class class method,<br \/>\nso that they&#8217;ll be used for surreal numbers:<\/p>\n<pre>\n&gt;instance Ord (Surreal) where\n&gt;   x &lt;= y  = srleq x y\n<\/pre>\n<p> The <code>where<\/code> clause of the instance declaration contains<br \/>\na perfectly normal list of function declarations. The only reason for separating the function declarations from the instance declaration is<br \/>\nbecause it allowed us to write the type declaration, and it made the<br \/>\ncode clearer to have a separate declaration of the &le; operation (with the explanation of what &le; means for surreal numbers) than it would have to<br \/>\nput the implementation in-line.<\/p>\n<p> With that declaration, we can now use <em>all<\/em> of the comparison operations defined in <code>Ord<\/code> on our surreal numbers:<\/p>\n<pre>\n*Main&gt; srZero &lt;= srPlusOne\nTrue\n*Main&gt; srZero &lt;= srMinusOne\nFalse\n*Main&gt; srPlusOneHalf &gt; srZero\nTrue\n*Main&gt; srPlusOneHalf  srMinusOne &lt;= srZero\nTrue\n*Main&gt; compare srZero srMinusOne\nGT\n*Main&gt;\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>One thing that we&#8217;ve seen already in Haskell programs is type classes. Today, we&#8217;re going to try to take our first look real look at them in detail &#8211; both how to use them, and how to define them. This still isn&#8217;t the entire picture around type-classes; we&#8217;ll come back for another look at them [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[89],"tags":[],"class_list":["post-276","post","type-post","status-publish","format-standard","hentry","category-haskell"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p4lzZS-4s","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/posts\/276","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/comments?post=276"}],"version-history":[{"count":0,"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/posts\/276\/revisions"}],"wp:attachment":[{"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/media?parent=276"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/categories?post=276"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/tags?post=276"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}