文章目录
Function
in_range min max x =
min <= x && x <= max
Pattern Matching
Pattern matching consists of specifying patterns to which some data should conform and then checking to see if it does and deconstructing the data according to those patterns.
When defining functions, you can define separate function bodies for different patterns. This leads to really neat code that’s simple and readable. You can pattern match on any data type — numbers, characters, lists, tuples, etc.
is_zero 0 = True
is_zero _ = False
Guards
Whereas patterns are a way of making sure a value conforms to some form and deconstructing it, guards are a way of testing whether some property of a value (or several of them) are true or false. That sounds a lot like an if statement and it’s very similar. The thing is that guards are a lot more readable when you have several conditions and they play really nicely with patterns.
Many times, the last guard is otherwise. otherwise is defined simply as otherwise = True and catches everything. This is very similar to patterns, only they check if the input satisfies a pattern but guards check for boolean conditions. If all the guards of a function evaluate to False (and we haven’t provided an otherwise catch-all guard), evaluation falls through to the next pattern. That’s how patterns and guards play nicely together. If no suitable guards or patterns are found, an error is thrown.+
fac n
| n <= 1 = 1
| otherwise = n * fac (n - 1)
Where
in_range min max x = ilb && iub
where
ilb = min <= x
iub = max >= x
Let it be
in_range min max x =
let in_lower_bound = min <= x
in_upper_bound = max >= x
in
in_lower_bound && in_upper_bound
ghci> [let square x = x * x in (square 5, square 3, square 2)] -- [(25,9,4)]
ghci> (let (a,b,c) = (1,2,3) in a+b+c) * 100 -- 600
ghci> let boot x y z = x * y + z in boot 3 4 2 -- 14
ghci> boot -- <interactive>:1:0: Not in scope: `boot'
Case
case expression of pattern -> result
pattern -> result
pattern -> result
...
Recursion
Recursion is actually a way of defining functions in which the function is applied inside its own definition. Definitions in mathematics are often given recursively.
Recursion is important to Haskell because unlike imperative languages, you do computations in Haskell by declaring what something is instead of declaring how you get it. That’s why there are no while loops or for loops in Haskell and instead we many times have to use recursion to declare what something is.
Pattern matching goes great with recursion! Most imperative languages don’t have pattern matching so you have to make a lot of if else statements to test for edge conditions.
-- 阶乘
fac n =
if n < 1 then
1
else
n * fac (n - 1)
-- 列表中的最大值
maximum' :: (Ord a) => [a] -> a
maximum' [] = error "maximum of empty list"
maximum' [x] = x
maximum' (x:xs) -- (x:xs) a very common idiom when doing recursion with lists
| x > maxTail = x
| otherwise = maxTail
where maxTail = maximum' xs
Types
Haskell has a static type system. The type of every expression is known at compile time, which leads to safer code.
::
is read as “has type of”.
in_range :: Integer -> Integer -> Integer -> Bool
Int
Int stands for integer. It’s used for whole numbers. 7 can be an Int but 7.2 cannot. Int is bounded, which means that it has a minimum and a maximum value. Usually on 32-bit machines the maximum possible Int is 2147483647 and the minimum is -2147483648.
Integer
Integer stands for, er … also integer. The main difference is that it’s not bounded so it can be used to represent really really big numbers. I mean like really big. Int, however, is more efficient.
Float
Float is a real floating point with single precision.
Double
Double is a real floating point with double the precision!
Bool
Bool is a boolean type. It can have only two values: True and False.
Char
Char represents a character. It’s denoted by single quotes. A list of characters is a string.
TypeClasses
A typeclass is a sort of interface that defines some behavior. If a type is a part of a typeclass, that means that it supports and implements the behavior the typeclass describes. A lot of people coming from OOP get confused by typeclasses because they think they are like classes in object oriented languages. Well, they’re not. You can think of them kind of as Java interfaces, only better.
Eq
Eq is used for types that support equality testing. The functions its members implement are == and /=. So if there’s an Eq class constraint for a type variable in a function, it uses == or /= somewhere inside its definition. All the types we mentioned previously except for functions are part of Eq, so they can be tested for equality.
Ord
Ord is for types that have an ordering.
Show
Members of Show can be presented as strings. All types covered so far except for functions are a part of Show. The most used function that deals with the Show typeclass is show. It takes a value whose type is a member of Show and presents it to us as a string.
Read
Read is sort of the opposite typeclass of Show. The read function takes a string and returns a type which is a member of Read.
Enum
Enum members are sequentially ordered types — they can be enumerated. The main advantage of the Enum typeclass is that we can use its types in list ranges. They also have defined successors and predecesors, which you can get with the succ and pred functions. Types in this class: (), Bool, Char, Ordering, Int, Integer, Float and Double.
Bounded
Bounded members have an upper and a lower bound.
Num
Num is a numeric typeclass. Its members have the property of being able to act like numbers. Let’s examine the type of a number.
Integral
Integral is also a numeric typeclass. Num includes all numbers, including real numbers and integral numbers, Integral includes only integral (whole) numbers. In this typeclass are Int and Integer.
Floating
Floating includes only floating point numbers, so Float and Double.
if
in_range min max x =
if ilb then iub else False
where
ilb = min <= x
iub = max >= x
List
[1, 2, 3, 4, 5] :: [Integer]
-- List 相加
ghci> [1,2,3,4] ++ [9,10,11,12]
-- 在头部插入
ghci> 5:[1,2,3,4,5] -- [5,1,2,3,4,5]
-- 索引
ghci> [1, 2, 3, 4, 5] !! 3 -- 4
-- 列表比较,从前往后比:若相等则比较下一个;否则返回比较结果
ghci> [3,2,1] > [2,1,0] -- True
ghci> [3,2,1] > [2,10,100] -- True
ghci> [3,4,2] > [3,4] -- True
ghci> [3,4,2] > [2,4] -- True
ghci> [3,4,2] == [3,4,2] -- True
[1, 3..100]
asc :: Int -> Int -> [Int]
asc n m
| m < n = []
| m == n = [m]
| m > n = n : asc (n + 1)
Functions on Lists
-
head - the first element of the list
-- head takes a list and returns its head. The head of a list is basically its first element. head :: [a] -> a
-
tail - the list of elements without the first one
-- tail takes a list and returns its tail. In other words, it chops off a list's head. tail :: [a] -> [a]
-
last - the last element
-- last takes a list and returns its last element. ghci> last [5,4,3,2,1] -- 1
-
length - the length of the list
length :: [a] -> Int
-
init - the list of elements without the last one
-- init takes a list and returns everything except its last element. init :: [a] -> [a]
-
null - the list is empty or not
-
reverse
-- reverse reverses a list. ghci> reverse [5,4,3,2,1] -- [1,2,3,4,5]
-
take
-- take takes number and a list. It extracts that many elements from the beginning of the list. Watch. ghci> take 3 [5,4,3,2,1] -- [5,4,3] ghci> take 1 [3,9,3] -- [3] ghci> take 5 [1,2] -- [1,2] ghci> take 0 [6,6,6] -- []
-
drop
-- drop works in a similar way, only it drops the number of elements from the beginning of a list.
-
maximum / minimum
-
sum
-
product
-
elem
-- elem takes a thing and a list of things and tells us if that thing is an element of the list. It's usually called as an infix function because it's easier to read that way.
-
cycle
-- cycle takes a list and cycles it into an infinite list. If you just try to display the result, it will go on forever so you have to slice it off somewhere. ghci> take 10 (cycle [1,2,3]) -- [1,2,3,1,2,3,1,2,3,1]
-
repeat
-- repeat takes an element and produces an infinite list of just that element. It's like cycling a list with only one element. ghci> take 10 (repeat 5) -- [5,5,5,5,5,5,5,5,5,5]
-
replicate
ghci> replicate 3 10 -- [10,10,10]
List Comprehension
ghci> [x*2 | x <- [1..10]] -- [2,4,6,8,10,12,14,16,18,20]
ghci> [x*2 | x <- [1..10], x*2 >= 12] -- [12,14,16,18,20]
ghci> [ x | x <- [50..100], x `mod` 7 == 3] -- [52,59,66,73,80,87,94]
ghci> [ x*y | x <- [2,5,10], y <- [8,10,11], x*y > 50] -- [55,80,100,110]
Tuple
-- fst
-- fst takes a pair and returns its first component.
ghci> fst (8,11) -- 8
ghci> fst ("Wow", False) -- "Wow"
-- snd
-- snd takes a pair and returns its second component. Surprise!
ghci> snd (8,11) -- 11
ghci> snd ("Wow", False) -- False
-- zip
-- It takes two lists and then zips them together into one list by joining the matching elements into pairs.
-- It's a really simple function but it has loads of uses.
-- It's especially useful for when you want to combine two lists in a way or traverse two lists simultaneously.
ghci> zip [1,2,3,4,5] [5,5,5,5,5] -- [(1,5),(2,5),(3,5),(4,5),(5,5)]
ghci> zip [1 .. 5] ["one", "two", "three", "four", "five"] -- [(1,"one"),(2,"two"),(3,"three"),(4,"four"),(5,"five")]
Higher Order Functions
Haskell functions can take functions as parameters and return functions as return values. A function that does either of those is called a higher order function.
Higher order functions aren’t just a part of the Haskell experience, they pretty much are the Haskell experience. It turns out that if you want to define computations by defining what stuff is instead of defining steps that change some state and maybe looping them, higher order functions are indispensable. They’re a really powerful way of solving problems and thinking about programs.
Curried functions
All the functions that accepted several parameters so far have been curried functions.
compareWithHundred :: (Num a, Ord a) => a -> Ordering
-- compareWithHundred x = compare 100 x
-- 第二行的代码可以被重写为第四行,因为 compare 100 返回了一个 “输入一个数,并与 100 比较” 的函数
compareWithHundred = compare 100
Maps & filters
Mapping and filtering is the bread and butter of every functional programmer’s toolbox.
map takes a function and a list and applies that function to every element in the list, producing a new list.
ghci> map (+3) [1,5,3,1,6] -- [4,8,6,4,9]
ghci> map (++ "!") ["BIFF", "BANG", "POW"] -- ["BIFF!","BANG!","POW!"]
ghci> map (replicate 3) [3..6] -- [[3,3,3],[4,4,4],[5,5,5],[6,6,6]]
filter is a function that takes a predicate (a predicate is a function that tells whether something is true or not, so in our case, a function that returns a boolean value) and a list and then returns the list of elements that satisfy the predicate.
ghci> filter (>3) [1,5,3,2,1,6,4,3,2,1] -- [5,6,4] ghci> filter (==3) [1,2,3,4,5] -- [3] ghci> filter even [1..10] -- [2,4,6,8,10] ghci> let notNull x = not (null x) in filter notNull [[1,2,3],[],[3,4,5],[2,2],[],[],[]] -- [[1,2,3],[3,4,5],[2,2]]
takeWhile takes a predicate and a list and then goes from the beginning of the list and returns its elements while the predicate holds true. Once an element is found for which the predicate doesn’t hold, it stops.
ghci> sum (takeWhile (<10000) (filter odd (map (^2) [1..]))) -- 166650
Lambdas
Lambdas are basically anonymous functions that are used because we need some functions only once. Normally, we make a lambda with the sole purpose of passing it to a higher-order function. To make a lambda, we write a \ (because it kind of looks like the greek letter lambda if you squint hard enough) and then we write the parameters, separated by spaces. After that comes a -> and then the function body. We usually surround them by parentheses, because otherwise they extend all the way to the right.
ghci> map (\(a,b) -> a + b) [(1,2),(3,5),(6,3),(2,6),(2,5)] -- [3,8,9,8,7] ghci> zipWith (\a b -> (a * 30 + 3) / b) [5,4,3,2,1] [1,2,3,4,5] -- [153.0,61.5,31.0,15.75,6.6]
Only folds and horses
A fold takes a binary function, a starting value (I like to call it the accumulator) and a list to fold up. The binary function itself takes two parameters. The binary function is called with the accumulator and the first (or last) element and produces a new accumulator. Then, the binary function is called again with the new accumulator and the now new first (or last) element, and so on. Once we’ve walked over the whole list, only the accumulator remains, which is what we’ve reduced the list to.
sum' :: (Num a) => [a] -> a sum' xs = foldl (\acc x -> acc + x) 0 xs -- The lambda function (\acc x -> acc + x) is the same as (+).ghci> sum' [3,5,2,1] -- 11
foldr works in a similar way to the left fold, only the accumulator eats up the values from the right.
The foldl1 and foldr1 functions work much like foldl and foldr, only you don’t need to provide them with an explicit starting value. They assume the first (or last) element of the list to be the starting value and then start the fold with the element next to it.
scanl and scanr are like foldl and foldr, only they report all the intermediate accumulator states in the form of a list. There are also scanl1 and scanr1, which are analogous to foldl1 and foldr1.
When using a scanl, the final result will be in the last element of the resulting list while a scanr will place the result in the head.
ghci> scanl (+) 0 [3,5,2,1] -- [0,3,8,10,11] ghci> scanr (+) 0 [3,5,2,1] -- [11,8,3,1,0] ghci> scanl1 (\acc x -> if x > acc then x else acc) [3,4,5,3,7,9,2,1] -- [3,4,5,5,7,9,9,9] ghci> scanl (flip (:)) [] [3,2,1] -- [[],[3],[2,3],[1,2,3]]
Function application with $
($) :: (a -> b) -> a -> b f $ x = f x
Whereas normal function application (putting a space between two things) has a really high precedence, the $ function has the lowest precedence. Function application with a space is left-associative (so f a b c is the same as ((f a) b) c)), function application with $ is right-associative.
Well, because $ is right-associative, f (g (z x))
is equal to f $ g $ z x
.
And so, we can rewrite sum (filter (> 10) (map (\*2) [2..10]))
as sum $ filter (> 10) $ map (\*2) [2..10]
.
Function composition
(.) :: (b -> c) -> (a -> b) -> a -> c f . g = \x -> f (g x)
The expression negate . (* 3)
returns a function that takes a number, multiplies it by 3 and then negates it.
Function composition is right-associative, so we can compose many functions at a time. The expression f (g (z x))
is equivalent to (f . g . z) x
.
Many times, a point free style is more readable and concise, because it makes you think about functions and what kind of functions composing them results in instead of thinking about data and how it’s shuffled around.
Modules
Loading Modules
A Haskell module is a collection of related functions, types and typeclasses.
A Haskell program is a collection of modules where the main module loads up the other modules and then uses the functions defined in them to do something.
Having code split up into several modules has quite a lot of advantages. If a module is generic enough, the functions it exports can be used in a multitude of different programs. If your own code is separated into self-contained modules which don’t rely on each other too much (we also say they are loosely coupled), you can reuse them later on. It makes the whole deal of writing code more manageable by having it split into several parts, each of which has some sort of purpose.
The syntax for importing modules in a Haskell script is import <module name>
. This must be done before defining any functions, so imports are usually done at the top of the file.
One script can, of course, import several modules. Just put each import statement into a separate line.
import Data.List (nub, sort) import Data.List hiding (nub) import qualified Data.Map import qualified Data.Map as M
Data.List
intersperse
intersperse
takes an element and a list and then puts that element in between each pair of elements in the list.
ghci> intersperse '.' "MONKEY" -- "M.O.N.K.E.Y" ghci> intersperse 0 [1,2,3,4,5,6] -- [1,0,2,0,3,0,4,0,5,0,6]
intercalate
intercalate
takes a list of lists and a list. It then inserts that list in between all those lists and then flattens the result.
ghci> intercalate " " ["hey","there","guys"] -- "hey there guys" ghci> intercalate [0,0,0] [[1,2,3],[4,5,6],[7,8,9]] -- [1,2,3,0,0,0,4,5,6,0,0,0,7,8,9]
transpose
transpose
transposes a list of lists. If you look at a list of lists as a 2D matrix, the columns become the rows and vice versa.
ghci> map sum $ transpose [[0,3,5,9],[10,0,0,9],[8,5,1,-1]] -- [18,8,6,17]
concat
concat
flattens a list of lists into just a list of elements.
ghci> concat ["foo","bar","car"] -- "foobarcar" ghci> concat [[3,4,5],[2,3,4],[2,1,1]] -- [3,4,5,2,3,4,2,1,1]
It will just remove one level of nesting. So if you want to completely flatten [[[2,3],[3,4,5],[2]],[[2,3],[3,4]]], which is a list of lists of lists, you have to concatenate it twice.
concatMap
Doing concatMap
is the same as first mapping a function to a list and then concatenating the list with concat.
ghci> concatMap (replicate 4) [1..3] -- [1,1,1,1,2,2,2,2,3,3,3,3]
and
and
takes a list of boolean values and returns True only if all the values in the list are True.
ghci> and $ map (>4) [5,6,7,8] -- True ghci> and $ map (==4) [4,4,4,3,4] -- False
or
or
is like and, only it returns True if any of the boolean values in a list is True.
ghci> or $ map (==4) [2,3,4,5,6,1] -- True ghci> or $ map (>4) [1,2,3] -- False
any & all
any
and all
take a predicate and then check if any or all the elements in a list satisfy the predicate, respectively.
Usually we use these two functions instead of mapping over a list and then doing and or or.
ghci> any (==4) [2,3,5,6,1,4] -- True ghci> all (>4) [6,9,10] -- True ghci> all (`elem` ['A'..'Z']) "HEYGUYSwhatsup" -- False ghci> any (`elem` ['A'..'Z']) "HEYGUYSwhatsup" -- True
iterate
iterate
takes a function and a starting value. It applies the function to the starting value, then it applies that function to the result, then it applies the function to that result again, etc. It returns all the results in the form of an infinite list.
ghci> take 10 $ iterate (*2) 1 -- [1,2,4,8,16,32,64,128,256,512] ghci> take 3 $ iterate (++ "haha") "haha" -- ["haha","hahahaha","hahahahahaha"]
splitAt
splitAt
takes a number and a list. It then splits the list at that many elements, returning the resulting two lists in a tuple.
ghci> splitAt 3 "heyman" -- ("hey","man") ghci> splitAt 100 "heyman" -- ("heyman","") ghci> splitAt (-3) "heyman" -- ("","heyman") ghci> let (a,b) = splitAt 3 "foobar" in b ++ a -- "barfoo"
takeWhile
takeWhile
is a really useful little function. It takes elements from a list while the predicate holds and then when an element is encountered that doesn’t satisfy the predicate, it’s cut off. It turns out this is very useful.
ghci> sum $ takeWhile (<10000) $ map (^3) [1..] -- 53361
dropWhile
dropWhile
is similar, only it drops all the elements while the predicate is true. Once predicate equates to False, it returns the rest of the list. An extremely useful and lovely function!
ghci> dropWhile (/=' ') "This is a sentence" -- " is a sentence" ghci> dropWhile (<3) [1,2,2,2,3,4,5,4,3,2,1] -- [3,4,5,4,3,2,1]
span
span is kind of like takeWhile, only it returns a pair of lists.
- The first list contains everything the resulting list from takeWhile would contain if it were called with the same predicate and the same list.
- The second list contains the part of the list that would have been dropped.
ghci> let (fw, rest) = span (/=' ') "This is a sentence" in "First word:" ++ fw ++ ", the rest:" ++ rest -- "First word: This, the rest: is a sentence"
break
Whereas span spans the list while the predicate is true, break
breaks it when the predicate is first true.
Doing break p
is the equivalent of doing span (not . p)
.
ghci> break (==4) [1,2,3,4,5,6,7] -- ([1,2,3],[4,5,6,7]) ghci> span (/=4) [1,2,3,4,5,6,7] -- ([1,2,3],[4,5,6,7])
When using break, the second list in the result will start with the first element that satisfies the predicate.
sort
sort
simply sorts a list.
The type of the elements in the list has to be part of the Ord typeclass, because if the elements of a list can’t be put in some kind of order, then the list can’t be sorted.
ghci> sort [8,5,3,2,1,6,4,2] -- [1,2,2,3,4,5,6,8] ghci> sort "This will be sorted soon" -- " Tbdeehiillnooorssstw"
group
group
takes a list and groups adjacent elements into sublists if they are equal.
ghci> group [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7] -- [[1,1,1,1],[2,2,2,2],[3,3],[2,2,2],[5],[6],[7]]
If we sort a list before grouping it, we can find out how many times each element appears in the list.
ghci> map (\l@(x:xs) -> (x,length l)) . group . sort $ [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7] -- [(1,4),(2,7),(3,2),(5,1),(6,1),(7,1)]
inits & tails
inits and tails are like init and tail, only they recursively apply that to a list until there’s nothing left. Observe.
ghci> inits "w00t" -- ["","w","w0","w00","w00t"] ghci> tails "w00t" -- ["w00t","00t","0t","t",""] ghci> let w = "w00t" in zip (inits w) (tails w) -- [("","w00t"),("w","00t"),("w0","0t"),("w00","t"),("w00t","")]
isInfixOf & isPrefixOf & isSuffixOf
isInfixOf searches for a sublist within a list and returns True if the sublist we’re looking for is somewhere inside the target list.
ghci> "cat" `isInfixOf` "im a cat burglar" -- True ghci> "Cat" `isInfixOf` "im a cat burglar" -- False ghci> "cats" `isInfixOf` "im a cat burglar" -- False
isPrefixOf and isSuffixOf search for a sublist at the beginning and at the end of a list, respectively.
ghci> "hey" `isPrefixOf` "hey there!" -- True ghci> "hey" `isPrefixOf` "oh hey there!" -- False ghci> "there!" `isSuffixOf` "oh hey there!" -- True ghci> "there!" `isSuffixOf` "oh hey there" -- False
elem & notElem
elem and notElem check if an element is or isn’t inside a list.
partition
partition takes a list and a predicate and returns a pair of lists. The first list in the result contains all the elements that satisfy the predicate, the second contains all the ones that don’t.
ghci> partition (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy" -- ("BOBMORGAN","sidneyeddy") ghci> partition (>3) [1,3,5,6,3,2,1,0,3,7] -- ([5,6,7],[1,3,3,2,1,0,3])
find
find takes a list and a predicate and returns the first element that satisfies the predicate. But it returns that element wrapped in a Maybe value. We’ll be covering algebraic data types more in depth in the next chapter but for now, this is what you need to know: a Maybe value can either be Just something or Nothing. Much like a list can be either an empty list or a list with some elements, a Maybe value can be either no elements or a single element. And like the type of a list of, say, integers is [Int], the type of maybe having an integer is Maybe Int.
ghci> find (>4) [1,2,3,4,5,6] -- Just 5 ghci> find (>9) [1,2,3,4,5,6] -- Nothing ghci> :t find -- find :: (a -> Bool) -> [a] -> Maybe a
Data.Char
isControl checks whether a character is a control character.
isSpace checks whether a character is a white-space characters. That includes spaces, tab characters, newlines, etc.
isLower checks whether a character is lower-cased.
isUpper checks whether a character is upper-cased.
isAlpha checks whether a character is a letter.
isAlphaNum checks whether a character is a letter or a number.
isPrint checks whether a character is printable. Control characters, for instance, are not printable.
isDigit checks whether a character is a digit.
isOctDigit checks whether a character is an octal digit.
isHexDigit checks whether a character is a hex digit.
isLetter checks whether a character is a letter.
isMark checks for Unicode mark characters. Those are characters that combine with preceding letters to form latters with accents. Use this if you are French.
isNumber checks whether a character is numeric.
isPunctuation checks whether a character is punctuation.
isSymbol checks whether a character is a fancy mathematical or currency symbol.
isSeparator checks for Unicode spaces and separators.
isAscii checks whether a character falls into the first 128 characters of the Unicode character set.
isLatin1 checks whether a character falls into the first 256 characters of Unicode.
isAsciiUpper checks whether a character is ASCII and upper-case.
isAsciiLower checks whether a character is ASCII and lower-case.
toUpper converts a character to upper-case. Spaces, numbers, and the like remain unchanged.
toLower converts a character to lower-case.
toTitle converts a character to title-case. For most characters, title-case is the same as upper-case.
digitToInt converts a character to an Int. To succeed, the character must be in the ranges ‘0’…‘9’, ‘a’…‘f’ or ‘A’…‘F’.
intToDigit is the inverse function of digitToInt. It takes an Int in the range of 0…15 and converts it to a lower-case character.
The ord and chr functions convert characters to their corresponding numbers and vice versa:
ghci> ord 'a' -- 97 ghci> chr 97 -- 'a' ghci> map ord "abcdefgh" -- [97,98,99,100,101,102,103,104]
Data.Map
Association lists (also called dictionaries) are lists that are used to store key-value pairs where ordering doesn’t matter. For instance, we might use an association list to store phone numbers, where phone numbers would be the values and people’s names would be the keys. We don’t care in which order they’re stored, we just want to get the right phone number for the right person.
The most obvious way to represent association lists in Haskell would be by having a list of pairs. The first component in the pair would be the key, the second component the value.
fromList
The fromList function takes an association list (in the form of a list) and returns a map with the same associations.
ghci> Map.fromList [("betty","555-2938"),("bonnie","452-2928"),("lucille","205-2928")] -- fromList [("betty","555-2938"),("bonnie","452-2928"),("lucille","205-2928")] ghci> Map.fromList [(1,2),(3,4),(3,2),(5,5)] -- fromList [(1,2),(3,2),(5,5)]
empty
empty represents an empty map. It takes no arguments, it just returns an empty map.
ghci> Map.empty -- fromList []
insert
insert takes a key, a value and a map and returns a new map that’s just like the old one, only with the key and value inserted.
ghci> Map.empty -- fromList [] ghci> Map.insert 3 100 Map.empty -- fromList [(3,100)] ghci> Map.insert 5 600 (Map.insert 4 200 ( Map.insert 3 100 Map.empty)) -- fromList [(3,100),(4,200),(5,600)] ghci> Map.insert 5 600 . Map.insert 4 200 . Map.insert 3 100 $ Map.empty -- fromList [(3,100),(4,200),(5,600)]
null
null checks if a map is empty.
size
size reports the size of a map.
singleton
singleton takes a key and a value and creates a map that has exactly one mapping.
lookup
lookup works like the Data.List lookup, only it operates on maps. It returns Just something if it finds something for the key and Nothing if it doesn’t.
member
member is a predicate takes a key and a map and reports whether the key is in the map or not.
map & filter
map and filter work much like their list equivalents.
toList
toList is the inverse of fromList.
keys & elems
keys and elems return lists of keys and values respectively. keys is the equivalent of map fst . Map.toList and elems is the equivalent of map snd . Map.toList.
Data.Set
The Data.Set module offers us, well, sets. Like sets from mathematics. Sets are kind of like a cross between lists and maps. All the elements in a set are unique. And because they’re internally implemented with trees (much like maps in Data.Map), they’re ordered. Checking for membership, inserting, deleting, etc. is much faster than doing the same thing with lists. The most common operation when dealing with sets are inserting into a set, checking for membership and converting a set to a list.
Because the names in Data.Set clash with a lot of Prelude and Data.List names, we do a qualified import.
import qualified Data.Set as Set
fromList
The fromList function works much like you would expect. It takes a list and converts it into a set.
intersection
As you can see, the items are ordered and each element is unique. Now let’s use the intersection function to see which elements they both share.
difference
We can use the difference function to see which letters are in the first set but aren’t in the second one and vice versa.
union
Or we can see all the unique letters used in both sentences by using union.
null & size & member & empty & singleton & insert & delete
The null, size, member, empty, singleton, insert and delete functions all work like you’d expect them to.
Making our own modules
http://learnyouahaskell.com/modules#loading-modules
-- Geometry - the name of folder-- Sphere - the name of source file -- volume, area - the functions that to be exportedmodule Geometry.Sphere ( volume , area ) where volume :: Float -> Float volume radius = (4.0 / 3.0) * pi * (radius ^ 3) area :: Float -> Float area radius = 4 * pi * (radius ^ 2)
Making Our Own Types and Typeclasses
data means that we’re defining a new data type. The part before the = denotes the type, which is Bool. The parts after the = are value constructors. They specify the different values that this type can have. The | is read as or. So we can read this as: the Bool type can have a value of True or False. Both the type name and the value constructors have to be capital cased.
data Bool = False | True
data Shape = Circle Float Float Float | Rectangle Float Float Float Float data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show) surface :: Shape -> Float surface (Circle _ _ r) = pi * r ^ 2 surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)
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) ghci> surface (Rectangle (Point 0 0) (Point 100 100)) -- 10000.0 ghci> surface (Circle (Point 0 0) 24) -- 1809.5574
-- By doing Shape(..), we exported all the value constructors for Shape, so that means that whoever imports our module can make shapes by using the Rectangle and Circle value constructors. It's the same as writing Shape (Rectangle, Circle).module Shapes ( Point(..) , Shape(..) , surface , nudge , baseCircle , baseRect ) where
Record syntax
data Person = Person { firstName :: String , lastName :: String , age :: Int , height :: Float , phoneNumber :: String , flavor :: String } deriving (Show)
Type parameters
data Maybe a = Nothing | Just a
The a here is the type parameter. And because there’s a type parameter involved, we call Maybe a type constructor. Depending on what we want this data type to hold when it’s not Nothing, this type constructor can end up producing a type of Maybe Int, Maybe Car, Maybe String, etc. No value can have a type of just Maybe, because that’s not a type per se, it’s a type constructor. In order for this to be a real type that a value can be part of, it has to have all its type parameters filled up.
data Vector a = Vector a a a deriving (Show) vplus :: (Num t) => Vector t -> Vector t -> Vector t (Vector i j k) `vplus` (Vector l m n) = Vector (i+l) (j+m) (k+n) vectMult :: (Num t) => Vector t -> t -> Vector t (Vector i j k) `vectMult` m = Vector (i*m) (j*m) (k*m) scalarMult :: (Num t) => Vector t -> Vector t -> t (Vector i j k) `scalarMult` (Vector l m n) = i*l + j*m + k*n
Derived instances
data Person = Person { firstName :: String , lastName :: String , age :: Int } deriving (Eq, Show, Read)
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday deriving (Eq, Ord, Show, Read, Bounded, Enum)
Type synonyms
type String = [Char]
Input & Output
An I/O action will be performed when we give it a name of main and then run our program.
main = do putStrLn "Hello, what's your name?" name <- getLine putStrLn ("Hey " ++ name ++ ", you rock!")
Because of that, main always has a type signature of main :: IO something, where *something* is some concrete type. By convention, we don’t usually specify a type declaration for main.
import Data.Char main = do putStrLn "What's your first name?" firstName <- getLine putStrLn "What's your last name?" lastName <- getLine let bigFirstName = map toUpper firstName bigLastName = map toUpper lastName putStrLn $ "hey " ++ bigFirstName ++ " " ++ bigLastName ++ ", how are you?"
Files and Streams
getContents is an I/O action that reads everything from the standard input until it encounters an end-of-file character. Its type is getContents :: IO String.
Functionally Solving Problems
Reverse Polish notation calculator
solveRPN :: (Num a, Read a) => String -> a solveRPN = head . foldl foldingFunction [] . words where foldingFunction (x:y:ys) "*" = (x * y):ys foldingFunction (x:y:ys) "+" = (x + y):ys foldingFunction (x:y:ys) "-" = (y - x):ys foldingFunction xs numberString = read numberString:xs
type FilePath = String data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode