Type System in Haskell
The type system seems to really magical if you are newbie in Haskell. Haskell is a static typed language that works like dynamic. You can ignore type declaration and let the type inference system do it for you. For example,
Prelude> let f a b = a + b Prelude> f 1 2 Prelude> 3 Prelude> :t f Prelude> f :: Num a => a -> a -> a
what we do here is defining a function
f that takes two parameters
b and return their sum. We have not declared what are the types of a and b yet, but it works fine!
We can use
:t to see the type of f, wow, the type inference system predict the type of function parameters for us. It says that function f takes two parameters a, whose type is
Num and return another Number a, really smart! Note that
Num here is a type class(different from type actually, we will cover later) in Haskell that represent number like int 1 or float 1.0, etc.
But what if we provide two string instead of number?
Prelude> f "ab" "bb" <interactive>:17:1: No instance for (Num [Char]) arising from a use of ‘f’ In the expression: f "ab" "bb" In an equation for ‘it’: it = f "ab" "bb"
Ops, an error occurs, saying that the function parameters are not in type
Num, instead, they are type
[Char](string is just an array of char, like in C++).
We have seen types in above examples, string likes "ab" has type
[Char]. Lets see more examples:
Prelude> :t 1 1 :: Num a => a Prelude> :t 1.0 1.0 :: Fractional a => a Prelude> :t 'a' 'a' :: Char
We can see that 1 has type
Num a, 1.0 has type
Fractional a but 'a' has type
Char. You may notice that there is an
a variable in type
Num a and
Fractional a, what is the
a here? It seems more reasonable if 1 has type
Num and 1.0 has type
Fractional. But actually,
Fractional are type classes which we will see later instead of type like
Char. Types in Haskell can be an instance of type classes, the
a variable in
Num a is a type and it declares that type
a is an instance of type class
Num. That's enough for now, we will go detail for type classes later.
Create Custom Types
Create type in Haskell is easy, but there is two concept we need to know first.
data Yesorno = Yes | No isYes :: Yesorno -> Bool isYes Yes = True isYes No = False
Here, we use the keyword
data to declare a new type
No on the right is called value constructors in Haskell. To define a type, we need to tell Haskell what are the available values. In the Yesorno example, there are only two values, Yes or No.
In the second line, we declare the type of function
isYes , which take a
Yesorno type parameter and return a
Bool type value.(
:: is used to declare type in Haskell, indeed, we can omit the type declaration here because Haskell's smart type inference system will do that for us, but it is always clear to declare ourself)
The next two lines show another concept in Haskell called
pattern match. We define the function by defining all the input cases(the good news is we only have two cases here...). If the input parameter is Yes, we return True, if No, we return False. That's all for the Yesorno function. Below shows the function usage.
*Main> isYes Yes True *Main> isYes No False
Sometimes we do not need to create new type, suppose we write a program that use the Map from int to string a lot. Note that Map is a type constructor that take two parameters, for example,
Map a b defines a type map that map type a to type b. So every time we need to use this kind of map, we need to write
Map Int String, a little bit inefficient, right? Luckily, we can use type synonym in Haskell.
import Data.Map type MapIntString = Map Int String
What we do here is declaring a synonym called
MapIntString for type
Map Int String, after that, we can use
MapIntString instead of
Map Int String in later programs, really helpful!
We have meet type classes above, for example,
Fractional. Below is the definition of
class Num a where (+) :: a -> a -> a (*) :: a -> a -> a (-) :: a -> a -> a negate :: a -> a abs :: a -> a signum :: a -> a fromInteger :: Integer -> a
Type class is defined using
class keyword, then the type class name Num, following a type variable
a(not need to be a, can be b or c...). In addition, it declare some basic functions such as plus, multiply, minus operations, which should be implemented by type.
To declare that a type is an instance of a type class, we can use the keyword
instance like below:
instance Num Int
Int is declared to be an instance of type class
Num. The purpose of type class is to abstract some behaviours as interface. If a type is an instance of type class, it need to confirm with the interface. In the case above,
Int need to implement functions declared in
Num, so that variable of type
Int like 1, 2, 3 can support plus, multiply and minus operations.
There are some others type classes in Haskell, such as
Show is used to turn a value to string,
Eq is used to compare values. To make
Yesorno type an instance of these type classes, we can also use the keyword
data Yesorno = Yes | No deriving (Show, Eq) Prelude> Yes == No False Prelude> Yes Yes
If you are familiar with Python, you can think type class in Haskell is abstract base class(ABC) in Python, type in Haskell is just class in Python.
from abc import ABCMeta class A: __metaclass__ = ABCMeta @abstractmethod def my_abstract_method(self): pass class B(A): def my_abstract_method(self): return 'class B'
We can see that the method
my_abstract_method in class A is an abstractmethod, which need to be implemented if class B inherit class A. So we can use ABC as an interface, just like that in Haskell.