Sunday, November 20, 2011

LYAH Chapter 3 - Types and Typeclasses


Chapter 3: Types and Typeclasses

Explicit Type Declaration
Common Haskell Types
Type Variables
Type Classes 101
The Eq Type Class
The Ord Type Class
The Show Type Class
The Read Type Class
The Enum Type Class
The Bounded Type Class.
The Num Type Class
The Floating Type Class
The Integral Type Class
Some Final Notes on Type Classes


Believe the type

A type is a kind of label/category that every expression has.  True is a boolean, "hello" is a string, etc.
Functions also have types. When writing our own functions, we can choose to give them an explicit type declaration. This is generally considered to be good practice except when writing very short functions.
Common types.

  • Int stands for integer. Int is bounded, which means that it has a minimum and a maximum value. Usually on 32-bit machines the maximum possible Int is 2147483647 and the minimum is -2147483648.
  • Integer stands for, er … also integer. The main difference is that it's not bounded so it can be used to represent really really big numbers. I mean like really big. Int, however, is more efficient.
    • factorial :: Integer -> Integer  
    • factorial n = product [1..n]  
    • ghci> factorial 50  --> 30414093201713378043612608166064768844377641568960512000000000000 
  •  Float is a real floating point with single precision.
    • circumference :: Float -> Float  
    • circumference r = 2 * pi * r  
    • ghci> circumference 4.0  --> 25.132742  
  • Double is a real floating point with double the precision!
    • circumference' :: Double -> Double  
    • circumference' r = 2 * pi * r  
    • ghci> circumference' 4.0  --> 25.132741228718345  
  • Bool is a boolean type. It can have only two values: True and False.
  • Char represents a character. It's denoted by single quotes. A list of characters is a string.
  • Tuples are types but they are dependent on their length as well as the types of their components, so there is theoretically an infinite number of tuple types, which is too many to cover in this tutorial. 
  • Note that the empty tuple () is also a type which can only have a single value: ()

Type variables

  • Types are written in capital case, type variable start with a lowercase
  • ghci> :t head  -->  head :: [a] -> a 
    • a can be of any type. 
    • This is much like generics in other languages, only in Haskell it's much more powerful because it allows us to easily write very general functions if they don't use any specific behavior of the types in them. 
    • Functions that have type variables are called polymorphic functions
    • The type declaration of head states that it takes a list of any type and returns one element of that type.
    • Although type variables can have names longer than one character, we usually give them names of a, b, c, d …
  • ghci> :t fst  --> fst :: (a, b) -> a   
    • We can use fst on a pair that contains any two types. 
    • Just because a and b are different type variables, they don't have to be different types. 

Typeclasses 101

  • A typeclass is a sort of interface that defines some behavior. 
  • If a type is a part of a typeclass, that means that it supports and implements the behavior the typeclass describes. 
  • A lot of people coming from OOP get confused by typeclasses because they think they are like classes in object oriented languages. Well, they're not. 
  • You can think of them kind of as Java interfaces, only better.
  • Eq
    • ghci> :t (==)  -->  (==) :: (Eq a) => a -> a -> Bool  
    • Everything before the => symbol is called a class constraint
    • "The equality function takes any two values that are of the same type and returns a Bool. The type of those two values must be a member of the Eq class (class constraint)."
    • The Eq typeclass provides an interface for testing for equality. Any type where it makes sense to test for equality between two values of that type should be a member of the Eq class. 
    • All standard Haskell types except for IO (the type for dealing with input and output) and functions are a part of the Eq typeclass.
    • The elem function has a type of (Eq a) => a -> [a] -> Bool because it uses == over a list to check whether some value we're looking for is in it.
  • If a function is comprised only of special characters, it's considered an infix function by default 
