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`` and ``foldl'``. Don't worry if you do not understand it yet. Just remember -- **always use** ``foldl'`` instead of ``foldl``. The latter is a trap left by the Haskell zen-masters for unsuspecting newbies. (Same for ``foldr'`` vs ``foldr``) 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? .. code-block:: haskell 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. .. code-block:: haskell -- 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): .. code-block:: haskell 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: .. code-block:: haskell displayPagination 545 10 16 -- OUTPUT: -- 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: .. code-block:: haskell 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: .. code-block:: haskell type ItemsPerPage = Int type TotalItems = Int type CurrentPage = Int type NumOfPagesToDisplay = Int displayPagination :: NumOfPagesToDisplay -> TotalItems -> ItemsPerPage -> CurrentPage -> String displayPagination 13 321 10 3 -- -- OUTPUT: -- displayPagination 13 321 10 15 -- -- OUTPUT: -- **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? - What is the difference between expressions and statements in `C `_ and `Java `_? Are you writing statements, or expressions in Haskell? - Can you define a function in Haskell that has only ``let`` bindings? Why / why not? 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? .. code-block:: haskell 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