软糖

Haskell 7 Create new Types and Typeclass

Taking Notes from http://learnyouahaskell.com

Algebraic Data Types

Option 1: data

data ValueName = Value Constructor

1
2
data Bool = False | True
data Shape = Circle Float Float Float | Rectangle Float Float Float Float

Shape is type, but Circle is not.
[]、False 或 5,它们都是不包含参数的值构造子

1
2
ghci> :t Circle
Circle :: Float -> Float -> Float -> Shape

Example

1
2
3
surface :: Shape -> Float
surface (Circle _ _ r) = pi * r ^ 2
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)

deriving

1
2
3
4
5
6
7
8
data Point = Point Float Float deriving (Show)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)
surface :: Shape -> Float
surface (Circle _ r) = pi * r ^ 2
surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
surface (Rectangle (Point 0 0) (Point 100 100))

Export in module

1
2
3
4
5
6
7
8
module Shapes
( Point(..)
, Shape(..)
, surface
, nudge
, baseCircle
, baseRect
) where

.. means exports all of Value Contructors

Options 2: Record Syntax

1
2
3
4
data Car = Car {company :: String, model :: String, year :: Int} deriving (Show)
ghci> Car {company="Ford", model="Mustang", year=1967}
Car {company = "Ford", model = "Mustang", year = 1967}

Options 3: Type parameters

1
data Maybe a = Nothing | Just a
  • a is Type Parameter
  • Maybe is Type Constructor
  • Maybe String is Type

Haskell 中有一个严格的约定,那就是永远不要在 data 声明中添加类型约束

Options 4: Derived instances

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
deriving (Eq, Ord, Show, Read, Bounded, Enum)
-- Show and Red instance
ghci> Wednesday
Wednesday
ghci> show Wednesday
"Wednesday"
ghci> read "Saturday" :: Day
Saturday
-- Eq and Ord instance
ghci> Saturday == Saturday
True
ghci> Saturday > Friday
True
-- Bounded instance
ghci> succ Monday
Tuesday
ghci> pred Saturday
Friday
ghci> [Thursday .. Sunday]
[Thursday,Friday,Saturday,Sunday]
ghci> [minBound .. maxBound] :: [Day]
[Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday]

Options 5: Type Synonyms

type alias

1
2
3
4
5
6
7
type String = [Char]
type AssocList k v = [(k, v)]
// partial type constructor
type IntMap v = Map Int v
type IntMap = Map Int

Recursive data structures

1
2
3
4
data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord)
// Or
data List a = Empty | Cons { listHead :: a, listTail :: List a} deriving (Show, Read, Eq, Ord)
1
2
3
4
5
6
ghci> Empty
Empty
ghci> 5 `Cons` Empty
Cons 5 Empty
ghci> 4 `Cons` (5 `Cons` Empty)
Cons 4 (Cons 5 Empty)

Infix Op

  • 我们可以只用特殊字符来定义函数,这样他们就会自动具有中缀的性质
  • 在 Value Constructor 上也可以使用
1
2
3
4
infixr 5 :-:
data List a = Empty | a :-: (List a) deriving (Show, Read, Eq, Ord)
infixl 7 *

priority * > :-:

Typeclasses

Class and Instance

typeclasses are like interfaces

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
x == y = not (x /= y)
x /= y = not (x == y)
data TrafficLight = Red | Yellow | Green
// TrafficLight is a in class Eq
instance Eq TrafficLight where
Red == Red == True
Green == Green = True
Yellow == Yellow = True
_ == _ == False
instance Show TrafficLight where
show Red = "Red light"
show Yellow = "Yellow light"
show Green = "Green light"
  • class is for defining new typeclasses
  • instance is for making our types instances of typeclasses

Subclass

1
2
class (Eq a) => Num a where
...

(Eq a) is class contraints

1
2
3
4
instance Eq (Maybe m) where
Just x == Just y = x == y
Nothing == Nothing = True
_ == _ = False
  • Maybe is not type, it’s type constructor.
  • Maybe m is a type.
  • so cannot write like this: instance Eq Maybe where
  • (Maybe m) is a for preivous example
1
2
3
4
instance (Eq m) => Eq (Maybe m) where
Just x == Just y = x == y
Nothing == Nothing = True
_ == _ == False

This is required for comparing value inside Maybe.

Example: Yes/No Typeclass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
instance YesNo Int where
yesno 0 = False
yesno _ = True
instance YesNo [a] where
yesno [] = False
yesno _ = True
instance YesNo Bool where
yesno = id
instance YesNo (Maybe a) where
yesno (Just _) = True
yesno Nothing = False
instance YesNo TrafficLight where
yesno Red = False
yesno _ = True

id is function :: a -> a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ghci> yesno $ length []
False
ghci> yesno "haha"
True
ghci> yesno ""
False
ghci> yesno $ Just 0
True
ghci> yesno True
True
ghci> yesno []
False
ghci> yesno [0,0,0]
True
ghci> :t yesno
yesno :: (YesNo a) => a -> Bool

A if statement function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
yesnoIf :: (YesNo y) => y -> a -> a -> a
yesnoIf yesnoVal yesResult noResult =
if yesno yesnoVal then yesResult else noResult
ghci> yesnoIf [] "YEAH!" "NO!"
"NO!"
ghci> yesnoIf [2,3,4] "YEAH!" "NO!"
"YEAH!"
ghci> yesnoIf True "YEAH!" "NO!"
"YEAH!"
ghci> yesnoIf (Just 500) "YEAH!" "NO!"
"YEAH!"
ghci> yesnoIf Nothing "YEAH!" "NO!"
"NO!"

Functor Typeclass

1
2
class Functor f where
fmap :: (a -> b) -> f a -> f b
  • f needs a Type Constructor
  • f is not real type, such as Int Bool and Maybe Int
  • Maybe is a Type Constructor

map is a fmap that works only on Lists

1
2
instance Functor [] where
fmap = map

Warning: we cannot use [a] at here, Remember we need a Type Constructor

1
2
3
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
1
2
3
instance Functor (Either a) where
fmap f (Right x) = Right (f x)
fmap f (Left x) = Left x
  • Either a an instance instead of just Either
  • Either takes two parameter

Kind

  • :k return kind info
  • A * means that the type is a concrete type
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ghci> :k Int
Int :: *
ghci> :k Maybe
Maybe :: * -> *
ghci> :k Maybe Int
Maybe Int :: *
ghci> :k Either
Either :: * -> * -> *
ghci> :k Either String
Either String :: * -> *
ghci> :k Either String Int
Either String Int :: *