Basic Typeclasses
  • Eq is used for types that support equality testing. 
    • The functions its members implement are == and /=
    • So if there's an Eq class constraint for a type variable in a function, it uses == or /= somewhere inside its definition. 
    • All the types we mentioned previously except for functions are part of Eq, so they can be tested for equality.
    • class Eq a where
        (==) :: a -> a -> Bool
  • Ord is for types that have an ordering.
    • ghci> :t (>)  -->  (>) :: (Ord a) => a -> a -> Bool  
    • All the types we covered so far except for functions are part of OrdOrd covers all the standard comparing functions such as >,<>= and <=. The compare function takes two Ord members of the same type and returns an ordering. Ordering is a type that can be GTLT or EQ, meaning greater thanlesser than and equal, respectively.
    • To be a member of Ord, a type must first have membership in the prestigious and exclusive Eq club.   class Eq a => Ord a
      • ghci> "Abrakadabra" < "Zebra"  -->  True  
      • ghci> "Abrakadabra" `compare` "Zebra"  -->  LT  
  • Show  Members of can be presented as strings.
    • All types covered so far except for functions are a part of Show
    • The most used function that deals with the Show typeclass is show
    • It takes a value whose type is a member of Show and presents it to us as a string.
      • ghci> show 3  --> "3"  
      • ghci> show 5.334  --> "5.334"  
      • ghci> show True  --> "True"  
  • Read is sort of the opposite typeclass of Show. The read function takes a string and returns a type which is a member of Read.
    • ghci> read "True" || False  -->  True  
    • ghci> read "8.2" + 3.8  -->  12.0  
    • ghci> read "5" - 2  -->  3  
    • ghci> read "[1,2,3,4]" ++ [3]  -->  [1,2,3,4,3]  
    • So far so good. Again, all types covered so far are in this typeclass. But what happens if we try to do just read "4"?
      • ghci> read "4"  
      • <interactive>:1:0:  
      •     Ambiguous type variable `a' in the constraint:  
      •       `Read a' arising from a use of `read' at <interactive>:1:0-7
      • Probable fix: add a type signature that fixes these type variable(s)  
    • What GHCI is telling us here is that it doesn't know what we want in return. Notice that in the previous uses of read we did something with the result afterwards. That way, GHCI could infer what kind of result we wanted out of our read. If we used it as a boolean, it knew it had to return a Bool. But now, it knows we want some type that is part of the Read class, it just doesn't know which one. Let's take a look at the type signature of read.
      • ghci> :t read  -->  read :: (Read a) => String -> a  
    • See? It returns a type that's part of Read but if we don't try to use it in some way later, it has no way of knowing which type. That's why we can use explicit type annotations. Type annotations are a way of explicitly saying what the type of an expression should be. We do that by adding :: at the end of the expression and then specifying a type. 
      • ghci> read "5" :: Int  --> 5  
      • ghci> read "5" :: Float  --> 5.0  
      • ghci> (read "5" :: Float) * 4  --> 20.0  
      • ghci> read "[1,2,3,4]" :: [Int]  --> [1,2,3,4]  
      • ghci> read "(3, 'a')" :: (IntChar) --> (3'a')  
    • Most expressions are such that the compiler can infer what their type is by itself. But sometimes, the compiler doesn't know whether to return a value of type Int or Float for an expression like read "5". To see what the type is, Haskell would have to actually evaluate read "5". But since Haskell is a statically typed language, it has to know all the types before the code is compiled (or in the case of GHCI, evaluated). So we have to tell Haskell: "Hey, this expression should have this type, in case you don't know!".
  • Enum members are sequentially ordered types — they can be enumerated. 
    • The main advantage of the Enum typeclass is that we can use its types in list ranges. 
    • They also have defined successors and predecesors, which you can get with the succ and predfunctions. 
    • Types in this class: ()BoolCharOrderingIntIntegerFloat and Double.
    • ghci> ['a'..'e']  --> "abcde"  
    • ghci> [LT .. GT]  --> [LT,EQ,GT]  
    • ghci> [3 .. 5]  -->  [3,4,5]  
    • ghci> succ 'B'  -->  'C'  
  • Bounded members have an upper and a lower bound.
    • ghci> minBound :: Int  -->  -2147483648  
    • ghci> maxBound :: Char  -->  '\1114111'  
    • ghci> maxBound :: Bool  -->  True  
    • ghci> minBound :: Bool  -->  False  
    • minBound and maxBound are interesting because they have a type of (Bounded a) => a. In a sense they are polymorphic constants.
    • All tuples are also part of Bounded if the components are also in it.
      • ghci> maxBound :: (BoolIntChar)  -->  (True,2147483647,'\1114111'
  • Num is a numeric typeclass. Its members have the property of being able to act like numbers. 
    • ghci> :t 20  -->  20 :: (Num t) => t  
    • It appears that whole numbers are also polymorphic constants. They can act like any type that's a member of the Num typeclass.
      • ghci> 20 :: Int  -->  20  
      • ghci> 20 :: Integer  -->  20  
      • ghci> 20 :: Float   -->   20.0  
      • ghci> 20 :: Double   -->  20.0  
    • Those are types that are in the Num typeclass. If we examine the type of *, we'll see that it accepts all numbers.
      • ghci> :t (*)   -->   (*) :: (Num a) => a -> a -> a  
    • It takes two numbers of the same type and returns a number of that type. That's why (5 :: Int) * (6 :: Integer) will result in a type error whereas 5 * (6 :: Integer) will work just fine and produce an Integer because 5 can act like an Integer or anInt.
    • To join Num, a type must already be friends with Show and Eq.
  • Integral is also a numeric typeclass. Num includes all numbers, including real numbers and integral numbers, Integralincludes only integral (whole) numbers. In this typeclass are Int and Integer.
  • Floating includes only floating point numbers, so Float and Double.
  • fromIntegral A very useful function for dealing with numbers
    • It has a type declaration offromIntegral :: (Num b, Integral a) => a -> b
    • From its type signature we see that it takes an integral number and turns it into a more general number. 
      • That's useful when you want integral and floating point types to work together nicely. 
      • For instance, thelength function has a type declaration of length :: [a] -> Int instead of having a more general type of(Num b) => length :: [a] -> b
      • I think that's there for historical reasons or something, although in my opinion, it's pretty stupid. 
    • If we try to get a length of a list and then add it to 3.2, we'll get an error because we tried to add together an Int and a floating point number. So to get around this, we do fromIntegral (length [1,2,3,4]) + 3.2 and it all works out.


Topics Introduced

  • Types
  • Type Variables
  • Type Classes
  • Class constraints
  • Type annotations
  • "Polymorphic constants"



Type summary
·         Int on 32-bit machines the maximum possible Int is 2147483647 and the minimum is -2147483648.
·         Integer not bounded, Int is more efficient.
·         Float is a real floating point with single precision.
·         Double is a real floating point with double the precision
·         Bool is a boolean type. It can have only two values: True and False.
·         Char represents a character. It's denoted by single quotes. A list of characters is a string.
·         Tuples - infinite number of tuple types. The empty tuple () is also a type which can only have a single value: ()

Typeclass function summary

  • Eq 
    • == and /=
  • Ord 
    • >, <, >= and <=
    • compare, max, min 
  • Show 
    • show, showList, showsPrec
  • Read 
    • read, readList, readsPrec
  • Enum 
    • succ, pred
    • can use its types in list ranges
  • Bounded 
    • minBound and maxBound 
  • Num 
    • (+), (*), (-), negate, abs, signum, fromInteger
  • Integral includes only integral (whole) numbers. 
    • In this typeclass are Int and Integer.
  • Floating includes only floating point numbers, so Float and Double.
  • fromIntegral()


Questions

  • Why is succ 1.1 == 2.1 ??
  • Why is pred 1.1 == 0.10000000000000009 ??
  • If you do 5 * (6 :: Integer) Haskell infers the type of 5 to be Integer... how does Haskell convert the 5 to Integer?   Using (5 :: Integer) ??
  • What can you do with :: ?
  • How do you create a polymorphic constant like Bounded or 2::Int ??
  • How does a type become part of a typeclass?   instance, deriving

Explain the following:
  • class -- defines a type class
  • data -- defines an algebraic data type
  • instance -- make a datatype part of a typeclass, defining your own behaviour
  • deriving  -- make a datatype part of a typeclass, deriving default behaviour based on constituent types




No comments:

Post a Comment