ITMO CTD Haskell
Lecture slides on Functional programming course at the ITMO university CT department. You can find course description here: https://github.com/jagajaga/FP-Course-ITMO
data Lecture 3 = type
| class
| instance
Why it's not enough to have only basic types?
userFullId :: (Int, String, String) -> String
userFullId (uid, login, name) = show uid ++ ":" ++ loginghci> userFullId (3, "pumpkin", "Ivan")
"3:pumpkin"
type BinaryIntFunction = Int -> Int -> Int
type String = [Char]
type FilePath = String
type TripleList a = [(a, a, a)]
type SwapPair a b = (a, b) -> (b, a)userFullId :: User -> String
userFullId (uid, login, name) = show uid ++ ":" ++ logintype User = (Int, String, String)struct user {
int uid;
string login;
string pass;
};type User = (Int, String, String)data TrafficLight = Red | Yellow | Green | Blue-- pattern matching with types
lightName :: TrafficLight -> String
lightName Red = "red"
lightName Yellow = "yellow"
lightName Green = "green"
lightName Blue = "magenta"ghci> map lightName [Yellow, Red, Blue, Yellow]
["yellow","red","magenta","yellow"]GHC can warn you if you don't cover all cases in pattern-matching. So compile programs with -Wall!
data User = MkUser Int String String ┌─ type name
│
│ ┌─ constructor name (or constructor tag)
│ │
data User = MkUser Int String String
│ │ │ │
│ └────┴──────┴── types of fields
│
└ "data" keyworddata User = MkUser Int String StringgetUid :: User -> Int
getUid (MkUser uid _ _) = uid -- pattern should be in ()
getName :: User -> String
getName (MkUser _ name _) = nameghci> import Data.List (nub)
ghci> let users = [ MkUser 2 "Ivan" "123"
, MkUser 1 "Mark" "1"
, MkUser 3 "Ivan" "xxx"
]
ghci> nub $ map getName users -- unique names
["Ivan","Mark"]How to create values of type User?
Constructors are just ordinary functions! Only start with uppercase.
ghci> :t MkUser
MkUser :: Int -> String -> String -> Userdata Point2D a = Point2D a a -- constructor name can be the same as type namepointToList :: Point2D a -> [a]
pointToList (Point2D x y) = [x, y]ghci> pointToList (Point2D 5 10)
[5, 10]
ghci> doublePoint (Point2D 'a' 'b')
Point2D ('a', 'b') ('a', 'b')
ghci> maxCoord (Point2D 5 10)
10
ghci> distFromZero (Point2D 5 10)
11.180339887498949doublePoint :: Point2D a -> Point2D (a, a)
doublePoint (Point2D x y) = Point2D (x, y) (x, y) maxCoord :: Point2D Int -> Int
maxCoord (Point2D x y) = max x ydistFromZero :: Point2D Double -> Double
distFromZero (Point2D x y) = sqrt (x^2 + y^2)ghci> :t Point2D -- remeber, constructors are just functions
Point2D :: a -> a -> Point2D adata IntResult = Success Int
| Failure StringsafeDiv :: Int -> Int -> IntResult
safeDiv _ 0 = Failure "division by zero"
safeDiv x y = Success $ x `div` yghci> showResult $ safeDiv 7 2
"Result: 3"
ghci> showResult $ safeDiv 7 0
"Error: division by zero"showResult :: IntResult -> String
showResult (Success n) = "Result: " ++ show n
showResult (Failure e) = "Error: " ++ eghci> :t Success
Success :: Int -> IntResult
ghci> :t Failure
Failure :: String -> IntResult
data Vector a = Vector2D a a | Vector3D a a apackVector :: Vector a -> [a]
packVector (Vector2D x y) = [x, y]
packVector (Vector3D x y z) = [x, y, z]vecLen :: Vector Double -> Double
vecLen = sqrt . sum . map (^2) . packVectorghci> maximum $ map vecLen [Vector3D 1.1 2.2 4.5, Vector2D 3 4]
5.12835256198ghci> sortOn vecLen [Vector3D 1.1 2.2 4.5, Vector2D 3 4]
[Vector2D 3.0 4.0, Vector3D 1.1 2.2 4.5]ghci> :t Vector2D
Vector2D :: a -> a -> Vector a
ghci> :t Vector3D
Vector3D :: a -> a -> a -> Vector adata Maybe a = Nothing | Just a -- implemented in PreludemaybeSecond :: [a] -> Maybe a
maybeSecond (_:x:_) = Just x
maybeSecond _ = Nothingghci> :t Nothing
Nothing :: Maybe a
ghci> :t Just
Just :: a -> Maybe a
data Either a b = Left a | Right b -- implemented in PreludeeitherSecond :: [a] -> Either String a
eitherSecond [] = Left "list is empty"
eitherSecond [_] = Left "list has only single element"
eitherSecond (_:x:_) = Right xghci> :t Left
Left :: a -> Either a b
ghci> :t Right
Right :: b -> Either a b
data List a = Nil | Cons a (List a)myList :: List Int
myList = Cons 2 (Cons 1 (Cons 3 Nil))myMap :: (a -> b) -> List a -> List b
myMap _ Nil = Nil
myMap f (Cons x xs) = Cons (f x) (myMap f xs) ghci> myMap (`div` 2) myList
Cons 1 (Cons 0 (Cons 1 Nil))data [] a = [] | a : [a]ghci> :t Nil
Nil :: List a
ghci> :t Cons
Cons :: a -> List a -> List adata User = User
{ uid :: Int
, login :: String
, password :: String
}data User = User Int String String
uid :: User -> Int
uid (User i _ _) = i
login :: User -> String
login (User _ l _) = l
password :: User -> String
password (User _ _ p) = pivan :: User
ivan = User { login = "Ivan"
, password = "123"
, uid = 1
}isIvan :: User -> Bool
isIvan user = login user == "Ivan"isIvan :: User -> Bool
isIvan User{ login = userName } = userName == "Ivan"isIvan :: User -> Bool
isIvan User{ login = "Ivan" } = True
isIvan _ = FalsecloneIvan :: User
cloneIvan = ivan { uid = 2 } -- User 2 "Ivan" "123"ghci> data R = R { (-->) :: Int -> Int }
ghci> let r = R { (-->) = (+1) }
ghci> r --> 8
9
data Person
= User { uid :: Int, login :: String }
| Admin { aid :: Int, login :: String }login :: Person -> String -- after desugaring
login (User _ l) = l
login (Admin _ l) = lghci> uid $ Admin 0 "Vasya"
*** Exception: No match in record selector uidisAdmin :: Person -> Bool -- To match just the type of the construction
isAdmin Admin{} = True -- works even without records
isAdmin _ = FalseConclusion: records with sum types are not safe
data Man = Man { name :: String }
data Cat = Cat { name :: String }Record syntax restriction
Possible in GHC 8 with -XDuplicateRecordFields (not mature)
name :: ???Current production solution: use different names + libraries
data Man = Man { manName :: String }
data Cat = Cat { catName :: String }{-# LANGUAGE DuplicateRecordFields #-}
data Man = Man { name :: String }
data Cat = Cat { name :: String }
shoutOnHumanBeing :: Man -> String
shoutOnHumanBeing man = (name :: Man -> String) man ++ "!!1!" -- though...
isGrumpy :: Cat -> Bool
isGrumpy Cat{ name = "Grumpy" } = True
isGrumpy _ = False{-# LANGUAGE RecordWildCards #-}
data User = User
{ uid :: Int
, login :: String
, password :: String
} deriving (Show)
toUnsafeString :: User -> String
toUnsafeString User{ uid = 0, .. } = "ROOT: " ++ login ++ ", " ++ password
toUnsafeString User{..} = login ++ ":" ++ passwordFields are functions but with RWC you can treat them as values
Works with with DuplicateRecordFields!
evilMagic :: Man -> Cat
evilMagic Man{..} = Cat{..}ghci> evilMagic $ Man "Grumpy"
Cat {name = "Grumpy"}data Message = Message Stringnewtype Message = Message StringIf data type has only one constructor with only one field then it can be defined as newtype, which has more efficient runtime representation.
-- public key from secret key
derivePublicKey :: String -> String
checkKeyPair :: (String, String) -> Bool
checkKeyPair (secretKey, publicKey)
= publicKey == derivePublicKey secretKeyderivePublicKey :: SecretKey -> PublicKey
checkKeyPair :: (SecretKey, PublicKey) -> Bool
checkKeyPair (secretKey, publicKey) = publicKey == derivePublicKey secretKeynewtype PublicKey = PublicKey String
newtype SecretKey = SecretKey Stringcompile time guarantees + runtime performance = ❤
class Printable p where -- we don't care what 'p' stores internally
printMe :: p -> String
helloP :: Printable p => p -> String
helloP p = "Hello, " ++ printMe p ++ "!"For now you can think of type classes as of interfaces
data Foo = Foo | Bar -- don't care what we can do with 'Foo', care what it storesinstance Printable Foo where
printMe Foo = "Foo"
printMe Bar = "Bar (whatever)"In Haskell data and functions to work with data are separated.
data answers question: What does it store?
class answers question: What can we do with this data?
Connection between data and class — instance keyword
Polymorphic function to work with Printable
ghci> helloP Bar
"Hello, Bar (whatever)!"
ghci> helloP True
• No instance for (Printable Bool) arising from a use of ‘helloP’
• In the expression: helloP Trueclass Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
x == y = not (x /= y)
x /= y = not (x == y)
{-# MINIMAL (==) | (/=) #-} -- minimal complete definition{-# LANGUAGE InstanceSigs #-}
data TrafficLight = Red | Yellow | Green
instance Eq TrafficLight where
(==) :: TrafficLight -> TrafficLight -> Bool
Red == Red = True
Green == Green = True
Yellow == Yellow = True
_ == _ = FalsethreeSame :: Eq a => a -> a -> a -> Bool
threeSame x y z = x == y && y == zghci> threeSame Red Red Red
True
ghci> threeSame 'a' 'b' 'b'
FalseIt's suggested to use -XInstanceSigs to specify types of methods
-- simplified version of Ord class
class Eq a => Ord a where
compare :: a -> a -> Ordering
(<), (<=), (>=), (>) :: a -> a -> Bool
compare x y
| x == y = EQ
| x <= y = LT
| otherwise = GT
x <= y = compare x y /= GT
x < y = compare x y == LT
x >= y = compare x y /= LT
x > y = compare x y == GTdata Ordering = LT | EQ | GT-- | Basic numeric class.
class Num a where
{-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
(+), (-), (*) :: a -> a -> a -- self-explained
negate :: a -> a -- unary negation
abs :: a -> a -- absolute value
signum :: a -> a -- sign of number, abs x * signum x == x
fromInteger :: Integer -> a -- used for numeral literals polymorphism
x - y = x + negate y
negate x = 0 - xWhen you write something like 7 it's just a syntax sugar for
fromInteger 7. That's why numeric constants are polymorphic.
ghci> :t 5
5 :: Num p => p
ghci> :t fromInteger 5
fromInteger 5 :: Num a => a
ghci> 5 :: Int
5
ghci> 5 :: Double
5.0-- simplified version; used for converting things into String
class Show a where
show :: a -> StringShow is used (for example) when values are printed in GHCi
ghci> 5
5
ghci> show 5
"5"
ghci> "5"
"5"
ghci> show "5"
""5""
ghci> 5 :: Int
5
ghci> 5 :: Double
5.0
ghci> 5 :: Rational
5 % 1
Showing different numeric values
-- simplified version; used for parsing thigs from String
class Read a where
read :: String -> aUse Read when you need to parse String. Though be careful.
ghci> :t read
read :: Read a => String -> a
ghci> read "True"
*** Exception: Prelude.read: no parse
ghci> read "True" :: Bool
Trueghci> :module Text.Read -- safe read functions are not in Prelude, unfortunatelyread throws runtime exception. Use readMaybe/readEither .
ghci> :t readMaybe
readMaybe :: Read a => String -> Maybe a
ghci> :t readEither
readEither :: Read a => String -> Either String a
ghci> readMaybe "5" :: Maybe Int
Just 5
ghci> readMaybe "5" :: Maybe Bool
Nothing
ghci> readEither "5" :: Either String Bool -- don't worry, convenient way exist
Left "Prelude.read: no parse"subtract :: Num a => a -> a -> a
subtract x y = y - xcmpSum x y = if x < y then x + y else x * y
average :: Fractional a => a -> a -> a
average x y = (x + y) / 2ghci> :info Fractional
class Num a => Fractional a where
(/) :: a -> a -> a
recip :: a -> a
fromRational :: Rational -> a
{-# MINIMAL fromRational, (recip | (/)) #-}
-- Defined in ‘GHC.Real’
instance Fractional Float -- Defined in ‘GHC.Float’
instance Fractional Double -- Defined in ‘GHC.Float’
ghci> cmpSum x y = if x < y then x + y else x * y
ghci> :t cmpSum
cmpSum :: (Ord a, Num a) => a -> a -> aWhat is the most general type of this function?
GHCi can help with this!
foo :: (Ord a, Read a, Show b) => String -> a -> b -> b -> String
foo = undefined -- too difficult to implementundefined: write tomorrow, typecheck today!
foo :: (Ord a, Read a, Show b) => String -> a -> b -> b -> String
foo = error "Function `foo` crashes your code, don't call it!" ghci> :t undefined
undefined :: a
ghci> undefined
*** Exception: Prelude.undefined
CallStack (from HasCallStack):
error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
undefined, called at <interactive>:44:1 in interactive:Ghci27ghci> :t error
error :: [Char] -> a
ghci> error "Some meaningful message"
*** Exception: Some meaningful message
CallStack (from HasCallStack):
error, called at <interactive>:46:1 in interactive:Ghci27undefined and error can exist on prototyping stage. But, please, try not to use these functions.
data TrafficLight = Red | Yellow | Green | Blue
deriving Eq -- autoderiving instancesdata TrafficLight = Red | Yellow | Green | Blue
deriving (Eq, Ord, Enum, Bounded, Show, Read, Ix)ghci> :t maxBound
maxBound :: Bounded a => a
ghci> maxBound :: TrafficLight -- Bounded also has 'minBound'
Blue
ghci> [Yello .. maxBound] -- .. is from Enum instance
[Yellow, Green, Blue] ghci> show Blue
"Blue"
ghci> read "Blue" :: TrafficLight
Blueghci> Red == Yellow -- (==) is from Eq class
False
ghci> Red < Yellow -- (<) is from Ord class
Trueghci> :t fromEnum
fromEnum :: Enum a => a -> Int
ghci> :t toEnum
toEnum :: Enum a => Int -> a
ghci> fromEnum Green
2
ghci> toEnum 2 :: TrafficLight
Greendata FunBox = FB (Int -> String) -- remember? functions are first class values
deriving (Eq, Ord, Enum, Bounded, Show, Read, Ix) -- what can we derive?What we can derive for data types which store functions?
newtype Size = Size Int
deriving (Show, Read, Eq, Ord, Num){-# LANGUAGE GeneralizedNewtypeDeriving #-}module Lib
( module Exports
, FooB1 (..), FooB3 (FF)
, Data.List.nub, C.isUpper
, fooA, bazA, BAZB.isLower
) where
import Foo.A
import Foo.B (FooB2 (MkB1),
FooB3 (..))
import Prelude hiding (print)
import Bar.A (print, (<||>))
import Bar.B ()
import Baz.A as BAZA
import qualified Data.List
import qualified Data.Char as C hiding (chr)
import qualified Baz.B as BAZB (isLower)
import qualified Foo.X as Exports
import qualified Foo.Y as Exportsmodule Foo.A where fooA = 3module Foo.B
( FooB1, FooB2 (..),
FooB3 (FF, val)
) where
data FooB1 = MkFooB1
data FooB2 = MkB1 | MkB2
data FooB3 = FF { val :: Int }module Baz.B (C.isLower) where
import Data.Char as Cmodule Bar.B () where
class Printable p where
printMe :: p -> String
instance Printable Int where
printMe = showmodule Baz.A (bazA) where bazA = mapdata Doctor who = Tardis who who | Dalek InttimeTravel :: a -> a -> String
timeTravel _ _ = "Travel through time and space!"
exterminate :: Int -> String
exterminate n = unwords $ replicate n "Exterminate!"
travel :: Doctor who -> String
travel (Tardis a b) = timeTravel a b
travel (Dalek x) = exterminate xghci> travel (Tardis 0 0)
"Travel through time and space!"
ghci> travel (Dalek 3)
"Exterminate! Exterminate! Exterminate!"data Doctor who = Tardis who who
| Dalek Intghci> :t Tardis
Tardis :: who -> who -> Doctor who
ghci> :t Dalek
Dalek :: Int -> Doctor whof_Tardis :: who -> who -> (who -> who -> r) -> (Int -> r) -> r
f_Tardis a b = \tardis _dalek -> tardis a bf_Dalek :: Int -> (who -> who -> r) -> (Int -> r) -> r
f_Dalek x = \_tardis dalek -> dalek xf_travel :: ((who -> who -> String) -> (Int -> String) -> String) -> String
f_travel pattern = pattern timeTravel exterminateghci> f_travel (f_Tardis 0 0)
"Travel through time and space!"
ghci> f_travel (f_Dalek 3)
"Exterminate! Exterminate! Exterminate!"type Doctor_f who r = (who -> who -> r) -> (Int -> r) -> rf_Tardis :: who -> who -> Doctor_f who r
f_Tardis a b = \tardis _dalek -> tardis a b
f_Dalek :: Int -> Doctor_f who r
f_Dalek x = \_tardis dalek -> dalek xf_travel :: Doctor_f who String -> String
f_travel pattern = pattern timeTravel exterminateThis can be done better with sophisticated language extensions
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
x == y = not (x /= y)
x /= y = not (x == y)data EqC a = EqDict
{ eq :: a -> a -> Bool
, neq :: a -> a -> Bool
}instanceEqCWithEq :: (a -> a -> Bool) -> EqC a
instanceEqCWithEq myEq = EqDict
{ eq = myEq
, neq = \x y -> not $ x `myEq` y }
instanceEqCWithNeq :: (a -> a -> Bool) -> EqC a
instanceEqCWithNeq myNeq = EqDict
{ eq = \x y -> not $ x `myNeq` y
, neq = myNeq }isInList :: EqC a -> a -> [a] -> Bool
isInList eqc x = any (eq eqc x)ghci> isInList (instanceEqCWithEq (==)) 3 [2, 1, 3]
TrueBy ITMO CTD Haskell
Lecture about type aliases, algebraic data types, record syntax, type classes and module system .
Lecture slides on Functional programming course at the ITMO university CT department. You can find course description here: https://github.com/jagajaga/FP-Course-ITMO