Haskell - Types & Functions
Haskell - Types & Functions
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…
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] []
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])
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.]
Important points: the operation is (+) and the starting point (result for []) is 0. Then, an
equivalent fold:
mySum' xs = foldl (+) 0 xs .hs
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).
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
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]]
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…
The type “A -> B -> C” indicates a function that takes two arguments of type A and B, and
returns a C.
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
… 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
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
i.e. join is actually a function that takes a String and returns a function [String] ->
String.
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
We can de ne functions using partial function application as well. These are equivalent:
myConcat :: [[a]] -> [a] .hs
myConcat xs = foldl (++) [] xs
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
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
… 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
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.