© Stefania Loredana Nita and Marius Mihailescu 2019
Stefania Loredana Nita and Marius MihailescuHaskell Quick Syntax Referencehttps://doi.org/10.1007/978-1-4842-4507-1_10

10. Classes

Stefania Loredana Nita1  and Marius Mihailescu1
(1)
Bucharest, Romania
 

In the previous chapters, you created your own types. Now, it’s time to learn what classes are in Haskell and how to work with them. In this chapter, you’ll learn about the standard classes and then how to create your own classes.

Standard Classes

In Haskell, the most common standard classes are the following ones:
  • Eq is used when you work with == (is equal) and /= (is not equal).

  • Ord is used when you work with the operators <, <=, >, and >= and the functions min, max, and compare.

  • Enum is used in enumerations and lets you use syntax such as [Red .. Yellow].

  • Read includes the function read, which takes a string as a parameter and parses it into a value.

  • Show includes the function show, which takes a value as a parameter and converts it to a string.

  • Bounded is used in enumerations and includes the functions minBound and maxBound.

Do you remember in Chapter 4 when you used the command deriving followed by one of the previous classes? You did that because you wanted your type to use functions that actually belong to these classes. For example, to compare two dates of type DateInfo, you used deriving Eq. That allowed you to write myDate2 == myDate1.

The Eq Class

The following is the minimal definition of Eq from Prelude1:
class  Eq a  where
   (==), (/=) :: a -> a -> Bool
   x /= y     =  not (x == y)
   x == y     =  not (x /= y)
This says that if type a is an instance of Eq, it must support == and /=. The operators == and /= are called class methods , and they are defined in terms of each other. This means that a type in Eq should provide the definition for one of them, with the other being deduced automatically. Let’s take a look at ==, shown here:
(==) :: (Eq a) => a -> a -> Bool

This says that the == has the type a->a->Bool for every type a that is an instance of the class Eq.

When you already have a type, you can make it an instance of a class, which is a process called instance declaration .
data MyType = MyType {stringVal :: String, intVal :: Integer}
instance Eq MyType where
      (MyType s1 i1) == (MyType s2 i2) = (s1 == s2) && (i1 == i2)
*Main> MyType "apples" 2 == MyType "apples" 2
True
*Main> MyType "apples" 2 /= MyType "apples" 2
False

In the previous code, we defined the type MyType and then made it an instance of Eq. Further, we said that two values of type MyType are equal (==) if the string of the first value is equal to the string of the second value and the integer of the first value is equal to the integer of the second value. It worked. Observe that you use /= even if you don’t define it in the instance declaration of MyType; this is because Haskell knows how to automatically deduce it from the definition of ==.

Inheritance

All types you define should be an instance of Eq and even Show or Ord. When the definitions of methods of these classes are evident, then you can use deriving, as in Chapter 4. In this way, you avoid having to write complex definitions. But you can derive just some of the standard classes: Eq, Show, Ord, Enum, Bounded, Read.

A class can inherit another class. For example, a brief definition of Ord from Prelude2 is as follows:
class  (Eq a) => Ord a  where
    compare              :: a -> a -> Ordering
    (<), (<=), (>=), (>) :: a -> a -> Bool
    max, min             :: a -> a -> a
The => sign says that Ord inherits Eq, which means that a type that is an instance of Ord is also an instance of Eq. Thus, it must also implement == and /=. A class can inherit multiple classes.
class  (Num a, Ord a) => Real a  where
    -- | the rational equivalent of its real argument with full precision
    toRational          ::  a -> Rational

This definition is from Prelude,3 and the multiple inheritance is marked by the inherited classes in the parentheses.

Figure 10-1 presents the hierarchy of classes in Haskell, taken from the Haskell report4. The names of classes are in bold, and the instances are regular font. In every ellipse, -> means function, and [] means list. The arrows between ellipses show the inheritance relationships, indicating the inheriting class.
../images/475690_1_En_10_Chapter/475690_1_En_10_Fig1_HTML.jpg
Figure 10-1.

Hierarchy of classes in Haskell [4]

You can add constraints to your types, in the following keywords :
  • instance: You declare parametrized types.

  • class: You can add constraints, different from the ones in the class definition, in method signatures.

  • data: This constrains the constructor signatures.

Further, note the following:
  • In our examples, we used three definitions marked by the keywords class, data, and instance. In fact, these are separate, and there isn’t any rule that specifies how you should group them.

  • Classes are not types; they are categories of types, which means that an instance of a class is a type (not a value!).

  • You can’t define an instance of a class from type synonyms defined with the keyword type.

In the examples in this section, you saw that you can test whether two values are equal for different types. For example, you can test whether two integer values are equal, you can test whether two string values are equal, and you can test whether two values of type MyType are equal. So, you can apply the == operator in different types. This behavior is known as overloading (or less commonly known as ad hoc polymorphism ).

Creating Your Own Type Class

