diff --git a/src/Chapter1.hs b/src/Chapter1.hs index 406deeac..0ff756a3 100644 --- a/src/Chapter1.hs +++ b/src/Chapter1.hs @@ -4,7 +4,6 @@ Happy to see you here, on the way to the wonderful Functional Programming land with Haskell! Fight the fierce Monad Dragon and save the globe from despicable runtime exceptions! - We appreciate your curiosity and will try to provide you with all the necessary equipment for your training before the battle in the real FP world. Learning Functional Programming can be challenging. But we designed this training to be @@ -43,7 +42,6 @@ concepts on the way. In this chapter, you are going to learn: ✧ How to write your function from scratch ✧ Some standard Haskell functions - We are leaving a number of tasks on our path. Your goal is to solve them all and make the test for Chapter One green. @@ -57,10 +55,6 @@ Now, if you are ready, let's start! -- Single-line comments in Haskell start with -- -{- | This tutorial uses block comments to explain various concepts and provide -task description. --} - {- All code in Haskell is organised into modules. Each module corresponds to a single file. Modules then can be combined in a package. But you don't need to worry about this for now. We already created the package with module hierarchy @@ -70,69 +64,70 @@ Each Haskell module starts with the "module where" line. Modules should have the same name as the corresponding file with the `.hs` extension. -} -module Chapter1 where -{- | -In Haskell, we have __expressions__. Expressions can be represented by some -primitive values (numbers: 1, 100; characters: 'a', 'z'; booleans: True, False; -etc.) or by a combination of the primitive values and other expressions using -language syntax constructions (if-then-else, let-in, case-of, etc.) and various -functions (addition — (+), division — div, maximum — max, sorting — sort, -sortBy, sortOn, etc.) and variables. Functions are also expressions as well as -variables. - -If an expression is a combination of other values and expressions, it can be -__evaluated__ (or reduced) to a primitive value. The evaluation process is not -immediate, since Haskell is a __lazy language__ and it won't evaluate -expressions unless really necessary. You can see the evaluation results either -by running a Haskell program or by playing with some functions in the -interactive interpreter (explained later). - -Haskell is a __strongly-typed__ language, which means that each expression has -a type. Each value and function is associated with some type. You can't change -the value type. You can only pass a value to some function that will do its -work, and maybe produce a value of a different type. - -Types can be _specific_ (like `Int`, `Integer`, `Double` or `Bool`) and they -always start with an uppercase letter, or _polymorphic_ (aka general) specified -through the variables – begin with a lowercase letter. The concept of -polymorphism is more sophisticated than working with concrete types, thus we -won't dive too much into it in this chapter and will work with concrete -types for now. - -Furthermore, Haskell is a __statically-typed__ language, which means that each -expression has the type known at compile-time, rather than run-time. It allows -the compiler to catch some kinds of bugs in your program early; before you -even run it. - -Additionally to static typing, Haskell has __type inference__. This means that -you _don't need_ to specify the type of each expression as it is going to be -found out for each expression and subexpression by the powerful compiler. - -However, you are __strongly encouraged to write top-level function type -signatures__ and provide types in different situations where you don't -immediately see what types will be inferred. --} +-- | This tutorial uses block comments to explain various concepts and provide +-- task description. +module Chapter1 where +-- | +-- In Haskell, we have __expressions__. Expressions can be represented by some +-- primitive values (numbers: 1, 100; characters: 'a', 'z'; booleans: True, False; +-- etc.) or by a combination of the primitive values and other expressions using +-- language syntax constructions (if-then-else, let-in, case-of, etc.) and various +-- functions (addition — (+), division — div, maximum — max, sorting — sort, +-- sortBy, sortOn, etc.) and variables. Functions are also expressions as well as +-- variables. +-- +-- If an expression is a combination of other values and expressions, it can be +-- __evaluated__ (or reduced) to a primitive value. The evaluation process is not +-- immediate, since Haskell is a __lazy language__ and it won't evaluate +-- expressions unless really necessary. You can see the evaluation results either +-- by running a Haskell program or by playing with some functions in the +-- interactive interpreter (explained later). +-- +-- Haskell is a __strongly-typed__ language, which means that each expression has +-- a type. Each value and function is associated with some type. You can't change +-- the value type. You can only pass a value to some function that will do its +-- work, and maybe produce a value of a different type. +-- +-- Types can be _specific_ (like `Int`, `Integer`, `Double` or `Bool`) and they +-- always start with an uppercase letter, or _polymorphic_ (aka general) specified +-- through the variables – begin with a lowercase letter. The concept of +-- polymorphism is more sophisticated than working with concrete types, thus we +-- won't dive too much into it in this chapter and will work with concrete +-- types for now. +-- +-- Furthermore, Haskell is a __statically-typed__ language, which means that each +-- expression has the type known at compile-time, rather than run-time. It allows +-- the compiler to catch some kinds of bugs in your program early; before you +-- even run it. +-- +-- Additionally to static typing, Haskell has __type inference__. This means that +-- you _don't need_ to specify the type of each expression as it is going to be +-- found out for each expression and subexpression by the powerful compiler. +-- +-- However, you are __strongly encouraged to write top-level function type +-- signatures__ and provide types in different situations where you don't +-- immediately see what types will be inferred. - {- +{- Haskell is a __compiled__ language. In the illustration below, you can see the overall picture of the process from your code to the binary of the written program: +-----------+ +-------------+ +-------------+ -| | | | | | -| Parsing +------> Compilation +-----> Executable | -| | | | | | +\| | | | | | +\| Parsing +------> Compilation +-----> Executable | +\| | | | | | +-----------+ +-------------+ +-------------+ In comparison, such languages as Python, JavaScript, etc. are interpreted languages. And that could be illustrated in this different workflow: +-----------+ +----------------+ +------------+ -| | | | | | -| Parsing +------> Interpretation +-----> Evaluation | -| | | | | | +\| | | | | | +\| Parsing +------> Interpretation +-----> Evaluation | +\| | | | | | +-----------+ +----------------+ +------------+ So, when working with Haskell, you first need to compile the code in order to @@ -152,7 +147,7 @@ Assuming that you already have the Haskell compiler installed (we recommend GHC), you can start GHCi by executing the following command in your terminal from the root of this project. -$ ghci +\$ ghci > If you don't have Haskell yet, refer to the corresponding section of the README. @@ -174,7 +169,6 @@ Now, you can evaluate some expressions and see their results immediately. evaluated in GHCi, but our testing system doesn't check their output. They are here just to showcase the different usages of GHCi. - GHCi can do much more than evaluating expressions. It also contains some special commands starting with a colon. For example, to see the list of all available commands, type ":?" in your GHCi. @@ -187,474 +181,468 @@ ghci> :q -} -{- | -=⚔️= Task 1 - -Since types play a crucial role in Haskell, we can start by exploring the types of -some basic expressions. You can inspect the type of expression by using the ":t" -command in GHCi (short for ":type"). - -For example: - ->>> :t False -False :: Bool - -"::" in Haskell indicates that the type of the expression before, would be -specified after these symbols. -So, the output in this example means that 'False' has type 'Bool'. - -(ノ◕ヮ◕)ノ Your first task! Use GHCi to discover the types of the following - expressions and functions: - -> Try to guess first and then compare your expectations with GHCi output - ->>> :t True - ->>> :t 'a' - ->>> :t 42 - - -A pair of boolean and char: ->>> :t (True, 'x') - - -Boolean negation: ->>> :t not - - -Boolean 'and' operator: ->>> :t (&&) - - -Addition of two numbers: ->>> :t (+) - - -Maximum of two values: ->>> :t max - - -You might not understand each type at this moment, but don't worry! You've only -started your Haskell journey. Types will become your friends soon. - -Primitive types in Haskell include 'Int', 'Bool', 'Double', 'Char' and many -more. You've also seen the arrow "->" which is a function. When you see "A -> B --> C" you can think that this is a function that takes two arguments of types -"A" and "B" and returns a value of type "C". --} - -{- | -=⚔️= Task 2 - -After having our first look at the Haskell type system, we can do something more -exciting. Call to arms! In other words, let's call some functions. - -When calling a function in Haskell, you type a name of the function first, and -then you specify space-separated function arguments. That's right. No commas, no -parentheses. You only need to use () when grouping arguments (e.g. using other -expressions as arguments). - -For example, if the function `foo` takes two arguments, the call of this -function can look like this: - -ghci> foo arg1 (fun arg2) - -Operators in Haskell are also functions, and you can define your own operators -as well! The important difference between operators and functions is that -functions are specified using alphanumeric symbols, and operators are specified -using "operator" symbols. For example, addition — +, cons — :, list append — ++, -diamond operator — <>. Also, by default, you call operators in __infix__ -form (operator goes __after__ the first argument), while ordinary functions are -what-called __prefix__ form (the name goes first, before all arguments). - -ghci> :t add -add :: Int -> Int -> Int -ghci> :t (+) -(+) :: Int -> Int -> Int -ghci> add 1 2 -3 -ghci> 1 + 2 -3 - -♫ NOTE: in reality, the type of the + operator is the following: - ->>> :t (+) -(+) :: Num a => a -> a -> a - -> It may look scary to you, but we will cover all this 'Num' and "=>" later. For - now, you can think of this as a polymorphic function — in this case, the - operator, that can work with any numeric type, including 'Ints, 'Doubles, - etc. Or you can even pass the "+d" option to the ":t" command to see a simpler - type. In this case, polymorphic types will default to some standard types: - -ghci> :t +d (+) -(+) :: Integer -> Integer -> Integer - -Get ready for the next task, brave programmer! Evaluate the following -expressions in GHCi - -> As in the previous task, try to guess first and then compare your expectations - with the GHCi output. - -🕯 HINT: if you are curious, it might be interesting to explore the types of - functions and operators first. Remember this from the previous task? ;) - ->>> 1 + 2 - - ->>> 10 - 15 - - ->>> 10 - (-5) -- negative constants require () - - ->>> (3 + 5) < 10 - - ->>> True && False - - ->>> 10 < 20 || 20 < 5 - - ->>> 2 ^ 10 -- power - - ->>> not False - - ->>> div 20 3 -- integral division - - ->>> mod 20 3 -- integral division remainder - - ->>> max 4 10 - - ->>> min 5 (max 1 2) - - ->>> max (min 1 10) (min 5 7) - - -Because Haskell is a __statically-typed__ language, you see an error each time -you try to mix values of different types in situations where you are not -supposed to. Try evaluating the following expressions to see errors: - -ghci> not 'a' -ghci> max True 'x' -ghci> 10 + True - -This is a gentle way to get familiar with various error messages in Haskell. -In some cases, the error messages can be challenging to decipher and -understand their meaning. Haskell has a bad reputation for having not-so-helpful -error messages in some situations. But, of course, such a small challenge won't -stop you, right? You're a brave warrior, and you can finish all tasks despite -all obstacles! And we are always here to help and to decrypt these ancient -scripts together. --} - - -{- | -=🛡= Defining a function - -We have already learned how to use different functions and operators in Haskell. -Let's now check how they are defined and whether we can introduce our own. - -When defining a function in Haskell, you write its type signature on the first -line, and then its body on the following line(s). The type signature should be -written immediately from the start of a line. Haskell is an __indentation-__ and -__layout-sensitive__ language, so this is important to keep in mind. - -For example, here is the type signature of a function that takes a 'Double' and -an 'Int', and then returns an 'Int': - -@ -roundSubtract :: Double -> Int -> Int -@ - -We have already seen the "::" sequence of symbols when practising our skills in -GHCi. Now you know that this is the syntax for specifying types in your code as -well. - -The following line should be the function definition start line. You write the -function name again and give argument names in the same order as you wrote the types -followed by the "=" sign. And you provide the function implementation after "=". - -@ -roundSubtract x y = ceiling x - y -@ - -^ Here x corresponds to the 'Double', and y to 'Int'. - -The body of the function can be as big as you want. However, don't forget about -the indentation rules when your body exceeds the definition line. - -The same function body can be written on a separate line, minding the -indentation. - -@ -roundSubtract x y = - ceiling x - y -@ - -Putting everything together, the complete function definition looks like this: - -@ -roundSubtract :: Double -> Int -> Int -roundSubtract x y = ceiling x - y -@ - -Now you are ready for defining your own functions! --} - -{- | -In our training, for some functions types are provided for you. For others, you -need to write types manually to challenge yourself. - -Don't forget the main rule: -**Always provide type signatures for top-level functions in Haskell.** --} - - -{- | -=⚔️= Task 3 - -Below you see the function that finds the square of the sum of two integers. Your -task is to specify the type of this function. - ->>> squareSum 3 4 -49 --} - +-- | +-- =⚔️= Task 1 +-- +-- Since types play a crucial role in Haskell, we can start by exploring the types of +-- some basic expressions. You can inspect the type of expression by using the ":t" +-- command in GHCi (short for ":type"). +-- +-- For example: +-- +-- >>> :t False +-- False :: Bool +-- +-- "::" in Haskell indicates that the type of the expression before, would be +-- specified after these symbols. +-- So, the output in this example means that 'False' has type 'Bool'. +-- +-- (ノ◕ヮ◕)ノ Your first task! Use GHCi to discover the types of the following +-- expressions and functions: +-- +-- > Try to guess first and then compare your expectations with GHCi output +-- +-- >>> :t True +-- True :: Bool +-- >>> :t 'a' +-- 'a' :: Char +-- >>> :t 42 +-- 42 :: Num a => a +-- +-- A pair of boolean and char: +-- >>> :t (True, 'x') +-- (True, 'x') :: (Bool, Char) +-- +-- Boolean negation: +-- >>> :t not +-- not :: Bool -> Bool +-- +-- Boolean 'and' operator: +-- >>> :t (&&) +-- (&&) :: Bool -> Bool -> Bool +-- +-- Addition of two numbers: +-- >>> :t (+) +-- (+) :: Num a => a -> a -> a +-- +-- Maximum of two values: +-- >>> :t max +-- max :: Ord a => a -> a -> a +-- +-- You might not understand each type at this moment, but don't worry! You've only +-- started your Haskell journey. Types will become your friends soon. +-- +-- Primitive types in Haskell include 'Int', 'Bool', 'Double', 'Char' and many +-- more. You've also seen the arrow "->" which is a function. When you see "A -> B +-- -> C" you can think that this is a function that takes two arguments of types +-- "A" and "B" and returns a value of type "C". + +-- | +-- =⚔️= Task 2 +-- +-- After having our first look at the Haskell type system, we can do something more +-- exciting. Call to arms! In other words, let's call some functions. +-- +-- When calling a function in Haskell, you type a name of the function first, and +-- then you specify space-separated function arguments. That's right. No commas, no +-- parentheses. You only need to use () when grouping arguments (e.g. using other +-- expressions as arguments). +-- +-- For example, if the function `foo` takes two arguments, the call of this +-- function can look like this: +-- +-- ghci> foo arg1 (fun arg2) +-- +-- Operators in Haskell are also functions, and you can define your own operators +-- as well! The important difference between operators and functions is that +-- functions are specified using alphanumeric symbols, and operators are specified +-- using "operator" symbols. For example, addition — +, cons — :, list append — ++, +-- diamond operator — <>. Also, by default, you call operators in __infix__ +-- form (operator goes __after__ the first argument), while ordinary functions are +-- what-called __prefix__ form (the name goes first, before all arguments). +-- +-- ghci> :t add +-- add :: Int -> Int -> Int +-- ghci> :t (+) +-- (+) :: Int -> Int -> Int +-- ghci> add 1 2 +-- 3 +-- ghci> 1 + 2 +-- 3 +-- +-- ♫ NOTE: in reality, the type of the + operator is the following: +-- +-- >>> :t (+) +-- (+) :: Num a => a -> a -> a +-- +-- > It may look scary to you, but we will cover all this 'Num' and "=>" later. For +-- now, you can think of this as a polymorphic function — in this case, the +-- operator, that can work with any numeric type, including 'Ints, 'Doubles, +-- etc. Or you can even pass the "+d" option to the ":t" command to see a simpler +-- type. In this case, polymorphic types will default to some standard types: +-- +-- ghci> :t +d (+) +-- (+) :: Integer -> Integer -> Integer +-- +-- Get ready for the next task, brave programmer! Evaluate the following +-- expressions in GHCi +-- +-- > As in the previous task, try to guess first and then compare your expectations +-- with the GHCi output. +-- +-- 🕯 HINT: if you are curious, it might be interesting to explore the types of +-- functions and operators first. Remember this from the previous task? ;) +-- +-- >>> 1 + 2 +-- 3 +-- +-- >>> 10 - 15 +-- -5 +-- +-- >>> 10 - (-5) -- negative constants require () +-- 15 +-- +-- >>> (3 + 5) < 10 +-- True +-- +-- >>> True && False +-- False +-- +-- >>> 10 < 20 || 20 < 5 +-- True +-- +-- >>> 2 ^ 10 -- power +-- 1024 +-- +-- >>> not False +-- True +-- +-- >>> div 20 3 -- integral division +-- 6 +-- +-- >>> mod 20 3 -- integral division remainder +-- 2 +-- +-- >>> max 4 10 +-- 10 +-- +-- >>> min 5 (max 1 2) +-- 2 +-- +-- >>> max (min 1 10) (min 5 7) +-- 5 +-- +-- Because Haskell is a __statically-typed__ language, you see an error each time +-- you try to mix values of different types in situations where you are not +-- supposed to. Try evaluating the following expressions to see errors: +-- +-- ghci> not 'a' +-- ghci> max True 'x' +-- ghci> 10 + True +-- +-- This is a gentle way to get familiar with various error messages in Haskell. +-- In some cases, the error messages can be challenging to decipher and +-- understand their meaning. Haskell has a bad reputation for having not-so-helpful +-- error messages in some situations. But, of course, such a small challenge won't +-- stop you, right? You're a brave warrior, and you can finish all tasks despite +-- all obstacles! And we are always here to help and to decrypt these ancient +-- scripts together. + +-- | +-- =🛡= Defining a function +-- +-- We have already learned how to use different functions and operators in Haskell. +-- Let's now check how they are defined and whether we can introduce our own. +-- +-- When defining a function in Haskell, you write its type signature on the first +-- line, and then its body on the following line(s). The type signature should be +-- written immediately from the start of a line. Haskell is an __indentation-__ and +-- __layout-sensitive__ language, so this is important to keep in mind. +-- +-- For example, here is the type signature of a function that takes a 'Double' and +-- an 'Int', and then returns an 'Int': +-- +-- @ +-- roundSubtract :: Double -> Int -> Int +-- @ +-- +-- We have already seen the "::" sequence of symbols when practising our skills in +-- GHCi. Now you know that this is the syntax for specifying types in your code as +-- well. +-- +-- The following line should be the function definition start line. You write the +-- function name again and give argument names in the same order as you wrote the types +-- followed by the "=" sign. And you provide the function implementation after "=". +-- +-- @ +-- roundSubtract x y = ceiling x - y +-- @ +-- +-- ^ Here x corresponds to the 'Double', and y to 'Int'. +-- +-- The body of the function can be as big as you want. However, don't forget about +-- the indentation rules when your body exceeds the definition line. +-- +-- The same function body can be written on a separate line, minding the +-- indentation. +-- +-- @ +-- roundSubtract x y = +-- ceiling x - y +-- @ +-- +-- Putting everything together, the complete function definition looks like this: +-- +-- @ +-- roundSubtract :: Double -> Int -> Int +-- roundSubtract x y = ceiling x - y +-- @ +-- +-- Now you are ready for defining your own functions! + +-- | +-- In our training, for some functions types are provided for you. For others, you +-- need to write types manually to challenge yourself. +-- +-- Don't forget the main rule: +-- **Always provide type signatures for top-level functions in Haskell.** + +-- | +-- =⚔️= Task 3 +-- +-- Below you see the function that finds the square of the sum of two integers. Your +-- task is to specify the type of this function. +-- +-- >>> squareSum 3 4 +-- 49 +squareSum :: Int -> Int -> Int squareSum x y = (x + y) * (x + y) - -{- | -=⚔️= Task 4 - -Implement the function that takes an integer value and returns the next 'Int'. - ->>> next 10 -11 ->>> next (-4) --3 - -♫ NOTE: The current function body is defined using a special function called - "error". Don't panic, it is not broken. 'error' is like a placeholder, that - evaluates to an exception if you try evaluating it. And it also magically fits - every type 。.☆.*。. No need to worry much about "error" here, just replace the - function body with the proper implementation. --} +-- | +-- =⚔️= Task 4 +-- +-- Implement the function that takes an integer value and returns the next 'Int'. +-- +-- >>> next 10 +-- 11 +-- >>> next (-4) +-- -3 +-- +-- ♫ NOTE: The current function body is defined using a special function called +-- "error". Don't panic, it is not broken. 'error' is like a placeholder, that +-- evaluates to an exception if you try evaluating it. And it also magically fits +-- every type 。.☆.*。. No need to worry much about "error" here, just replace the +-- function body with the proper implementation. next :: Int -> Int -next x = error "next: not implemented!" - -{- | -After you've implemented the function (or even during the implementation), you -can run it in GHCi with your input. To do so, first, you need to load the module -with the function using the ":l" (short for ":load") command. - -ghci> :l src/Chapter1.hs - -After that, you can call the 'next' function as you already know how to do that. -Or any other function defined in this module! But remember, that you need to -reload the module again after you change the file's content. You can reload the -last loaded module by merely typing the ":r" command (no need to specify the -name again). - -ghci> :r - -A typical workflow looks like this: you load the module once using the ":l" -command, and then you should reload it using the ":r" command each time you -change it and want to check your changes. --} - -{- | -=⚔️= Task 5 - -Implement a function that returns the last digit of a given number. - ->>> lastDigit 42 -2 +next x = x + 1 + +-- | +-- After you've implemented the function (or even during the implementation), you +-- can run it in GHCi with your input. To do so, first, you need to load the module +-- with the function using the ":l" (short for ":load") command. +-- +-- ghci> :l src/Chapter1.hs +-- +-- After that, you can call the 'next' function as you already know how to do that. +-- Or any other function defined in this module! But remember, that you need to +-- reload the module again after you change the file's content. You can reload the +-- last loaded module by merely typing the ":r" command (no need to specify the +-- name again). +-- +-- ghci> :r +-- +-- A typical workflow looks like this: you load the module once using the ":l" +-- command, and then you should reload it using the ":r" command each time you +-- change it and want to check your changes.n + +-- | +-- =⚔️= Task 5 +-- +-- Implement a function that returns the last digit of a given number. +-- +-- >>> lastDigit 42 +-- 2 +-- +-- 🕯 HINT: use the `mod` function +-- +-- ♫ NOTE: You can discover possible functions to use via Hoogle: +-- https://hoogle.haskell.org/ +-- +-- Hoogle lets you search Haskell functions either by name or by type. You can +-- enter the type you expect a function to have, and Hoogle will output relevant +-- results. Or you can try to guess the function name, search for it and check +-- whether it works for you! -🕯 HINT: use the `mod` function - -♫ NOTE: You can discover possible functions to use via Hoogle: - https://hoogle.haskell.org/ - - Hoogle lets you search Haskell functions either by name or by type. You can - enter the type you expect a function to have, and Hoogle will output relevant - results. Or you can try to guess the function name, search for it and check - whether it works for you! --} -- DON'T FORGET TO SPECIFY THE TYPE IN HERE -lastDigit n = error "lastDigit: Not implemented!" - - -{- | -=⚔️= Task 6 - -Implement a function, that takes two numbers and returns the one closer to zero: - ->>> closestToZero 10 5 -5 ->>> closestToZero (-7) 3 -3 - - -🕯 HINT: You can use the 'abs' function and the __if-then-else__ Haskell syntax - for this task. - -'if-then-else' is a language construct for an expression that returns only one -branch depending on the checked condition. For example: - ->>> if even 10 then 0 else 1 -0 - -The 'if-then-else' constructs must always have both __then__ and __else__ -branches because it is an expression and it must always return some value. - -👩‍🔬 Due to lazy evaluation in Haskell, only the expression from the branch - satisfying the check will be returned and, therefore, evaluated. --} +lastDigit :: Int -> Int +lastDigit n = if n > 0 then mod n 10 else mod (abs n) 10 + +-- | +-- =⚔️= Task 6 +-- +-- Implement a function, that takes two numbers and returns the one closer to zero: +-- +-- >>> closestToZero 10 5 +-- 5 +-- >>> closestToZero (-7) 3 +-- 3 +-- +-- +-- 🕯 HINT: You can use the 'abs' function and the __if-then-else__ Haskell syntax +-- for this task. +-- +-- 'if-then-else' is a language construct for an expression that returns only one +-- branch depending on the checked condition. For example: +-- +-- >>> if even 10 then 0 else 1 +-- 0 +-- +-- The 'if-then-else' constructs must always have both __then__ and __else__ +-- branches because it is an expression and it must always return some value. +-- +-- 👩‍🔬 Due to lazy evaluation in Haskell, only the expression from the branch +-- satisfying the check will be returned and, therefore, evaluated. closestToZero :: Int -> Int -> Int -closestToZero x y = error "closestToZero: not implemented!" - - -{- | -=⚔️= Task 7 -Write a function that returns the middle number among three given numbers. - ->>> mid 3 1 2 -2 - -🕯 HINT: When checking multiple conditions, it is more convenient to use the - language construct called "guards" instead of multiple nested 'if-then-else' - expressions. The syntax of guards is the following: - -@ -sign :: Int -> Int -sign n - | n < 0 = (-1) - | n == 0 = 0 - | otherwise = 1 -@ - -You define different conditions in different branches, started by the '|' -symbol. And the functions check them from top to bottom, returning the first -value after "=" where the condition is true. - -♫ NOTE: The "=" sign goes after each branch, respectively. - -♫ NOTE: It is essential to have the same indentation before each branch "|"! - Remember, that Haskell is an indentation- and layout-sensitive language. - -Casual reminder about adding top-level type signatures for all functions :) --} - -mid x y z = error "mid: not implemented!" - -{- | -=⚔️= Task 8 - -Implement a function that checks whether a given character is a vowel. - -🕯 HINT: use guards - ->>> isVowel 'a' -True ->>> isVowel 'x' -False --} -isVowel c = error "isVowel: not implemented!" - - -{- | -== Local variables and functions - -So far, we've been playing only with simple expressions and function -definitions. However, in some cases, expressions may become complicated, and it -could make sense to introduce some helper variables. - -You can use the let-in construct in Haskell to define variables. -Here goes an example: - -@ -half :: Int -> Int -half n = let halfN = div n 2 in halfN -@ - -♫ NOTE: __let-in__ is also an expression! You can't just define variables; you - also need to return some expression that may use defined variables. - -The syntax for defining multiple variables requires to care about indentation -more, but there is nothing special in it as well: - -@ -halfAndTwice :: Int -> (Int, Int) -halfAndTwice n = - let halfN = div n 2 - twiceN = n * 2 - in (halfN, twiceN) -@ - -In addition to let-in (or sometimes even alternatively to let-in) you can use -the __where__ construct to define local variables and functions. -And, again, the example: - -@ -pythagoras :: Double -> Double -> Double -pythagoras a b = square a + square b +closestToZero x y = if abs x > abs y then y else x + +-- | +-- =⚔️= Task 7 +-- Write a function that returns the middle number among three given numbers. +-- +-- >>> mid 3 1 2 +-- 2 +-- +-- 🕯 HINT: When checking multiple conditions, it is more convenient to use the +-- language construct called "guards" instead of multiple nested 'if-then-else' +-- expressions. The syntax of guards is the following: +-- +-- @ +-- sign :: Int -> Int +-- sign n +-- | n < 0 = (-1) +-- | n == 0 = 0 +-- | otherwise = 1 +-- @ +-- +-- You define different conditions in different branches, started by the '|' +-- symbol. And the functions check them from top to bottom, returning the first +-- value after "=" where the condition is true. +-- +-- ♫ NOTE: The "=" sign goes after each branch, respectively. +-- +-- ♫ NOTE: It is essential to have the same indentation before each branch "|"! +-- Remember, that Haskell is an indentation- and layout-sensitive language. +-- +-- Casual reminder about adding top-level type signatures for all functions :) +mid :: Int -> Int -> Int -> Int +mid x y z + | (y >= x && x >= z) || (z >= x && x >= y) = x + | (x >= y && y >= z) || (z >= y && y >= x) = y + | otherwise = z + +-- | +-- =⚔️= Task 8 +-- +-- Implement a function that checks whether a given character is a vowel. +-- +-- 🕯 HINT: use guards +-- +-- >>> isVowel 'a' +-- True +-- >>> isVowel 'x' +-- False +isVowel :: Char -> Bool +isVowel c + | c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' = True + | otherwise = False + +-- | +-- == Local variables and functions +-- +-- So far, we've been playing only with simple expressions and function +-- definitions. However, in some cases, expressions may become complicated, and it +-- could make sense to introduce some helper variables. +-- +-- You can use the let-in construct in Haskell to define variables. +-- Here goes an example: +-- +-- @ +-- half :: Int -> Int +-- half n = let halfN = div n 2 in halfN +-- @ +-- +-- ♫ NOTE: __let-in__ is also an expression! You can't just define variables; you +-- also need to return some expression that may use defined variables. +-- +-- The syntax for defining multiple variables requires to care about indentation +-- more, but there is nothing special in it as well: +-- +-- @ +-- halfAndTwice :: Int -> (Int, Int) +-- halfAndTwice n = +-- let halfN = div n 2 +-- twiceN = n * 2 +-- in (halfN, twiceN) +-- @ +-- +-- In addition to let-in (or sometimes even alternatively to let-in) you can use +-- the __where__ construct to define local variables and functions. +-- And, again, the example: +-- +-- @ +-- pythagoras :: Double -> Double -> Double +-- pythagoras a b = square a + square b +-- where +-- square :: Double -> Double +-- square x = x ^ 2 +-- @ +-- +-- You can define multiple functions inside __where__! +-- Just remember to keep proper indentation. + +-- | +-- =⚔️= Task 9 +-- +-- Implement a function that returns the sum of the last two digits of a number. +-- +-- >>> sumLast2 42 +-- 6 +-- >>> sumLast2 134 +-- 7 +-- >>> sumLast2 1 +-- 1 +-- +-- Try to introduce variables in this task (either with let-in or where) to avoid +-- specifying complex expressions. +sumLast2 :: Int -> Int +sumLast2 n = addLast2 (last2 n) where - square :: Double -> Double - square x = x ^ 2 -@ - -You can define multiple functions inside __where__! -Just remember to keep proper indentation. --} - -{- | -=⚔️= Task 9 - -Implement a function that returns the sum of the last two digits of a number. - ->>> sumLast2 42 -6 ->>> sumLast2 134 -7 ->>> sumLast2 1 -1 - -Try to introduce variables in this task (either with let-in or where) to avoid -specifying complex expressions. --} - -sumLast2 n = error "sumLast2: Not implemented!" - - -{- | -=💣= Task 10* - -You did it! You've passed all the challenges in your first training! -Congratulations! -Now, are you ready for the boss at the end of this training??? - -Implement a function that returns the first digit of a given number. - ->>> firstDigit 230 -2 ->>> firstDigit 5623 -5 - -You need to use recursion in this task. Feel free to return to it later, if you -aren't ready for this boss yet! --} - -firstDigit n = error "firstDigit: Not implemented!" - + last2 :: Int -> Int + last2 x = if x > 0 then mod x 100 else mod (abs x) 100 + addLast2 :: Int -> Int + addLast2 x = div x 10 + mod x 10 + +-- | +-- =💣= Task 10* +-- +-- You did it! You've passed all the challenges in your first training! +-- Congratulations! +-- Now, are you ready for the boss at the end of this training??? +-- +-- Implement a function that returns the first digit of a given number. +-- +-- >>> firstDigit 230 +-- 2 +-- >>> firstDigit 5623 +-- 5 +-- +-- You need to use recursion in this task. Feel free to return to it later, if you +-- aren't ready for this boss yet! +firstDigit :: Int -> Int +firstDigit n + | n < 0 = firstDigit (abs n) + | n < 10 = n + | otherwise = firstDigit (div n 10) {- You did it! Now it is time to open a pull request with your changes diff --git a/src/Chapter2.hs b/src/Chapter2.hs index b98ceaf7..832c240d 100644 --- a/src/Chapter2.hs +++ b/src/Chapter2.hs @@ -75,791 +75,787 @@ When working with lists, the most practical module will be "Data.List": * https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-List.html -} - -{- | -=🛡= Lists - -__List__ is a crucial data type in Haskell and functional programming -in general. It represents a collection of elements of the _same type_. -The list type is written as the type of list element in square -brackets. For example, a list of integers will have type '[Int]' and a -list of booleans — '[Bool]'. - -[Interesting fact]: String in Haskell is a list of characters ('Char' -data type in Haskell) and is written as '[Char]'. But Haskell also -provides the "String" alias to '[Char]'. So, in some places, you will -see 'String', and in others, you will see '[Char]', but they mean the -same thing. We will explain better how it works in Chapter Three. For -now, you only need to know that you can use 'String' and '[Char]' -interchangeably. - -To create a list, you need to put elements in square brackets (the -same as used for the list type) and separate them by commas. Such -expressions are called __list literals__. For example, the expression -"[3, 5, 1]" creates a list of three numbers, where the first list -element is number 3, and the last element is number 1. Similarly, you -can create a list of two booleans: [False, True]. A list without -elements is just []. - -You probably noticed that lists could be of any type of -elements. Often you want to write a function that works with lists of -any type (but consistent inside one list). This feature is called -__parametric polymorphism__. It will be explained in more detail -later, but when working with lists, you often will see type signatures -like: - -@ -foo :: [a] -> [b] -> [a] -@ - -The above type signature means that this function takes two lists: one -with elements of some type "a" and another with elements of type "b" -(the function doesn't care about the specific types) and returns the -list with elements of the same type as the first list. Such words "a" -and "b" are called __type variables__. - -For comparison, specific types in Haskell start with an uppercase -letter (Int, Bool, Char, etc.), where type variables begin with a -lowercase letter (a, b, el, etc.). This is the way to distinguish -between these types. - -The Haskell standard library already provides a lot of functions to -work with lists. And you will need to operate a lot with standard -functions in the upcoming exercises. Remember, Hoogle is your friend! --} - -{- | -=⚔️= Task 1 - -Explore lists by checking types of various list expressions and -functions in GHCi and insert the corresponding resulting output below: - -List of booleans: ->>> :t [True, False] - - -String is a list of characters: ->>> :t "some string" - - -Empty list: ->>> :t [] - - -Append two lists: ->>> :t (++) - - -Prepend an element at the beginning of a list: ->>> :t (:) - - -Reverse a list: ->>> :t reverse - - -Take first N elements of a list: ->>> :t take - - -Create a list from N same elements: ->>> :t replicate - - -Split a string by line breaks: ->>> :t lines - - -Join a list of strings with line breaks: ->>> :t unlines - - --} - -{- | -=⚔️= Task 2 - -To understand the list type better, it is also beneficial to play with -list expressions in REPL. - -Evaluate the following expressions in GHCi and insert the answers. Try -to guess first, what you will see. - ->>> [10, 2] ++ [3, 1, 5] - ->>> [] ++ [1, 4] -- [] is an empty list - ->>> 3 : [1, 2] - ->>> 4 : 2 : [5, 10] -- prepend multiple elements - ->>> [1 .. 10] -- list ranges - ->>> [10 .. 1] - ->>> [10, 9 .. 1] -- backwards list with explicit step - ->>> length [4, 10, 5] -- list length - ->>> replicate 5 True - ->>> take 5 "Hello, World!" - ->>> drop 5 "Hello, World!" - ->>> zip "abc" [1, 2, 3] -- convert two lists to a single list of pairs - ->>> words "Hello Haskell World!" -- split the string into the list of words - - - -👩‍🔬 Haskell has a lot of syntax sugar. In the case with lists, any - list literal like "[3, 1, 2]" is syntax sugar for prepending elements - at the empty list: "3 : 1 : 2 : []". - -Don't forget that lists are the containers of the same-type -elements. Meaning, you can't combine lists of different types in any -situation. Let's try appending a list of booleans and a string (list -of characters) to see the error message: - -ghci> [True, False] ++ "string" -:4:18: error: - • Couldn't match type ‘Char’ with ‘Bool’ - Expected type: [Bool] - Actual type: [Char] - • In the second argument of ‘(++)’, namely ‘"string"’ - In the expression: [True, False] ++ "string" - In an equation for ‘it’: it = [True, False] ++ "string" - --} - -{- | -=🛡= Immutability - -At this point in our training, you need to learn that all values in -Haskell are immutable! Woohoo! But what does it mean for us? - -It means that when you apply a function to some variable, the value is -not changed. Instead, you create a new value each time. - ->>> import Data.List (sort) -- sort is not in Prelude ->>> x = [3, 1, 2] -- you can assign values to variables in GHCi ->>> sort x -[1,2,3] ->>> x -[3,1,2] - -The 'sort' function returns a new sorted list. It doesn't change the -original list, so you don't need to worry about accidentally spoiling -values of variables you defined before. --} - -{- | -=🛡= List implementation - -Let's talk a bit about list implementation details. Lists in Haskell -are implemented as __linked lists__ (or cons-lists). And because -everything in Haskell is immutable, adding elements at the beginning -of the lists is cheap. Haskell doesn't need to allocate new memory and -copy the whole list there; it can just create a new list from a new -element and a pointer to an already existing list. In other words, -tails of lists are shared. - -For these reasons, adding elements to and extracting elements from the -beginning of a list is much cheaper and faster than working with the -end of the list. - -In some sense, lists are similar to trains. Let's look at an illustration -of a two-element list: - - . . . . . o o o o o - _________ _________ ____ o - | y | | x | |[]\_n][. -_|________|_o_|________|_o_|__|____)< - oo oo oo oo oo 00-oo\_ - - y : x : [] - -You can see that adding new elements (railway carriages) to the left -is easy: you just need to connect them to the last element in the -chain. - - . . . . . o o o o o - _________ _________ _________ ____ o - | z | | y | | x | |[]\_n][. - _|________|_o_|________|_o_|________|_o_|__|____)< - oo oo oo oo oo oo oo 00-oo\_ - - z : y : x : [] - -But imagine how much difficult it would be to add new carriages to the right? - - . . . . . o o o o o - _________ _________ _________ ____ o - | y | | x | | z | |[]\_n][. - _|________|_o_|________|_o_|________|_o_|__|____)< - oo oo oo oo oo oo oo 00-oo\_ - - y : x : z : [] - -You can't simply attach a new carriage anymore. You need to detach the -locomotive, maybe move trains around the railway a bit for the proper -position, and only then attach everything back again. The same thing -with adding elements to the end of the list — it is a slow and costly -process. - --} - -{- | -=⚔️= Task 3 - -Let's write our first function to process lists in Haskell! Your first -implementation task is to write a function that returns all elements -of a list between two given positions inclusive (starting from zero). - -Remember that each function returns a new list. - ->>> subList 3 5 [1 .. 10] -[4,5,6] ->>> subList 3 0 [True, False, False, True, False] -[] - -♫ NOTE: When implementing, think about various corner cases. You - should return an empty list when given numbers that are negative. - -And also don't forget to check the 'Data.List' module. It is full of -yummy functions. - -Don't forget that you can load this module in GHCi to call functions -from it! - -ghci> :l src/Chapter2.hs --} +-- | +-- =🛡= Lists +-- +-- __List__ is a crucial data type in Haskell and functional programming +-- in general. It represents a collection of elements of the _same type_. +-- The list type is written as the type of list element in square +-- brackets. For example, a list of integers will have type '[Int]' and a +-- list of booleans — '[Bool]'. +-- +-- [Interesting fact]: String in Haskell is a list of characters ('Char' +-- data type in Haskell) and is written as '[Char]'. But Haskell also +-- provides the "String" alias to '[Char]'. So, in some places, you will +-- see 'String', and in others, you will see '[Char]', but they mean the +-- same thing. We will explain better how it works in Chapter Three. For +-- now, you only need to know that you can use 'String' and '[Char]' +-- interchangeably. +-- +-- To create a list, you need to put elements in square brackets (the +-- same as used for the list type) and separate them by commas. Such +-- expressions are called __list literals__. For example, the expression +-- "[3, 5, 1]" creates a list of three numbers, where the first list +-- element is number 3, and the last element is number 1. Similarly, you +-- can create a list of two booleans: [False, True]. A list without +-- elements is just []. +-- +-- You probably noticed that lists could be of any type of +-- elements. Often you want to write a function that works with lists of +-- any type (but consistent inside one list). This feature is called +-- __parametric polymorphism__. It will be explained in more detail +-- later, but when working with lists, you often will see type signatures +-- like: +-- +-- @ +-- foo :: [a] -> [b] -> [a] +-- @ +-- +-- The above type signature means that this function takes two lists: one +-- with elements of some type "a" and another with elements of type "b" +-- (the function doesn't care about the specific types) and returns the +-- list with elements of the same type as the first list. Such words "a" +-- and "b" are called __type variables__. +-- +-- For comparison, specific types in Haskell start with an uppercase +-- letter (Int, Bool, Char, etc.), where type variables begin with a +-- lowercase letter (a, b, el, etc.). This is the way to distinguish +-- between these types. +-- +-- The Haskell standard library already provides a lot of functions to +-- work with lists. And you will need to operate a lot with standard +-- functions in the upcoming exercises. Remember, Hoogle is your friend! + +-- | +-- =⚔️= Task 1 +-- +-- Explore lists by checking types of various list expressions and +-- functions in GHCi and insert the corresponding resulting output below: +-- +-- List of booleans: +-- >>> :t [True, False] +-- [True, False] :: [Bool] +-- +-- String is a list of characters: +-- >>> :t "some string" +-- "some string" :: String +-- +-- Empty list: +-- >>> :t [] +-- [] :: [a] +-- +-- Append two lists: +-- >>> :t (++) +-- (++) :: [a] -> [a] -> [a] +-- +-- Prepend an element at the beginning of a list: +-- >>> :t (:) +-- (:) :: a -> [a] -> [a] +-- +-- Reverse a list: +-- >>> :t reverse +-- reverse :: [a] -> [a] +-- +-- Take first N elements of a list: +-- >>> :t take +-- take :: Int -> [a] -> [a] +-- +-- Create a list from N same elements: +-- >>> :t replicate +-- replicate :: Int -> a -> [a] +-- +-- Split a string by line breaks: +-- >>> :t lines +-- lines :: String -> [String] +-- +-- Join a list of strings with line breaks: +-- >>> :t unlines +-- unlines :: [String] -> String + +-- | +-- =⚔️= Task 2 +-- +-- To understand the list type better, it is also beneficial to play with +-- list expressions in REPL. +-- +-- Evaluate the following expressions in GHCi and insert the answers. Try +-- to guess first, what you will see. +-- +-- >>> [10, 2] ++ [3, 1, 5] +-- [10,2,3,1,5] +-- +-- >>> [] ++ [1, 4] -- [] is an empty list +-- [1,4] +-- +-- >>> 3 : [1, 2] +-- [3,1,2] +-- +-- >>> 4 : 2 : [5, 10] -- prepend multiple elements +-- [4,2,5,10] +-- +-- >>> [1 .. 10] -- list ranges +-- [1,2,3,4,5,6,7,8,9,10] +-- +-- >>> [10 .. 1] +-- [] +-- +-- >>> [10, 9 .. 1] -- backwards list with explicit step +-- [10,9,8,7,6,5,4,3,2,1] +-- +-- >>> length [4, 10, 5] -- list length +-- 3 +-- +-- >>> replicate 5 True +-- [True,True,True,True,True] +-- +-- >>> take 5 "Hello, World!" +-- "Hello" +-- +-- >>> drop 5 "Hello, World!" +-- ", World!" +-- +-- >>> zip "abc" [1, 2, 3] -- convert two lists to a single list of pairs +-- [('a',1),('b',2),('c',3)] +-- +-- >>> words "Hello Haskell World!" -- split the string into the list of words +-- ["Hello","Haskell","World!"] +-- +-- +-- +-- 👩‍🔬 Haskell has a lot of syntax sugar. In the case with lists, any +-- list literal like "[3, 1, 2]" is syntax sugar for prepending elements +-- at the empty list: "3 : 1 : 2 : []". +-- +-- Don't forget that lists are the containers of the same-type +-- elements. Meaning, you can't combine lists of different types in any +-- situation. Let's try appending a list of booleans and a string (list +-- of characters) to see the error message: +-- +-- ghci> [True, False] ++ "string" +-- :4:18: error: +-- • Couldn't match type ‘Char’ with ‘Bool’ +-- Expected type: [Bool] +-- Actual type: [Char] +-- • In the second argument of ‘(++)’, namely ‘"string"’ +-- In the expression: [True, False] ++ "string" +-- In an equation for ‘it’: it = [True, False] ++ "string" + +-- | +-- =🛡= Immutability +-- +-- At this point in our training, you need to learn that all values in +-- Haskell are immutable! Woohoo! But what does it mean for us? +-- +-- It means that when you apply a function to some variable, the value is +-- not changed. Instead, you create a new value each time. +-- +-- >>> import Data.List (sort) -- sort is not in Prelude +-- >>> x = [3, 1, 2] -- you can assign values to variables in GHCi +-- >>> sort x +-- [1,2,3] +-- >>> x +-- [3,1,2] +-- +-- The 'sort' function returns a new sorted list. It doesn't change the +-- original list, so you don't need to worry about accidentally spoiling +-- values of variables you defined before. + +-- | +-- =🛡= List implementation +-- +-- Let's talk a bit about list implementation details. Lists in Haskell +-- are implemented as __linked lists__ (or cons-lists). And because +-- everything in Haskell is immutable, adding elements at the beginning +-- of the lists is cheap. Haskell doesn't need to allocate new memory and +-- copy the whole list there; it can just create a new list from a new +-- element and a pointer to an already existing list. In other words, +-- tails of lists are shared. +-- +-- For these reasons, adding elements to and extracting elements from the +-- beginning of a list is much cheaper and faster than working with the +-- end of the list. +-- +-- In some sense, lists are similar to trains. Let's look at an illustration +-- of a two-element list: +-- +-- . . . . . o o o o o +-- _________ _________ ____ o +-- | y | | x | |[]\_n][. +-- _|________|_o_|________|_o_|__|____)< +-- oo oo oo oo oo 00-oo\_ +-- +-- y : x : [] +-- +-- You can see that adding new elements (railway carriages) to the left +-- is easy: you just need to connect them to the last element in the +-- chain. +-- +-- . . . . . o o o o o +-- _________ _________ _________ ____ o +-- | z | | y | | x | |[]\_n][. +-- _|________|_o_|________|_o_|________|_o_|__|____)< +-- oo oo oo oo oo oo oo 00-oo\_ +-- +-- z : y : x : [] +-- +-- But imagine how much difficult it would be to add new carriages to the right? +-- +-- . . . . . o o o o o +-- _________ _________ _________ ____ o +-- | y | | x | | z | |[]\_n][. +-- _|________|_o_|________|_o_|________|_o_|__|____)< +-- oo oo oo oo oo oo oo 00-oo\_ +-- +-- y : x : z : [] +-- +-- You can't simply attach a new carriage anymore. You need to detach the +-- locomotive, maybe move trains around the railway a bit for the proper +-- position, and only then attach everything back again. The same thing +-- with adding elements to the end of the list — it is a slow and costly +-- process. + +-- | +-- =⚔️= Task 3 +-- +-- Let's write our first function to process lists in Haskell! Your first +-- implementation task is to write a function that returns all elements +-- of a list between two given positions inclusive (starting from zero). +-- +-- Remember that each function returns a new list. +-- +-- >>> subList 3 5 [1 .. 10] +-- [4,5,6] +-- >>> subList 3 0 [True, False, False, True, False] +-- [] +-- +-- ♫ NOTE: When implementing, think about various corner cases. You +-- should return an empty list when given numbers that are negative. +-- +-- And also don't forget to check the 'Data.List' module. It is full of +-- yummy functions. +-- +-- Don't forget that you can load this module in GHCi to call functions +-- from it! +-- +-- ghci> :l src/Chapter2.hs subList :: Int -> Int -> [a] -> [a] -subList = error "subList: Not implemented!" - -{- | -=⚔️= Task 4 +subList l r arr = drop l (take (r + 1) arr) + +-- | +-- =⚔️= Task 4 +-- +-- Implement a function that returns only the first half of a given list. +-- +-- >>> firstHalf [3, 4, 1, 2] +-- [3,4] +-- >>> firstHalf "bca" +-- "b" -Implement a function that returns only the first half of a given list. - ->>> firstHalf [3, 4, 1, 2] -[3,4] ->>> firstHalf "bca" -"b" --} -- PUT THE FUNCTION TYPE IN HERE -firstHalf l = error "firstHalf: Not implemented!" - - -{- | -=🛡= Pattern matching - -One of the coolest and most powerful features of Functional -Programming is __pattern matching__. This feature allows you to match -on different values of a type and produce results based on -patterns. The syntax of using pattern matching is similar to defining -ordinary functions, but instead of using variable names, you use the -values. - -For example, the "not" function that returns "the other" boolean is -implemented like this: - -@ -not :: Bool -> Bool -not True = False -not False = True -@ - -To perform pattern-matching, repeat a function name as many times as -many patterns you want to cover. The cool thing about Haskell is that -the compiler warns you if you forget to cover some cases. So you -always can be sure that your patterns are exhaustive! - -Note that you can pattern match on a variable too! Variable is like a -pattern that matches any value and gives it a name. You can think of -variables in function definitions as special cases of pattern -matching. - -You can pattern match on numbers as well! For example, if you want to -write a function that checks whether the given number is zero, you can -write it in the following way: - -@ -isZero :: Int -> Bool -isZero 0 = True -isZero n = False -@ - -Instead of "isZero n = False" you can write "isZero _ = False". The -symbol "_" (underscore) is called __hole__, and it is used when we -don't care about the value of a variable. It is like a pattern that -always matches (the same as a variable), but we don't use its value. - -👩‍🔬 Unlike 'switch' and 'case' in other languages, that try to go - through each switch and perform all actions in there until it reaches - the breakpoint, pattern matching on function parameters in Haskell - always returns only a single expression for a single branch. You can - think of this process as trying to match all patterns from the first - one to the last one and returning the expression on the right side - of "=" only for the pattern that matches first. This is a helpful - thing to keep in mind, especially when you have overlapping patterns. - Also note that, if no pattern matches the value, the function fails - at runtime. - - -In addition to pattern matching in the function definition, you can -also use the __case-of__ expression. With case-of, you specify the -patterns to match and expressions to return depending on a pattern -inside the function body. The main difference between 'case-of' and -top-level pattern matching is the fact that 'case' uses arrows (->) -instead of "=" for branch results. The "case" is often helpful when -function names are long, or pattern-matching on functions is awkward. - -To understand case-of, let's look at a function that takes two numbers -and a symbol, representing a math operation on these symbols. - -@ -evalOperation :: Char -> Int -> Int -> Int -evalOperation op x y = case op of - '+' -> x + y - '-' -> x - y - '*' -> x * y - '/' -> div x y - _ -> 0 -@ - -♫ NOTE: Each branch with a pattern should have the same alignment! - Remember that Haskell is a _layout-sensitive_ language. Also, note - that in the last line, "_" goes directly under the single quote to - have the same indentation 🔍. You can try copying the function and - change the indentation to see the parser error (which is not that - clever to identify the indentation errors). - -Since we are talking about lists in this chapter, let's see how we can -use pattern-matching on them! It turns out, pattern matching on lists -is an effective and inevitable technique. - -We can pattern-match on list literals directly: - -@ -isEmpty :: [a] -> Bool -isEmpty [] = True -isEmpty _ = False - -sumThree :: [Int] -> Int -sumThree [x, y, z] = x + y + z -sumThree _ = 0 - -onlyTwoElements :: [a] -> Bool -onlyTwoElements [_, _] = True -onlyTwoElements _ = False -@ - -Remember the ":" operator to add elements at the beginning of a list? -Turns out, in case of lists this operator can be used for pattern -matching as well! Isn't this cool? For example: - -@ --- return the first element of the list or default if the list is empty -headOrDef :: a -> [a] -> a -headOrDef def [] = def -headOrDef _ (x:_) = x - --- check if the first list element is zero -firstIsZero :: [Int] -> Bool -firstIsZero (0:_) = True -firstIsZero _ = False - --- check that a list has at least two elements -atLeastTwo :: [a] -> Bool -atLeastTwo (_ : _ : _) = True -atLeastTwo _ = False -@ - -When matching on the ":" pattern, the first element of the list goes -to the left side of ':' and the tail of the list goes to the right -side. You can have even nested patterns (as in the last example -above). In other words, when writing a pattern like "(x:y:xs)", it is -the same as writing "(x:(y:xs))". - -♫ NOTE: Often, pattern matching can be replaced with conditional - checks (if-then-else, guards) and vice versa. In some cases - pattern-matching can be more efficient; in other cases, it can produce - cleaner code or even more maintainable code due to pattern coverage - checker from the Haskell compiler. --} - -{- | -=⚔️= Task 5 - -Implement a function that checks whether the third element of a list -is the number 42. - ->>> isThird42 [1, 2, 42, 10] -True ->>> isThird42 [42, 42, 0, 42] -False --} -isThird42 = error "isThird42: Not implemented!" - - -{- | -=🛡= Recursion - -Often, when writing in a functional style, you end up implementing -__recursive__ functions. Recursive functions are nothing more than -calling the function itself from the body of the same function. - -Of course, you need some stopping conditions to exit the function -eventually, and you need to think carefully whether your function ever -reaches the stop condition. However, having pattern-matching in our -arsenal of skills significantly increases our chances of writing -correct functions. Nevertheless, you should think mindfully on how -your recursive function behaves on different corner-cases. - -A simple recursive function can divide a number by 2 until it reaches -zero: - -@ -divToZero :: Int -> Int -divToZero 0 = 0 -divToZero n = divToZero (div n 2) -@ - -But as you can see, the function is not that helpful per se. Often you -implement a helper function with some accumulator in order to collect -some information during recursive calls. - -For example, we can patch the previous function to count the number of -steps we need to take in order to reduce the number to zero. - -🤔 Blitz question: can you guess what this number represents? - -@ -divToZero :: Int -> Int -divToZero n = go 0 n - where - go :: Int -> Int -> Int - go acc 0 = acc - go acc n = go (acc + 1) (div n 2) -@ - -👩‍🔬 The pattern of having a recursive helper function is called "Recursive go": - - * https://kowainik.github.io/posts/haskell-mini-patterns#recursive-go - -One of the most useful capabilities of pattern matching on lists is -the ability to implement recursive functions with them as well! - -♫ NOTE: The canonical naming scheme for such patterns is `(x:xs)` - where x is the first element of the list, and xs — rest of the list - (which is a list as well that even could be empty). - -@ --- list length -len :: [a] -> Int -len [] = 0 -len (_:xs) = 1 + len xs - --- add 10 to every number of a list -addEvery10 :: [Int] -> [Int] -addEvery10 [] = [] -addEvery10 (x:xs) = (x + 10) : addEvery10 xs -@ - -When writing such functions, we usually handle two cases: empty list -and non-empty list (list with at least one element in the beginning) -and we decide what to do in each case. - -Most of the time, the case with a non-empty list uses the recursive -call to the function itself. - -An example of a standard Haskell function is 'concat' that takes a -list of lists and returns a flat list, appending all intermediate -lists: - -@ -concat :: [[a]] -> [a] -concat [] = [] -concat (x:xs) = x ++ concat xs -@ - -And it works like this: - ->>> concat [[2, 1, 3], [1, 2, 3, 4], [0, 5]] -[2,1,3,1,2,3,4,0,5] --} - - -{- | -=⚔️= Task 6 - -Implement a function that duplicates each element of the list - -🕯 HINT: Use recursion and pattern matching on the list. - ->>> duplicate [3, 1, 2] -[3,3,1,1,2,2] ->>> duplicate "abac" -"aabbaacc" - --} +firstHalf :: [a] -> [a] +firstHalf l = take (length l `div` 2) l + +-- | +-- =🛡= Pattern matching +-- +-- One of the coolest and most powerful features of Functional +-- Programming is __pattern matching__. This feature allows you to match +-- on different values of a type and produce results based on +-- patterns. The syntax of using pattern matching is similar to defining +-- ordinary functions, but instead of using variable names, you use the +-- values. +-- +-- For example, the "not" function that returns "the other" boolean is +-- implemented like this: +-- +-- @ +-- not :: Bool -> Bool +-- not True = False +-- not False = True +-- @ +-- +-- To perform pattern-matching, repeat a function name as many times as +-- many patterns you want to cover. The cool thing about Haskell is that +-- the compiler warns you if you forget to cover some cases. So you +-- always can be sure that your patterns are exhaustive! +-- +-- Note that you can pattern match on a variable too! Variable is like a +-- pattern that matches any value and gives it a name. You can think of +-- variables in function definitions as special cases of pattern +-- matching. +-- +-- You can pattern match on numbers as well! For example, if you want to +-- write a function that checks whether the given number is zero, you can +-- write it in the following way: +-- +-- @ +-- isZero :: Int -> Bool +-- isZero 0 = True +-- isZero n = False +-- @ +-- +-- Instead of "isZero n = False" you can write "isZero _ = False". The +-- symbol "_" (underscore) is called __hole__, and it is used when we +-- don't care about the value of a variable. It is like a pattern that +-- always matches (the same as a variable), but we don't use its value. +-- +-- 👩‍🔬 Unlike 'switch' and 'case' in other languages, that try to go +-- through each switch and perform all actions in there until it reaches +-- the breakpoint, pattern matching on function parameters in Haskell +-- always returns only a single expression for a single branch. You can +-- think of this process as trying to match all patterns from the first +-- one to the last one and returning the expression on the right side +-- of "=" only for the pattern that matches first. This is a helpful +-- thing to keep in mind, especially when you have overlapping patterns. +-- Also note that, if no pattern matches the value, the function fails +-- at runtime. +-- +-- +-- In addition to pattern matching in the function definition, you can +-- also use the __case-of__ expression. With case-of, you specify the +-- patterns to match and expressions to return depending on a pattern +-- inside the function body. The main difference between 'case-of' and +-- top-level pattern matching is the fact that 'case' uses arrows (->) +-- instead of "=" for branch results. The "case" is often helpful when +-- function names are long, or pattern-matching on functions is awkward. +-- +-- To understand case-of, let's look at a function that takes two numbers +-- and a symbol, representing a math operation on these symbols. +-- +-- @ +-- evalOperation :: Char -> Int -> Int -> Int +-- evalOperation op x y = case op of +-- '+' -> x + y +-- '-' -> x - y +-- '*' -> x * y +-- '/' -> div x y +-- _ -> 0 +-- @ +-- +-- ♫ NOTE: Each branch with a pattern should have the same alignment! +-- Remember that Haskell is a _layout-sensitive_ language. Also, note +-- that in the last line, "_" goes directly under the single quote to +-- have the same indentation 🔍. You can try copying the function and +-- change the indentation to see the parser error (which is not that +-- clever to identify the indentation errors). +-- +-- Since we are talking about lists in this chapter, let's see how we can +-- use pattern-matching on them! It turns out, pattern matching on lists +-- is an effective and inevitable technique. +-- +-- We can pattern-match on list literals directly: +-- +-- @ +-- isEmpty :: [a] -> Bool +-- isEmpty [] = True +-- isEmpty _ = False +-- +-- sumThree :: [Int] -> Int +-- sumThree [x, y, z] = x + y + z +-- sumThree _ = 0 +-- +-- onlyTwoElements :: [a] -> Bool +-- onlyTwoElements [_, _] = True +-- onlyTwoElements _ = False +-- @ +-- +-- Remember the ":" operator to add elements at the beginning of a list? +-- Turns out, in case of lists this operator can be used for pattern +-- matching as well! Isn't this cool? For example: +-- +-- @ +-- -- return the first element of the list or default if the list is empty +-- headOrDef :: a -> [a] -> a +-- headOrDef def [] = def +-- headOrDef _ (x:_) = x +-- +-- -- check if the first list element is zero +-- firstIsZero :: [Int] -> Bool +-- firstIsZero (0:_) = True +-- firstIsZero _ = False +-- +-- -- check that a list has at least two elements +-- atLeastTwo :: [a] -> Bool +-- atLeastTwo (_ : _ : _) = True +-- atLeastTwo _ = False +-- @ +-- +-- When matching on the ":" pattern, the first element of the list goes +-- to the left side of ':' and the tail of the list goes to the right +-- side. You can have even nested patterns (as in the last example +-- above). In other words, when writing a pattern like "(x:y:xs)", it is +-- the same as writing "(x:(y:xs))". +-- +-- ♫ NOTE: Often, pattern matching can be replaced with conditional +-- checks (if-then-else, guards) and vice versa. In some cases +-- pattern-matching can be more efficient; in other cases, it can produce +-- cleaner code or even more maintainable code due to pattern coverage +-- checker from the Haskell compiler. + +-- | +-- =⚔️= Task 5 +-- +-- Implement a function that checks whether the third element of a list +-- is the number 42. +-- +-- >>> isThird42 [1, 2, 42, 10] +-- True +-- >>> isThird42 [42, 42, 0, 42] +-- False +isThird42 :: [Int] -> Bool +isThird42 (_ : _ : 42 : _) = True +isThird42 _ = False + +-- | +-- =🛡= Recursion +-- +-- Often, when writing in a functional style, you end up implementing +-- __recursive__ functions. Recursive functions are nothing more than +-- calling the function itself from the body of the same function. +-- +-- Of course, you need some stopping conditions to exit the function +-- eventually, and you need to think carefully whether your function ever +-- reaches the stop condition. However, having pattern-matching in our +-- arsenal of skills significantly increases our chances of writing +-- correct functions. Nevertheless, you should think mindfully on how +-- your recursive function behaves on different corner-cases. +-- +-- A simple recursive function can divide a number by 2 until it reaches +-- zero: +-- +-- @ +-- divToZero :: Int -> Int +-- divToZero 0 = 0 +-- divToZero n = divToZero (div n 2) +-- @ +-- +-- But as you can see, the function is not that helpful per se. Often you +-- implement a helper function with some accumulator in order to collect +-- some information during recursive calls. +-- +-- For example, we can patch the previous function to count the number of +-- steps we need to take in order to reduce the number to zero. +-- +-- 🤔 Blitz question: can you guess what this number represents? +-- +-- @ +-- divToZero :: Int -> Int +-- divToZero n = go 0 n +-- where +-- go :: Int -> Int -> Int +-- go acc 0 = acc +-- go acc n = go (acc + 1) (div n 2) +-- @ +-- +-- 👩‍🔬 The pattern of having a recursive helper function is called "Recursive go": +-- +-- * https://kowainik.github.io/posts/haskell-mini-patterns#recursive-go +-- +-- One of the most useful capabilities of pattern matching on lists is +-- the ability to implement recursive functions with them as well! +-- +-- ♫ NOTE: The canonical naming scheme for such patterns is `(x:xs)` +-- where x is the first element of the list, and xs — rest of the list +-- (which is a list as well that even could be empty). +-- +-- @ +-- -- list length +-- len :: [a] -> Int +-- len [] = 0 +-- len (_:xs) = 1 + len xs +-- +-- -- add 10 to every number of a list +-- addEvery10 :: [Int] -> [Int] +-- addEvery10 [] = [] +-- addEvery10 (x:xs) = (x + 10) : addEvery10 xs +-- @ +-- +-- When writing such functions, we usually handle two cases: empty list +-- and non-empty list (list with at least one element in the beginning) +-- and we decide what to do in each case. +-- +-- Most of the time, the case with a non-empty list uses the recursive +-- call to the function itself. +-- +-- An example of a standard Haskell function is 'concat' that takes a +-- list of lists and returns a flat list, appending all intermediate +-- lists: +-- +-- @ +-- concat :: [[a]] -> [a] +-- concat [] = [] +-- concat (x:xs) = x ++ concat xs +-- @ +-- +-- And it works like this: +-- +-- >>> concat [[2, 1, 3], [1, 2, 3, 4], [0, 5]] +-- [2,1,3,1,2,3,4,0,5] + +-- | +-- =⚔️= Task 6 +-- +-- Implement a function that duplicates each element of the list +-- +-- 🕯 HINT: Use recursion and pattern matching on the list. +-- +-- >>> duplicate [3, 1, 2] +-- [3,3,1,1,2,2] +-- >>> duplicate "abac" +-- "aabbaacc" duplicate :: [a] -> [a] -duplicate = error "duplicate: Not implemented!" - - -{- | -=⚔️= Task 7 -Write a function that takes elements of a list only in even positions. - -🕯 HINT: You need to write a recursive function that pattern matches - on the list structure. Your function will have several cases and - probably needs to use nested pattern matching on lists of size at - least 2. Alternatively, you can use the "Recursive go" pattern. - ->>> takeEven [2, 1, 3, 5, 4] -[2,3,4] --} -takeEven = error "takeEven: Not implemented!" - -{- | -=🛡= Higher-order functions - -Some functions can take other functions as arguments. Such functions -are called __higher-order functions__ (HOFs). Check the types of some -common HOF list functions: - ->>> :t filter -filter :: (a -> Bool) -> [a] -> [a] ->>> :t map -map :: (a -> b) -> [a] -> [b] - -And few usage examples of those functions: - ->>> filter even [1..10] -- keep only even elements in the list -[2,4,6,8,10] ->>> map not [True, False, True] -- maps the 'not' function over each element of the given list -[False,True,False] - -Having HOFs in your language means that functions can be treated in -the same way as any other values and expressions: - - ✲ You can pass functions as arguments - ✲ You can return functions as results - ✲ You can compose functions easily to create new functions - ✲ You can have lists of functions - ✲ And much more! - -The ability to create __lambdas__ (or anonymous functions) nicely -complements the concept of HOF. For example, we can easily add -number 3 to each element of the list by introducing a lambda function: - ->>> map (\x -> x + 3) [0..5] -[3,4,5,6,7,8] - -The syntax of lambda functions is somewhat similar to normal ones, -except for you don't need to think about its name, which is -awesome. To establish the start of the lambda function, you should -write "\" which is a bit similar to the lambda symbol — λ. Then you -specify space-separated arguments. Instead of the "=" in the ordinary -function body, you should write "->" and then you can use these -arguments and all variables in scope inside the lambda-body. - -These are equal: - -@ -foo a b = a + b ---and -\a b -> a + b -@ - -What's even cooler is the ability to __apply functions partially__ -This means that you can provide only some arguments to a function and -treat the result as a function itself! You already know the 'div' -function: it takes two numbers and returns the result of the integral -division of those numbers. But if we apply 'div' to a number 10 -partially, we will get a new function that takes only one number and -returns the result of dividing 10 by that number. You can check the -difference by inspecting the types of corresponding expressions in -GHCi: - ->>> :t +d div -div :: Integer -> Integer -> Integer ->>> :t +d div 10 -div 10 :: Integer -> Integer - - -This fact can be used to pass partial applications of some functions -directly to other functions. - ->>> map (div 10) [1 .. 10] -[10,5,3,2,2,1,1,1,1,1] - -You can apply operators partially too! - ->>> filter (< 3) [2, 1, 3, 4, 0, 5] -[2,1,0] ->>> map (* 2) [1..5] -[2,4,6,8,10] - -The implementation of the "map" function is pretty straightforward if -you are already familiar with function application, recursion and -pattern matching. - -@ -map :: (a -> b) -> [a] -> [b] -map _ [] = [] -map f (x:xs) = f x : map f xs -@ - -Now you can see that there is nothing magic in HOFs in the end! --} - -{- | -=⚔️= Task 8 - -Implement a function that repeats each element as many times as the -value of the element itself - ->>> smartReplicate [3, 1, 2] -[3,3,3,1,2,2] - -🕯 HINT: Use combination of 'map' and 'replicate' --} +duplicate [] = [] +duplicate (x : xs) = x : x : duplicate xs + +-- | +-- =⚔️= Task 7 +-- Write a function that takes elements of a list only in even positions. +-- +-- 🕯 HINT: You need to write a recursive function that pattern matches +-- on the list structure. Your function will have several cases and +-- probably needs to use nested pattern matching on lists of size at +-- least 2. Alternatively, you can use the "Recursive go" pattern. +-- +-- >>> takeEven [2, 1, 3, 5, 4] +-- [2,3,4] +takeEven :: [a] -> [a] +takeEven [] = [] +takeEven [x] = [x] +takeEven (x : _ : xs) = x : takeEven xs + +-- | +-- =🛡= Higher-order functions +-- +-- Some functions can take other functions as arguments. Such functions +-- are called __higher-order functions__ (HOFs). Check the types of some +-- common HOF list functions: +-- +-- >>> :t filter +-- filter :: (a -> Bool) -> [a] -> [a] +-- >>> :t map +-- map :: (a -> b) -> [a] -> [b] +-- +-- And few usage examples of those functions: +-- +-- >>> filter even [1..10] -- keep only even elements in the list +-- [2,4,6,8,10] +-- >>> map not [True, False, True] -- maps the 'not' function over each element of the given list +-- [False,True,False] +-- +-- Having HOFs in your language means that functions can be treated in +-- the same way as any other values and expressions: +-- +-- ✲ You can pass functions as arguments +-- ✲ You can return functions as results +-- ✲ You can compose functions easily to create new functions +-- ✲ You can have lists of functions +-- ✲ And much more! +-- +-- The ability to create __lambdas__ (or anonymous functions) nicely +-- complements the concept of HOF. For example, we can easily add +-- number 3 to each element of the list by introducing a lambda function: +-- +-- >>> map (\x -> x + 3) [0..5] +-- [3,4,5,6,7,8] +-- +-- The syntax of lambda functions is somewhat similar to normal ones, +-- except for you don't need to think about its name, which is +-- awesome. To establish the start of the lambda function, you should +-- write "\" which is a bit similar to the lambda symbol — λ. Then you +-- specify space-separated arguments. Instead of the "=" in the ordinary +-- function body, you should write "->" and then you can use these +-- arguments and all variables in scope inside the lambda-body. +-- +-- These are equal: +-- +-- @ +-- foo a b = a + b +-- --and +-- \a b -> a + b +-- @ +-- +-- What's even cooler is the ability to __apply functions partially__ +-- This means that you can provide only some arguments to a function and +-- treat the result as a function itself! You already know the 'div' +-- function: it takes two numbers and returns the result of the integral +-- division of those numbers. But if we apply 'div' to a number 10 +-- partially, we will get a new function that takes only one number and +-- returns the result of dividing 10 by that number. You can check the +-- difference by inspecting the types of corresponding expressions in +-- GHCi: +-- +-- >>> :t +d div +-- div :: Integer -> Integer -> Integer +-- >>> :t +d div 10 +-- div 10 :: Integer -> Integer +-- +-- +-- This fact can be used to pass partial applications of some functions +-- directly to other functions. +-- +-- >>> map (div 10) [1 .. 10] +-- [10,5,3,2,2,1,1,1,1,1] +-- +-- You can apply operators partially too! +-- +-- >>> filter (< 3) [2, 1, 3, 4, 0, 5] +-- [2,1,0] +-- >>> map (* 2) [1..5] +-- [2,4,6,8,10] +-- +-- The implementation of the "map" function is pretty straightforward if +-- you are already familiar with function application, recursion and +-- pattern matching. +-- +-- @ +-- map :: (a -> b) -> [a] -> [b] +-- map _ [] = [] +-- map f (x:xs) = f x : map f xs +-- @ +-- +-- Now you can see that there is nothing magic in HOFs in the end! + +-- | +-- =⚔️= Task 8 +-- +-- Implement a function that repeats each element as many times as the +-- value of the element itself +-- +-- >>> smartReplicate [3, 1, 2] +-- [3,3,3,1,2,2] +-- +-- 🕯 HINT: Use combination of 'map' and 'replicate' smartReplicate :: [Int] -> [Int] -smartReplicate l = error "smartReplicate: Not implemented!" - -{- | -=⚔️= Task 9 - -Implement a function that takes a number, a list of lists and returns -the list with only those lists that contain a passed element. - ->>> contains 3 [[1, 2, 3, 4, 5], [2, 0], [3, 4]] -[[1,2,3,4,5],[3,4]] - -🕯 HINT: Use the 'elem' function to check whether an element belongs to a list --} -contains = error "contains: Not implemented!" - - -{- | -=🛡= Eta-reduction - -Another consequence of the HOFs and partial application is -__eta-reduction__. This term is used to call the simplification of -functions over their arguments. Specifically, if we have `foo x = bar -10 x`, this precisely means that `foo` is a partially applied `bar -10`. And we can write it like `foo = bar 10`. - -This concept can be used to write functions as well. - -For example, - -@ -nextInt :: Int -> Int -nextInt n = add 1 n -@ - -Could be written with the eta-reduced form: - -@ -nextInt :: Int -> Int -nextInt = add 1 -@ - -♫ NOTE: See that the initial type of the function is not changed and - it works absolutely the same. We just can skip the last argument and - amend its usage in the function body. --} - -{- | -=⚔️= Task 10 - -Let's now try to eta-reduce some of the functions and ensure that we -mastered the skill of eta-reducing. --} +smartReplicate = concatMap (\x -> replicate x x) + +-- | +-- =⚔️= Task 9 +-- +-- Implement a function that takes a number, a list of lists and returns +-- the list with only those lists that contain a passed element. +-- +-- >>> contains 3 [[1, 2, 3, 4, 5], [2, 0], [3, 4]] +-- [[1,2,3,4,5],[3,4]] +-- +-- 🕯 HINT: Use the 'elem' function to check whether an element belongs to a list +contains :: Int -> [[Int]] -> [[Int]] +contains t = filter (\x -> t `elem` x) + +-- | +-- =🛡= Eta-reduction +-- +-- Another consequence of the HOFs and partial application is +-- __eta-reduction__. This term is used to call the simplification of +-- functions over their arguments. Specifically, if we have `foo x = bar +-- 10 x`, this precisely means that `foo` is a partially applied `bar +-- 10`. And we can write it like `foo = bar 10`. +-- +-- This concept can be used to write functions as well. +-- +-- For example, +-- +-- @ +-- nextInt :: Int -> Int +-- nextInt n = add 1 n +-- @ +-- +-- Could be written with the eta-reduced form: +-- +-- @ +-- nextInt :: Int -> Int +-- nextInt = add 1 +-- @ +-- +-- ♫ NOTE: See that the initial type of the function is not changed and +-- it works absolutely the same. We just can skip the last argument and +-- amend its usage in the function body. + +-- | +-- =⚔️= Task 10 +-- +-- Let's now try to eta-reduce some of the functions and ensure that we +-- mastered the skill of eta-reducing. divideTenBy :: Int -> Int -divideTenBy x = div 10 x +divideTenBy = div 10 -- TODO: type ;) -listElementsLessThan x l = filter (< x) l +listElementsLessThan :: Int -> [Int] -> [Int] +listElementsLessThan x = filter (< x) -- Can you eta-reduce this one??? -pairMul xs ys = zipWith (*) xs ys - -{- | -=🛡= Lazy evaluation - -Another unique Haskell feature is __lazy evaluation__. Haskell is lazy -by default, which means that it doesn't evaluate expressions when not -needed. The lazy evaluation has many benefits: avoid doing redundant -work, provide more composable interfaces. And in this section, we will -focus on Haskell's ability to create infinite data structures and work -with them! - -For instance, the Haskell standard library has the 'repeat' function -that returns an infinite list created from a given element. Of course, -we can't print an infinite list to our terminal; it will take an -infinite amount of time! But we can work with parts of it due to lazy -evaluation: - ->>> take 5 (repeat 0) -[0,0,0,0,0] - -Another useful construction is an infinite list of all numbers! - ->>> take 4 [0 ..] -[0,1,2,3] - -Isn't this awesome?! Now we can unleash the real power of the -Infinity Stone! - -♫ NOTE: Infinite lists bring great power, but with great power comes - great responsibility. Functions like 'length' hang when called on - infinite lists. So make sure you think about such corner cases in the - implementations of your functions if you expect them to work on the - infinite lists. --} - -{- | -=⚔️= Task 11 - -Rotating a list by a single element is the process of moving the first -element of the list to the end. - -Implement a function to rotate a given finite list by N elements. Try -to do it more efficiently than rotating by a single element N times. - -On invalid input (negative rotation coefficient) it should return an empty -list. - ->>> rotate 1 [1,2,3,4] -[2,3,4,1] ->>> rotate 3 [1,2,3,4] -[4,1,2,3] - -🕯 HINT: Use the 'cycle' function --} -rotate = error "rotate: Not implemented!" - -{- | -=💣= Task 12* - -Now you should be ready for the final boss at the end of this chapter! -To defeat the boss, implement the reversing function that takes a list -and reverses it. - ->>> rewind [1 .. 5] -[5,4,3,2,1] - -♫ NOTE: The Haskell standard library already provides the "reverse" - function, but in this task, you need to implement it manually. No - cheating! --} -rewind = error "rewind: Not Implemented!" - +pairMul :: [Int] -> [Int] -> [Int] +pairMul = zipWith (*) + +-- | +-- =🛡= Lazy evaluation +-- +-- Another unique Haskell feature is __lazy evaluation__. Haskell is lazy +-- by default, which means that it doesn't evaluate expressions when not +-- needed. The lazy evaluation has many benefits: avoid doing redundant +-- work, provide more composable interfaces. And in this section, we will +-- focus on Haskell's ability to create infinite data structures and work +-- with them! +-- +-- For instance, the Haskell standard library has the 'repeat' function +-- that returns an infinite list created from a given element. Of course, +-- we can't print an infinite list to our terminal; it will take an +-- infinite amount of time! But we can work with parts of it due to lazy +-- evaluation: +-- +-- >>> take 5 (repeat 0) +-- [0,0,0,0,0] +-- +-- Another useful construction is an infinite list of all numbers! +-- +-- >>> take 4 [0 ..] +-- [0,1,2,3] +-- +-- Isn't this awesome?! Now we can unleash the real power of the +-- Infinity Stone! +-- +-- ♫ NOTE: Infinite lists bring great power, but with great power comes +-- great responsibility. Functions like 'length' hang when called on +-- infinite lists. So make sure you think about such corner cases in the +-- implementations of your functions if you expect them to work on the +-- infinite lists. + +-- | +-- =⚔️= Task 11 +-- +-- Rotating a list by a single element is the process of moving the first +-- element of the list to the end. +-- +-- Implement a function to rotate a given finite list by N elements. Try +-- to do it more efficiently than rotating by a single element N times. +-- +-- On invalid input (negative rotation coefficient) it should return an empty +-- list. +-- +-- >>> rotate 1 [1,2,3,4] +-- [2,3,4,1] +-- >>> rotate 3 [1,2,3,4] +-- [4,1,2,3] +-- +-- 🕯 HINT: Use the 'cycle' function +rotate :: Int -> [Int] -> [Int] +rotate n l = take (length l) (drop n (cycle l)) + +-- | +-- =💣= Task 12* +-- +-- Now you should be ready for the final boss at the end of this chapter! +-- To defeat the boss, implement the reversing function that takes a list +-- and reverses it. +-- +-- >>> rewind [1 .. 5] +-- [5,4,3,2,1] +-- +-- ♫ NOTE: The Haskell standard library already provides the "reverse" +-- function, but in this task, you need to implement it manually. No +-- cheating! +rewind :: [Int] -> [Int] +rewind [] = [] +rewind (x : xs) = rewind xs ++ [x] {- You did it! Now it is time to open pull request with your changes