From 885e8208afd60409926ab78ac4462468b92191d1 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Thu, 23 Feb 2023 22:18:02 +0100 Subject: [PATCH 01/35] build: stack.yaml --- stack.yaml | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 stack.yaml diff --git a/stack.yaml b/stack.yaml new file mode 100644 index 000000000..2ad1d9595 --- /dev/null +++ b/stack.yaml @@ -0,0 +1,67 @@ +# This file was automatically generated by 'stack init' +# +# Some commonly used options have been documented as comments in this file. +# For advanced use and comprehensive documentation of the format, please see: +# https://docs.haskellstack.org/en/stable/yaml_configuration/ + +# Resolver to choose a 'specific' stackage snapshot or a compiler version. +# A snapshot resolver dictates the compiler version and the set of packages +# to be used for project dependencies. For example: +# +# resolver: lts-3.5 +# resolver: nightly-2015-09-21 +# resolver: ghc-7.10.2 +# +# The location of a snapshot can be provided as a file or url. Stack assumes +# a snapshot provided as a file might change, whereas a url resource does not. +# +# resolver: ./custom-snapshot.yaml +# resolver: https://example.com/snapshots/2018-01-01.yaml +resolver: + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/11.yaml + +# User packages to be built. +# Various formats can be used as shown in the example below. +# +# packages: +# - some-directory +# - https://example.com/foo/bar/baz-0.0.2.tar.gz +# subdirs: +# - auto-update +# - wai +packages: +- . +# Dependency packages to be pulled from upstream that are not in the resolver. +# These entries can reference officially published versions as well as +# forks / in-progress versions pinned to a git hash. For example: +# +# extra-deps: +# - acme-missiles-0.3 +# - git: https://github.com/commercialhaskell/stack.git +# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a +# +# extra-deps: [] + +# Override default flag values for local packages and extra-deps +# flags: {} + +# Extra package databases containing global packages +# extra-package-dbs: [] + +# Control whether we use the GHC we find on the path +# system-ghc: true +# +# Require a specific version of Stack, using version ranges +# require-stack-version: -any # Default +# require-stack-version: ">=2.9" +# +# Override the architecture used by Stack, especially useful on Windows +# arch: i386 +# arch: x86_64 +# +# Extra directories used by Stack for building +# extra-include-dirs: [/path/to/dir] +# extra-lib-dirs: [/path/to/dir] +# +# Allow a newer minor version of GHC than the snapshot specifies +# compiler-check: newer-minor From bbfbb0edf1e756bd02a8653c6748671614094bab Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Thu, 23 Feb 2023 22:30:08 +0100 Subject: [PATCH 02/35] feat(chapter-1): Task 1 ghci :t types inserted --- src/Chapter1.hs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Chapter1.hs b/src/Chapter1.hs index 406deeaca..3aa9f2843 100644 --- a/src/Chapter1.hs +++ b/src/Chapter1.hs @@ -209,31 +209,30 @@ So, the output in this example means that 'False' has type 'Bool'. > 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. From f10a3364cec538f6825dcadc70a2194959272089 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Thu, 23 Feb 2023 22:43:51 +0100 Subject: [PATCH 03/35] feat(chapter-1): Task 2 solved --- src/Chapter1.hs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Chapter1.hs b/src/Chapter1.hs index 3aa9f2843..b3a1ac925 100644 --- a/src/Chapter1.hs +++ b/src/Chapter1.hs @@ -300,43 +300,43 @@ expressions in GHCi 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 From 230e1e37c2339f666ce1c0cbcbf23a1ed18f2d81 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Thu, 23 Feb 2023 22:47:21 +0100 Subject: [PATCH 04/35] feat(chapter-1): Task 3 and 4 solved --- src/Chapter1.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Chapter1.hs b/src/Chapter1.hs index b3a1ac925..34c240444 100644 --- a/src/Chapter1.hs +++ b/src/Chapter1.hs @@ -427,7 +427,7 @@ task is to specify the type of this function. >>> squareSum 3 4 49 -} - +squareSum :: Double -> Double -> Double squareSum x y = (x + y) * (x + y) @@ -448,7 +448,7 @@ Implement the function that takes an integer value and returns the next 'Int'. function body with the proper implementation. -} next :: Int -> Int -next x = error "next: not implemented!" +next x = x + 1 {- | After you've implemented the function (or even during the implementation), you From 6ebc211df0919e3daabf85681e5651397b2a62ee Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Thu, 23 Feb 2023 23:14:09 +0100 Subject: [PATCH 05/35] feat(chapter-1): Task 5 --- src/Chapter1.hs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Chapter1.hs b/src/Chapter1.hs index 34c240444..57984b958 100644 --- a/src/Chapter1.hs +++ b/src/Chapter1.hs @@ -488,9 +488,8 @@ Implement a function that returns the last digit of a given number. 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!" - +lastDigit :: Int -> Int +lastDigit n = abs n `mod` 10 {- | =⚔️= Task 6 From 5d986b6a0c19b9307898db45701ccfaedc563f23 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Thu, 23 Feb 2023 23:18:52 +0100 Subject: [PATCH 06/35] feat(chapter-1): solved Task 6 --- src/Chapter1.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chapter1.hs b/src/Chapter1.hs index 57984b958..6105822f2 100644 --- a/src/Chapter1.hs +++ b/src/Chapter1.hs @@ -518,7 +518,7 @@ branches because it is an expression and it must always return some value. satisfying the check will be returned and, therefore, evaluated. -} closestToZero :: Int -> Int -> Int -closestToZero x y = error "closestToZero: not implemented!" +closestToZero x y = if (abs x) < (abs y) then x else y {- | From f4d7b6ba8a306fe8b46a29cd0bc8798396f78536 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Thu, 23 Feb 2023 23:32:21 +0100 Subject: [PATCH 07/35] feat(chapter-1): solved Task 7 --- src/Chapter1.hs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Chapter1.hs b/src/Chapter1.hs index 6105822f2..b7dd8e8a4 100644 --- a/src/Chapter1.hs +++ b/src/Chapter1.hs @@ -551,8 +551,11 @@ value after "=" where the condition is true. Casual reminder about adding top-level type signatures for all functions :) -} - -mid x y z = error "mid: not implemented!" +mid :: Int -> Int -> Int -> Int +mid x y z + | x <= y && y <= z = y + | x >= y && y >= z = y + | otherwise = mid y z x {- | =⚔️= Task 8 From cc5b6b6fad69bf17233dd17517f82012cba4679e Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Thu, 23 Feb 2023 23:37:31 +0100 Subject: [PATCH 08/35] fix(chapter-1): fixed missing \n Task 1 --- src/Chapter1.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Chapter1.hs b/src/Chapter1.hs index b7dd8e8a4..af2be4e78 100644 --- a/src/Chapter1.hs +++ b/src/Chapter1.hs @@ -214,6 +214,7 @@ True :: Bool 'a' :: Char >>> :t 42 42 :: Num a => a + A pair of boolean and char: >>> :t (True, 'x') (True, 'x') :: (Bool, Char) From 2cb8944f666a3540d48a3fbf6be9ea2ba26ae2f1 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Thu, 23 Feb 2023 23:43:40 +0100 Subject: [PATCH 09/35] fix(chapter-1): Task 3 --- src/Chapter1.hs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Chapter1.hs b/src/Chapter1.hs index af2be4e78..8a4754b98 100644 --- a/src/Chapter1.hs +++ b/src/Chapter1.hs @@ -428,7 +428,7 @@ task is to specify the type of this function. >>> squareSum 3 4 49 -} -squareSum :: Double -> Double -> Double +squareSum :: Int -> Int -> Int squareSum x y = (x + y) * (x + y) @@ -570,7 +570,19 @@ True >>> isVowel 'x' False -} -isVowel c = error "isVowel: not implemented!" +isVowel :: Char -> Bool +isVowel c + | c == 'a' = True + | c == 'e' = True + | c == 'i' = True + | c == 'o' = True + | c == 'u' = True + | c == 'A' = True + | c == 'E' = True + | c == 'I' = True + | c == 'O' = True + | c == 'U' = True + | otherwise = False {- | From 18541609cd58b7dc2fa2b938efdb873d815b9eab Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 00:08:17 +0100 Subject: [PATCH 10/35] fix(chapter-1): Task 9 --- src/Chapter1.hs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Chapter1.hs b/src/Chapter1.hs index 8a4754b98..76efdbfb0 100644 --- a/src/Chapter1.hs +++ b/src/Chapter1.hs @@ -645,10 +645,11 @@ Implement a function that returns the sum of the last two digits of a number. Try to introduce variables in this task (either with let-in or where) to avoid specifying complex expressions. -} - -sumLast2 n = error "sumLast2: Not implemented!" - - +sumLast2 :: Int -> Int +sumLast2 n = (lastDigit n) + (secondLastDigit n) + where + secondLastDigit last = (((mod (abs n) 100) - lastDigit n) `div` 10) + {- | =💣= Task 10* From 87daf0475fae4ec9374c098c7ad836ffd3118a65 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 00:15:54 +0100 Subject: [PATCH 11/35] fix(chapter-1): Task 9 --- src/Chapter1.hs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Chapter1.hs b/src/Chapter1.hs index 76efdbfb0..e20efa1a9 100644 --- a/src/Chapter1.hs +++ b/src/Chapter1.hs @@ -556,7 +556,7 @@ mid :: Int -> Int -> Int -> Int mid x y z | x <= y && y <= z = y | x >= y && y >= z = y - | otherwise = mid y z x + | otherwise = mid y z x {- | =⚔️= Task 8 @@ -571,7 +571,7 @@ True False -} isVowel :: Char -> Bool -isVowel c +isVowel c | c == 'a' = True | c == 'e' = True | c == 'i' = True @@ -646,10 +646,12 @@ Try to introduce variables in this task (either with let-in or where) to avoid specifying complex expressions. -} sumLast2 :: Int -> Int -sumLast2 n = (lastDigit n) + (secondLastDigit n) - where - secondLastDigit last = (((mod (abs n) 100) - lastDigit n) `div` 10) - +sumLast2 n = (lastDigit n) + secondLastDigit + where + lastTwo = (mod (abs n) 100) + last = lastDigit n + secondLastDigit = (lastTwo - last) `div` 10 + {- | =💣= Task 10* From 6f0082a53af8d0e3d1b3eb934dd7eec24756518e Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 08:47:00 +0100 Subject: [PATCH 12/35] fix(chapter-1): extra Task --- src/Chapter1.hs | 7 +++--- stack.yaml | 67 ------------------------------------------------- 2 files changed, 4 insertions(+), 70 deletions(-) delete mode 100644 stack.yaml diff --git a/src/Chapter1.hs b/src/Chapter1.hs index e20efa1a9..553e65330 100644 --- a/src/Chapter1.hs +++ b/src/Chapter1.hs @@ -669,9 +669,10 @@ Implement a function that returns the first digit of a given number. 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!" - +firstDigit :: Int -> Int +firstDigit n + | abs 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/stack.yaml b/stack.yaml deleted file mode 100644 index 2ad1d9595..000000000 --- a/stack.yaml +++ /dev/null @@ -1,67 +0,0 @@ -# This file was automatically generated by 'stack init' -# -# Some commonly used options have been documented as comments in this file. -# For advanced use and comprehensive documentation of the format, please see: -# https://docs.haskellstack.org/en/stable/yaml_configuration/ - -# Resolver to choose a 'specific' stackage snapshot or a compiler version. -# A snapshot resolver dictates the compiler version and the set of packages -# to be used for project dependencies. For example: -# -# resolver: lts-3.5 -# resolver: nightly-2015-09-21 -# resolver: ghc-7.10.2 -# -# The location of a snapshot can be provided as a file or url. Stack assumes -# a snapshot provided as a file might change, whereas a url resource does not. -# -# resolver: ./custom-snapshot.yaml -# resolver: https://example.com/snapshots/2018-01-01.yaml -resolver: - url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/11.yaml - -# User packages to be built. -# Various formats can be used as shown in the example below. -# -# packages: -# - some-directory -# - https://example.com/foo/bar/baz-0.0.2.tar.gz -# subdirs: -# - auto-update -# - wai -packages: -- . -# Dependency packages to be pulled from upstream that are not in the resolver. -# These entries can reference officially published versions as well as -# forks / in-progress versions pinned to a git hash. For example: -# -# extra-deps: -# - acme-missiles-0.3 -# - git: https://github.com/commercialhaskell/stack.git -# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a -# -# extra-deps: [] - -# Override default flag values for local packages and extra-deps -# flags: {} - -# Extra package databases containing global packages -# extra-package-dbs: [] - -# Control whether we use the GHC we find on the path -# system-ghc: true -# -# Require a specific version of Stack, using version ranges -# require-stack-version: -any # Default -# require-stack-version: ">=2.9" -# -# Override the architecture used by Stack, especially useful on Windows -# arch: i386 -# arch: x86_64 -# -# Extra directories used by Stack for building -# extra-include-dirs: [/path/to/dir] -# extra-lib-dirs: [/path/to/dir] -# -# Allow a newer minor version of GHC than the snapshot specifies -# compiler-check: newer-minor From ff95475589c2273dd6aed6fb19ccd8abfb047657 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 09:15:28 +0100 Subject: [PATCH 13/35] fix(chapter-1): handle negative numbers --- src/Chapter1.hs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Chapter1.hs b/src/Chapter1.hs index 553e65330..1d2dc54e5 100644 --- a/src/Chapter1.hs +++ b/src/Chapter1.hs @@ -671,8 +671,10 @@ aren't ready for this boss yet! -} firstDigit :: Int -> Int firstDigit n - | abs n < 10 = n - | otherwise = firstDigit (div n 10) + | absN < 10 = absN + | otherwise = firstDigit (div absN 10) + where + absN = abs n {- You did it! Now it is time to open a pull request with your changes From 35a504f1120ce8f281db8288778ab5cf5ad6da1d Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 10:35:11 +0100 Subject: [PATCH 14/35] fix(chapter-2): Task 1 --- src/Chapter2.hs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Chapter2.hs b/src/Chapter2.hs index b98ceaf7d..e4bb15ef2 100644 --- a/src/Chapter2.hs +++ b/src/Chapter2.hs @@ -136,43 +136,43 @@ 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 -} From 91160f58dac5988a8f0a8714e63e72b064643ebc Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 10:39:53 +0100 Subject: [PATCH 15/35] fix(chapter-2): Task 2 --- .gitignore | 14 ++++++++++++++ src/Chapter2.hs | 26 +++++++++++++------------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 3b5dbd594..cb25100c1 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,17 @@ TAGS # other .DS_Store .env +/out/production/learn4haskell/Chapter1.hs +/out/production/learn4haskell/Chapter2.hs +/out/production/learn4haskell/Chapter3.hs +/out/production/learn4haskell/Chapter4.hs +/out/test/learn4haskell/Test/Chapter1.hs +/out/test/learn4haskell/Test/Chapter2.hs +/out/test/learn4haskell/Test/Chapter3.hs +/out/test/learn4haskell/Test/Chapter4.hs +/out/test/learn4haskell/DoctestChapter1.hs +/out/test/learn4haskell/DoctestChapter2.hs +/out/test/learn4haskell/DoctestChapter3.hs +/out/test/learn4haskell/DoctestChapter4.hs +/out/test/learn4haskell/Spec.hs +/stack.yaml diff --git a/src/Chapter2.hs b/src/Chapter2.hs index e4bb15ef2..6fc9e2e63 100644 --- a/src/Chapter2.hs +++ b/src/Chapter2.hs @@ -186,31 +186,31 @@ 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 From 91f8ce517a74558661c244dd497367a1728a15d0 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 11:05:49 +0100 Subject: [PATCH 16/35] fix(chapter-2): Task 3 --- src/Chapter2.hs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Chapter2.hs b/src/Chapter2.hs index 6fc9e2e63..0dd2c769c 100644 --- a/src/Chapter2.hs +++ b/src/Chapter2.hs @@ -336,7 +336,11 @@ from it! ghci> :l src/Chapter2.hs -} subList :: Int -> Int -> [a] -> [a] -subList = error "subList: Not implemented!" +subList i j list + | i < 0 = [] + | j < 0 = [] + | i > j = [] + | otherwise = drop i (take (j + 1) list) {- | =⚔️= Task 4 From 7fb937b7434c2bf3e5d9c881f87e1dd7d5cb691c Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 11:11:14 +0100 Subject: [PATCH 17/35] fix(chapter-2): Task 4 --- src/Chapter2.hs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Chapter2.hs b/src/Chapter2.hs index 0dd2c769c..abc6f7a90 100644 --- a/src/Chapter2.hs +++ b/src/Chapter2.hs @@ -352,9 +352,8 @@ Implement a function that returns only the first half of a given list. >>> firstHalf "bca" "b" -} --- PUT THE FUNCTION TYPE IN HERE -firstHalf l = error "firstHalf: Not implemented!" - +firstHalf :: [a] -> [a] +firstHalf l = subList 0 (length l `div` 2 - 1) l {- | =🛡= Pattern matching From 4e8865a51eef0a6370f3dfaf72365e317d9f2c4c Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 11:19:29 +0100 Subject: [PATCH 18/35] fix(chapter-2): Task 5 --- src/Chapter2.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Chapter2.hs b/src/Chapter2.hs index abc6f7a90..a12a9f308 100644 --- a/src/Chapter2.hs +++ b/src/Chapter2.hs @@ -504,7 +504,9 @@ True >>> isThird42 [42, 42, 0, 42] False -} -isThird42 = error "isThird42: Not implemented!" +isThird42 :: [Int] -> Bool +isThird42 (_:_:42:_) = True +isThird42 _ = False {- | From e33eb0de73c2d9af45283bf145e5dd0fb5da1fea Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 11:41:53 +0100 Subject: [PATCH 19/35] feat(chapter-2): Task 4 --- src/Chapter2.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Chapter2.hs b/src/Chapter2.hs index a12a9f308..1b2958964 100644 --- a/src/Chapter2.hs +++ b/src/Chapter2.hs @@ -336,7 +336,7 @@ from it! ghci> :l src/Chapter2.hs -} subList :: Int -> Int -> [a] -> [a] -subList i j list +subList i j list | i < 0 = [] | j < 0 = [] | i > j = [] @@ -506,7 +506,7 @@ False -} isThird42 :: [Int] -> Bool isThird42 (_:_:42:_) = True -isThird42 _ = False +isThird42 _ = False {- | From 1255f807680bb2726be4405dac3ce601afbc0d7b Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 15:59:49 +0100 Subject: [PATCH 20/35] feat(chapter-2): Task 6 --- src/Chapter2.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Chapter2.hs b/src/Chapter2.hs index 1b2958964..89eb1e739 100644 --- a/src/Chapter2.hs +++ b/src/Chapter2.hs @@ -611,7 +611,8 @@ Implement a function that duplicates each element of the list -} duplicate :: [a] -> [a] -duplicate = error "duplicate: Not implemented!" +duplicate [] = [] +duplicate (x:xs) = x:x:(duplicate xs) {- | From 61c71e544f93395d32472283aa1930f35710b75a Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 16:07:43 +0100 Subject: [PATCH 21/35] feat(chapter-2): Task 7 --- src/Chapter2.hs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Chapter2.hs b/src/Chapter2.hs index 89eb1e739..da59fee1f 100644 --- a/src/Chapter2.hs +++ b/src/Chapter2.hs @@ -627,7 +627,13 @@ Write a function that takes elements of a list only in even positions. >>> takeEven [2, 1, 3, 5, 4] [2,3,4] -} -takeEven = error "takeEven: Not implemented!" +takeEven :: [a] -> [a] +takeEven l = go True l + where + go :: Bool -> [a] -> [a] + go _ [] = [] + go True (x:xs) = x : go False xs + go False (x:xs) = go True xs {- | =🛡= Higher-order functions From 761d07ceb85e4c336b0d94ad91839841bd9a853a Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 21:45:27 +0100 Subject: [PATCH 22/35] fix(chapter-2): Task 8 --- src/Chapter2.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Chapter2.hs b/src/Chapter2.hs index da59fee1f..28f8b0922 100644 --- a/src/Chapter2.hs +++ b/src/Chapter2.hs @@ -633,7 +633,7 @@ takeEven l = go True l go :: Bool -> [a] -> [a] go _ [] = [] go True (x:xs) = x : go False xs - go False (x:xs) = go True xs + go False (_:xs) = go True xs {- | =🛡= Higher-order functions @@ -740,7 +740,7 @@ value of the element itself 🕯 HINT: Use combination of 'map' and 'replicate' -} smartReplicate :: [Int] -> [Int] -smartReplicate l = error "smartReplicate: Not implemented!" +smartReplicate l = concat (map (\x -> replicate x x) l) {- | =⚔️= Task 9 From 7a6aad24de42d164bf4f11178ccf2d8695274980 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 21:58:41 +0100 Subject: [PATCH 23/35] feat(chapter-2): :sparkles: implement Task 9 --- src/Chapter2.hs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Chapter2.hs b/src/Chapter2.hs index 28f8b0922..0ad33dbe8 100644 --- a/src/Chapter2.hs +++ b/src/Chapter2.hs @@ -753,7 +753,13 @@ the list with only those lists that contain a passed element. 🕯 HINT: Use the 'elem' function to check whether an element belongs to a list -} -contains = error "contains: Not implemented!" +contains :: Int -> [[Int]] -> [[Int]] +contains n l = go n l where + go :: Int -> [[Int]] -> [[Int]] + go _ [] = [] + go n (x:xs) = if elem n x + then x : go n xs + else go n xs {- | From b06bd78f4ee2c36bf4cc0fc96c7d6649a20223f6 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 22:10:24 +0100 Subject: [PATCH 24/35] feat: :sparkles: impl Task 10 --- src/Chapter2.hs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Chapter2.hs b/src/Chapter2.hs index 0ad33dbe8..a00bbab37 100644 --- a/src/Chapter2.hs +++ b/src/Chapter2.hs @@ -799,13 +799,15 @@ 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 +pairMul :: Num a => [a] -> [a] -> [a] +pairMul = zipWith (*) {- | =🛡= Lazy evaluation From 92529dc677a15ef1e31b3ca7c6b15accf793f1e8 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 22:24:09 +0100 Subject: [PATCH 25/35] feat: :zap: impl Task 11 --- src/Chapter2.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Chapter2.hs b/src/Chapter2.hs index a00bbab37..f9e9f9029 100644 --- a/src/Chapter2.hs +++ b/src/Chapter2.hs @@ -862,7 +862,9 @@ list. 🕯 HINT: Use the 'cycle' function -} -rotate = error "rotate: Not implemented!" +rotate :: Int -> [a] -> [a] +rotate n _ | n < 0 = [] +rotate n l = take (length l) $ drop n $ cycle l {- | =💣= Task 12* From 16e5dd38d71a4039b9c8f18cfb681371bfcc5d79 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Fri, 24 Feb 2023 22:36:35 +0100 Subject: [PATCH 26/35] feat: :sparkles: impl Task 12 --- src/Chapter2.hs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Chapter2.hs b/src/Chapter2.hs index f9e9f9029..96f84576c 100644 --- a/src/Chapter2.hs +++ b/src/Chapter2.hs @@ -880,8 +880,11 @@ and reverses it. function, but in this task, you need to implement it manually. No cheating! -} -rewind = error "rewind: Not Implemented!" - +rewind :: [a] -> [a] +rewind l = go l [] where + go :: [a] -> [a] -> [a] + go [] acc = acc + go (x:xs) acc = go xs (x:acc) {- You did it! Now it is time to open pull request with your changes From f133a8cb09ba1e3eb58e82f4d4d650db87d0cf32 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Mon, 27 Feb 2023 13:08:51 +0100 Subject: [PATCH 27/35] fix(chapter-1): Task 9 no shadowing --- src/Chapter1.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Chapter1.hs b/src/Chapter1.hs index 1d2dc54e5..2808645ab 100644 --- a/src/Chapter1.hs +++ b/src/Chapter1.hs @@ -649,8 +649,8 @@ sumLast2 :: Int -> Int sumLast2 n = (lastDigit n) + secondLastDigit where lastTwo = (mod (abs n) 100) - last = lastDigit n - secondLastDigit = (lastTwo - last) `div` 10 + lastD = lastDigit n + secondLastDigit = (lastTwo - lastD) `div` 10 {- | =💣= Task 10* From d18d537fb651f3606f65c90258b131a05dbb7578 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Mon, 27 Feb 2023 13:55:04 +0100 Subject: [PATCH 28/35] feat(chapter-3): Task 1 --- src/Chapter3.hs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Chapter3.hs b/src/Chapter3.hs index 061811064..9cbe66b1a 100644 --- a/src/Chapter3.hs +++ b/src/Chapter3.hs @@ -343,6 +343,12 @@ Define the Book product data type. You can take inspiration from our description of a book, but you are not limited only by the book properties we described. Create your own book type of your dreams! -} +data Book = Book + { bookName :: String + , bookAuthor :: String + , bookPages :: Int + , bookCover :: String + } deriving (Show) {- | =⚔️= Task 2 From eb1bdd57d074c348db3172e0fc0e7585dc8f6c65 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Mon, 27 Feb 2023 14:34:41 +0100 Subject: [PATCH 29/35] feat(chapter-3): Task 2 & 3 --- src/Chapter3.hs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/Chapter3.hs b/src/Chapter3.hs index 9cbe66b1a..57da92ccd 100644 --- a/src/Chapter3.hs +++ b/src/Chapter3.hs @@ -381,6 +381,45 @@ after the fight. The battle has the following possible outcomes: ♫ NOTE: In this task, you need to implement only a single round of the fight. -} +data Knight = Knight + { + knightHealth :: Int, + knightAttack :: Int, + knightGold :: Int + } deriving (Show) + +data Monster = Monster + { + monsterHealth :: Int, + monsterAttack :: Int, + monsterGold :: Int + } deriving (Show) + +fight :: Knight -> Monster -> Int +fight k m | (knightAttack k >= monsterHealth m) = knightGold k + monsterGold m +fight k m | (knightAttack k < monsterHealth m && knightHealth k <= monsterAttack m) = (-1) +fight k _ = knightGold k + +arthur :: Knight +arthur = Knight { knightAttack = 10, knightHealth = 10, knightGold = 0 } + +dragon :: Monster +dragon = Monster { monsterAttack = 10, monsterHealth = 10, monsterGold = 100 } + +win :: Int +win = fight arthur dragon + +tooStrongMonster :: Monster +tooStrongMonster = Monster { monsterAttack = 20, monsterHealth = 20, monsterGold = 10000 } + +dead :: Int +dead = fight arthur tooStrongMonster + +turtleMonster :: Monster +turtleMonster = Monster { monsterAttack = 1, monsterHealth = 30, monsterGold = 10 } + +firstRound :: Int +firstRound = fight arthur turtleMonster {- | =🛡= Sum types @@ -468,6 +507,8 @@ Create a simple enumeration for the meal types (e.g. breakfast). The one who comes up with the most number of names wins the challenge. Use your creativity! -} +data Meal = Breakfast | SecondBreakfast | Lunch | TeeTime | AfternoonSnack | Dinner | MidnightSnack + {- | =⚔️= Task 4 From 2cb5c602d73cd439b09b51be429ecfb3265cb253 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Mon, 27 Feb 2023 21:22:45 +0100 Subject: [PATCH 30/35] refactor(chapter-2): :sparkles: more consize code --- src/Chapter2.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Chapter2.hs b/src/Chapter2.hs index 96f84576c..3e8fc979e 100644 --- a/src/Chapter2.hs +++ b/src/Chapter2.hs @@ -612,7 +612,7 @@ Implement a function that duplicates each element of the list -} duplicate :: [a] -> [a] duplicate [] = [] -duplicate (x:xs) = x:x:(duplicate xs) +duplicate (x:xs) = x:x: duplicate xs {- | @@ -757,7 +757,7 @@ contains :: Int -> [[Int]] -> [[Int]] contains n l = go n l where go :: Int -> [[Int]] -> [[Int]] go _ [] = [] - go n (x:xs) = if elem n x + go n (x:xs) = if n `elem` x then x : go n xs else go n xs From 96748faa1834d85069e736880bc90902895d56da Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Mon, 27 Feb 2023 21:23:17 +0100 Subject: [PATCH 31/35] feat(chapter-3): :zap: Task 4 --- src/Chapter3.hs | 1343 +++++++++++++++++++++++++---------------------- 1 file changed, 705 insertions(+), 638 deletions(-) diff --git a/src/Chapter3.hs b/src/Chapter3.hs index 57da92ccd..a1dea1c5c 100644 --- a/src/Chapter3.hs +++ b/src/Chapter3.hs @@ -35,7 +35,6 @@ your solutions. Okay. Ready? Set. Go! -} - {- =⚗️= Language extensions* @@ -68,466 +67,530 @@ Haskell has several different ways to create entirely new data types. Let's talk about them all and master our skill of data types construction. -} -{- | -=🛡= Type aliases - -The simplest way to introduce a new type in Haskell is __type aliases__. Type -aliases are nothing more than just other names to already existing types. -A few examples: - -@ -type IntList = [Int] -type IntPair = (Int, Int) -@ - -Type aliases are just syntactic sugar and would be replaced by the real types -during compilation, so they don't bring too much to the table of data types. -However, they may make the life of a developer easier in some places. - -One of the most common type aliases is 'String' that we already mentioned in the -List section of the previous chapter. And it is defined in the following way: - -@ -type String = [Char] -@ - -Now it makes much more sense of why 'String' is related to lists. You can see -that any list function could be used on 'String's as well. - -👩‍🔬 Due to the implementation details of lists, such representation of String - is highly inefficient. It is unfortunate that the list of characters is the - default String type. Experienced Haskellers use more efficient string types - 'Text' and 'ByteString' from Haskell libraries: 'text' and 'bytestring' - correspondingly. But, for the simplicity of this training, we are using - 'String'. - -Another common type alias in the standard library is "FilePath", which is the -same as 'String' (which is the same as '[Char]'): - -@ -type FilePath = String -@ - -Usually, you are encouraged to introduce new data types instead of using aliases -for existing types. But it is still good to know about type aliases. They have a -few use-cases, and you can meet them in various Haskell libraries as well. --} - -{- | -=🛡= ADT - -Let's not limit ourselves with just type aliases and define some real new data -structures. Are we the creators of new worlds or what? - -Type in Haskell is like a box of information description that the object of that -type should contain. - -Haskell uses Algebraic Data Types (ADT) system of types. That means that there -are two types of types: product and sum types. - -To give you some basic understanding of the difference between these two, let's -go to the book shop. A book in there represents a product type: each book has -the name, author, cover, pages, etc. And all of these properties are mandatory -and come with the book. Bookshelf, in its turn, is a sum type. Each book in a -shelf is a different type, and you can choose one of them at once (there is no -such book where two or more physical books are sewed together). - -We will show you an example now, just to illustrate all the above and then will -explain each concept separately. Note, this is not real syntax. - -@ --- Product type -Book: - book name - AND book author - AND book cover - AND book pages - - --- Sum type -BookShelf: - Good book 1 : {Book} - OR Good book 2 : {Book} - OR Cheap book 3 : {Book} -@ - -👩‍🔬 We use AND in product types to represent the notion of having all fields at - the same time. In contrast, for the sum types, we use OR to tell only about a - single possibility. AND in logic corresponds to multiplication in math, and OR - corresponds to addition. You see that there is some math theory behind the - concept of data types in Haskell, and that's why they are called Algebraic Data - Types. --} - -{- | -=🛡= Product type - -Let's now see how product data types look like in Haskell. - -Product type should have a type name, one type constructor (the function that -lets you create the value of the type later) and the description of the fields -it consists of in the view of types. - -When defining a custom type in Haskell, you use the __"data"__ keyword, write -the __type name__ you come up with after it, and then after the "=" sign you -specify the __constructor name__ followed by the __fields__. - -When in action, a custom data type could be used in the following use case. -A definition of a data type for a knight with a name and number of victories -can look like this: - -@ - ┌─ type name - │ - │ ┌─ constructor name (or constructor tag) - │ │ -data Knight = MkKnight String Int - │ │ │ - │ └────┴─── types of fields - │ - └ "data" keyword -@ - -♫ NOTE: The constructor can have the same name as the type itself. And in most - cases, they are indeed named identically. This is not a problem for Haskell, - because types live in the types namespace, and constructors live in the value - namespace. So there won't be any collisions and misunderstandings from the - compiler side. The names are unambiguous. - -You can use the constructor name, to create a value of the "Knight" type. - -@ -arthur :: Knight -arthur = MkKnight "Arthur" 100 -@ - -A constructor is just a function from fields to the type! You can verify this in GHCi: - -ghci> :t MkKnight -MkKnight :: String -> Int -> Knight - -As in a regular function, you need to provide a 'String' and an 'Int' to the -'MkKnight' constructor in order to get the full-fledged 'Knight'. - -Also, you can write a function that takes a Knight and returns its name. -It is convenient to use pattern matching for that: - -@ -knightName :: Knight -> String -knightName (MkKnight name _) = name -@ - -And you can extract the name in GHCi: - -ghci> knightName arthur -"Arthur" - -It is comfy to have such getters for all types, so Haskell provides a syntax for -defining __records__ — named parameters for the product data type fields. - -Records have a similar syntax for defining in Haskell as (unnamed) ordinary -product types, but fields are specified in the {} separated by a comma. Each -field should have a name and a type in this form: 'fieldName :: FieldType'. - -The same definition of 'Knight' but with records should be written in the -following way: - -@ -data Knight = MkKnight - { knightName :: String - , knightVictories :: Int - } -@ - -The above type definition is equivalent to the one we had before. We just gave -names to our fields for clarity. In addition, we also automatically get -functions "knightName :: Knight -> String" and "knightVictories :: Knight -> -Int". This is what records bring us — free getters! - -The pattern matching on constructors of such records can stay the same. Besides, -you can use field names as getters. - -👩‍🔬 We are using a particular naming scheme of record field names in record - types in order to avoid name collisions. We add the data type name prefix in - front of each usual field name for that. As we saw, records create getters for - us, which are actually top-level functions. In Haskell, all functions defined at - top-level are available in the whole scope within a module. But Haskell forbids - creating multiple functions with the same name. The Haskell ecosystem has - numerous ways of solving this so-called "record" problem. Still, for simplicity - reasons, we are going to use disambiguation by a prefix which is one of the - standard resolutions for the scope problem. - -In addition to getting top-level getters automatically, you get the following -features for free when using records over unnamed type fields: - - 1. Specify names during constructions — additional visual help for code. - 2. Record-update syntax. - -By default, all functions and constructors work with positional arguments -(unnamed, that are identified by its position in the type declaration). You -write a function or a constructor name first, and then you pass arguments -separated by spaces. But once we declare a product type as a record, we can use -field names for specifying constructor values. That means that the position -doesn't matter anymore as long as we specify the names. So it is like using -named arguments but only for constructors with records. -This is an alternative way of defining values of custom records. - -Let's introduce Sir Arthur properly! - -@ -arthur :: Knight -arthur = MkKnight - { knightName = "Arthur" - , knightVictories = 100 - } -@ - -After we created our custom types and defined some values, we may want to change -some fields of our values. But we can't actually change anything! Remember that -all values in Haskell are immutable, and you can't just change a field of some -type. You need to create a new value! Fortunately, for records, we can use the -__record update syntax__. Record update syntax allows creating new objects of a -record type by assigning new values to the fields of some existing record. - -The syntax for record update is the following: - -@ -lancelot :: Knight -lancelot = arthur { knightName = "Lancelot" } -@ - -Without records, we had to write a custom setter function for each field of each -data type. - -@ -setKnightName :: String -> Knight -> Knight -setKnightName newName (MkKnight _ victories) = - MkKnight newName victories -@ - -♫ NOTE: By default, GHCi doesn't know how to display values of custom types. If - you want to explore custom data types in the REPL, you need to add a magical - "deriving (Show)" line (will be explained later in this chapter) at the end of a - record. Like so: - -@ -data Knight = MkKnight - { knightName :: String - , knightVictories :: Int - } deriving (Show) -@ - -Now GHCi should be able to show the values of your types! Try playing with our -knights in GHCi to get the idea behind records. - -🕯 HINT: At this point, you may want to be able to enter multi-line strings in - GHCi. Start multi-line blocks by typing the ":{" command, and close such blocks - using the ":}" command. - -ghci> :{ -ghci| data Knight = MkKnight -ghci| { knightName :: String -ghci| , knightVictories :: Int -ghci| } deriving (Show) -ghci| :} -ghci> - -Although, it may be easier to define data types in the module, and load it -afterwards. --} - -{- | -=⚔️= Task 1 - -Define the Book product data type. You can take inspiration from our description -of a book, but you are not limited only by the book properties we described. -Create your own book type of your dreams! --} +-- | +-- =🛡= Type aliases +-- +-- The simplest way to introduce a new type in Haskell is __type aliases__. Type +-- aliases are nothing more than just other names to already existing types. +-- A few examples: +-- +-- @ +-- type IntList = [Int] +-- type IntPair = (Int, Int) +-- @ +-- +-- Type aliases are just syntactic sugar and would be replaced by the real types +-- during compilation, so they don't bring too much to the table of data types. +-- However, they may make the life of a developer easier in some places. +-- +-- One of the most common type aliases is 'String' that we already mentioned in the +-- List section of the previous chapter. And it is defined in the following way: +-- +-- @ +-- type String = [Char] +-- @ +-- +-- Now it makes much more sense of why 'String' is related to lists. You can see +-- that any list function could be used on 'String's as well. +-- +-- 👩‍🔬 Due to the implementation details of lists, such representation of String +-- is highly inefficient. It is unfortunate that the list of characters is the +-- default String type. Experienced Haskellers use more efficient string types +-- 'Text' and 'ByteString' from Haskell libraries: 'text' and 'bytestring' +-- correspondingly. But, for the simplicity of this training, we are using +-- 'String'. +-- +-- Another common type alias in the standard library is "FilePath", which is the +-- same as 'String' (which is the same as '[Char]'): +-- +-- @ +-- type FilePath = String +-- @ +-- +-- Usually, you are encouraged to introduce new data types instead of using aliases +-- for existing types. But it is still good to know about type aliases. They have a +-- few use-cases, and you can meet them in various Haskell libraries as well. + +-- | +-- =🛡= ADT +-- +-- Let's not limit ourselves with just type aliases and define some real new data +-- structures. Are we the creators of new worlds or what? +-- +-- Type in Haskell is like a box of information description that the object of that +-- type should contain. +-- +-- Haskell uses Algebraic Data Types (ADT) system of types. That means that there +-- are two types of types: product and sum types. +-- +-- To give you some basic understanding of the difference between these two, let's +-- go to the book shop. A book in there represents a product type: each book has +-- the name, author, cover, pages, etc. And all of these properties are mandatory +-- and come with the book. Bookshelf, in its turn, is a sum type. Each book in a +-- shelf is a different type, and you can choose one of them at once (there is no +-- such book where two or more physical books are sewed together). +-- +-- We will show you an example now, just to illustrate all the above and then will +-- explain each concept separately. Note, this is not real syntax. +-- +-- @ +-- -- Product type +-- Book: +-- book name +-- AND book author +-- AND book cover +-- AND book pages +-- +-- +-- -- Sum type +-- BookShelf: +-- Good book 1 : {Book} +-- OR Good book 2 : {Book} +-- OR Cheap book 3 : {Book} +-- @ +-- +-- 👩‍🔬 We use AND in product types to represent the notion of having all fields at +-- the same time. In contrast, for the sum types, we use OR to tell only about a +-- single possibility. AND in logic corresponds to multiplication in math, and OR +-- corresponds to addition. You see that there is some math theory behind the +-- concept of data types in Haskell, and that's why they are called Algebraic Data +-- Types. + +-- | +-- =🛡= Product type +-- +-- Let's now see how product data types look like in Haskell. +-- +-- Product type should have a type name, one type constructor (the function that +-- lets you create the value of the type later) and the description of the fields +-- it consists of in the view of types. +-- +-- When defining a custom type in Haskell, you use the __"data"__ keyword, write +-- the __type name__ you come up with after it, and then after the "=" sign you +-- specify the __constructor name__ followed by the __fields__. +-- +-- When in action, a custom data type could be used in the following use case. +-- A definition of a data type for a knight with a name and number of victories +-- can look like this: +-- +-- @ +-- ┌─ type name +-- │ +-- │ ┌─ constructor name (or constructor tag) +-- │ │ +-- data Knight = MkKnight String Int +-- │ │ │ +-- │ └────┴─── types of fields +-- │ +-- └ "data" keyword +-- @ +-- +-- ♫ NOTE: The constructor can have the same name as the type itself. And in most +-- cases, they are indeed named identically. This is not a problem for Haskell, +-- because types live in the types namespace, and constructors live in the value +-- namespace. So there won't be any collisions and misunderstandings from the +-- compiler side. The names are unambiguous. +-- +-- You can use the constructor name, to create a value of the "Knight" type. +-- +-- @ +-- arthur :: Knight +-- arthur = MkKnight "Arthur" 100 +-- @ +-- +-- A constructor is just a function from fields to the type! You can verify this in GHCi: +-- +-- ghci> :t MkKnight +-- MkKnight :: String -> Int -> Knight +-- +-- As in a regular function, you need to provide a 'String' and an 'Int' to the +-- 'MkKnight' constructor in order to get the full-fledged 'Knight'. +-- +-- Also, you can write a function that takes a Knight and returns its name. +-- It is convenient to use pattern matching for that: +-- +-- @ +-- knightName :: Knight -> String +-- knightName (MkKnight name _) = name +-- @ +-- +-- And you can extract the name in GHCi: +-- +-- ghci> knightName arthur +-- "Arthur" +-- +-- It is comfy to have such getters for all types, so Haskell provides a syntax for +-- defining __records__ — named parameters for the product data type fields. +-- +-- Records have a similar syntax for defining in Haskell as (unnamed) ordinary +-- product types, but fields are specified in the {} separated by a comma. Each +-- field should have a name and a type in this form: 'fieldName :: FieldType'. +-- +-- The same definition of 'Knight' but with records should be written in the +-- following way: +-- +-- @ +-- data Knight = MkKnight +-- { knightName :: String +-- , knightVictories :: Int +-- } +-- @ +-- +-- The above type definition is equivalent to the one we had before. We just gave +-- names to our fields for clarity. In addition, we also automatically get +-- functions "knightName :: Knight -> String" and "knightVictories :: Knight -> +-- Int". This is what records bring us — free getters! +-- +-- The pattern matching on constructors of such records can stay the same. Besides, +-- you can use field names as getters. +-- +-- 👩‍🔬 We are using a particular naming scheme of record field names in record +-- types in order to avoid name collisions. We add the data type name prefix in +-- front of each usual field name for that. As we saw, records create getters for +-- us, which are actually top-level functions. In Haskell, all functions defined at +-- top-level are available in the whole scope within a module. But Haskell forbids +-- creating multiple functions with the same name. The Haskell ecosystem has +-- numerous ways of solving this so-called "record" problem. Still, for simplicity +-- reasons, we are going to use disambiguation by a prefix which is one of the +-- standard resolutions for the scope problem. +-- +-- In addition to getting top-level getters automatically, you get the following +-- features for free when using records over unnamed type fields: +-- +-- 1. Specify names during constructions — additional visual help for code. +-- 2. Record-update syntax. +-- +-- By default, all functions and constructors work with positional arguments +-- (unnamed, that are identified by its position in the type declaration). You +-- write a function or a constructor name first, and then you pass arguments +-- separated by spaces. But once we declare a product type as a record, we can use +-- field names for specifying constructor values. That means that the position +-- doesn't matter anymore as long as we specify the names. So it is like using +-- named arguments but only for constructors with records. +-- This is an alternative way of defining values of custom records. +-- +-- Let's introduce Sir Arthur properly! +-- +-- @ +-- arthur :: Knight +-- arthur = MkKnight +-- { knightName = "Arthur" +-- , knightVictories = 100 +-- } +-- @ +-- +-- After we created our custom types and defined some values, we may want to change +-- some fields of our values. But we can't actually change anything! Remember that +-- all values in Haskell are immutable, and you can't just change a field of some +-- type. You need to create a new value! Fortunately, for records, we can use the +-- __record update syntax__. Record update syntax allows creating new objects of a +-- record type by assigning new values to the fields of some existing record. +-- +-- The syntax for record update is the following: +-- +-- @ +-- lancelot :: Knight +-- lancelot = arthur { knightName = "Lancelot" } +-- @ +-- +-- Without records, we had to write a custom setter function for each field of each +-- data type. +-- +-- @ +-- setKnightName :: String -> Knight -> Knight +-- setKnightName newName (MkKnight _ victories) = +-- MkKnight newName victories +-- @ +-- +-- ♫ NOTE: By default, GHCi doesn't know how to display values of custom types. If +-- you want to explore custom data types in the REPL, you need to add a magical +-- "deriving (Show)" line (will be explained later in this chapter) at the end of a +-- record. Like so: +-- +-- @ +-- data Knight = MkKnight +-- { knightName :: String +-- , knightVictories :: Int +-- } deriving (Show) +-- @ +-- +-- Now GHCi should be able to show the values of your types! Try playing with our +-- knights in GHCi to get the idea behind records. +-- +-- 🕯 HINT: At this point, you may want to be able to enter multi-line strings in +-- GHCi. Start multi-line blocks by typing the ":{" command, and close such blocks +-- using the ":}" command. +-- +-- ghci> :{ +-- ghci| data Knight = MkKnight +-- ghci| { knightName :: String +-- ghci| , knightVictories :: Int +-- ghci| } deriving (Show) +-- ghci| :} +-- ghci> +-- +-- Although, it may be easier to define data types in the module, and load it +-- afterwards. + +-- | +-- =⚔️= Task 1 +-- +-- Define the Book product data type. You can take inspiration from our description +-- of a book, but you are not limited only by the book properties we described. +-- Create your own book type of your dreams! data Book = Book - { bookName :: String - , bookAuthor :: String - , bookPages :: Int - , bookCover :: String - } deriving (Show) - -{- | -=⚔️= Task 2 - -Prepare to defend the honour of our kingdom! A monster attacks our brave knight. -Help him to fight this creature! - -Define data types for Knights and Monsters, and write the "fight" function. - -Both a knight and a monster have the following properties: - - ✦ Health (the number of health points) - ✦ Attack (the number of attack units) - ✦ Gold (the number of coins) - -When a monster fights a knight, the knight hits first, and the monster hits back -only if it survives (health is bigger than zero). A hit decreases the amount of -health by the number represented in the "attack" field. - -Implement the "fight" function, that takes a monster and a knight, performs the -fight following the above rules and returns the amount of gold the knight has -after the fight. The battle has the following possible outcomes: - - ⊛ Knight wins and takes the loot from the monster and adds it to their own - earned treasure - ⊛ Monster defeats the knight. In that case return -1 - ⊛ Neither the knight nor the monster wins. On such an occasion, the knight - doesn't earn any money and keeps what they had before. - -♫ NOTE: In this task, you need to implement only a single round of the fight. - --} + { bookName :: String, + bookAuthor :: String, + bookPages :: Int, + bookCover :: String + } + deriving (Show) + +-- | +-- =⚔️= Task 2 +-- +-- Prepare to defend the honour of our kingdom! A monster attacks our brave knight. +-- Help him to fight this creature! +-- +-- Define data types for Knights and Monsters, and write the "fight" function. +-- +-- Both a knight and a monster have the following properties: +-- +-- ✦ Health (the number of health points) +-- ✦ Attack (the number of attack units) +-- ✦ Gold (the number of coins) +-- +-- When a monster fights a knight, the knight hits first, and the monster hits back +-- only if it survives (health is bigger than zero). A hit decreases the amount of +-- health by the number represented in the "attack" field. +-- +-- Implement the "fight" function, that takes a monster and a knight, performs the +-- fight following the above rules and returns the amount of gold the knight has +-- after the fight. The battle has the following possible outcomes: +-- +-- ⊛ Knight wins and takes the loot from the monster and adds it to their own +-- earned treasure +-- ⊛ Monster defeats the knight. In that case return -1 +-- ⊛ Neither the knight nor the monster wins. On such an occasion, the knight +-- doesn't earn any money and keeps what they had before. +-- +-- ♫ NOTE: In this task, you need to implement only a single round of the fight. data Knight = Knight - { - knightHealth :: Int, - knightAttack :: Int, - knightGold :: Int - } deriving (Show) + { knightName :: String, + knightHealth :: Int, + knightAttack :: Int, + knightGold :: Int + } + deriving (Show) data Monster = Monster - { - monsterHealth :: Int, - monsterAttack :: Int, - monsterGold :: Int - } deriving (Show) + { monsterName :: String, + monsterHealth :: Int, + monsterAttack :: Int, + monsterGold :: Int + } + deriving (Show) fight :: Knight -> Monster -> Int -fight k m | (knightAttack k >= monsterHealth m) = knightGold k + monsterGold m -fight k m | (knightAttack k < monsterHealth m && knightHealth k <= monsterAttack m) = (-1) +fight k m | knightAttack k >= monsterHealth m = knightGold k + monsterGold m +fight k m | knightAttack k < monsterHealth m = -1 fight k _ = knightGold k arthur :: Knight -arthur = Knight { knightAttack = 10, knightHealth = 10, knightGold = 0 } +arthur = Knight {knightName = "Arthur", knightAttack = 10, knightHealth = 10, knightGold = 0} dragon :: Monster -dragon = Monster { monsterAttack = 10, monsterHealth = 10, monsterGold = 100 } +dragon = Monster {monsterName = "Dragon", monsterAttack = 10, monsterHealth = 10, monsterGold = 100} win :: Int win = fight arthur dragon tooStrongMonster :: Monster -tooStrongMonster = Monster { monsterAttack = 20, monsterHealth = 20, monsterGold = 10000 } +tooStrongMonster = Monster {monsterName = "Gold Dragon", monsterAttack = 20, monsterHealth = 20, monsterGold = 10000} dead :: Int dead = fight arthur tooStrongMonster turtleMonster :: Monster -turtleMonster = Monster { monsterAttack = 1, monsterHealth = 30, monsterGold = 10 } - -firstRound :: Int -firstRound = fight arthur turtleMonster - -{- | -=🛡= Sum types - -Another powerful ambassador of ADTs is the __sum type__. Unlike ordinary records -(product types) that always have all the fields you wrote, sum types represent -alternatives of choices. Sum types can be seen as "one-of" data structures. They -contain many product types (described in the previous section) as alternatives. - -To define a sum type, you have to specify all possible constructors separated -by "|". Each constructor on its own could have an ADT, that describes -this branch of the alternative. - -There is at least one famous sum type that you have already seen — 'Bool' — the -simplest example of a sum type. - -@ -data Bool = False | True -@ - -'Bool' is a representer of so-called __enumeration__ — a special case of sum -types, a sum of nullary constructors (constructors without fields). -Sum types can have much more than two constructors (but don't abuse this)! - -Look at this one. We need more than two constructors to mirror the "Magic Type". -And none of the magic streams needs any fields. Just pure magic ;) - -@ -data MagicType - = DarkMagic - | LightMagic - | NeutralMagic -@ - -However, the real power of sum types unleashes when you combine them with -fields. As we mentioned, each "|" case in the sum type could be an ADT, so, -naturally, you can have constructors with fields, which are product types from -the previous section. If you think about it, the enumeration also contains a -product type, as it is absolutely legal to create a data type with one -constructor and without any fields: `data Emptiness = TotalVoid`. - -To showcase such sum type, let's represent a possible loot from successfully -completing an adventure: - -@ -data Loot - = Sword Int -- attack - | Shield Int -- defence - | WizardStaff Power SpellLevel -@ - -You can create values of the sum types by using different constructors: - -@ -woodenSword :: Loot -woodenSword = Sword 2 - -adamantiumShield :: Loot -adamantiumShield = Shield 3000 -@ - -And you can pattern match on different constructors as well. - -@ -acceptLoot :: Loot -> String -acceptLoot loot = case loot of - Sword _ -> "Thanks! That's a great sword!" - Shield _ -> "I'll accept this shield as a reward!" - WizardStaff _ _ -> "What?! I'm not a wizard, take it back!" -@ - - -To sum up all the above, a data type in Haskell can have zero or more -constructors, and each constructor can have zero or more fields. This altogether -gives us product types (records with fields) and sum types (alternatives). The -concept of product types and sum types is called __Algebraic Data Type__. They -allow you to model your domain precisely, make illegal states unrepresentable -and provide more flexibility when working with data types. --} - -{- | -=⚔️= Task 3 - -Create a simple enumeration for the meal types (e.g. breakfast). The one who -comes up with the most number of names wins the challenge. Use your creativity! --} - +turtleMonster = Monster {monsterName = "Turtle", monsterAttack = 1, monsterHealth = 30, monsterGold = 10} + +firstRoundResult :: Int +firstRoundResult = fight arthur turtleMonster + +-- | +-- =🛡= Sum types +-- +-- Another powerful ambassador of ADTs is the __sum type__. Unlike ordinary records +-- (product types) that always have all the fields you wrote, sum types represent +-- alternatives of choices. Sum types can be seen as "one-of" data structures. They +-- contain many product types (described in the previous section) as alternatives. +-- +-- To define a sum type, you have to specify all possible constructors separated +-- by "|". Each constructor on its own could have an ADT, that describes +-- this branch of the alternative. +-- +-- There is at least one famous sum type that you have already seen — 'Bool' — the +-- simplest example of a sum type. +-- +-- @ +-- data Bool = False | True +-- @ +-- +-- 'Bool' is a representer of so-called __enumeration__ — a special case of sum +-- types, a sum of nullary constructors (constructors without fields). +-- Sum types can have much more than two constructors (but don't abuse this)! +-- +-- Look at this one. We need more than two constructors to mirror the "Magic Type". +-- And none of the magic streams needs any fields. Just pure magic ;) +-- +-- @ +-- data MagicType +-- = DarkMagic +-- | LightMagic +-- | NeutralMagic +-- @ +-- +-- However, the real power of sum types unleashes when you combine them with +-- fields. As we mentioned, each "|" case in the sum type could be an ADT, so, +-- naturally, you can have constructors with fields, which are product types from +-- the previous section. If you think about it, the enumeration also contains a +-- product type, as it is absolutely legal to create a data type with one +-- constructor and without any fields: `data Emptiness = TotalVoid`. +-- +-- To showcase such sum type, let's represent a possible loot from successfully +-- completing an adventure: +-- +-- @ +-- data Loot +-- = Sword Int -- attack +-- | Shield Int -- defence +-- | WizardStaff Power SpellLevel +-- @ +-- +-- You can create values of the sum types by using different constructors: +-- +-- @ +-- woodenSword :: Loot +-- woodenSword = Sword 2 +-- +-- adamantiumShield :: Loot +-- adamantiumShield = Shield 3000 +-- @ +-- +-- And you can pattern match on different constructors as well. +-- +-- @ +-- acceptLoot :: Loot -> String +-- acceptLoot loot = case loot of +-- Sword _ -> "Thanks! That's a great sword!" +-- Shield _ -> "I'll accept this shield as a reward!" +-- WizardStaff _ _ -> "What?! I'm not a wizard, take it back!" +-- @ +-- +-- +-- To sum up all the above, a data type in Haskell can have zero or more +-- constructors, and each constructor can have zero or more fields. This altogether +-- gives us product types (records with fields) and sum types (alternatives). The +-- concept of product types and sum types is called __Algebraic Data Type__. They +-- allow you to model your domain precisely, make illegal states unrepresentable +-- and provide more flexibility when working with data types. + +-- | +-- =⚔️= Task 3 +-- +-- Create a simple enumeration for the meal types (e.g. breakfast). The one who +-- comes up with the most number of names wins the challenge. Use your creativity! data Meal = Breakfast | SecondBreakfast | Lunch | TeeTime | AfternoonSnack | Dinner | MidnightSnack -{- | -=⚔️= Task 4 - -Define types to represent a magical city in the world! A typical city has: - -⍟ Optional castle with a __name__ (as 'String') -⍟ Wall, but only if the city has a castle -⍟ Church or library but not both -⍟ Any number of houses. Each house has one, two, three or four __people__ inside. - -After defining the city, implement the following functions: - - ✦ buildCastle — build a castle in the city. If the city already has a castle, - the old castle is destroyed, and the new castle with the __new name__ is built - ✦ buildHouse — add a new living house - ✦ buildWalls — build walls in the city. But since building walls is a - complicated task, walls can be built only if the city has a castle - and at least 10 living __people__ inside in all houses of the city in total. --} +-- | +-- =⚔️= Task 4 +-- +-- Define types to represent a magical city in the world! A typical city has: +-- +-- ⍟ Optional castle with a __name__ (as 'String') +-- ⍟ Wall, but only if the city has a castle +-- ⍟ Church or library but not both +-- ⍟ Any number of houses. Each house has one, two, three or four __people__ inside. +-- +-- After defining the city, implement the following functions: +-- +-- ✦ buildCastle — build a castle in the city. If the city already has a castle, +-- the old castle is destroyed, and the new castle with the __new name__ is built +-- ✦ buildHouse — add a new living house +-- ✦ buildWalls — build walls in the city. But since building walls is a +-- complicated task, walls can be built only if the city has a castle +-- and at least 10 living __people__ inside in all houses of the city in total. +data Culture = Church | Library deriving (Show) + +newtype House = House {housePeople :: Int} deriving (Show) + +newtype Castle = Castle {name :: String} deriving (Show) + +data City where + WalledCity :: + { cityCastle :: Castle, + cityCulture :: Culture, + cityHouses :: [House] + } -> + City + CastleCity :: + { cityCastle :: Castle, + cityCulture :: Culture, + cityHouses :: [House] + } -> + City + VillageCity :: + {cityCulture :: Culture, cityHouses :: [House]} -> + City + deriving (Show) + +peopleInCity :: [House] -> Int +peopleInCity houses = sum $ map housePeople houses + +canBuildWalls :: [House] -> Bool +canBuildWalls houses = peopleInCity houses >= 10 + +buildCastle :: City -> String -> City +buildCastle city name = case city of + VillageCity {cityCulture, cityHouses} -> CastleCity {cityCastle = Castle name, cityCulture = cityCulture, cityHouses = cityHouses} + _ -> city {cityCastle = Castle name} + +createHouse :: Int -> [House] +createHouse people + | people > 0 && people < 5 = [House people] + | otherwise = [] + +buildHouse :: City -> Int -> City +buildHouse city people = city {cityHouses = createHouse people ++ cityHouses city} + +createCity :: String -> Culture -> [House] -> City +createCity name culture houses + | canBuildWalls houses = WalledCity (Castle name) culture houses + | otherwise = CastleCity (Castle name) culture houses + +buildWalls :: City -> City +buildWalls city = case city of + WalledCity {} -> city -- already has walls + CastleCity {cityHouses, cityCulture, cityCastle} | canBuildWalls cityHouses -> WalledCity cityCastle cityCulture cityHouses + _ -> city -- no castle or not enough people to build walls + +auenland :: City +auenland = VillageCity Library $ createHouse 1 + +auenlandCannotBuildWalls :: City +auenlandCannotBuildWalls = buildWalls auenland + +auenlandCannotBuildWallsEvenWithCastle :: City +auenlandCannotBuildWallsEvenWithCastle = buildWalls $ buildCastle auenlandCannotBuildWalls "Auenland Castle" + +grownAuenland :: City +grownAuenland = buildHouse auenland 4 + +auenlandWithCastle :: City +auenlandWithCastle = buildCastle grownAuenland "Auenland Castle" + +auenlandWithCastleAndWalls :: City +auenlandWithCastleAndWalls = buildWalls auenlandWithCastle {- =🛡= Newtypes @@ -610,12 +673,12 @@ introducing extra newtypes. implementation of the "hitPlayer" function at all! -} data Player = Player - { playerHealth :: Int - , playerArmor :: Int - , playerAttack :: Int - , playerDexterity :: Int - , playerStrength :: Int - } + { playerHealth :: Int, + playerArmor :: Int, + playerAttack :: Int, + playerDexterity :: Int, + playerStrength :: Int + } calculatePlayerDamage :: Int -> Int -> Int calculatePlayerDamage attack strength = attack + strength @@ -629,178 +692,179 @@ calculatePlayerHit damage defense health = health + defense - damage -- The second player hits first player and the new first player is returned hitPlayer :: Player -> Player -> Player hitPlayer player1 player2 = - let damage = calculatePlayerDamage - (playerAttack player2) - (playerStrength player2) - defense = calculatePlayerDefense - (playerArmor player1) - (playerDexterity player1) - newHealth = calculatePlayerHit - damage - defense - (playerHealth player1) - in player1 { playerHealth = newHealth } - -{- | -=🛡= Polymorphic data types - -Similar to functions, data types in Haskell can be __polymorphic__. This means -that they can use some type variables as placeholders, representing general -types. You can either reason about data types in terms of such variables (and -don't worry about the specific types), or substitute variables with some -particular types. -Such polymorphism in Haskell is an example of __parametric polymorphism__. - -The process of defining a polymorphic type is akin to an ordinary data type -definition. The only difference is that all the type variables should go after -the type name so that you can reuse them in the constructor fields later. - -For example, - -@ -data Foo a = MkFoo a -@ - -Note that both product and sum types can be parameterised. - -> Actually, we've already seen a polymorphic data type! Remember Lists from Chapter Two? - -To give an example of a custom polymorphic type, let's implement a -"TreasureChest" data type. Our treasure chest is flexible, and it can store some -amount of gold. Additionally there is some space for one more arbitrary -treasure. But that could be any treasure, and we don't know what it is -beforehand. - -In Haskell words, the data type can be defined like this: - -@ -data TreasureChest x = TreasureChest - { treasureChestGold :: Int - , treasureChestLoot :: x - } -@ - -You can see that a treasure chest can store any treasure, indeed! We call it -treasure 'x'. - -And when writing functions involving the "TreasureChest" type, we don't always -need to know what kind of treasure is inside besides gold. -We can either use a type variable in our type signature: - -@ -howMuchGoldIsInMyChest :: TreasureChest x -> Int -@ - -or we can specify a concrete type: - -@ -isEnoughDiamonds :: TreasureChest Diamond -> Bool -@ - -In the same spirit, we can implement a function that creates a treasure chest with some -predefined amount of gold and a given treasure: - -@ -mkMehChest :: x -> TreasureChest x -mkMehChest treasure = TreasureChest - { treasureChestGold = 50 - , treasureChestLoot = treasure - } -@ - - -Polymorphic Algebraic Data Types are a great deal! One of the most common and -useful standard polymorphic types is __"Maybe"__. It represents the notion of -optional value (maybe the value is there, or maybe it is not). -"Maybe" is defined in the standard library in the following way: - -@ -data Maybe a - = Nothing - | Just a -@ - -Haskell doesn't have a concept of "null" values. If you want to work with -potentially absent values, use the "Maybe" type explicitly. - -> Is there a good way to avoid null-pointer bugs? Maybe. © Jasper Van der Jeugt - -Another standard polymorphic data type is "Either". It stores either the value -of one type or a value of another. - -@ -data Either a b - = Left a - | Right b -@ - -♫ NOTE: It can help to explore types of constructors "Nothing", "Just", "Left" - and "Right". Let's stretch our fingers and blow off the dust from our GHCi and - check that! - -You can pattern match on values of the "Either" type as well as on any other -custom data type. - -@ -showEither :: Either String Int -> String -showEither (Left msg) = "Left with string: " ++ msg -showEither (Right n) = "Right with number: " ++ show n -@ - -Now, after we covered polymorphic types, you are finally ready to learn how -lists are actually defined in the Haskell world. Behold the mighty list type! - -@ -data [] a - = [] - | a : [a] -@ - -Immediately we know what all of that means! -The ":" is simply the constructor name for the list. Constructors in Haskell can -be defined as infix operators as well (i.e. be written after the first argument, -the same way we write `1 + 2` and not `+ 1 2`), but only if they start with a -colon ":". The ":" is taken by lists. Now you see why we were able to pattern -match on it? - -The type name uses built-in syntax to reserve the square brackets [] exclusively -for lists but, otherwise, is a simple polymorphic recursive sum type. - -If you rename some constructor and type names, the list type could look quite -simple, as any of us could have written it: - -@ -data List a - = Empty - | Cons a (List a) -@ - -♫ NOTE: We use () to group "List" with "a" type variable in the second field of - the "Cons" constructor. This is done to tell the compiler that "List" and "a" - should go together as one type. --} - -{- | -=⚔️= Task 6 - -Before entering the real world of adventures and glorious victories, we should -prepare for different things in this world. It is always a good idea to -understand the whole context before going on a quest. And, before fighting a -dragon, it makes sense to prepare for different unexpected things. So let's -define data types describing a Dragon Lair! - - ⍟ A lair has a dragon and possibly a treasure chest (as described in the - previous section). A lair also may not contain any treasures, but we'll never - know until we explore the cave! - ⍟ A dragon can have a unique magical power. But it can be literally anything! - And we don't know in advance what power it has. - -Create data types that describe such Dragon Lair! Use polymorphism to -parametrise data types in places where values can be of any general type. - -🕯 HINT: 'Maybe' that some standard types we mentioned above are useful for - maybe-treasure ;) --} + let damage = + calculatePlayerDamage + (playerAttack player2) + (playerStrength player2) + defense = + calculatePlayerDefense + (playerArmor player1) + (playerDexterity player1) + newHealth = + calculatePlayerHit + damage + defense + (playerHealth player1) + in player1 {playerHealth = newHealth} + +-- | +-- =🛡= Polymorphic data types +-- +-- Similar to functions, data types in Haskell can be __polymorphic__. This means +-- that they can use some type variables as placeholders, representing general +-- types. You can either reason about data types in terms of such variables (and +-- don't worry about the specific types), or substitute variables with some +-- particular types. +-- Such polymorphism in Haskell is an example of __parametric polymorphism__. +-- +-- The process of defining a polymorphic type is akin to an ordinary data type +-- definition. The only difference is that all the type variables should go after +-- the type name so that you can reuse them in the constructor fields later. +-- +-- For example, +-- +-- @ +-- data Foo a = MkFoo a +-- @ +-- +-- Note that both product and sum types can be parameterised. +-- +-- > Actually, we've already seen a polymorphic data type! Remember Lists from Chapter Two? +-- +-- To give an example of a custom polymorphic type, let's implement a +-- "TreasureChest" data type. Our treasure chest is flexible, and it can store some +-- amount of gold. Additionally there is some space for one more arbitrary +-- treasure. But that could be any treasure, and we don't know what it is +-- beforehand. +-- +-- In Haskell words, the data type can be defined like this: +-- +-- @ +-- data TreasureChest x = TreasureChest +-- { treasureChestGold :: Int +-- , treasureChestLoot :: x +-- } +-- @ +-- +-- You can see that a treasure chest can store any treasure, indeed! We call it +-- treasure 'x'. +-- +-- And when writing functions involving the "TreasureChest" type, we don't always +-- need to know what kind of treasure is inside besides gold. +-- We can either use a type variable in our type signature: +-- +-- @ +-- howMuchGoldIsInMyChest :: TreasureChest x -> Int +-- @ +-- +-- or we can specify a concrete type: +-- +-- @ +-- isEnoughDiamonds :: TreasureChest Diamond -> Bool +-- @ +-- +-- In the same spirit, we can implement a function that creates a treasure chest with some +-- predefined amount of gold and a given treasure: +-- +-- @ +-- mkMehChest :: x -> TreasureChest x +-- mkMehChest treasure = TreasureChest +-- { treasureChestGold = 50 +-- , treasureChestLoot = treasure +-- } +-- @ +-- +-- +-- Polymorphic Algebraic Data Types are a great deal! One of the most common and +-- useful standard polymorphic types is __"Maybe"__. It represents the notion of +-- optional value (maybe the value is there, or maybe it is not). +-- "Maybe" is defined in the standard library in the following way: +-- +-- @ +-- data Maybe a +-- = Nothing +-- | Just a +-- @ +-- +-- Haskell doesn't have a concept of "null" values. If you want to work with +-- potentially absent values, use the "Maybe" type explicitly. +-- +-- > Is there a good way to avoid null-pointer bugs? Maybe. © Jasper Van der Jeugt +-- +-- Another standard polymorphic data type is "Either". It stores either the value +-- of one type or a value of another. +-- +-- @ +-- data Either a b +-- = Left a +-- | Right b +-- @ +-- +-- ♫ NOTE: It can help to explore types of constructors "Nothing", "Just", "Left" +-- and "Right". Let's stretch our fingers and blow off the dust from our GHCi and +-- check that! +-- +-- You can pattern match on values of the "Either" type as well as on any other +-- custom data type. +-- +-- @ +-- showEither :: Either String Int -> String +-- showEither (Left msg) = "Left with string: " ++ msg +-- showEither (Right n) = "Right with number: " ++ show n +-- @ +-- +-- Now, after we covered polymorphic types, you are finally ready to learn how +-- lists are actually defined in the Haskell world. Behold the mighty list type! +-- +-- @ +-- data [] a +-- = [] +-- | a : [a] +-- @ +-- +-- Immediately we know what all of that means! +-- The ":" is simply the constructor name for the list. Constructors in Haskell can +-- be defined as infix operators as well (i.e. be written after the first argument, +-- the same way we write `1 + 2` and not `+ 1 2`), but only if they start with a +-- colon ":". The ":" is taken by lists. Now you see why we were able to pattern +-- match on it? +-- +-- The type name uses built-in syntax to reserve the square brackets [] exclusively +-- for lists but, otherwise, is a simple polymorphic recursive sum type. +-- +-- If you rename some constructor and type names, the list type could look quite +-- simple, as any of us could have written it: +-- +-- @ +-- data List a +-- = Empty +-- | Cons a (List a) +-- @ +-- +-- ♫ NOTE: We use () to group "List" with "a" type variable in the second field of +-- the "Cons" constructor. This is done to tell the compiler that "List" and "a" +-- should go together as one type. + +-- | +-- =⚔️= Task 6 +-- +-- Before entering the real world of adventures and glorious victories, we should +-- prepare for different things in this world. It is always a good idea to +-- understand the whole context before going on a quest. And, before fighting a +-- dragon, it makes sense to prepare for different unexpected things. So let's +-- define data types describing a Dragon Lair! +-- +-- ⍟ A lair has a dragon and possibly a treasure chest (as described in the +-- previous section). A lair also may not contain any treasures, but we'll never +-- know until we explore the cave! +-- ⍟ A dragon can have a unique magical power. But it can be literally anything! +-- And we don't know in advance what power it has. +-- +-- Create data types that describe such Dragon Lair! Use polymorphism to +-- parametrise data types in places where values can be of any general type. +-- +-- 🕯 HINT: 'Maybe' that some standard types we mentioned above are useful for +-- maybe-treasure ;) {- =🛡= Typeclasses @@ -867,7 +931,6 @@ instance ArchEnemy Bool where getArchEnemy True = "False" getArchEnemy False = "True" - instance ArchEnemy Int where getArchEnemy :: Int -> String getArchEnemy i = case i of @@ -916,7 +979,6 @@ ghci> revealArchEnemy "An adorable string that has no enemies (✿◠ω◠)" • In the expression: revealArchEnemy "An adorable string that has no enemies (✿◠ω◠)" In an equation for 'it': it = revealArchEnemy "An adorable string that has no enemies (✿◠ω◠)" - Interestingly, it is possible to reuse existing instances of data types in the same typeclass instances as well. And we also can reuse the __constraints__ in the instance declaration for that! @@ -939,26 +1001,23 @@ type exists. You can see how we reuse the fact that the underlying type has this instance and apply this typeclass method to it. -} -{- | -=⚔️= Task 7 - -Often we want to combine several values of a type and get a single value of the -exact same type. We can combine different things: treasures, adventures, groups -of heroes, etc.. So it makes sense to implement a typeclass for such a concept -and define helpful instances. - -We will call such a typeclass "Append". You can find its definition below. - -Implement instances of "Append" for the following types: - - ✧ The "Gold" newtype where append is the addition - ✧ "List" where append is list concatenation - ✧ *(Challenge): "Maybe" where append is appending of values inside "Just" constructors - --} +-- | +-- =⚔️= Task 7 +-- +-- Often we want to combine several values of a type and get a single value of the +-- exact same type. We can combine different things: treasures, adventures, groups +-- of heroes, etc.. So it makes sense to implement a typeclass for such a concept +-- and define helpful instances. +-- +-- We will call such a typeclass "Append". You can find its definition below. +-- +-- Implement instances of "Append" for the following types: +-- +-- ✧ The "Gold" newtype where append is the addition +-- ✧ "List" where append is list concatenation +-- ✧ *(Challenge): "Maybe" where append is appending of values inside "Just" constructors class Append a where - append :: a -> a -> a - + append :: a -> a -> a {- =🛡= Standard Typeclasses and Deriving @@ -1055,7 +1114,6 @@ Implement data types and typeclasses, describing such a battle between two contestants, and write a function that decides the outcome of a fight! -} - {- You did it! Now it is time to open pull request with your changes and summon @vrom911 for the review! @@ -1066,3 +1124,12 @@ and summon @vrom911 for the review! Deriving: https://kowainik.github.io/posts/deriving Extensions: https://kowainik.github.io/posts/extensions -} + +main :: IO () +main = do + print auenland + print grownAuenland + print auenlandWithCastle + print auenlandWithCastleAndWalls + print auenlandCannotBuildWalls + print auenlandCannotBuildWallsEvenWithCastle From 293241451e8a82e824ff3480f8a6d374f4b5b39a Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Mon, 27 Feb 2023 21:40:29 +0100 Subject: [PATCH 32/35] feat(chapter-3): :tada: Task 5 --- src/Chapter3.hs | 47 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/src/Chapter3.hs b/src/Chapter3.hs index a1dea1c5c..da4c20754 100644 --- a/src/Chapter3.hs +++ b/src/Chapter3.hs @@ -672,22 +672,47 @@ introducing extra newtypes. 🕯 HINT: if you complete this task properly, you don't need to change the implementation of the "hitPlayer" function at all! -} + +newtype Health = Health Int +health :: Health -> Int +health (Health value) = value +newtype Armor = Armor Int +armor :: Armor -> Int +armor (Armor value) = value +newtype Attack = Attack Int +attack :: Attack -> Int +attack (Attack value) = value +newtype Dexterity = Dexterity Int +dexterity :: Dexterity -> Int +dexterity (Dexterity value) = value +newtype Strenght = Strenght Int +strenght :: Strenght -> Int +strenght (Strenght value) = value + data Player = Player - { playerHealth :: Int, - playerArmor :: Int, - playerAttack :: Int, - playerDexterity :: Int, - playerStrength :: Int + { playerHealth :: Health, + playerArmor :: Armor, + playerAttack :: Attack, + playerDexterity :: Dexterity, + playerStrength :: Strenght } -calculatePlayerDamage :: Int -> Int -> Int -calculatePlayerDamage attack strength = attack + strength +newtype Damage = Damage Int +damage :: Damage -> Int +damage (Damage value) = value + +calculatePlayerDamage :: Attack -> Strenght -> Damage +calculatePlayerDamage a s = Damage $ attack a + strenght s + +newtype Defense = Defense Int +defense :: Defense -> Int +defense (Defense value) = value -calculatePlayerDefense :: Int -> Int -> Int -calculatePlayerDefense armor dexterity = armor * dexterity +calculatePlayerDefense :: Armor -> Dexterity -> Defense +calculatePlayerDefense a d = Defense $ armor a * dexterity d -calculatePlayerHit :: Int -> Int -> Int -> Int -calculatePlayerHit damage defense health = health + defense - damage +calculatePlayerHit :: Damage -> Defense -> Health -> Health +calculatePlayerHit dam def h = Health $ health h + defense def - damage dam -- The second player hits first player and the new first player is returned hitPlayer :: Player -> Player -> Player From 88e7cb1d30ac2839d29133fd23ab5d76882da7c6 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Mon, 27 Feb 2023 22:07:12 +0100 Subject: [PATCH 33/35] feat(chapter-3): :fire: impl Task 6 --- src/Chapter3.hs | 45 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/Chapter3.hs b/src/Chapter3.hs index da4c20754..0d20fc8a7 100644 --- a/src/Chapter3.hs +++ b/src/Chapter3.hs @@ -399,11 +399,11 @@ fight k _ = knightGold k arthur :: Knight arthur = Knight {knightName = "Arthur", knightAttack = 10, knightHealth = 10, knightGold = 0} -dragon :: Monster -dragon = Monster {monsterName = "Dragon", monsterAttack = 10, monsterHealth = 10, monsterGold = 100} +dragon1 :: Monster +dragon1 = Monster {monsterName = "Dragon1", monsterAttack = 10, monsterHealth = 10, monsterGold = 100} win :: Int -win = fight arthur dragon +win = fight arthur dragon1 tooStrongMonster :: Monster tooStrongMonster = Monster {monsterName = "Gold Dragon", monsterAttack = 20, monsterHealth = 20, monsterGold = 10000} @@ -674,18 +674,27 @@ introducing extra newtypes. -} newtype Health = Health Int + health :: Health -> Int health (Health value) = value + newtype Armor = Armor Int + armor :: Armor -> Int armor (Armor value) = value + newtype Attack = Attack Int + attack :: Attack -> Int attack (Attack value) = value + newtype Dexterity = Dexterity Int + dexterity :: Dexterity -> Int dexterity (Dexterity value) = value + newtype Strenght = Strenght Int + strenght :: Strenght -> Int strenght (Strenght value) = value @@ -697,14 +706,16 @@ data Player = Player playerStrength :: Strenght } -newtype Damage = Damage Int +newtype Damage = Damage Int + damage :: Damage -> Int damage (Damage value) = value calculatePlayerDamage :: Attack -> Strenght -> Damage -calculatePlayerDamage a s = Damage $ attack a + strenght s +calculatePlayerDamage a s = Damage $ attack a + strenght s + +newtype Defense = Defense Int -newtype Defense = Defense Int defense :: Defense -> Int defense (Defense value) = value @@ -890,6 +901,20 @@ hitPlayer player1 player2 = -- -- 🕯 HINT: 'Maybe' that some standard types we mentioned above are useful for -- maybe-treasure ;) +data Treasure treasureLoot = TreasureChest + { treasureChestGold :: Int, + treasureChestLoot :: treasureLoot + } deriving (Show) + +data Dragon dM = Dragon + { dragonName :: String, + magicPower :: Maybe dM + } deriving (Show) + +data Liar dM treasureLoot = Liar { + dragon :: (Dragon dM), + treasure :: Maybe (Treasure treasureLoot) + } deriving (Show) {- =🛡= Typeclasses @@ -1152,9 +1177,17 @@ Extensions: https://kowainik.github.io/posts/extensions main :: IO () main = do + print "---------------------------" + print "Task 5" print auenland print grownAuenland print auenlandWithCastle print auenlandWithCastleAndWalls print auenlandCannotBuildWalls print auenlandCannotBuildWallsEvenWithCastle + print "---------------------------" + print "Task 6" + print $ Liar { + dragon = Dragon "Fire Dragon Eremil" $ Just "Fire Spell", + treasure = Just $ TreasureChest { treasureChestGold = 100, treasureChestLoot = "Dragon Plate" } + } From cbf03ca6f7e9a1cd6ce582755ff81ccdcfa8cd2c Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Mon, 27 Feb 2023 22:30:50 +0100 Subject: [PATCH 34/35] feat(chapter-3): :art: impl Task 7 --- src/Chapter3.hs | 49 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/src/Chapter3.hs b/src/Chapter3.hs index 0d20fc8a7..aa8e739ea 100644 --- a/src/Chapter3.hs +++ b/src/Chapter3.hs @@ -904,17 +904,20 @@ hitPlayer player1 player2 = data Treasure treasureLoot = TreasureChest { treasureChestGold :: Int, treasureChestLoot :: treasureLoot - } deriving (Show) + } + deriving (Show) data Dragon dM = Dragon { dragonName :: String, magicPower :: Maybe dM - } deriving (Show) + } + deriving (Show) -data Liar dM treasureLoot = Liar { - dragon :: (Dragon dM), +data Liar dM treasureLoot = Liar + { dragon :: (Dragon dM), treasure :: Maybe (Treasure treasureLoot) - } deriving (Show) + } + deriving (Show) {- =🛡= Typeclasses @@ -1066,9 +1069,26 @@ instance and apply this typeclass method to it. -- ✧ The "Gold" newtype where append is the addition -- ✧ "List" where append is list concatenation -- ✧ *(Challenge): "Maybe" where append is appending of values inside "Just" constructors +newtype Gold = Gold {goldAmount :: Int} deriving (Show, Eq) + class Append a where append :: a -> a -> a +instance Append Gold where + append :: Gold -> Gold -> Gold + append (Gold a) (Gold b) = Gold (a + b) + +instance Append [a] where + append :: [a] -> [a] -> [a] + append a b = a ++ b + +instance Append a => Append (Maybe a) where + append :: Maybe a -> Maybe a -> Maybe a + append (Just a) (Just b) = Just $ append a b + append (Just a) Nothing = Just a + append Nothing (Just b) = Just b + append Nothing Nothing = Nothing + {- =🛡= Standard Typeclasses and Deriving @@ -1187,7 +1207,18 @@ main = do print auenlandCannotBuildWallsEvenWithCastle print "---------------------------" print "Task 6" - print $ Liar { - dragon = Dragon "Fire Dragon Eremil" $ Just "Fire Spell", - treasure = Just $ TreasureChest { treasureChestGold = 100, treasureChestLoot = "Dragon Plate" } - } + print $ + Liar + { dragon = Dragon "Fire Dragon Eremil" $ Just "Fire Spell", + treasure = Just $ TreasureChest {treasureChestGold = 100, treasureChestLoot = "Dragon Plate"} + } + + print "---------------------------" + print "Task 7" + + print $ append Gold {goldAmount = 10} Gold {goldAmount = 20} + print $ append [1, 2] [3, 4, 5] + print $ append (Just "Something") Nothing + print $ append Nothing (Just "Something") + print $ append (Just "Someting and ") (Just "Something") + print $ append (Just [1]) (Just [2, 3, 4, 5]) From 3fad13c4ddd8240837464102836a2b29217b3a50 Mon Sep 17 00:00:00 2001 From: Wolfram Sokollek Date: Mon, 27 Feb 2023 23:10:31 +0100 Subject: [PATCH 35/35] feat(chapter-3): :sparkles: impl Task 8 --- src/Chapter3.hs | 52 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/src/Chapter3.hs b/src/Chapter3.hs index aa8e739ea..635fdf07b 100644 --- a/src/Chapter3.hs +++ b/src/Chapter3.hs @@ -1149,6 +1149,25 @@ implement the following functions: 🕯 HINT: to implement this task, derive some standard typeclasses -} +data Weekday + = Monday + | Tuesday + | Wednesday + | Thursday + | Friday + | Saturday + | Sunday + deriving (Show, Read, Eq, Ord, Enum, Bounded) + +isWeekend :: Weekday -> Bool +isWeekend day = day == Saturday || day == Sunday + +nextDay :: Weekday -> Weekday +nextDay day = if day == maxBound then minBound else succ day + +daysToParty :: Weekday -> Int +daysToParty day = fromEnum Friday - fromEnum day + {- =💣= Task 9* @@ -1195,26 +1214,30 @@ Deriving: https://kowainik.github.io/posts/deriving Extensions: https://kowainik.github.io/posts/extensions -} +printTaskHeader :: String -> IO () +printTaskHeader taskName = do + putStrLn "" + putStrLn "---------------------------" + putStrLn $ "---------- Task-" ++ taskName ++ " ---------" + putStrLn "---------------------------" + main :: IO () main = do - print "---------------------------" - print "Task 5" + printTaskHeader "5" print auenland print grownAuenland print auenlandWithCastle print auenlandWithCastleAndWalls print auenlandCannotBuildWalls print auenlandCannotBuildWallsEvenWithCastle - print "---------------------------" - print "Task 6" + printTaskHeader "6" print $ Liar { dragon = Dragon "Fire Dragon Eremil" $ Just "Fire Spell", treasure = Just $ TreasureChest {treasureChestGold = 100, treasureChestLoot = "Dragon Plate"} } - print "---------------------------" - print "Task 7" + printTaskHeader "7" print $ append Gold {goldAmount = 10} Gold {goldAmount = 20} print $ append [1, 2] [3, 4, 5] @@ -1222,3 +1245,20 @@ main = do print $ append Nothing (Just "Something") print $ append (Just "Someting and ") (Just "Something") print $ append (Just [1]) (Just [2, 3, 4, 5]) + + printTaskHeader "8" + + print $ "isWeekend Sunday: " ++ show (isWeekend Sunday) + print $ "isWeekend Monday: " ++ show (isWeekend Monday) + + print $ "nextDay Sunday: " ++ show (nextDay Sunday) + print $ "nextDay Monday: " ++ show (nextDay Monday) + print $ "nextDay Friday: " ++ show (nextDay Friday) + print $ "daysToParty Friday: " ++ show (daysToParty Friday) + + {-variable with name allWeekDays of all enum values from Weekday -} + let allWeekDays = [minBound .. maxBound] :: [Weekday] + + let output = map (\day -> print $ "daysToParty " ++ show day ++ ": " ++ show (daysToParty day)) allWeekDays + {- print output -} + sequence_ output