Now, let’s create a simple example. You’ll see how to define a data type called Animal, which can be Cat, Dog, or Parrot. Then, you’ll tell Haskell which is equal to which and how to show them.
data Animal = Cat | Dog | Parrot
instance Eq Animal where
  Cat == Cat = True
  Dog == Dog = True
  Parrot == Parrot = True
  _ == _ = False
instance Show Animal where
  show Cat = "In ancient times cats were worshipped as gods; they have not forgotten this."
  show Dog = "It is Human's best friend."
  show Parrot = "It repeats everything you say."
Now let’s test it.
*Main> Cat == Cat
True
*Main> Cat == Parrot
False
*Main> show Cat
"In ancient times cats were worshipped as gods; they have not forgotten this."

Advanced Type Classes

Now, let’s suppose you want to define a class Set , which allows you to add an element to the set and test whether a value is an element of that set. You might proceed as follows:
class Set a where
     add :: a -> b -> a
     isElem :: a -> b -> Bool
instance Set [c] where -- List becomes instance of Set
     add xs x = x:xs
     isElem = flip elem
But this definition is not correct, because type b in the method definitions is unknown, so Haskell will not know from where to take it. A way to correct this is to use type classes with multiple parameters, which allows you to integrate b into the type of the class.
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
class Eq b => Set a b where
  add :: a -> b -> a
  isElem :: a -> b -> Bool
instance Eq c => Set [c] c where
  add = flip (:)
  isElem = flip elem
The previous code uses language extensions. This is marked by the LANGUAGE pragma.
{-# LANGUAGE <Extension>, <Extension> #-}

Language extensions enable features of Haskell that are useful in certain contexts. In this example, we needed multiparameter type classes. In addition, we used flexible instances, which allows a type parameter to occur twice in a type class instance (for Set [c] c).

Still, the definition is not entirely correct because it will lead to ambiguities. Your intuition might tell you that the type of set will provide the type of its elements, so the type of elements depends on the type of the set. For example, if a is [p], then b is p. To make things clear for the compiler, you add another language extension, called functional dependency :
{-# LANGUAGE FunctionalDependencies #-}
 class Eq b => Set a b | a -> b where ...

In the previous definition, | a -> b means “a uniquely identifies b.” In other words, for a given b, it will be just one a.

You can add as many constraints as you want (of course, they need to make sense) to a class definition, and in a multiparameter class you can use more than two parameters.

Maybe, Just, and Nothing

In Prelude, Maybe is a type that has two constructors: Just a or Nothing. In situations in which a type has more constructors, it must be constructed with only one constructor, so Maybe is constructed with Just a (a can be any type) or Nothing. Let’s take a closer look.
  • Nothing: When constructed with Nothing, Maybe is defined as a constant that becomes a member of Maybe a, for all types a, because Nothing doesn’t take a parameter type;.

  • Just a: When constructed with Just, Maybe is used as a type parameter a; in this scenario, Just behaves as a function from a to Maybe a, meaning its type is a-> Maybe a.

When Maybe is used with pattern matching, two patterns are necessary, one for each constructor. Here’s an example:
case maybeExample of
    Nothing -> "This is the Noting constructor."
    Just a -> "This is the Just constructor, with value " ++ (show a)

Maybe is mostly used to extend types with the Nothing value, i.e., the absence of a value. This approach prevents errors. In other programming languages, the “no value” is treated with a NULL reference .

Functor

The type class Functor provides a way to make the operations from a base type work with a new type constructed by transforming the base type into the new one. Functor contains the function fmap, which is used to map the function that takes values from the base type with functions that take values from the new type.

Functor can be combined with Maybe.
case maybeExample of
  Nothing  -> Nothing
  Just a -> Just (f a)

In the first branch, there is no value, so return Nothing; in the second branch, there is the value a, so apply the function f to a.

For example, if you work with a value valueI of type Maybe Integer and a function f that goes from an integer to other integer (in other words, Int -> Int), then you can use fmap f valueI to apply f directly on the Maybe Integer value. When using Maybe, it’s safe to think there is nothing to worry about if it didn’t get a value.

Summary

In this chapter, you learned the following:
  • What standard classes Haskell has and what the class hierarchy looks like

  • How to inherit standard classes

  • How to define your own type classes and how to avoid common mistakes in class definitions

  • What language extensions are and when you can use them

  • What Maybe, Just, and Nothing are and how can you use them with Functor

References

  1. 1.

    A. Serrano Mena. Beginning Haskell: A Project-Based Approach (Apress, 2014)

     
  2. 2.

    B. Heeren and J. Hage, “Type class directives,” International Workshop on Practical Aspects of Declarative Languages (Springer, 2005)

     
  3. 3.

    W. Kahl and J. Scheffczyk. “Named Instances for Haskell Type Classes,” in proceedings of the 2001 Haskell Workshop, UU-CS-2001-23 (Tech. Rep., 2001)

     
  4. 4.
     
  5. 5.
     
  6. 6.
     
  7. 7.
     
  8. 8.
     
  9. 9.
     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.74.154