You A Haskell Chapter 7
We can import modules by using the import
keyword.
import Data.List
numUniques :: (Eq a) => [a] -> Int
= length . nub numUniques
To import in GHCI:
> :m + Data.List ghci
To import a set of modules in GHCI:
> :m + Data.List Data.Map Data.Set ghci
To import only a few parts of a module:
import Data.List (nub, sort)
To import everything except something:
import Data.List hiding(nub)
We can import qualified as well, which requires typing out the full name:
import qualified Data.Map
We can rename it with as
.
import qualified Data.Map as M
intersperse
takes an element and a list and puts that
element in between each pair of elements in that list.
> intersperse '.' "MONKEY"
ghci"M.O.N.K.E.Y"
> intersperse 0 [1,2,3,4,5,6]
ghci1,0,2,0,3,0,4,0,5,0,6] [
intercalate
takes a list of lists and a list and inserts
that element between those lists and flattens the lists.
> intercalate " " ["hey","there","guys"]
ghci"hey there guys"
> intercalate [0,0,0] [1,2,3],[4,5,6],[7,8,9]]
ghci1,2,3,0,0,0,4,5,6,0,0,0,7,8,9] [
transpose
transposes a list of lists. The columns become
the rows and the rows become the columns.
> transpose [1,2,3],[4,5,6],[7,8,9]]
ghci1,4,7],[2,5,8],[3,6,9]]
[> transpose ["hey","there","guys"]
ghci"htg","ehu","yey","rs","e"] [
Let’s say we have some list where we want to sum up column-wise.
We can do this:
map sum $ transpose [0,3,5,9],[10,0,0,9],[8,5,1,-1]]
18,8,6,17] [
foldl'
and foldll'
are the strict versions
of foldl
and foldll
. Instead of returning a
thunk
, they calculate intermediate values, which lets them
be more effective.
concat
flattens a list of lists into a list of elements
one level.
> concat ["foo","bar","car"]
ghci"foobarcar"
> concat [3,4,5],[2,3,4],[2,1,1]]
ghci3,4,5,2,3,4,2,1,1] [
concatMap
is the same as first mapping a function to a
list and then concatenating the list with concat
.
> concatMap (replicate 4) [1..3]
ghci1,1,1,1,2,2,2,2,3,3,3,3] [
and
takes a list of boolean values and returns
True
only if all the values in the list are
True
.
> and $ map (>4) [5,6,7,8]
ghciTrue
> and $ map (==4) [4,4,4,3,4]
ghciFalse
or
is like and
, only it returns true if any
of the boolean values is True
.
> or $ map (==4) [2,3,4,5,6,1]
ghciTrue
> or $ map (>4) [1,2,3]
ghciFalse
any
and all
work as you would expect,
checking if all conform or any conform.
> any (==4) [2,3,5,6,1,4]
ghciTrue
> all (>4) [6,9,10]
ghciTrue
> all (`elem` ['A'..'Z']) "HEYGUYSwhatsup"
ghciFalse
> any (`elem` ['A'..'Z']) "HEYGUYSwhatsup"
ghciTrue
iterate
takes a function and a starting value. It
applies the function to the starting value, then it applies that
function to the result infinitely.
> take 10 $ iterate (*2) 1
ghci1,2,4,8,16,32,64,128,256,512]
[> take 3 $ iterate (++ "haha"
ghci"haha","hahahaha","hahahahahaha"] [
splitAt
takes a number and a list. It splits the list at
that many elements, returns a tuple.
> splitAt 3 "heyman"
ghci
(> splitAt 100 "heyman"
ghci
(> splitAt (-3) "heyman"
ghci
(> let (a,b) = splitAt 3 "foobar" in b ++ a
ghci"barfoo"
takeWhile
takes elements from a list while the predicate
is true.
> takeWhile (>3) [6,5,4,3,2,1,2,3,4,5,4,3,2,1]
ghci6,5,4]
[> takeWhile (/=' ') "This is a sentence"
ghci"This"
What if we wanted to know the sum of all third powers that are under 10,000?
We can do something like this:
We make an infinite list of third powers, chop them off when they’re greater than 10000, then sum them.
> sum $ takeWhile (<10000) $ map (^3) [1..]
ghci53361
dropWhile
only drops elements while the predicate is
true. Once it becomes False, it returns the rest of the list.
> dropWhile (/= ' ') "This is a sentence"
ghci" is a sentence"
> dropWhile (<3) [1,2,2,2,3,4,5,4,3,2,1]
ghci3,4,5,4,3,2,1] [
Imagine finding out when the stock value of a stock exceeds $1000.
> let stock = [(994.4,2008,9,1),(995.2,2008,9,2),(999.2,2008,9,3),(1001.4,2008,9,4),(998.3,2008,9,5)]
ghci> head (dropWhile ((val,y,m,d) -> val < 1000) stock)
ghci1001.4,2008,9,4) (
span
is like takeWhile
, only it returns a
pair of lists.
> let (fw, rest) = span (/=' ') "This is a sentence" in "First word:" ++ fw ++ ", the rest:" ++ rest
ghci"First word: This, the rest: is a sentence"
break
is the opposite, it splits when the predicate is
first true.
> break (==4) [1,2,3,4,5,6,7]
ghci1,2,3],[4,5,6,7])
([> span (/=4) [1,2,3,4,5,6,7]
ghci1,2,3],[4,5,6,7]) ([
sort
sorts a list. They must conform to Ord
to be sortable.
> sort [8,5,3,2,1,6,4,2]
ghci1,2,2,3,4,5,6,8]
[> sort "This will be sorted soon"
ghci" Tbdeehiillnooorssstw"
group
takes a list and groups adjacent elements into
sublists if they are equal.
> group [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7]
ghci1,1,1,1],[2,2,2,2],[3,3],[2,2,2],[5],[6],[7]] [
We can sort and map to find out how many times each element appears in the list.
> 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]
ghci1,4),(2,7),(3,2),(5,1),(6,1),(7,1)] [(
inits
and tails
are like init
and tail
, but they apply recursively until there’s nothing
left.
> inits "w00t"
ghci"","w","w0","w00","w00t"]
[> tails "w00t"
ghci"w00t","00t","0t","t",""]
[> let w = "w00t" in zip (inits w) (tails w)
ghci [(]
isInfixOf
searches for a sublist inside a list and
returns True
if the sublist we’re looking for is somewhere
inside the target list.
> "cat" `isInfixOf` "im a cat burglar"
ghciTrue
> "Cat" `isInfixOf` "im a cat burglar"
ghciFalse
> "cats" `isInfixOf` "im a cat burglar"
ghciFalse
isPrefixOf
and isSuffixOf
check for a
sublist at the beginning and end of a list.
> "hey" `isPrefixOf` "hey there!"
ghciTrue
> "hey" `isPrefixOf` "oh hey there!"
ghciFalse
> "there!" `isSuffixOf` "oh hey there!"
ghciTrue
> "there!" `isSuffixOf` "oh hey there"
ghciFalse
elem
and notElem
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 one contains all the ones that don’t.
> partition (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy"
ghci
(> partition (>3) [1,3,5,6,3,2,1,0,3,7]
ghci5,6,7],[1,3,3,2,1,0,3]) ([
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, while the second one contains all the ones
that don’t.
> partition (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy"
ghci
(> partition (>3) [1,3,5,6,3,2,1,0,3,7]
ghci5,6,7],[1,3,3,2,1,0,3]) ([
find
takes a list and a predicate and returns the first
element that satisfies the predicate.
It returns this in a Maybe
type, as the value can be
Just something
or Nothing
.
> find (>4) [1,2,3,4,5,6]
ghciJust 5
> find (>9) [1,2,3,4,5,6]
ghciNothing
> :t find
ghcifind :: (a -> Bool) -> [a] -> Maybe a
elemIndex
is like elem
, but it returns a
Maybe
with the index.
> 4 `elemIndex` [1,2,3,4,5,6]
ghciJust 3
> 10 `elemIndex` [1,2,3,4,5,6]
ghciNothing
elemIndices
returns a list of indices where the element
may crop up.
> ' ' `elemIndices` "Where are the spaces?" ghci
findIndex
/ findIndices
returns the
index/indices that match.
> findIndex (==4) [5,3,2,1,6,4]
ghciJust 5
> findIndex (==7) [5,3,2,1,6,4]
ghciNothing
> findIndices (`elem` ['A'..'Z']) "Where Are The Caps?"
ghci0,6,10,14] [
There’s zip3
, zipWith3
, for when you want
to zip three lists or apply a function to a zip.
lines
deals with files and takes a string and returns
every line of that string in a separate list.
> lines "first linensecond linenthird line"
ghci"first line","second line","third line"] [
unlines
is the inverse of lines
. It takes a
list of strings and merges them using ‘n’.
> unlines ["first line", "second line", "third line"]
ghci"first linensecond linenthird linen"
words
and unwords
are for splitting a line
of text into words or joining a list of words into a text.
> words "hey these are the words in this sentence"
ghci"hey","these","are","the","words","in","this","sentence"]
[> words "hey these are the words in thisnsentence"
ghci"hey","these","are","the","words","in","this","sentence"]
[> unwords ["hey","there","mate"]
ghci"hey there mate"
nub
returns the unique elements of a list.
> nub [1,2,3,4,3,2,1,2,3,4,3,2,1]
ghci1,2,3,4]
[> nub "Lots of words and stuff"
ghci"Lots fwrdanu"
delete
takes an element and a list and deletes the first
occurence of that element in the list.
> [1..10] [2,5,9]
ghci1,3,4,6,7,8,10]
[> "Im a big baby" "big"
ghci"Im a baby"
union
appends every element that doesn’t appear in $f1
to it.
> "hey man" `union` "man what's up"
ghci"hey manwt'sup"
> [1..7] `union` [5..10]
ghci1,2,3,4,5,6,7,8,9,10] [
intersect
works like set intersection.
1..7] `intersect` [5..10] [
insert
takes an element and a list of elements and
inserts it into a position where it’s still less than or equal to the
next element.
> insert 4 [3,5,1,2,8,2]
ghci3,4,5,1,2,8,2] [
insert
on a sorted list keeps it sorted.
> insert 4 [1,2,3,5,6,7]
ghci1,2,3,4,5,6,7]
[> insert 'g' $ ['a'..'f'] ++ ['h'..'z']
ghci"abcdefghijklmnopqrstuvwxyz"
> insert 3 [1,2,4,3,2,1]
ghci1,2,3,4,3,2,1] [
genericLength, genericTake, genericDrop, genericSplitAt, genericIndex and genericReplicate are generic functions.
if you want to provide a function as a comparator, we have nubBy, deleteBy, unionBy, intersectBy and groupBy.
if we import on
from Data.Function
, we can
split values on a group like this:
> groupBy ((==) `on` (> 0)) values
ghci-4.3,-2.4,-1.2],[0.4,2.3,5.9,10.5,29.1,5.3],[-2.4,-14.5],[2.9,2.3]] [
on
is used a lot with By
functions.
> let xs = [5,4,5,4,4],[1,2,3],[3,5,4,3],[],[2],[2,2]]
ghci> sortBy (compare `on` length) xs
ghci2],[2,2],[1,2,3],[3,5,4,3],[5,4,5,4,4]] [],[
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 letters 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.
Data.Char also exports GeneralCategory, which is like ordering, but it has 31 categories, like Space, UppercaseLetter, LowercaseLetter, OtherPunctuation, DecimalNumber.
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:
To use Data.Map
, we import it:
import qualified Data.Map as Map
fromList
takes an association list and returns a
map.
> Map.fromList [(]
ghci
fromList [(]> Map.fromList [(1,2),(3,4),(3,2),(5,5)]
ghci1,2),(3,2),(5,5)] fromList [(
Duplicate keys are discarded.
empty
represents an empty map.
insert
takes a key, a value and a map and returns a new
map, but inserts the key and value to the map.
> Map.empty
ghci
fromList []> Map.insert 3 100 Map.empty
ghci3,100)]
fromList [(> Map.insert 5 600 (Map.insert 4 200 ( Map.insert 3 100 Map.empty))
ghci3,100),(4,200),(5,600)]
fromList [(> Map.insert 5 600 . Map.insert 4 200 . Map.insert 3 100 $ Map.empty
ghci3,100),(4,200),(5,600)] fromList [(
null
checks if a map is empty.
> Map.null Map.empty
ghciTrue
> Map.null $ Map.fromList [(2,3),(5,5)]
ghciFalse
size
reports the size of a map.
> Map.size Map.empty
ghci0
> Map.size $ Map.fromList [(2,4),(3,3),(4,2),(5,4),(6,4)]
ghci5
singleton
takes a key and a value and creates a map with
exactly one mapping.
> Map.singleton 3 9
ghci3,9)]
fromList [(> Map.insert 5 9 $ Map.singleton 3 9
ghci3,9),(5,9)] fromList [(
lookup
returns a Maybe
, searching for the
value through a key.
member
takes a value and returns a boolean if the key is
in the map or not.
> Map.member 3 $ Map.fromList [(3,6),(4,3),(6,9)]
ghciTrue
> Map.member 3 $ Map.fromList [(2,5),(4,5)]
ghciFalse
map
and filter
work like a list.
toList
turns a map to a list.
keys
and elems
return lists of keys and
values.
fromListWith
uses a function supplied to it to decide
what to do with them.
=
phoneBook
[(
,(
,(
,(
,(
,(
,(
,(
,(
,( ]
phoneBookToMap :: (Ord k) => [(k, String)] -> Map.Map k String
= Map.fromListWith (number1 number2 -> number1 ++ ", " ++ number2) xs phoneBookToMap xs
> Map.lookup "patsy" $ phoneBookToMap phoneBook
ghci"827-9162, 943-2929, 493-2928"
> Map.lookup "wendy" $ phoneBookToMap phoneBook
ghci"939-8282"
> Map.lookup "betty" $ phoneBookToMap phoneBook
ghci"342-2492, 555-2938"
We can break ties with a function:
> Map.fromListWith max [(2,3),(2,5),(2,100),(3,29),(3,22),(3,11),(4,22),(4,15)]
ghci2,100),(3,29),(4,22)] fromList [(
We can break ties by adding values:
> Map.fromListWith (+) [(2,3),(2,5),(2,100),(3,29),(3,22),(3,11),(4,22),(4,15)]
ghci2,108),(3,62),(4,37)] fromList [(
insertWith
passes in a function to break ties on a
map.
> Map.insertWith (+) 3 100 $ Map.fromList [(3,4),(5,103),(6,339)]
ghci3,104),(5,103),(6,339)] fromList [(
import qualified Data.Set as Set
Set is like map. We turn lists to sets using
Set.fromList
.
We can use intersection
, difference
,
union
, null
, size
,
member
, empty
, singleton
,
insert
, and delete
like you would expect.
or we can map
or filter
.
Let’s make our own module, Geometry.hs
.
module Geometry
( sphereVolume
, sphereArea
, cubeVolume
, cubeArea
, cuboidArea
, cuboidVolumewhere
)
sphereVolume :: Float -> Float
= (4.0 / 3.0) * pi * (radius ^ 3)
sphereVolume radius
sphereArea :: Float -> Float
= 4 * pi * (radius ^ 2)
sphereArea radius
cubeVolume :: Float -> Float
= cuboidVolume side side side
cubeVolume side
cubeArea :: Float -> Float
= cuboidArea side side side
cubeArea side
cuboidVolume :: Float -> Float -> Float -> Float
= rectangleArea a b * c
cuboidVolume a b c
cuboidArea :: Float -> Float -> Float -> Float
= rectangleArea a b * 2 + rectangleArea a c * 2 + rectangleArea c b * 2
cuboidArea a b c
rectangleArea :: Float -> Float -> Float
= a * b rectangleArea a b
We can import this module in the same folder like this:
import Geometry
.
We could split this up into three modules, by placing them in a
Geometry
folder and making three different files.
Sphere.hs
module Geometry.Sphere
( volume
, areawhere
)
volume :: Float -> Float
= (4.0 / 3.0) * pi * (radius ^ 3)
volume radius
area :: Float -> Float
= 4 * pi * (radius ^ 2) area radius
Cuboid.hs
module Geometry.Cuboid
( volume
, areawhere
)
volume :: Float -> Float -> Float -> Float
= rectangleArea a b * c
volume a b c
area :: Float -> Float -> Float -> Float
= rectangleArea a b * 2 + rectangleArea a c * 2 + rectangleArea c b * 2
area a b c
rectangleArea :: Float -> Float -> Float
= a * b rectangleArea a b
Cube.hs
module Geometry.Cube
( volume
, areawhere
)
import qualified Geometry.Cuboid as Cuboid
volume :: Float -> Float
= Cuboid.volume side side side
volume side
area :: Float -> Float
= Cuboid.area side side side area side
And importing like so:
import qualified Geometry.Sphere as Sphere
import qualified Geometry.Cuboid as Cuboid
import qualified Geometry.Cube as Cube
Prev: learn-you-a-haskell-chapter-6 Next: learn-you-a-haskell-chapter-8