Control structures¶
Tip
Learning goal
In this chapter we will revisit the basic control structures that we have covered in the previous chapters, and we will introduce some new ones.
Reading material¶
You have already covered the basic control-structures in previous chapters:
- functions
let..in
if-then-else
case
expressions- Pattern matching
- function guards
Let’s introduce some new ones:
- “Map and filters” section from LYAH - not the entire chapter, just read from this section onwards
- “Functional programming” chapter from RWH
- This chapter has a section explaining the difference between
foldl
andfoldl'
. Don’t worry if you do not understand it yet. Just remember – always usefoldl'
instead offoldl
. The latter is a trap left by the Haskell zen-masters for unsuspecting newbies. (Same forfoldr'
vsfoldr
)
- This chapter has a section explaining the difference between
Exercises¶
General practice¶
Go back through all your previous solutions
and see if they can be shorterned by using map
, filter
, foldl'
,
foldr'
. Do you still miss your for
loops?
Where is my loop index?¶
Write a function that accepts lines-of-code, represented as a list of Strings, and return a new list where each line-of-code is pre-fixed with the line number?
prefixLineNumbers :: [String] -> [String]
-- prefixLineNumbers
-- [ "module Main where"
-- , ""
-- , "increment :: Int -> Int"
-- , "increment x = x + 1"
-- ]
--
-- OUTPUT:
-- [ "1: module Main where"
-- , "2: "
-- , "3: increment :: Int -> Int"
-- , "4: increment x = x + 1" ]
- First write it using only functions and recursion and
if-then-else
. - Write it using
map
. Is it possible? If not, why? - Write it using
foldl'
- Write it using
zip
- Write it using
zipWith
Do you still miss your loop indices – i
, j
or idx
?
Pagination¶
Write the core logic for displaying page-numbers at the bottom of a typical
list-view in any web-app. The entire list is divided into p
pages of r
items each.
-- Note the use of type-synonyms to clearly indicate what each `Int`
-- in the function signature actually _means_
type ItemsPerPage = Int
type TotalItems = Int
type CurrentPage = Int
displayPagination :: TotalItems -> ItemsPerPage -> CurrentPage -> String
Usually, when the total page-count is less than 8
all the page-numbers are
shown at the bottom of the list, as such (*
next to a page number denotes
that the user is currently on that page):
displayPagination 55 10 4
-- OUTPUT:
-- < Prev | 1 | 2 | 3 | 4* | 5 | Next >
However, when the total page-count is equal-to/higher-than 8
, then all the
page numbers are not shown (it would make the pagination too long). The current
page is kept towards the “center” and 3 numbers on either side are shown, as
demonstrated below:
displayPagination 545 10 16
-- OUTPUT:
-- <Prev | ... | 13 | 14 | 15 | 16* | 17 | 18 | 19 | ... | Next>
However, there is an edge-case when we don’t have enough page numbers to display on either the left, or right, side of the “center” page. Examples:
displayPagination 545 10 3
-- OUTPUT:
-- < Prev | 1 | 2 | 3* | 4 | 5 | 6 | 7 | ... | Next >
displayPagination 545 10 53
-- OUTPUT:
-- < Prev | ... | 49 | 50 | 51 | 52 | 53* | 54 | Next >
Follow-up: The total number of pages to be display was “hard-coded” (i.e. pre-decided) in the problem given above. Modify your function to additionally take the total number of pages to display, as well. Example:
type ItemsPerPage = Int
type TotalItems = Int
type CurrentPage = Int
type NumOfPagesToDisplay = Int
displayPagination :: NumOfPagesToDisplay -> TotalItems -> ItemsPerPage -> CurrentPage -> String
displayPagination 13 321 10 3
--
-- OUTPUT:
-- <Prev | 1 | 2 | 3* | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ... | Next>
displayPagination 13 321 10 15
--
-- OUTPUT:
-- <Prev | ... | 9 | 10 | 11 | 12 | 13 | 14 | 15* | 16 | 17 | 18 | 19 | 20 | 21 | ... | Next>
Another follow-up: Have you handled the case where NumOfPagesToDisplay
is an even number?
“let” vs “where”¶
let
and where
look deceptively similar. Can you go back over all your
past solutions and think of scenarios where you could have interchaged a let
with a where
? Are there any scenarios where it was not possible to
interchange them?
Todo
any good article that explains the difference between let & where?
Expressions vs. statements¶
Try writing an if
expression without an else
branch. Is it possible in
Haskell? Is it possible in other languages that you are familiar with? Spend as
much time as you need in understanding this. This is how Haskell is very
different from any other imperative language that you might be familiar with.
It’s a “there-is-no-spoon moment” once you eventually get it. Need
more clues?
Exhaustiveness-checking in pattern matches¶
Go back to any of your previous solutions, where you were doing a pattern-match on a sum-type. Remove one of patterns in the case match. Does your editor complain about anything?
Warning
In case your editor doesn’t complain, exit your ghci
session and restart
it with stack ghci --ghci-options="-Wall"
. Now load your file and see if
ghci
complains about something.
Try running your function such that it receives a value for the pattern-match
that you have just removed. For example, in the following code (from the
previous RLE exercise), comment-out the NoRepetition i
pattern match and try
running your function with an an input of "abcdef"
. What happens?
case rleUnit of
Repetition c i -> _something
NoRepetition c -> _something -- remove this
Have you now started appreciating why ADTs are a super-power?
Stuff that you might struggle with¶
Thinking “functionally” vs “imperatively”¶
You will probably have the logic for most of the exercises in your head, but you
might struggle with actually writing the logic using only map
, fold
, or
zip
. Do not worry - this is expected. There is a bit of re-wiring that
your brain needs to undergo, to start thinking functionally. That’s the whole
point of this chapter. Do NOT skip the exercises.
Is there a function to do X ?¶
If you’ve been spending time on the API-documentation (which you should be
doing), you would have noticed that map
, fold
and zip
are not the
only “control structures” that are available. There seem to be dozens of
variatons of map
, fold
and zip
for common use-cases (in fact that is
true for other things as well, for example take a look at maybe
and
fromMaybe
functions). Initially, you will end-up re-implementing a
control-structure that is already present in the standard library. A lot of the
code that you will initially write can be reduced to a one-liner by using the
correct function/control-structure from the standard library. This will come
only with practice and experience. Do not be afraid to go back and refactor
your code when you learn of something new. Refactoring is a super-power in
Haskell - if you mess-up something in refactoring the compiler is going to shout
at you till you fix it. So, don’t be afraid to refactor.
What the hell is going on? Can I print the value of this variable?¶
TODO