0% found this document useful (0 votes)
38 views

Haskell - Types & Functions

Basic of language Haskell

Uploaded by

Kallan
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
38 views

Haskell - Types & Functions

Basic of language Haskell

Uploaded by

Kallan
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 9

5/19

Haskell: Types & Functions


Functions and Operators
We have two ways to express calculations:
[in x] operators: 4 + 2, a && b div returns int, round down
function calls: div 8 3, take 2 [1,2,3,4,5], someFunc a b c

They can be interchanged: (+) and (&&) are functions; `div` and `take` and `someFunc`
are operators. i.e. these are equivalent: ` : 和 ~ 一个键的, 不是单引号
Operator Function
“+” here is a function True, False
4 + 2 (+) 4 2
a && b (&&) a b true or false is wrong spelling
&& logic and
8 `div` 3 div 8 3
2 `take` [1,2,3,4,5] take 2 [1,2,3,4,5] give me the first 2 things in this list, output is [1,2]
(a `someFunc` b) c someFunc a b c

Sometimes code can be more readable if you swap functions and operators: I often write “a
`div` 2” for symmetry with “a / 2”. "/" return float
Sometimes it's necessary to have a function, even though you have an operation de ned as
an operator…

5/21 Parts of Lists


There are many ways to dissect lists in Haskell.
head/tail: the rst/rest of the list (but consider a cons pattern, which might be more
readable).
Expression Result
head [8,9,10,11] 8 head gives the 1st element of the list
tail [8,9,10,11] [9,10,11] tail gives the list not including the 1st element
head [] error
tail [] error

take/drop: get/throw away the rst elements from a list. output is a list
Expression Result
take 3 [10,20,30,40,50] [10,20,30] take the first 3 elements
take 1 "Hello" "H" (not 'H')
take 3 [1,2] [1,2]
drop 3 [10,20,30,40,50] [40,50] drop the first 3 elements
drop 3 [1,2] []

takeWhile/dropWhile: take/drop while a condition is true.


splitAt: chop a list in two at a speci c position.

Expression Result
takeWhile even [2,4,6,7,10] [2,4,6] 到第一个condition是false的element就停了
Expression Result
takeWhile even [2,4,6,7,10,12] [2,4,6]
dropWhile even [2,4,6,7,10,12] [7,10,12]
splitAt 2 [10,20,30,40,50] ([10,20],[30,40,50])
splitAt 4 [10,20,30,40,50] ([10,20,30,40],[50])

More List Processing


Processing lists so far: list comprehensions and recursion on lists. There are more built-in
tools that are worth mentioning…
map: apply a function to each element in a list. an implementation of gcd:
Expression Result myGcd 0 b = b
map succ [1,42,8] [2,43,9] myGcd a 0 = a
map sqrt [4,25,2] [2.0,5.0,1.414…] myGcd a b = myGcd b (mod a b)
map hailstone (divisors 30) [1,10,16,3,5,46]
map (gcd 10) [3,4,5,6] [1,2,5,2] gcd: greatest common divider, 最大公约数
do gcd 10 3 , gcd 10 4 , .....
gcd is a built-in function in Haskell
filter: Take only elements of a list that meet some condition.

Expression Result even and isPrime are functions that can take an element in the
filter even [1..8] [2,4,6,8] list and return T or F. filter needs a function like even and
filter isPrime [2..10] [2,3,5,7]
isPrime to work
We could have de ned divisors with filter as:
divisors n = filter (divides n) [2..(n `div` 2)] .hs
where divides a b = (a `mod` b) == 0

Both map and filter duplicate things we can do with list comprehensions (or recursion): use
whichever is easier to read in the speci c situation.
… but not foldr: used to apply a function across a list.
[Compare the reduce function/method in Python, Ruby, Scala, JavaScript, Spark.]

An example: sum the values in a list. We could to it recursively:


mySum [] = 0 .hs
mySum (x:xs) = x + mySum xs

Important points: the operation is (+) and the starting point (result for []) is 0. Then, an
equivalent fold:
mySum' xs = foldl (+) 0 xs .hs

0: answer is 0 if the list is empty


If we call this on [1,2,3], it expands to:
foldl (+) 0 [1,2,3]
== (+) ((+) ((+) 0 1) 2) 3 foldl, l for left
== ((0 + 1) + 2) + 3
从base case 始,从左往右过list

The arguments to the fold: Haskell wiki: Fold; Learn


2 argument function / operation You A Haskell: folds
1. the operation: function that combines the accumulator and an
element. function can be self-defined
2. the zero: correct result for an empty list, and where to start the accumulator.
not technically 0, should be the appropriate result if list is empty
eg. foldl (+) 1000 [1,2,3] ouputs 1006 since 1000+1+2+3 = 1006
3. the list.

The foldr function does the same thing, but associates the other way:
foldr (+) 0 [1,2,3]
foldr, r for right 从base case 始,从右往左过list
== 1 + (2 + (3 + 0))
If there's a choice, foldl should be faster, since it's working from the head of the list (but
there's more to say about that, later).

foldr and foldl are surprisingly exible. For example:


myLength xs = foldl oneMore 0 xs .hs
where oneMore n _ = n+1

When it's called:


myLength "abc"
== oneMore (oneMore (oneMore 0 'a') 'b') 'c'
== oneMore (oneMore 1 'b') 'c'
== oneMore 2 'c'
== 3

Two more:
myConcat xs = foldl (++) [] xs .hs
myReverse xs = foldl prefix [] xs
where prefix xs x = x:xs
myConcat [[1,2], [3,4], [5,6]]
== ([1,2] ++ [3,4]) ++ [5,6]
== [1,2,3,4,5,6] foldr and foldl are defined in Prelude. if don't want to load in the
myReverse [1,2,3,4] definition for foldr and foldl, run: import Prelude hiding (foldr, foldl)
== prefix (prefix (prefix (prefix [] 1) 2) 3) 4
== prefix (prefix (prefix [1] 2) 3) 4
== prefix (prefix [2,1] 3) 4
== prefix [3,2,1] 4
== [4,3,2,1]

It's (usually?) possible to swap foldr and foldl if you change the Haskell wiki: Foldr Foldl
Foldl'
operation appropriately.
myReverse' xs = foldr postfix [] xs .hs
where postfix x xs = xs ++ [x]
myReverse' [1,2,3,4]
== postfix 1 (postfix 2 (postfix 3 (postfix 4 [])))
== postfix 1 (postfix 2 (postfix 3 [4]))
== postfix 1 (postfix 2 [4,3])
== postfix 1 [4,3,2]
== [4,3,2,1]

Haskell Types
We have already seen some simple types:
type 首字母 大写
Bool: boolean True or False
Char: one character
Int: xed-precision signed integer (usually 64-bit)
Float/Double: oating-point values

And compound types:


Lists which contain several values of a single type, written “[Type]”.
String: list of characters. Shortcut for “[Char]”.

All type names start with a uppercase character. Functions and arguments start with
lowercase.
Types can be explicitly declared with the :: syntax:
val :: Int 相当于 int val; in C .hs
val = 6 val1, val2 :: Int 也可以
lst :: [Int]
lst = [1,2,3]
lstlst :: [[Int]]
lstlst = [[1,2,3], [4,5]]

…or queried with the GHCi :t command:


Prelude> :t 6 GHCI
6 :: Num t => t :t val return val 的 type
Prelude> :t "Hello"
"Hello" :: [Char]

More types…
Integer: arbitrary-precision integer:
Prelude> let base=2 :: Int GHCI
Prelude> let power=64 :: Int
Prelude> base ^ power
0 <-- overflow
Prelude> let base=2 :: Integer
Prelude> let power=64 :: Integer
Prelude> base ^ power
Integer 不会 overflow
18446744073709551616

tuples: collection of values with a xed length, but may have different types in each position.
Different type signatures/lengths are considered different types.
Prelude> (1, "Hello") :: (Int, String) GHCI
(1,"Hello")
Prelude> (1,2,3) :: (Int,Int,Int)
(1,2,3)
Prelude> [(1,'a'), (2,'b')] :: [(Int,Char)]
[(1,'a'),(2,'b')]
Prelude> [(1,'a'), (2,2)]
error…
Prelude> [(1,2,3), (4,5)]
error…

Functions also have a type. It can (and should) be explicitly declared.


half_of :: Float -> Float half_of :: argument_type -> return_type .hs
half_of x = x/2
myPower :: Int -> Int -> Int
myPower :: argument1_type -> argument2_type -> return_type
myPower _ 0 = 1
myPower x y = x * myPower x (y-1)

The type “A -> B -> C” indicates a function that takes two arguments of type A and B, and
returns a C.

5/26 Some functions can work on a variety of types.


*Main> :t zip
eg. zip [1,2,3,4] "abcd"
GHCI
zip :: [a] -> [b] -> [(a, b)] 'a' and 'b' are type placeholder output: [(1,'a'),(2,'b'),(3,'c'),(4,'d')]
*Main> :t half_of
half_of :: Float -> Float
*Main> let another_half_of x = x/2
*Main> :t another_half_of
another_half_of :: Fractional a => a -> a
Here, a and b are type variables that can represent any type. The function another_half_of
has a more general type because it wasn't explicitly declared.

Functions can also be passed as arguments or returned (as we have seen). Their types are
given in the type signature.
*Main> :t map
takes a function (that takes type a and returns type b) and a list of a as
GHCI
map :: (a -> b) -> [a] -> [b] input, returns a list of b as output
*Main> :t filter
filter :: (a -> Bool) -> [a] -> [a]
flip_args :: (a -> b -> c) -> b -> a -> c .hs
flip_args f x y = f y x

Types are important in Haskell. Important enough that you can search by type signature in
Hoogle.
Some searches to try:
I think map might exist: “(a -> b) -> [a] -> [b]”
… with wrong argument order: “[a] -> (a -> b) -> [b]”
What kind of arithmetic is there? “Double -> Double -> Double”
What can I do to a list? “[Int] -> [Int]”

Everything has a type, even if you don't specify it. Haskell will do type inference to guess the
types of values. It's surprisingly good at it.
myAnd False _ = False .hs
myAnd True a = a
*Main> :t myAnd GHCI
myAnd :: Bool -> Bool -> Bool input, input, output

The inferred types can be useful, but it's often better to give the type explicitly. There can be
performance implications: these functions will perform differently:
myPower :: Int -> Int -> Int input, input, output .hs

myPower :: Integer -> Integer -> Integer .hs

… because Int operations are processor instructions; Integer operations are calls to an
arbitrary-precision integer library.

If you give an explicit type, you can get better error messages when what you write has
different types than you intended.
Rule starting with Exercise 3: must give explicit types of functions you declare.

Type Classes
A type class is a set of operations.
Like a Java interface: a type (“class” in Java) that implements the right operations can
declare itself as implementing it.
Like Go interfaces, but explicitly declared on the type.
A little like a C++ abstract class: de nes a set of operations that must be implemented
on a speci c type (“class” in C++).

e.g. if (==) and (/=) can be applied to values of a type, it is in the Eq type class.
Type classes are indicated in types with “=>”. e.g.
Prelude> let theSame x y = x == y GHCI
Prelude> :t theSame
theSame :: Eq a => a -> a -> Bool
with this definition of theSame, a can be type int, string, tuple, etc
theSame :: Eq a => a -> a -> Bool .hs
theSame x y = x == y Eq can be replaced by other type class

Other important type classes:


Ord: Eq plus can be ordered ((<), (>=), min, …).
Num: Number-like things ((+), (*), abs, …).
Fractional: Num plus true division ((/), recip). recip: reciprocal
Show: can be converted to a string for display.
Enum: has previous/next elements ( pred, succ)

e.g.
*Main> :t 6 GHCI
6 :: Num t => t
*Main> :t 6/4
6/4 :: Fractional a => a
*Main> :t qsort
qsort :: Ord t => [t] -> [t]
*Main> :t (<)
(<) :: Ord a => a -> a -> Bool

[Hoogle aside: are there other sorts? Ord t => [t] -> [t]]

Type inference will often give a type class, not a speci c type. Types with classes are more
exible: can be used on any value/type in the class.
Type classes (and type variables) provide easy and exible polymorphism in Haskell:
functions can operate on any type(s) where the operations used in their de nition make
sense.

Partially-Applied Functions
Consider the type declaration for a two-argument function:
join :: String -> [String] -> String .hs

This is actually parsed as:


join :: String -> ([String] -> String) .hs

i.e. join is actually a function that takes a String and returns a function [String] ->
String.

So, we can write:


join :: String -> [String] -> String .hs
-- ...
commajoin :: [String] -> String
commajoin = join ", "
Then:
*Main> commajoin ["one", "two", "three"] GHCI
"one, two, three"

When a function has values applied to some (but not all) of its arguments, it is referred to as
a partial function. missing input argument
A partial function can be treated like any other function. Every function application we have
done could have been parenthesized left associating:
map sqrt someList == (map sqrt) someList
foldl (+) 0 someList == ((foldl (+)) 0) someList

That means that all of these are the same calculation:


Prelude> div 10 2 GHCI
5
Prelude> (div 10) 2
5
Prelude> let d10=(div 10) in d10 2
5

We can de ne functions using partial function application as well. These are equivalent:
myConcat :: [[a]] -> [a] .hs
myConcat xs = foldl (++) [] xs

myConcat' :: [[a]] -> [a]


myConcat' = foldl (++) []

When you need to pass a function as an argument, a partially-applied function can be used.
This example did this with “divides n”:
divisors n = filter (divides n) [2..(n `div` 2)] .hs
where divides a b = (a `mod` b) == 0

Operators can also be partially-applied, but more exibly, since you can easily get to either
argument.
Prelude> map (12/) [6,2,12] GHCI
[2.0,6.0,1.0]
Prelude> map (/12) [6,2,12]
[0.5,0.16666666666666666,1.0]

Curried Functions
Functions (like all examples so far) that take multiple arguments by taking single arguments
multiple times are called curried functions. (After Haskell Curry.)
The other possibility (in Haskell) is to wrap multiple arguments in a tuple: an uncurried
function. e.g.
myDiv :: (Int, Int) -> Int .hs
myDiv (n,d) = div n d

There are functions to curry and uncurry two-argument functions:


*Main> (uncurry div) (10,2) GHCI
5 uncurried f (x,y): function f that takes 1 input which is a tuple (x,y)
*Main> (curry myDiv) 10 2
curried f x y : function f that takes 2 inputs, x and y
5

key word: uncurry, curry


These are often useful during list manipulations, particularly with zip, which puts two lists
together into tuples:
*Main> :t zip GHCI
zip :: [a] -> [b] -> [(a, b)] zip takes 2 arguments and outputs a list
*Main> zip [1,2,3] ['a','b','c']
[(1,'a'),(2,'b'),(3,'c')]

Using uncurry, we can operate on these pairs:


addPairwise :: Num a => [a] -> [a] -> [a] .hs
addPairwise xs ys = map (uncurry (+)) (zip xs ys) zip xs ys returns (xs,ys)
addPairwise [1,2,3] [4,5,6] (uncurry (+)) (xs,ys) returns xs+ys
== map (uncurry (+)) (zip [1,2,3] [4,5,6])
== [(uncurry (+)) (1,4), (uncurry (+)) (2,5), (uncurry (+)) (3,6)]
== [(+) 1 4, (+) 2 5, (+) 3 6]
== [5, 7, 9]

Manipulating Functions
Once you start to think of functions as values, there are many useful ways to manipulate
them. We have seen:
partial function application,
currying/uncurrying.
But also…

Function composition with (.) (like “ ” in a math class). Lets us combine two functions,
so:
(f.g) x == f (g x) f of g of x : f ( g(x) )
Some functions we could have de ned this way:
hailLen :: Int -> Int .hs
hailLen = length . hailSeq

reverseJoin :: String -> [String] -> String


reverseJoin s = (join s) . reverse

weekday :: Day -> Int


weekday = snd . mondayStartWeek
[See also tacit- or pointfree-style de nitions.]

Reverse function arguments of a (two-argument curried) function with flip. Useful to


partially-apply on the second argument.
joinPrimes :: String -> String .hs
joinPrimes = (flip join) ["2", "3", "5", "7", "11"]

myReverse'' :: [a] -> [a]


myReverse'' xs = foldl (flip (:)) [] xs
*Main> joinPrimes "," GHCI
"2,3,5,7,11"
*Main> joinPrimes "+"
"2+3+5+7+11"
*Main> myReverse'' "testing"
"gnitset"
Lambda Expressions
Haskell has rst-class functions: functions are values just like integers, lists, etc. They can be
passed as arguments, assigned names, etc.
When we de ne things in our code:
val :: Int .hs
val = 6
half_of :: Float -> Float .hs
half_of x = x/2

… val is value of type Int, and half_of is a value of type Float -> Float.

Functions can also be created with lambda expressions. The Greek letter λ is spelled “\” in
Haskell.
This de nes an equivalent function:
half_of' :: Float -> Float .hs
half_of' = \x -> x/2
The lambda expression \x -> x+x could be read “a value [function] that takes an argument
called x and returns x+x”.
[Similar to lambda expressions in Python, Java, C#, C++11; blocks in Ruby; anonymous functions in
JavaScript, Go, Scala.]

Like partial application, lambdas can be useful to de ne functions on their own, but are
more likely to be used to create readable larger expressions. For example,
addToEach :: Num a => a -> [a] -> [a] .hs
addToEach n lst = map (\x -> x+n) lst

… is arguably more readable than…


addToEach' :: Num a => a -> [a] -> [a] .hs
addToEach' n lst = map addN lst
where addN x = x+n addToEach' ' n lst = map (+ n) lst 也可以
用的是partial operation
Lambda could be more clear than partial application. e.g. which is more readable?
commajoin0, commajoin1, commajoin2 :: [String] -> String .hs
commajoin0 = \xs -> join ", " xs
commajoin1 = join ", "
commajoin2 xs = join ", " xs

[I'm not sure it's the lambda, but maybe?]

commajoin2 is the easiest to read. the other two are expressions that return a function
Course Notes Home. CMPT 383, Summer 2020. Copyright © 2018–2020 Greg Baker.

You might also like