├── .github ├── CODEOWNERS ├── pull_request_template.md └── workflows │ └── ci.yml ├── images ├── Learn4Haskell.png └── HacktoberFest2020-Learn4Haskell-Banner.png ├── test ├── DoctestChapter1.hs ├── DoctestChapter2.hs ├── DoctestChapter3.hs ├── DoctestChapter4.hs ├── Spec.hs └── Test │ ├── Chapter3.hs │ ├── Chapter4.hs │ ├── Chapter2.hs │ └── Chapter1.hs ├── CHANGELOG.md ├── .gitignore ├── .stylish-haskell.yaml ├── Makefile ├── CODE_OF_CONDUCT.md ├── learn4haskell.cabal ├── README.md ├── LICENSE └── src ├── Chapter1.hs ├── Chapter4.hs ├── Chapter2.hs └── Chapter3.hs /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @chshersh @vrom911 2 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Solutions for Chapter {NUMBER} 2 | 3 | cc @vrom911 @chshersh 4 | -------------------------------------------------------------------------------- /images/Learn4Haskell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leonidas-from-XIV/learn4haskell/master/images/Learn4Haskell.png -------------------------------------------------------------------------------- /images/HacktoberFest2020-Learn4Haskell-Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leonidas-from-XIV/learn4haskell/master/images/HacktoberFest2020-Learn4Haskell-Banner.png -------------------------------------------------------------------------------- /test/DoctestChapter1.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | import Test.DocTest (doctest) 4 | 5 | 6 | main :: IO () 7 | main = doctest 8 | [ "src/Chapter1.hs" 9 | ] 10 | -------------------------------------------------------------------------------- /test/DoctestChapter2.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | import Test.DocTest (doctest) 4 | 5 | 6 | main :: IO () 7 | main = doctest 8 | [ "src/Chapter2.hs" 9 | ] 10 | -------------------------------------------------------------------------------- /test/DoctestChapter3.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | import Test.DocTest (doctest) 4 | 5 | 6 | main :: IO () 7 | main = doctest 8 | [ "src/Chapter3.hs" 9 | ] 10 | -------------------------------------------------------------------------------- /test/DoctestChapter4.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | import Test.DocTest (doctest) 4 | 5 | 6 | main :: IO () 7 | main = doctest 8 | [ "src/Chapter4.hs" 9 | ] 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | `learn4haskell` uses [PVP Versioning][1]. 4 | The changelog is available [on GitHub][2]. 5 | 6 | ## 0.0.0.0 7 | 8 | * Initially created. 9 | 10 | [1]: https://pvp.haskell.org 11 | [2]: https://github.com/kowainik/learn4haskell/releases 12 | -------------------------------------------------------------------------------- /test/Spec.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | import Test.Hspec (hspec) 4 | 5 | import Test.Chapter1 6 | import Test.Chapter2 7 | import Test.Chapter3 8 | import Test.Chapter4 9 | 10 | 11 | main :: IO () 12 | main = hspec $ do 13 | chapter1 14 | chapter2 15 | chapter3 16 | chapter4 17 | -------------------------------------------------------------------------------- /test/Test/Chapter3.hs: -------------------------------------------------------------------------------- 1 | module Test.Chapter3 2 | ( chapter3 3 | ) where 4 | 5 | import Test.Hspec (Spec, describe, it, shouldBe) 6 | 7 | import Chapter3 8 | 9 | 10 | chapter3 :: Spec 11 | chapter3 = describe "Chapter3" $ do 12 | describe "Chapter3Normal" $ it "" $ True `shouldBe` True 13 | describe "Chapter3Advanced" $ it "" $ True `shouldBe` True 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Haskell 2 | dist 3 | dist-* 4 | cabal-dev 5 | *.o 6 | *.hi 7 | *.chi 8 | *.chs.h 9 | *.dyn_o 10 | *.dyn_hi 11 | *.prof 12 | *.aux 13 | *.hp 14 | *.eventlog 15 | .virtualenv 16 | .hsenv 17 | .hpc 18 | .cabal-sandbox/ 19 | cabal.sandbox.config 20 | cabal.config 21 | cabal.project.local 22 | .ghc.environment.* 23 | .HTF/ 24 | .hie/ 25 | # Stack 26 | .stack-work/ 27 | stack.yaml.lock 28 | 29 | ### IDE/support 30 | # Vim 31 | [._]*.s[a-v][a-z] 32 | [._]*.sw[a-p] 33 | [._]s[a-v][a-z] 34 | [._]sw[a-p] 35 | *~ 36 | tags 37 | 38 | # IntellijIDEA 39 | .idea/ 40 | .ideaHaskellLib/ 41 | *.iml 42 | 43 | # Atom 44 | .haskell-ghc-mod.json 45 | 46 | # VS 47 | .vscode/ 48 | 49 | # Emacs 50 | *# 51 | .dir-locals.el 52 | TAGS 53 | 54 | # other 55 | .DS_Store 56 | -------------------------------------------------------------------------------- /.stylish-haskell.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | - simple_align: 3 | cases: true 4 | top_level_patterns: true 5 | records: true 6 | 7 | # Import cleanup 8 | - imports: 9 | align: none 10 | list_align: after_alias 11 | pad_module_names: false 12 | long_list_align: inline 13 | empty_list_align: inherit 14 | list_padding: 4 15 | separate_lists: true 16 | space_surround: false 17 | 18 | - language_pragmas: 19 | style: vertical 20 | remove_redundant: true 21 | 22 | # Remove trailing whitespace 23 | - trailing_whitespace: {} 24 | 25 | columns: 100 26 | 27 | newline: native 28 | 29 | language_extensions: 30 | - BangPatterns 31 | - ConstraintKinds 32 | - DataKinds 33 | - DefaultSignatures 34 | - DeriveAnyClass 35 | - DeriveDataTypeable 36 | - DeriveGeneric 37 | - DerivingStrategies 38 | - DerivingVia 39 | - ExplicitNamespaces 40 | - FlexibleContexts 41 | - FlexibleInstances 42 | - FunctionalDependencies 43 | - GADTs 44 | - GeneralizedNewtypeDeriving 45 | - InstanceSigs 46 | - KindSignatures 47 | - LambdaCase 48 | - MultiParamTypeClasses 49 | - MultiWayIf 50 | - NamedFieldPuns 51 | - NoImplicitPrelude 52 | - OverloadedStrings 53 | - QuasiQuotes 54 | - RecordWildCards 55 | - ScopedTypeVariables 56 | - StandaloneDeriving 57 | - TemplateHaskell 58 | - TupleSections 59 | - TypeApplications 60 | - TypeFamilies 61 | - ViewPatterns 62 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | cabal clean 4 | 5 | .PHONY: build 6 | build: 7 | cabal build --enable-tests --write-ghc-environment-files=always 8 | 9 | .PHONY: test-chapter1 10 | test-chapter1: 11 | cabal test doctest-chapter1 --enable-tests --test-show-details=direct 12 | cabal run learn4haskell-test --enable-tests -- -m "Chapter1" 13 | 14 | .PHONY: test-chapter1-basic 15 | test-chapter1-basic: 16 | cabal test doctest-chapter1 --enable-tests --test-show-details=direct 17 | cabal run learn4haskell-test --enable-tests -- -m "Chapter1Normal" 18 | 19 | .PHONY: test-chapter2 20 | test-chapter2: 21 | cabal test doctest-chapter2 --enable-tests --test-show-details=direct 22 | cabal run learn4haskell-test --enable-tests -- -m "Chapter2" 23 | 24 | .PHONY: test-chapter2-basic 25 | test-chapter2-basic: 26 | cabal test doctest-chapter2 --enable-tests --test-show-details=direct 27 | cabal run learn4haskell-test --enable-tests -- -m "Chapter2Normal" 28 | 29 | .PHONY: test-chapter3 30 | test-chapter3: 31 | cabal test doctest-chapter3 --enable-tests --test-show-details=direct 32 | cabal run learn4haskell-test --enable-tests -- -m "Chapter3" 33 | 34 | .PHONY: test-chapter3-basic 35 | test-chapter3-basic: 36 | cabal test doctest-chapter3 --enable-tests --test-show-details=direct 37 | cabal run learn4haskell-test --enable-tests -- -m "Chapter3Normal" 38 | 39 | .PHONY: test-chapter4 40 | test-chapter4: 41 | cabal test doctest-chapter4 --enable-tests --test-show-details=direct 42 | cabal run learn4haskell-test --enable-tests -- -m "Chapter4" 43 | 44 | .PHONY: test-chapter4-basic 45 | test-chapter4-basic: 46 | cabal test doctest-chapter4 --enable-tests --test-show-details=direct 47 | cabal run learn4haskell-test --enable-tests -- -m "Chapter4Normal" 48 | 49 | .PHONY: test-all 50 | test-all: 51 | cabal test all --enable-tests --test-show-details=direct 52 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This code of conduct outlines our expectations for all those who participate 4 | in the Kowainik organization. 5 | 6 | We invite all those who participate in Kowainik to help us create safe 7 | and positive experiences for everyone. 8 | 9 | ## Our Standards 10 | 11 | A primary goal of Kowainik is to make the Haskell open source community friendlier. 12 | As such, we are committed to providing a friendly, safe and welcoming environment for all. 13 | So, we are using the following standards in our organization. 14 | 15 | ### Be inclusive. 16 | 17 | We welcome and support people of all backgrounds and identities. This includes, 18 | but is not limited to members of any sexual orientation, gender identity and expression, 19 | race, ethnicity, culture, national origin, social and economic class, educational level, 20 | colour, immigration status, sex, age, size, family status, political belief, religion, 21 | and mental and physical ability. 22 | 23 | ### Be respectful. 24 | 25 | We won't all agree all the time, but disagreement is no excuse for disrespectful behaviour. 26 | We will all experience frustration from time to time, but we cannot allow that frustration 27 | become personal attacks. An environment where people feel uncomfortable or threatened 28 | is not a productive or creative one. 29 | 30 | ### Choose your words carefully. 31 | 32 | Always conduct yourself professionally. Be kind to others. Do not insult or put down others. 33 | Harassment and exclusionary behaviour aren't acceptable. This includes, but is not limited to: 34 | 35 | * Threats of violence. 36 | * Discriminatory language. 37 | * Personal insults, especially those using racist or sexist terms. 38 | * Advocating for, or encouraging, any of the above behaviours. 39 | 40 | ### Don't harass. 41 | 42 | In general, if someone asks you to stop something, then stop. When we disagree, try to understand why. 43 | Differences of opinion and disagreements are mostly unavoidable. What is important is that we resolve 44 | disagreements and differing views constructively. 45 | 46 | ### Make differences into strengths. 47 | 48 | Different people have different perspectives on issues, 49 | and that can be valuable for solving problems or generating new ideas. Being unable to understand why 50 | someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that we all make mistakes, 51 | and blaming each other doesn’t get us anywhere. 52 | 53 | Instead, focus on resolving issues and learning from mistakes. 54 | 55 | ## Reporting Guidelines 56 | 57 | If you are subject to or witness unacceptable behaviour, or have any other concerns, 58 | please notify us as soon as possible. 59 | 60 | You can reach us via the following email address: 61 | 62 | * xrom.xkov@gmail.com 63 | -------------------------------------------------------------------------------- /test/Test/Chapter4.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wno-type-defaults #-} 2 | 3 | {-# LANGUAGE TypeApplications #-} 4 | 5 | module Test.Chapter4 6 | ( chapter4 7 | ) where 8 | 9 | import Test.Hspec (Spec, describe, it, shouldBe) 10 | 11 | import Chapter4 12 | 13 | 14 | chapter4 :: Spec 15 | chapter4 = describe "Chapter4" $ do 16 | chapter4normal 17 | chapter4advanced 18 | 19 | chapter4normal :: Spec 20 | chapter4normal = describe "Chapter4Normal" $ do 21 | describe "Task2: Functor for Secret" $ do 22 | let trap = Trap "it's a trap" 23 | it "doen't affect trap" $ 24 | fmap @(Secret String) @Bool not trap `shouldBe` trap 25 | it "change reward, same type" $ 26 | fmap @(Secret String) @Bool not (Reward False) `shouldBe` Reward True 27 | it "change reward, other type" $ 28 | fmap @(Secret String) @Int even (Reward 5) `shouldBe` Reward False 29 | it "change reward, other type" $ 30 | fmap @(Secret String) @Int even (Reward 4) `shouldBe` Reward True 31 | describe "Task4: Applicative for Secret" $ do 32 | let trap :: Secret String Int 33 | trap = Trap "it's a trap" 34 | it "pure int" $ 35 | pure @(Secret String) "x" `shouldBe` Reward "x" 36 | it "pure bool" $ 37 | pure @(Secret String) False `shouldBe` Reward False 38 | it "trap <*> reward" $ 39 | Trap "it's a trap" <*> Reward 42 `shouldBe` trap 40 | it "trap <*> trap" $ 41 | Trap "it's a trap" <*> Trap "42" `shouldBe` trap 42 | it "reward <*> trap" $ 43 | Reward not <*> Trap 42 `shouldBe` Trap 42 44 | it "reward <*> reward - same type" $ 45 | Reward not <*> Reward True `shouldBe` (Reward False :: Secret String Bool) 46 | it "reward <*> reward" $ 47 | Reward odd <*> Reward 42 `shouldBe` (Reward False :: Secret String Bool) 48 | describe "Task6: Monad for Secret" $ do 49 | it "Trap" $ (Trap "aaar" >>= halfSecret) `shouldBe` Trap "aaar" 50 | it "Reward even" $ (Reward 42 >>= halfSecret) `shouldBe` Reward 21 51 | it "Reward odd" $ (Reward 11 >>= halfSecret) `shouldBe` Trap "it's a trap" 52 | 53 | chapter4advanced :: Spec 54 | chapter4advanced = describe "Chapter4Advanced" $ 55 | describe "Task 8*: Before the Final Boss" $ do 56 | it "Nothing - Nothing" $ andM Nothing Nothing `shouldBe` Nothing 57 | it "Nothing - Just" $ andM Nothing (Just True) `shouldBe` Nothing 58 | it "Just True - Nothing" $ andM (Just True) Nothing `shouldBe` Nothing 59 | it "Just False - Nothing" $ andM (Just False) Nothing `shouldBe` Just False 60 | it "Just - Just : False" $ andM (Just True) (Just False) `shouldBe` Just False 61 | it "Just - Just : True" $ andM (Just True) (Just True) `shouldBe` Just True 62 | 63 | halfSecret :: Int -> Secret String Int 64 | halfSecret n 65 | | even n = Reward (div n 2) 66 | | otherwise = Trap "it's a trap" 67 | -------------------------------------------------------------------------------- /learn4haskell.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: learn4haskell 3 | version: 0.0.0.0 4 | synopsis: Learn Haskell basics in 4 pull requests 5 | description: 6 | Learn Haskell basics in 4 pull requests. 7 | See [README.md](https://github.com/kowainik/learn4haskell#learn4haskell) for more details. 8 | homepage: https://github.com/kowainik/learn4haskell 9 | bug-reports: https://github.com/kowainik/learn4haskell/issues 10 | license: MPL-2.0 11 | license-file: LICENSE 12 | author: Veronika Romashkina, Dmitrii Kovanikov 13 | maintainer: Kowainik 14 | copyright: 2020 Kowainik 15 | build-type: Simple 16 | extra-doc-files: README.md 17 | CHANGELOG.md 18 | tested-with: GHC == 8.10.2 19 | 20 | source-repository head 21 | type: git 22 | location: https://github.com/kowainik/learn4haskell.git 23 | 24 | common common-options 25 | build-depends: base ^>= 4.14.0.0 26 | 27 | ghc-options: -Wall 28 | -Wcompat 29 | -Widentities 30 | -Wincomplete-uni-patterns 31 | -Wincomplete-record-updates 32 | -Wredundant-constraints 33 | if impl(ghc >= 8.2) 34 | ghc-options: -fhide-source-paths 35 | if impl(ghc >= 8.4) 36 | ghc-options: -Wpartial-fields 37 | if impl(ghc >= 8.10) 38 | ghc-options: -Wunused-packages 39 | 40 | default-language: Haskell2010 41 | 42 | common common-doctest 43 | import: common-options 44 | hs-source-dirs: test 45 | build-depends: doctest ^>= 0.17 46 | ghc-options: -threaded 47 | 48 | library 49 | import: common-options 50 | hs-source-dirs: src 51 | exposed-modules: Chapter1 52 | Chapter2 53 | Chapter3 54 | Chapter4 55 | 56 | test-suite learn4haskell-test 57 | import: common-options 58 | type: exitcode-stdio-1.0 59 | hs-source-dirs: test 60 | main-is: Spec.hs 61 | other-modules: Test.Chapter1 62 | Test.Chapter2 63 | Test.Chapter3 64 | Test.Chapter4 65 | build-depends: learn4haskell 66 | , hspec ^>= 2.7.4 67 | , hspec-hedgehog 68 | , hedgehog >= 1.0.2 && < 2 69 | ghc-options: -threaded 70 | -rtsopts 71 | -with-rtsopts=-N 72 | 73 | test-suite doctest-chapter1 74 | import: common-doctest 75 | type: exitcode-stdio-1.0 76 | main-is: DoctestChapter1.hs 77 | 78 | test-suite doctest-chapter2 79 | import: common-doctest 80 | type: exitcode-stdio-1.0 81 | main-is: DoctestChapter2.hs 82 | 83 | test-suite doctest-chapter3 84 | import: common-doctest 85 | type: exitcode-stdio-1.0 86 | main-is: DoctestChapter3.hs 87 | 88 | test-suite doctest-chapter4 89 | import: common-doctest 90 | type: exitcode-stdio-1.0 91 | main-is: DoctestChapter4.hs 92 | -------------------------------------------------------------------------------- /test/Test/Chapter2.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wno-type-defaults #-} 2 | 3 | module Test.Chapter2 4 | ( chapter2 5 | ) where 6 | 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | import Chapter2 10 | 11 | 12 | chapter2 :: Spec 13 | chapter2 = describe "Chapter2" $ do 14 | chapter2normal 15 | chapter2advanced 16 | 17 | chapter2normal :: Spec 18 | chapter2normal = describe "Chapter2Normal" $ do 19 | describe "Task3: subList" $ do 20 | it "range on empty list" $ subList 1 2 emptyInts `shouldBe` emptyInts 21 | it "range within" $ subList 2 5 [0..7] `shouldBe` [2..5] 22 | it "range 0 .. len" $ subList 0 10 [0..10] `shouldBe` [0..10] 23 | it "range negative" $ subList (-1) 5 [0..5] `shouldBe` [] 24 | it "range overflow" $ subList 0 5 [0, 1] `shouldBe` [0, 1] 25 | it "range x > y" $ subList 5 2 [0..5] `shouldBe` [] 26 | it "range equal" $ subList 0 0 [0..3] `shouldBe` [0] 27 | describe "Task4: firstHalf" $ do 28 | it "empty" $ firstHalf emptyInts `shouldBe` emptyInts 29 | it "even len" $ firstHalf [1,3,5,7] `shouldBe` [1, 3] 30 | it "odd len" $ firstHalf [1,3,5,7, 10] `shouldBe` [1, 3] 31 | describe "Task5: isThird42" $ do 32 | it "empty" $ isThird42 emptyInts `shouldBe` False 33 | it "one elem" $ isThird42 [42] `shouldBe` False 34 | it "two elem" $ isThird42 [1, 42] `shouldBe` False 35 | it "three elem with 42" $ isThird42 [1, 2, 42] `shouldBe` True 36 | it "three elem without 42" $ isThird42 [1, 2, 3] `shouldBe` False 37 | it "more than three elem with 42" $ isThird42 [1, 2, 42, 4, 5, 6] `shouldBe` True 38 | it "more than three elem without 42" $ isThird42 [1..10] `shouldBe` False 39 | describe "Task6: duplicate" $ do 40 | it "empty" $ duplicate emptyInts `shouldBe` emptyInts 41 | it "one elem" $ duplicate [0] `shouldBe` [0, 0] 42 | it "two elems" $ duplicate [-1, 0] `shouldBe` [-1, -1, 0, 0] 43 | it "many elems" $ duplicate [0..5] `shouldBe` [0,0,1,1,2,2,3,3,4,4,5,5] 44 | describe "Task7: takeEven" $ do 45 | it "empty" $ takeEven emptyInts `shouldBe` emptyInts 46 | it "one elem" $ takeEven [1] `shouldBe` [1] 47 | it "two elem" $ takeEven [1,2] `shouldBe` [1] 48 | it "many elems" $ takeEven [0 .. 10] `shouldBe` [0, 2 .. 10] 49 | describe "Task8: smartReplicate" $ do 50 | it "empty" $ smartReplicate emptyInts `shouldBe` emptyInts 51 | it "one elem: 0" $ smartReplicate [0] `shouldBe` emptyInts 52 | it "one elem: negative" $ smartReplicate [-5] `shouldBe` emptyInts 53 | it "many positive" $ smartReplicate [0..3] `shouldBe` [1, 2, 2, 3, 3, 3] 54 | it "many negative" $ smartReplicate [0, -1 .. -3] `shouldBe` [] 55 | describe "Task9: contains" $ do 56 | it "empty" $ contains 0 ([] :: [[Int]]) `shouldBe` ([] :: [[Int]]) 57 | it "one with elem" $ contains 0 [[5, 0, 1]] `shouldBe` [[5, 0, 1]] 58 | it "one with elem, one without" $ contains 0 [[5, 0, 1], [1..4]] `shouldBe` [[5, 0, 1]] 59 | it "one with elem, one without" $ contains 0 [[1..4], [5, 0, 1]] `shouldBe` [[5, 0, 1]] 60 | it "all without" $ contains 0 [[1..4], [5,4..1]] `shouldBe` ([] :: [[Int]]) 61 | it "all with" $ contains 5 [[1..5], [6,5..1]] `shouldBe` [[1..5], [6,5..1]] 62 | describe "Task11: rotate" $ do 63 | it "empty list" $ rotate 5 emptyInts `shouldBe` emptyInts 64 | it "empty list with 0" $ rotate 0 emptyInts `shouldBe` emptyInts 65 | it "list rotate 0" $ rotate 0 [1..5] `shouldBe` [1..5] 66 | it "list rotate len" $ rotate 5 [1..5] `shouldBe` [1..5] 67 | it "list rotate n" $ rotate 3 [1..5] `shouldBe` [4,5,1,2,3] 68 | it "list rotate len + n" $ rotate 8 [1..5] `shouldBe` [4,5,1,2,3] 69 | it "empty on negative" $ rotate (-5) [1..5] `shouldBe` emptyInts 70 | 71 | chapter2advanced :: Spec 72 | chapter2advanced = describe "Chapter2Advanced" $ 73 | describe "Task12*: rewind" $ do 74 | it "empty" $ rewind emptyInts `shouldBe` emptyInts 75 | it "one elem" $ rewind [1] `shouldBe` [1] 76 | it "many elems" $ rewind [1..10] `shouldBe` [10,9..1] 77 | it "many elems random" $ rewind [5,1,9,56,32,7,11] `shouldBe` [11,7,32,56,9,1,5] 78 | 79 | emptyInts :: [Int] 80 | emptyInts = [] 81 | -------------------------------------------------------------------------------- /test/Test/Chapter1.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wno-type-defaults #-} 2 | 3 | module Test.Chapter1 4 | ( chapter1 5 | ) where 6 | 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | 9 | import qualified Hedgehog.Range as Range (linear) 10 | import qualified Hedgehog.Gen as Gen (int) 11 | 12 | import Test.Hspec.Hedgehog (hedgehog, (===), forAll) 13 | 14 | import Chapter1 15 | 16 | 17 | chapter1 :: Spec 18 | chapter1 = describe "Chapter1" $ do 19 | chapter1normal 20 | chapter1advanced 21 | 22 | reverseInt :: Int -> Int 23 | reverseInt x = (*) (signum x) . read . reverse . show . abs $ x 24 | 25 | chapter1normal :: Spec 26 | chapter1normal = describe "Chapter1Normal" $ do 27 | describe "Task4: next" $ do 28 | it "returns next Int for 42" $ next 42 `shouldBe` 43 29 | it "returns next Int for negative" $ next (-5) `shouldBe` (-4) 30 | it "returns next Int for 0" $ next 0 `shouldBe` 1 31 | describe "Task5: lastDigit" $ do 32 | it "last digit of 0" $ lastDigit 0 `shouldBe` 0 33 | it "last digit of 0 < x < 10" $ lastDigit 5 `shouldBe` 5 34 | it "last digit of 10 < x < 100" $ lastDigit 34 `shouldBe` 4 35 | it "last digit of 100 < x < 1000" $ lastDigit 341 `shouldBe` 1 36 | it "last digit of big num" $ lastDigit 1234789 `shouldBe` 9 37 | it "last digit of negative" $ lastDigit (-12) `shouldBe` 2 38 | describe "Task6: closestToZero" $ do 39 | it "both positive, 1st wins" $ closestToZero 100 200 `shouldBe` 100 40 | it "both positive, 2nd wins" $ closestToZero 200 100 `shouldBe` 100 41 | it "both negative, 2nd wins" $ closestToZero (-200) (-100) `shouldBe` (-100) 42 | it "both negative, 1st wins" $ closestToZero (-100) (-200) `shouldBe` (-100) 43 | it "with 0, 1st wins" $ closestToZero 0 (-200) `shouldBe` 0 44 | it "with 0, 2nd wins" $ closestToZero 10 0 `shouldBe` 0 45 | it "equals" $ closestToZero 42 42 `shouldBe` 42 46 | it "positive, negative, pos wins" $ closestToZero 11 (-12) `shouldBe` 11 47 | it "positive, negative, neg wins" $ closestToZero 12 (-11) `shouldBe` (-11) 48 | describe "Task7: mid" $ do 49 | it "positives up " $ mid 10 20 30 `shouldBe` 20 50 | it "positives down" $ mid 30 20 10 `shouldBe` 20 51 | it "positives mix " $ mid 20 30 10 `shouldBe` 20 52 | it "negatives down" $ mid (-10) (-20) (-30) `shouldBe` (-20) 53 | it "negatives up " $ mid (-30) (-20) (-10) `shouldBe` (-20) 54 | it "negatives mix " $ mid (-20) (-30) (-10) `shouldBe` (-20) 55 | it "all equal" $ mid 1 1 1 `shouldBe` 1 56 | it "all equal, except 1" $ mid 1 1 2 `shouldBe` 1 57 | describe "Task8: isVowel" $ do 58 | it "true for vowels" $ all isVowel "aeiou" `shouldBe` True 59 | it "false for non-vowels" $ isVowel 'c' `shouldBe` False 60 | it "false for symbol" $ isVowel '+' `shouldBe` False 61 | describe "Task9: sumLast2" $ do 62 | it "sumLast2 0" $ sumLast2 0 `shouldBe` 0 63 | it "sumLast2 0 < 10" $ sumLast2 9 `shouldBe` 9 64 | it "sumLast2 10 < 100" $ sumLast2 56 `shouldBe` 11 65 | it "sumLast2 100 < 1000" $ sumLast2 987 `shouldBe` 15 66 | it "sumLast2 0 > -10" $ sumLast2 (-9) `shouldBe` 9 67 | it "sumLast2 -10 > -100" $ sumLast2 (-56) `shouldBe` 11 68 | it "sumLast2 -100 > -1000" $ sumLast2 (-987) `shouldBe` 15 69 | describe "Task 4 & 5 : first and last digit" $ do 70 | it "last digit is the first digit of the reversed number" $ hedgehog $ do 71 | x <- forAll $ Gen.int (Range.linear (-200) 200) 72 | (firstDigit x :: Int) === (lastDigit (reverseInt x) :: Int) 73 | 74 | chapter1advanced :: Spec 75 | chapter1advanced = describe "Chapter1Advanced" $ 76 | describe "Task 10*" $ do 77 | it "first digit 0" $ firstDigit 0 `shouldBe` 0 78 | it "first digit 0 < 10" $ firstDigit 9 `shouldBe` 9 79 | it "first digit 10 < 100" $ firstDigit 58 `shouldBe` 5 80 | it "first digit 100 < 1000" $ firstDigit 158 `shouldBe` 1 81 | it "first digit big" $ firstDigit 467321 `shouldBe` 4 82 | it "first digit 0 > -10" $ firstDigit (-9) `shouldBe` 9 83 | it "first digit -10 > -100" $ firstDigit (-58) `shouldBe` 5 84 | it "first digit -100 > -1000" $ firstDigit (-158) `shouldBe` 1 85 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # Trigger the workflow on push or pull request, but only for the master branch 4 | on: 5 | pull_request: 6 | push: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | name: Build Learn4Haskell 12 | runs-on: ubuntu-16.04 13 | strategy: 14 | matrix: 15 | cabal: ["3.2"] 16 | ghc: ["8.10.2"] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | if: github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.ref == 'refs/heads/master' 21 | 22 | - uses: actions/setup-haskell@v1.1.2 23 | name: Setup Haskell 24 | with: 25 | ghc-version: ${{ matrix.ghc }} 26 | cabal-version: ${{ matrix.cabal }} 27 | 28 | - uses: actions/cache@v2.1.1 29 | name: Cache ~/.cabal/store 30 | with: 31 | path: ~/.cabal/store 32 | key: ${{ runner.os }}-${{ matrix.ghc }}-cabal 33 | 34 | - name: Build 35 | run: | 36 | cabal v2-build --enable-tests --enable-benchmarks 37 | 38 | chapter1: 39 | name: Chapter One 40 | runs-on: ubuntu-16.04 41 | strategy: 42 | matrix: 43 | cabal: ["3.2"] 44 | ghc: ["8.10.2"] 45 | steps: 46 | - uses: actions/checkout@v2 47 | if: github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.ref == 'refs/heads/master' 48 | 49 | - uses: actions/setup-haskell@v1.1.2 50 | name: Setup Haskell 51 | with: 52 | ghc-version: ${{ matrix.ghc }} 53 | cabal-version: ${{ matrix.cabal }} 54 | 55 | - uses: actions/cache@v2.1.1 56 | name: Cache ~/.cabal/store 57 | with: 58 | path: ~/.cabal/store 59 | key: ${{ runner.os }}-${{ matrix.ghc }}-cabal 60 | 61 | - name: Chapter 1 - Doctest 62 | run: | 63 | cabal v2-test doctest-chapter1 --enable-tests --test-show-details=direct 64 | 65 | - name: Chapter 1 - Tests 66 | run: | 67 | cabal run learn4haskell-test --enable-tests -- -m "Chapter1Normal" 68 | 69 | - name: Chapter 1 - Tests - Advanced 70 | continue-on-error: true 71 | run: | 72 | cabal run learn4haskell-test --enable-tests -- -m "Chapter1Advanced" 73 | 74 | chapter2: 75 | name: Chapter Two 76 | runs-on: ubuntu-16.04 77 | strategy: 78 | matrix: 79 | cabal: ["3.2"] 80 | ghc: ["8.10.2"] 81 | steps: 82 | - uses: actions/checkout@v2 83 | if: github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.ref == 'refs/heads/master' 84 | 85 | - uses: actions/setup-haskell@v1.1.2 86 | name: Setup Haskell 87 | with: 88 | ghc-version: ${{ matrix.ghc }} 89 | cabal-version: ${{ matrix.cabal }} 90 | 91 | - uses: actions/cache@v2.1.1 92 | name: Cache ~/.cabal/store 93 | with: 94 | path: ~/.cabal/store 95 | key: ${{ runner.os }}-${{ matrix.ghc }}-cabal 96 | 97 | - name: Chapter 2 - Doctest 98 | run: | 99 | cabal v2-test doctest-chapter2 --enable-tests --test-show-details=direct 100 | 101 | - name: Chapter 2 - Tests 102 | run: | 103 | cabal run learn4haskell-test --enable-tests -- -m "Chapter2Normal" 104 | 105 | - name: Chapter 2 - Tests - Advanced 106 | continue-on-error: true 107 | run: | 108 | cabal run learn4haskell-test --enable-tests -- -m "Chapter2Advanced" 109 | 110 | chapter3: 111 | name: Chapter Three 112 | runs-on: ubuntu-16.04 113 | strategy: 114 | matrix: 115 | cabal: ["3.2"] 116 | ghc: ["8.10.2"] 117 | steps: 118 | - uses: actions/checkout@v2 119 | if: github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.ref == 'refs/heads/master' 120 | 121 | - uses: actions/setup-haskell@v1.1.2 122 | name: Setup Haskell 123 | with: 124 | ghc-version: ${{ matrix.ghc }} 125 | cabal-version: ${{ matrix.cabal }} 126 | 127 | - uses: actions/cache@v2.1.1 128 | name: Cache ~/.cabal/store 129 | with: 130 | path: ~/.cabal/store 131 | key: ${{ runner.os }}-${{ matrix.ghc }}-cabal 132 | 133 | - name: Chapter 3 - Doctest 134 | run: | 135 | cabal v2-test doctest-chapter3 --enable-tests --test-show-details=direct 136 | 137 | chapter4: 138 | name: Chapter Four 139 | runs-on: ubuntu-16.04 140 | strategy: 141 | matrix: 142 | cabal: ["3.2"] 143 | ghc: ["8.10.2"] 144 | steps: 145 | - uses: actions/checkout@v2 146 | if: github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.ref == 'refs/heads/master' 147 | 148 | - uses: actions/setup-haskell@v1.1.2 149 | name: Setup Haskell 150 | with: 151 | ghc-version: ${{ matrix.ghc }} 152 | cabal-version: ${{ matrix.cabal }} 153 | 154 | - uses: actions/cache@v2.1.1 155 | name: Cache ~/.cabal/store 156 | with: 157 | path: ~/.cabal/store 158 | key: ${{ runner.os }}-${{ matrix.ghc }}-cabal 159 | 160 | - name: Chapter 4 - Doctest 161 | run: | 162 | cabal v2-test doctest-chapter4 --enable-tests --test-show-details=direct 163 | 164 | - name: Chapter 4 - Tests 165 | run: | 166 | cabal run learn4haskell-test --enable-tests -- -m "Chapter4Normal" 167 | 168 | - name: Chapter 4 - Tests - Advanced 169 | continue-on-error: true 170 | run: | 171 | cabal run learn4haskell-test --enable-tests -- -m "Chapter4Advanced" 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # learn4haskell 2 | 3 | ![Learn4Haskell](/images/HacktoberFest2020-Learn4Haskell-Banner.png) 4 | 5 | It's the time of the year when thousand pull requests are starting to float in 6 | the air like a leaf on the wind 🍃 7 | 8 | It's Hacktoberfest! And we are happy to be part of this fantastic event. 9 | 10 | Usually, people contribute to projects within the communities they spend most of their time 11 | aleady and don't try to go out of those boundaries. 12 | But why not use this time to learn something challenging, something fresh, 13 | something that you never had time for? 14 | 15 | You can get the benefits of Hacktoberfest while learning something new 16 | fascinating concepts – Functional Programming with Haskell. 17 | 18 | And we're here to help! 19 | 20 | * 4 Pull Request to get the T-Shirt or plant a tree as stands in the Hacktoberfest rules. 21 | * 4 Pull Request to learn to program in Haskell. 22 | * 4 Pull Request to blow your mind. 23 | 24 | ## Table of Contents 25 | 26 | * [What is Learn4Haskell](#what-is-learn4haskell) 27 | * [Course Plan](#course-plan) 28 | * [Goals](#goals) 29 | * [Who can participate](#who-can-participate) 30 | * [What you will get from this course](#what-you-will-get-from-this-course) 31 | * [How to get started](#how-to-get-started) 32 | * [Installing Haskell](#installing-haskell) 33 | * [Haskell IDE](#haskell-ide) 34 | * [How to develop](#how-to-develop) 35 | * [Who we are](#who-we-are) 36 | * [How can you help](#how-can-you-help) 37 | 38 | ## What is Learn4Haskell 39 | 40 | Learn4Haskell is a GitHub-located course that will get you into the Haskell 41 | Functional Programming world in just 4 Pull Requests. 42 | 43 | This course is organised as a coding project. So you can complete 44 | the course without needing to exit your editor. 45 | 46 | This works in the following way. When you decide to start the project, all you 47 | need to do is to fork the project. We have prepared 4 separate modules — chapters. 48 | Each part contains educational material and lots of examples that we provide in 49 | a simple form that doesn't require you to know anything about functional programming beforehand. 50 | Also, each chapter contains a number of exercises on everything that is 51 | explained by us. You can solve the tasks on your way and at the end open a PR to 52 | your fork with this chapter's solution and summon us (by shouting out our 53 | nicknames there). We would be happy to give you feedback on your progress, 54 | explain problematic concepts or just support you mentally! 55 | 56 | Each chapter contains unique information and covers different topics. We suggest 57 | going through them in order. However, if you think that some of the chapters 58 | are already familiar to you, feel free to skip onto the next one. 59 | If you would like to talk to us, you can even rely on PRs for the chapter you 60 | have questions about. 61 | 62 | Chapters are stuffed with information, but are aimed to be completed 63 | without additional resources. You may spend an evening per chapter, but we swear 64 | it's worth it! 65 | 66 | At the end of the course you should be able to independently create and read 67 | basic Haskell code and understand Monads and other famous concepts of Functional 68 | Programming. 69 | 70 | ### Course Plan 71 | 72 | Here is a more concrete plan of the mystical 4 Chapters we prepared for 73 | you. These are the highlights of each part. 74 | 75 | * __Chapter One__ – What is Haskell, what are its particularities, basic Haskell 76 | syntax, functions, types, expressions. 77 | * __Chapter Two__ – FP concepts in the language, immutability, pattern matching, 78 | recursion, polymorphism, laziness, Higher-ordered functions, partial 79 | applications, eta-reduction. 80 | * __Chapter Three__ – Focus on Types. Type aliases, ADTs, Product types and 81 | Records, Sum types and Enumerations, Newtypes, Typeclasses. 82 | * __Chapter Four__ – Kinds. Three monsters of functional programming: Functor, Applicative, 83 | Monad. 84 | 85 | ## Goals 86 | 87 | We created the Learn4Haskell project in pursuit of the following goals: 88 | 89 | * Help others to learn Haskell 90 | * Give a beginner-friendly and self-consistent course with theory and practice 91 | in the same place 92 | * Explain Haskell topics before each task, but strive to be concise and useful 93 | at the same time. It's a tough balance! 94 | * Help people who want to participate in Hacktoberfest and Open-Source, but also 95 | want to learn new things during this process 96 | * Provide review and feedback on solutions, so people are never alone in this 97 | challenging yet exciting journey! 98 | * Give people who completed this course all the necessary understandings to 99 | be able to work with basic projects that use standard features. We also intend 100 | that you have a strong basis on what they should do to be able to continue their functional programming 101 | studies. 102 | 103 | ## Who can participate 104 | 105 | Everyone! 106 | 107 | We welcome everyone and would be happy to assist you in this journey! 108 | 109 | The course is intended for people who don't know Haskell or know only language 110 | basics, though. 111 | 112 | If you are already an experienced Haskell developer and have come here for learning 113 | advanced topics, this course might not be that for you. But you still can help us! 114 | Your feedback and suggestions would be helpful for us as well as for the 115 | language newcomers who decide to work with this course. 116 | 117 | ## What you will get from this course 118 | 119 | This course has many benefits upon completion. Check them out to be sure that it fits 120 | your expectations! 121 | 122 | The participation in this course would give you: 123 | 124 | * 4 Pull Requests required for Hacktoberfest completion 125 | * Basic knowledge of the most functional programming language 126 | * Understanding of the functional programming concepts that you would be able to use in your 127 | day-to-day life afterwards 128 | * On-the-fly feedback and help from experienced Haskell developers and educators 129 | * Interesting challenges 130 | * Fun! 131 | 132 | Honestly this seems like a pretty rad deal! 133 | 134 | ## So how do I get started? 135 | 136 | Starting to learn Haskell with Learn4Haskell is a piece of cake! 137 | 138 | 1. [Fork this repository](https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/fork-a-repo). 139 | 2. :warning: Add the `hacktoberfest` topic to your fork. Otherwise, [your PRs won't count](https://hacktoberfest.digitalocean.com/hacktoberfest-update). 140 | 3. Enable GitHub Actions for your fork repository. 141 | * Visit: https://github.com//learn4haskell/actions 142 | 4. [Install the Haskell compiler](#installing-haskell). 143 | 5. Open the `src/Chapter1.hs` file, and start learning and solving tasks! 144 | 6. After you finish the first chapter (or any other chapter, or even if you are 145 | stuck in the middle), open 146 | [Pull Request](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request) 147 | __to your fork__ with the solution and mention @vrom911 and @chshersh and we 148 | would be on our way for the review. 149 | > Note, that you should open a PR for your fork of this repo, not this repo. 150 | > Everyone has their own solutions to the included tasks, and they don't mix together 151 | > well in one repo 🙂 152 | 153 | > However, if you find some bugs or problems in this repo, you can of 154 | > open a PR to Learn4Haskell directly. We appreciate any help and feedback! 155 | 156 | Learn4Haskell has 4 chapters that you can walk through and submit 4 pull requests to 157 | complete the Hacktoberfest event (or just for knowledge and your own enjoyment). 158 | 159 | So, you can start right now with forking. Following this will describe how you can 160 | install all the necessary items to be able to run this course locally. 161 | 162 | ### Installing Haskell 163 | 164 | If you're on Windows, install the `haskell-dev` and `make` packages [using Chocolatey](https://chocolatey.org/install). 165 | ```shell 166 | choco install haskell-dev make 167 | refreshenv 168 | ``` 169 | 170 | Then, do [the workaround](https://www.stackage.org/blog/2020/08/ghc-8-10-2-windows-workaround) to alleviate a GHC 8.10.2 issue on Windows which prevents the test suite from building correctly. 171 | 172 | If you're on Linux or macOS, then the process is easy: 173 | 174 | 1. Install [ghcup](https://www.haskell.org/ghcup/) and follow `ghcup` 175 | instructions for successful installation (remember to restart your terminal afterwards to avoid an `unknown ghcup command` error on the next step). 176 | 2. Install the latest version of the Haskell compiler — GHC — and the 177 | [Cabal](https://www.haskell.org/cabal/) build tool. After you install 178 | `ghcup`, it is easy to install the rest with a few commands from your 179 | terminal 180 | 181 | ```shell 182 | ghcup install ghc 8.10.2 183 | ghcup set ghc 8.10.2 184 | ghcup install cabal 3.2.0.0 185 | ``` 186 | 3. Run `cabal update` to fetch the latest info about Haskell packages. 187 | 188 | ### Haskell IDE 189 | 190 | If you don't have any IDE preferences, we recommend installing 191 | [Visual Studio Code](https://code.visualstudio.com/download) with the 192 | [Haskell plugin](https://marketplace.visualstudio.com/items?itemName=haskell.haskell). 193 | The mentioned plugin would give you everything required to immediately start coding with Haskell. 194 | 195 | ### How to develop 196 | 197 | The course assumes that you install Haskell tooling (GHC and Cabal), edit code 198 | in the corresponding chapters, run GHCi (Haskell interpreter, explained in the 199 | course) from the root of this project and load your chapters to check your code. 200 | Don't worry, each Chapter explains all the needed information! 201 | 202 | We also provide Makefile with commands to test your solutions locally with the included 203 | prepared test-suite. We have also configured the CI using GitHub 204 | Actions on Learn4Haskell to check your answers at GitHub automatically! 205 | 206 | To run all tests for Chapter One: 207 | 208 | ```shell 209 | make test-chapter1 210 | ``` 211 | 212 | To run tests only for basic tasks for Chapter One (without the advanced tasks): 213 | 214 | ```shell 215 | make test-chapter1-basic 216 | ``` 217 | 218 | Similar commands are provided for all chapters from One to Four. 219 | 220 | ## Who we are 221 | 222 | [Veronika (@vrom911)](https://vrom911.github.com/) and 223 | [Dmitrii (@chshersh)](https://kodimensional.dev/) are experienced Haskell developers. 224 | Together we drive this open source organisation — 225 | [Kowainik](https://kowainik.github.io/). We have a lot of open source projects 226 | and libraries in Haskell that are used in the Haskell community. We are also 227 | working on a lot of tutorials and guides in Haskell and mentoring people who are 228 | keen to learn Haskell as well. Moreover, Dmitrii has a few years of experience 229 | teaching Haskell courses in the university to CS students. 230 | 231 | We are passionate about Functional Programming and Haskell in particular. But at 232 | the same time we understand how difficult it can be to get into all these 233 | ideas on your own. That is why we've decided to start this course to help 234 | newcomers. With the interactive learning process and live discussions we've included, Haskell 235 | will not be that scary. We will do our best so that it especially won't be the case 236 | for you or any others participating here! 237 | 238 | ## How can you help 239 | 240 | You can help us by supporting us on Ko-Fi or via GitHub sponsorship program: 241 | 242 | * [Kowainik Ko-Fi](https://ko-fi.com/kowainik) 243 | * [Veronika Romashkina via GitHub](https://github.com/sponsors/vrom911) 244 | * [Dmitrii Kovanikov via GitHub](https://github.com/sponsors/chshersh) 245 | 246 | 247 | We also appreciate any feedback on our course a lot! You can submit your 248 | feedback using the following form: 249 | * https://docs.google.com/forms/d/e/1FAIpQLScBVhLxq5CgGnAfIGUE-fCoOUqeGkDY2HXzbT7KV2jjLOsmjQ/viewform 250 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /src/Chapter1.hs: -------------------------------------------------------------------------------- 1 | {- 👋 Welcome, Brave folks! 2 | 3 | Happy to see you here, on the way to the wonderful Functional Programming land 4 | with Haskell! Fight the fierce Monad Dragon and save the globe from despicable 5 | runtime exceptions! 6 | 7 | 8 | We appreciate your curiosity and will try to provide you with all the necessary 9 | equipment for your training before the battle in the real FP world. Learning 10 | Functional Programming can be challenging. But we designed this training to be 11 | beginner-friendly and helpful to everyone! 12 | 13 | Practice your functional skills, and choose your class in the end, who you want 14 | to become: Monad Wizard, Exceptions Crusher, Recursion Priest, Free Fighter, 15 | Composition Warlock, and many more! 16 | 17 | Here is how it works: 18 | 19 | ★ Make sure that you familiarise yourself with this repo's README in order to 20 | understand why and what we are trying to achieve with this. 21 | ★ For each Chapter, learn the material that we provide and try to solve each 22 | task proposed to consolidate the results. 23 | ★ Create a PR with solutions to as many tasks as you can after each Chapter is 24 | finished. 25 | ★ Make sure that tests for the Chapter you are working at are passing. 26 | ★ Receive some feedback and suggestions from us. 27 | ★ Merge the PR. 28 | ★ Go to the next Chapter. 29 | ★ When finished, tell us how you liked it. Share it with your friends. Support 30 | us on Ko-Fi or GitHub sponsorship. 31 | 32 | == You are on __Chapter One__. 33 | 34 | Here we will give the basic Haskell syntax knowledge and explain other common 35 | concepts on the way. In this chapter, you are going to learn: 36 | 37 | ✧ Haskell language main particularities 38 | ✧ How to use Haskell interactively 39 | ✧ Expressions 40 | ✧ Types 41 | ✧ Basic language syntax: calling functions, if-then-else, defining local 42 | variables 43 | ✧ How to write your function from scratch 44 | ✧ Some standard Haskell functions 45 | 46 | 47 | We are leaving a number of tasks on our path. Your goal is to solve them all and 48 | make the test for Chapter One green. 49 | 50 | After finishing the PR, you can choose to summon us, @vrom911 and @chshersh, to 51 | look at your solution in order to give some advice on your code or help with 52 | understanding and exercise solutions. This is optional; however, you can ask us 53 | for review only if you want some feedback on your solutions. 54 | 55 | Now, if you are ready, let's start! 56 | -} 57 | 58 | -- Single-line comments in Haskell start with -- 59 | 60 | {- | This tutorial uses block comments to explain various concepts and provide 61 | task description. 62 | -} 63 | 64 | {- All code in Haskell is organised into modules. Each module corresponds to a 65 | single file. Modules then can be combined in a package. But you don't need to 66 | worry about this for now. We already created the package with module hierarchy 67 | for you. 68 | 69 | Each Haskell module starts with the "module where" line. 70 | Modules should have the same name as the corresponding file with 71 | the `.hs` extension. 72 | -} 73 | module Chapter1 where 74 | 75 | {- | 76 | In Haskell, we have __expressions__. Expressions can be represented by some 77 | primitive values (numbers: 1, 100; characters: 'a', 'z'; booleans: True, False; 78 | etc.) or by a combination of the primitive values and other expressions using 79 | language syntax constructions (if-then-else, let-in, case-of, etc.) and various 80 | functions (addition — (+), division — div, maximum — max, sorting — sort, 81 | sortBy, sortOn, etc.) and variables. Functions are also expressions as well as 82 | variables. 83 | 84 | If an expression is a combination of other values and expressions, it can be 85 | __evaluated__ (or reduced) to a primitive value. The evaluation process is not 86 | immediate, since Haskell is a __lazy language__ and it won't evaluate 87 | expressions unless really necessary. You can see the evaluation results either 88 | by running a Haskell program or by playing with some functions in the 89 | interactive interpreter (explained later). 90 | 91 | Haskell is a __strongly-typed__ language, which means that each expression has 92 | a type. Each value and function is associated with some type. You can't change 93 | the value type. You can only pass a value to some function that will do its 94 | work, and maybe produce a value of a different type. 95 | 96 | Types can be _specific_ (like `Int`, `Integer`, `Double` or `Bool`) and they 97 | always start with an uppercase letter, or _polymorphic_ (aka general) specified 98 | through the variables – begin with the lowercase letter. The concept of 99 | polymorphism is more sophisticated than working with concrete types, thus we 100 | won't dive too much into it in this chapter and will work with the concrete 101 | types for now. 102 | 103 | Furthermore, Haskell is a __statically-typed__ language, which means that each 104 | expression has the type known at compile-time, rather than run-time. It allows 105 | the compiler to catch some kinds of bugs in your program early; before you 106 | even run it. 107 | 108 | Additionally to static typing, Haskell has __type inference__. This means that 109 | you _don't need_ to specify the type of each expression as it is going to be 110 | found out for each expression and subexpression by the powerful compiler. 111 | 112 | However, you are __strongly encouraged to write top-level function type 113 | signatures__ and provide types in different situations where you don't 114 | immediately see what types will be inferred. 115 | -} 116 | 117 | 118 | {- 119 | Haskell is a __compiled__ language. At the illustration below, you can see the 120 | overall picture of the process from your code to the binary of the written 121 | program: 122 | 123 | +-----------+ +-------------+ +-------------+ 124 | | | | | | | 125 | | Parsing +------> Compilation +-----> Executable | 126 | | | | | | | 127 | +-----------+ +-------------+ +-------------+ 128 | 129 | In comparison, such languages as Python, JavaScript, etc. are interpreted 130 | languages. And that could be illustrated in the different workflow: 131 | 132 | +-----------+ +----------------+ +------------+ 133 | | | | | | | 134 | | Parsing +------> Interpretation +-----> Evaluation | 135 | | | | | | | 136 | +-----------+ +----------------+ +------------+ 137 | 138 | So, when working with Haskell, you first need to compile the code in order to 139 | run it later. 140 | 141 | However, most of the time while working with Haskell (especially when learning 142 | Haskell), you use __GHCi__ to play with functions, see their behaviour and 143 | explore API of external libraries. GHCi is a __Haskell REPL__ 144 | (read-eval-print-loop). It allows calling functions directly in an interactive 145 | shell. GHCi interprets Haskell expressions and statements and prints the 146 | evaluation result of each expression. 147 | 148 | As you can see, Haskell supports both paradigms: you can either compile your 149 | code and run the executable, or interpret the code and run it directly. 150 | 151 | Assuming that you already have the Haskell compiler installed (we recommend 152 | GHC), you can start GHCi by executing the following command in your terminal 153 | from the root of this project. 154 | 155 | $ ghci 156 | 157 | > If you don't have Haskell yet, refer to the corresponding section of the 158 | README. 159 | 160 | Now, you can evaluate some expressions and see their results immediately. 161 | 162 | >>> 1 + 2 163 | 3 164 | 165 | ♫ NOTE: Each time you see a line starting with >>> in this training, it means 166 | that the text after >>> is supposed to be evaluated in GHCi, and the evaluation 167 | result should be printed on the next line if it has any. In your GHCi, you 168 | probably have the string "Prelude> " as your prompt. For some expressions, we 169 | already prefilled the result, but for others, you need to insert it on your own. 170 | We use lovely Haskell tools to test the results of the GHCi evaluation as well, 171 | so you'd better insert exact answers in order to make them pass the testing! 172 | 173 | You will also see lines started with "ghci>". They are also supposed to be 174 | evaluated in GHCi, but our testing system doesn't check their output. 175 | They are here just to showcase the different usages of GHCi. 176 | 177 | 178 | GHCi can do much more than evaluating expressions. It also contains some special 179 | commands starting with a colon. For example, to see the list of all available 180 | commands, type ":?" in your GHCi. 181 | 182 | ghci> :? 183 | 184 | To quit GHCi, enter the ":q" command (short for ":quit"). 185 | 186 | ghci> :q 187 | 188 | -} 189 | 190 | {- | 191 | =⚔️= Task 1 192 | 193 | Since types play a crucial role in Haskell, we can start by exploring types of 194 | some basic expressions. You can inspect the type of expression by using the ":t" 195 | command in GHCi (short for ":type"). 196 | 197 | For example: 198 | 199 | >>> :t False 200 | False :: Bool 201 | 202 | "::" in Haskell indicates that the type of the expression before, would be 203 | specified after these symbols. 204 | So, the output in this example means that 'False' has type 'Bool'. 205 | 206 | (ノ◕ヮ◕)ノ Your first task! Use GHCi to discover types of the following 207 | expressions and functions: 208 | 209 | > Try to guess first and then compare your expectations with GHCi output 210 | 211 | >>> :t True 212 | True :: Bool 213 | >>> :t 'a' 214 | 'a' :: Char 215 | >>> :t 42 216 | 42 :: Num p => p 217 | 218 | A pair of boolean and char: 219 | >>> :t (True, 'x') 220 | (True, 'x') :: (Bool, Char) 221 | 222 | Boolean negation: 223 | >>> :t not 224 | not :: Bool -> Bool 225 | 226 | Boolean 'and' operator: 227 | >>> :t (&&) 228 | (&&) :: Bool -> Bool -> Bool 229 | 230 | Addition of two numbers: 231 | >>> :t (+) 232 | (+) :: Num a => a -> a -> a 233 | 234 | Maximum of two values: 235 | >>> :t max 236 | max :: Ord a => a -> a -> a 237 | 238 | You might not understand each type at this moment, but don't worry! You've only 239 | started your Haskell journey. Types will become your friends soon. 240 | 241 | Primitive types in Haskell include 'Int', 'Bool', 'Double', 'Char' and many 242 | more. You've also seen the arrow "->" which is a function. When you see "A -> B 243 | -> C" you can think that this is a function that takes two arguments of types 244 | "A" and "B" and returns a value of type "C". 245 | -} 246 | 247 | {- | 248 | =⚔️= Task 2 249 | 250 | After having our first look at the Haskell type system, we can do something more 251 | exciting. Call to arms! In other words, let's call some functions. 252 | 253 | When calling a function in Haskell, you type a name of the function first, and 254 | then you specify space-separated function arguments. That's right. No commas, no 255 | parenthesis. You only need to use () when grouping arguments (e.g. using other 256 | expressions as arguments). 257 | 258 | For example, if the function `foo` takes two arguments, the call of this 259 | function can look like this: 260 | 261 | ghci> foo arg1 (fun arg2) 262 | 263 | Operators in Haskell are also functions, and you can define your own operators 264 | as well! The important difference between operators and functions is that 265 | functions are specified using alphanumeric symbols, and operators are specified 266 | using "operator" symbols. For example, addition — +, cons — :, list append — ++, 267 | diamond operator — <>. Also, by default, you call operators in the __infix__ 268 | form (operator goes __after__ the first argument), while ordinary functions are 269 | what-called __prefix__ form (the name goes first, before all arguments). 270 | 271 | ghci> :t add 272 | Add :: Int -> Int -> Int 273 | ghci> :t (+) 274 | (+) :: Int -> Int -> Int 275 | ghci> add 1 2 276 | 3 277 | ghci> 1 + 2 278 | 3 279 | 280 | ♫ NOTE: in reality, the type of the + operator is the following: 281 | 282 | >>> :t (+) 283 | (+) :: Num a => a -> a -> a 284 | 285 | > It may look scary to you, but we will cover all this 'Num' and "=>" later. For 286 | now, you can think of this as a polymorphic function — in this case, the 287 | operator, that can work with any numeric types, including 'Int's, 'Double's, 288 | etc. Or you can even pass the "+d" option to the ":t" command to see a simpler 289 | type. In this case, polymorphic types will default to some standard types: 290 | 291 | ghci> :t +d (+) 292 | (+) :: Integer -> Integer -> Integer 293 | 294 | Get ready for the next task, brave programmer! Evaluate the following 295 | expressions in GHCi 296 | 297 | > As in the previous task, try to guess first and then compare your expectations 298 | with the GHCi output. 299 | 300 | 🕯 HINT: if you are curious, it might be interesting to explore types of 301 | functions and operators first. Remember this from the previous task? ;) 302 | 303 | >>> 1 + 2 304 | 3 305 | 306 | >>> 10 - 15 307 | -5 308 | 309 | >>> 10 - (-5) -- negative constants require () 310 | 15 311 | 312 | >>> (3 + 5) < 10 313 | True 314 | 315 | >>> True && False 316 | False 317 | 318 | >>> 10 < 20 || 20 < 5 319 | True 320 | 321 | >>> 2 ^ 10 -- power 322 | 1024 323 | 324 | >>> not False 325 | True 326 | 327 | >>> div 20 3 -- integral division 328 | 6 329 | 330 | >>> mod 20 3 -- integral division remainder 331 | 2 332 | 333 | >>> max 4 10 334 | 10 335 | 336 | >>> min 5 (max 1 2) 337 | 2 338 | 339 | >>> max (min 1 10) (min 5 7) 340 | 5 341 | 342 | Because Haskell is a __statically-typed__ language, you see an error each time 343 | you try to mix values of different types in situations where you are not 344 | supposed to. Try evaluating the following expressions to see errors: 345 | 346 | ghci> not 'a' 347 | ghci> max True 'x' 348 | ghci> 10 + True 349 | 350 | This is a gentle way to get familiar with various error messages in Haskell. 351 | In some cases, the error messages could be challenging to decipher and 352 | understand their meaning. Haskell has a bad reputation for having not-so-helpful 353 | error messages in some situations. But, of course, such a small challenge won't 354 | stop you, right? You're a brave warrior, and you can finish all tasks despite 355 | all obstacles! And we are always here to help and to decrypt these ancient 356 | scripts together. 357 | -} 358 | 359 | 360 | {- | 361 | =🛡= Defining a function 362 | 363 | We have already learned how to use different functions and operators in Haskell. 364 | Let's now check how they are defined and whether we can introduce our own. 365 | 366 | When defining a function in Haskell, you write its type signature on the first 367 | line, and then its body on the following line(s). The type signature should be 368 | written immediately from the start of a line. Haskell is __indentation-__ and 369 | __layout-sensitive__ language, so this is important to keep in mind. 370 | 371 | For example, here is the type signature of a function that takes a 'Double' and 372 | an 'Int', and then returns an 'Int': 373 | 374 | @ 375 | roundSubtract :: Double -> Int -> Int 376 | @ 377 | 378 | We have already seen the "::" sequence of symbols when practising our skills in 379 | GHCi. Now you know that this is the syntax for specifying types in your code as 380 | well. 381 | 382 | The following line should be the function definition start line. You write the 383 | function name again and give argument names in the same order as you wrote types 384 | followed by the "=" sign. And you provide the function implementation after "=". 385 | 386 | @ 387 | roundSubtract x y = ceiling x - y 388 | @ 389 | 390 | ^ Here x corresponds to the 'Double', and y to 'Int'. 391 | 392 | The body of the function can be as big as you want. However, don't forget about 393 | the indentation rules when your body exceeds the definition line. 394 | 395 | The same function body can be written on a separate line, minding the 396 | indentation. 397 | 398 | @ 399 | roundSubtract x y = 400 | ceiling x - y 401 | @ 402 | 403 | Putting everything together, the complete function definition looks like this: 404 | 405 | @ 406 | roundSubtract :: Double -> Int -> Int 407 | roundSubtract x y = ceiling x - y 408 | @ 409 | 410 | Now you are ready for defining your own functions! 411 | -} 412 | 413 | {- | 414 | In our training, for some functions types are provided for you. For others, you 415 | need to write types manually to challenge yourself. 416 | 417 | Don't forget the main rule: 418 | **Always provide type signatures for top-level functions in Haskell.** 419 | -} 420 | 421 | 422 | {- | 423 | =⚔️= Task 3 424 | 425 | Below you see the function that finds a square of the sum of two integers. Your 426 | task is to specify the type of this function. 427 | 428 | >>> squareSum 3 4 429 | 49 430 | -} 431 | 432 | squareSum :: Int -> Int -> Int 433 | squareSum x y = (x + y) * (x + y) 434 | 435 | 436 | {- | 437 | =⚔️= Task 4 438 | 439 | Implement the function that takes an integer value and returns the next 'Int'. 440 | 441 | >>> next 10 442 | 11 443 | >>> next (-4) 444 | -3 445 | 446 | ♫ NOTE: The current function body is defined using a special function called 447 | "error". Don't panic, it is not broken. 'error' is like a placeholder, that 448 | evaluates to an exception if you try evaluating it. And it also magically fits 449 | every type 。.☆.*。. No need to worry much about "error" here, just replace the 450 | function body with the proper implementation. 451 | -} 452 | next :: Int -> Int 453 | next x = x + 1 454 | 455 | {- | 456 | After you've implemented the function (or even during the implementation), you 457 | can run it in GHCi with your input. To do so, first, you need to load the module 458 | with the function using the ":l" (short for ":load") command. 459 | 460 | ghci> :l src/Chapter1.hs 461 | 462 | After that, you can call the 'next' function as you already know how to do that. 463 | Or any other function defined in this module! But remember, that you need to 464 | reload the module again after you change the file's content. You can reload the 465 | last loaded module by merely typing the ":r" command (no need to specify the 466 | name again). 467 | 468 | ghci> :r 469 | 470 | A typical workflow looks like this: you load the module once using the ":l" 471 | command, and then you should reload it using the ":r" command each time you 472 | change it and want to check your changes. 473 | -} 474 | 475 | {- | 476 | =⚔️= Task 5 477 | 478 | Implement a function that returns the last digit of a given number. 479 | 480 | >>> lastDigit 42 481 | 2 482 | 483 | 🕯 HINT: use the `mod` function 484 | 485 | ♫ NOTE: You can discover possible functions to use via Hoogle: 486 | https://hoogle.haskell.org/ 487 | 488 | Hoogle lets you search Haskell functions either by name or by type. You can 489 | enter the type you expect a function to have, and Hoogle will output relevant 490 | results. Or you can try to guess the function name, search for it and check 491 | whether it works for you! 492 | -} 493 | -- DON'T FORGET TO SPECIFY THE TYPE IN HERE 494 | lastDigit :: Int -> Int 495 | lastDigit n = mod (abs n) 10 496 | 497 | 498 | {- | 499 | =⚔️= Task 6 500 | 501 | Implement a function, that takes two numbers and returns the one closer to zero: 502 | 503 | >>> closestToZero 10 5 504 | 5 505 | >>> closestToZero (-7) 3 506 | 3 507 | 508 | 509 | 🕯 HINT: You can use the 'abs' function and the __if-then-else__ Haskell syntax 510 | for this task. 511 | 512 | 'if-then-else' is a language construction for expression that returns only one 513 | branch depending on the checked condition. For example: 514 | 515 | >>> if even 10 then 0 else 1 516 | 0 517 | 518 | The 'if-then-else' constructions must always have both __then__ and __else__ 519 | branches because it is an expression and it must always return some value. 520 | 521 | 👩‍🔬 Due to lazy evaluation in Haskell, only the expression from the branch 522 | satisfying the check will be returned and, therefore, evaluated. 523 | -} 524 | closestToZero :: Int -> Int -> Int 525 | closestToZero x y = if abs x < abs y then x else y 526 | 527 | 528 | {- | 529 | =⚔️= Task 7 530 | Write a function that returns the middle number among three given numbers. 531 | 532 | >>> mid 3 1 2 533 | 2 534 | 535 | 🕯 HINT: When checking multiple conditions, it is more convenient to use the 536 | language construction called "guards" instead of multiple nested 'if-then-else' 537 | expressions. The syntax of guards is the following: 538 | 539 | @ 540 | sign :: Int -> Int 541 | sign n 542 | | n < 0 = (-1) 543 | | n == 0 = 0 544 | | otherwise = 1 545 | @ 546 | 547 | You define different conditions in different branches, started by the '|' 548 | symbol. And the functions check them from top to bottom, returning the first 549 | value after "=" where the condition is true. 550 | 551 | ♫ NOTE: The "=" sign goes after each branch, respectively. 552 | 553 | ♫ NOTE: It is essential to have the same indentation before each branch "|"! 554 | Remember, that Haskell is indentation- and layout-sensitive language. 555 | 556 | Casual reminder about adding top-level type signatures for all functions :) 557 | -} 558 | 559 | mid :: Int -> Int -> Int -> Int 560 | mid x y z 561 | | x <= y && x <= z = if y < z then y else z 562 | | y <= x && y <= z = if x < z then x else z 563 | | z <= x && z <= x = if y < x then y else x 564 | | otherwise = z 565 | 566 | {- | 567 | =⚔️= Task 8 568 | 569 | Implement a function that checks whether a given character is a vowel. 570 | 571 | 🕯 HINT: use guards 572 | 573 | >>> isVowel 'a' 574 | True 575 | >>> isVowel 'x' 576 | False 577 | -} 578 | 579 | isVowel :: Char -> Bool 580 | isVowel 'a' = True 581 | isVowel 'e' = True 582 | isVowel 'i' = True 583 | isVowel 'o' = True 584 | isVowel 'u' = True 585 | isVowel _ = False 586 | 587 | 588 | {- | 589 | == Local variables and functions 590 | 591 | So far, we've been playing only with simple expressions and function 592 | definitions. However, in some cases, expressions may become complicated, and it 593 | could make sense to introduce some helper variables. 594 | 595 | You can use the let-in construction in Haskell to define variables. 596 | Here goes an example: 597 | 598 | @ 599 | half :: Int -> Int 600 | half n = let halfN = div n 2 in halfN 601 | @ 602 | 603 | ♫ NOTE: __let-in__ is also an expression! You can't just define variables; you 604 | also need to return some expression that may use defined variables. 605 | 606 | The syntax for defining multiple variables requires to care about indentation 607 | more, but there is nothing special in it as well: 608 | 609 | @ 610 | halfAndTwice :: Int -> (Int, Int) 611 | halfAndTwice n = 612 | let halfN = div n 2 613 | twiceN = n * 2 614 | in (halfN, twiceN) 615 | @ 616 | 617 | In addition to let-in (or sometimes even alternatively to let-in) you can use 618 | the __where__ construction to define local variables and functions. 619 | And, again, the example: 620 | 621 | @ 622 | pythagoras :: Double -> Double -> Double 623 | pythagoras a b = square a + square b 624 | where 625 | square :: Double -> Double 626 | square x = x ^ 2 627 | @ 628 | 629 | You can define multiple functions inside __where__! 630 | Just remember to keep proper indentation. 631 | -} 632 | 633 | {- | 634 | =⚔️= Task 9 635 | 636 | Implement a function that returns the sum of the last two digits of a number. 637 | 638 | >>> sumLast2 42 639 | 6 640 | >>> sumLast2 134 641 | 7 642 | >>> sumLast2 1 643 | 1 644 | 645 | Try to introduce variables in this task (either with let-in or where) to avoid 646 | specifying complex expressions. 647 | -} 648 | 649 | sumLast2 :: Int -> Int 650 | sumLast2 n = 651 | let n' = abs n 652 | lasttwo = n' `mod` 100 653 | final = n' `mod` 10 654 | penultimate = lasttwo `div` 10 in 655 | final + penultimate 656 | 657 | 658 | {- | 659 | =💣= Task 10* 660 | 661 | You did it! You've passed all the challenges in your first training! 662 | Congratulations! 663 | Now, are you ready for the boss at the end of this training??? 664 | 665 | Implement a function that returns the first digit of a given number. 666 | 667 | >>> firstDigit 230 668 | 2 669 | >>> firstDigit 5623 670 | 5 671 | 672 | You need to use recursion in this task. Feel free to return to it later, if you 673 | aren't ready for this boss yet! 674 | -} 675 | 676 | firstDigit :: Int -> Int 677 | firstDigit n 678 | | n' < 10 = n' 679 | | otherwise = firstDigit (n' `div` 10) 680 | where n' = abs n 681 | 682 | 683 | {- 684 | You did it! Now it is time to open pull request with your changes 685 | and summon @vrom911 and @chshersh for the review! 686 | -} 687 | 688 | {- 689 | =📜= Additional resources 690 | 691 | Modules: http://learnyouahaskell.com/modules 692 | Let vs where: https://wiki.haskell.org/Let_vs._Where 693 | Packages and modules in Haskell: https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/packages.html 694 | -} 695 | -------------------------------------------------------------------------------- /src/Chapter4.hs: -------------------------------------------------------------------------------- 1 | {- 👋 Welcome to Chapter Four / Four of our journey, Staunch Wanderer! 2 | 3 | Our adventure together has almost come to an end. It has been a 4 | magical era of mysteries and joy. But it is still no time to 5 | relax. This is a critical chapter, where you finally will battle with 6 | Monads! Mmm, exciting! 7 | 8 | This Chapter requires the knowledge from the previous modules, so it 9 | is highly encouraged to at least look through the material of those 10 | chapters. 11 | 12 | Let's also rewind how the training works. 13 | 14 | == You are on __Chapter Four__. 15 | 16 | In the final chapter, we are going to get acquainted with more 17 | standard typeclasses that usually scare people off from Functional 18 | Programming. Still, we hope that with all that we have been through, 19 | these concepts won't frighten you anymore. 20 | 21 | So, in this chapter, you are going to master: 22 | 23 | ✧ Kinds 24 | ✧ Functors 25 | ✧ Applicatives 26 | ✧ Monads 27 | 28 | As usual, the explanations are in the Haskell comments of this 29 | module. We are leaving a number of tasks on our path. Your goal is to 30 | solve them all. 31 | 32 | After finishing the PR, you can choose to summon us, @vrom911 and 33 | @chshersh, to look at your solution in order to give some advice on 34 | your code. This is optional; however, you can ask us for review only 35 | if you want some feedback on your solutions. 36 | 37 | Perfect. Let's crush this! 38 | -} 39 | 40 | {-# LANGUAGE ConstraintKinds #-} 41 | {-# LANGUAGE InstanceSigs #-} 42 | 43 | module Chapter4 where 44 | 45 | {- | 46 | =🛡= Kinds 47 | 48 | Before we attempt to defeat the mighty and scary Monad Dragon, we need 49 | to talk about __kinds__. Understanding kinds is vital for the 50 | wholesome perception of the Haskell type system. 51 | 52 | All values in Haskell have types. But it turns out, types themselves 53 | also have their own "types". Such "type of a type" is called __kind__. 54 | 55 | Kinds describe the shape of a type. And kinds are much simpler than 56 | types. Primitive types like 'Int' have kind * (star). You can check 57 | information about kinds in GHCi using the ":k" command (short for ":kind"): 58 | 59 | >>> :k Int 60 | Int :: * 61 | 62 | ♫ NOTE: An alternative name for the star * kind is "Type". GHC is 63 | slowly replacing the naming of kind as * with "Type". 64 | 65 | As data types have different types, in the same manner, there are more 66 | than one kind of types. For example, data types like "Maybe" (that 67 | have an additional type parameter) have a different kind. "Maybe" is 68 | parameterised by a type variable. So, "Maybe" by itself doesn't have a 69 | kind *. We call "Maybe" a __type constructor__. There are no values of 70 | type "Maybe" without any parameters specified. As a consequence, you 71 | can't write the following function: 72 | 73 | @ 74 | foo :: Maybe -> Int 75 | @ 76 | 77 | It is invalid, because "Maybe" needs some type parameters. In some 78 | sense, you can look at "Maybe" as a function on type-level from types 79 | to types: you give "Maybe" some type, and you get a concrete type in 80 | the end. And that is the accurate description. Its kind is indeed a 81 | function of two stars. You can check the kind of 'Maybe' in GHCi as 82 | well: 83 | 84 | >>> :k Maybe 85 | Maybe :: * -> * 86 | >>> :k Maybe Int 87 | Maybe Int :: * 88 | 89 | You can think of types with kind * as types in their complete final 90 | form. Types of kind * have values. Types of any other kind don't have 91 | values. 92 | 93 | Haskell has one more standard kind — __Constraint__. This is the kind 94 | for typeclasses. You can see ordinary types of kind * on both sides of 95 | the function arrow (->) in type signatures. And you can see 96 | constraints to the left side of the context arrow (=>). 97 | 98 | You can check the kind of some standard typeclass like "Eq" to verify this: 99 | 100 | >>> :k Eq 101 | Eq :: * -> Constraint 102 | 103 | We hope kinds will become your kind friends by the end of this chapter :) 104 | -} 105 | 106 | {- | 107 | =⚔️= Task 1 108 | 109 | Prepare for the first task! To complete the first challenge, you need 110 | to explore kinds of different types in GHCi and insert the GHCi output 111 | after each command. 112 | 113 | As always, try to guess the output first! And don't forget to insert 114 | the output in here: 115 | 116 | >>> :k Char 117 | Char :: * 118 | 119 | >>> :k Bool 120 | Bool :: * 121 | 122 | >>> :k [Int] 123 | [Int] :: * 124 | 125 | >>> :k [] 126 | [] :: * -> * 127 | 128 | >>> :k (->) 129 | (->) :: * -> * -> * 130 | 131 | >>> :k Either 132 | Either :: * -> * -> * 133 | 134 | >>> data Trinity a b c = MkTrinity a b c 135 | >>> :k Trinity 136 | Trinity :: * -> * -> * -> * 137 | 138 | >>> data IntBox f = MkIntBox (f Int) 139 | >>> :k IntBox 140 | IntBox :: (* -> *) -> * 141 | 142 | -} 143 | 144 | {- | 145 | =🛡= Functor 146 | 147 | Let's continue our journey of exploring challenging concepts by 148 | meeting 'Functor'. You may have heard of it and been told or imagined 149 | that this is some Mathematical word, and there is nothing to do with 150 | Functional Programming. But it is simpler. 151 | 152 | 'Functor' is just a typeclass. If we think about it from this 153 | perspective, we would be able to objectively see what it could bring 154 | to the FP world and us specifically, and stop labelling it Math 155 | because of the name only. 156 | 157 | And maybe we can start by looking at its definition? 158 | 159 | @ 160 | class Functor f where 161 | fmap :: (a -> b) -> f a -> f b 162 | @ 163 | 164 | As you can see, this is a class definition, but with a few more 165 | challenging moments, we want to highlight in there. 166 | 167 | First of all, you can see that 'Functor' has one method called 'fmap', 168 | and it is a higher-order function — it takes a function from 'a' to 169 | 'b' as an argument, then something called 'f a', and returns 'f b'. 170 | 171 | Before we dive into the details of 'Functor' implementation in 172 | Haskell, let's try to see the meaning of this typeclass and its 173 | method. And providing an analogy could help us with that. 174 | 175 | 'Functor' allows changing the value (and even its type) in some 176 | context; we know this from the method's type signature. Let's say that 177 | a chest is our context, and its content is the value inside that 178 | context. You can replace the chest content with something else: you 179 | can fill it with gold, you can take all the coins out the chest and 180 | buy clothes using that money, and put back clothes in the chest 181 | instead. Basically, you can replace the content of a chest with 182 | anything else. This process of take-change-put is exactly what 'fmap' 183 | describes! 184 | 185 | That should make more sense now. But let's go back to Haskell. Functor 186 | as a typeclass is defined for a type variable called 'f'. But from the 187 | definition of 'fmap' you can notice that 'f' is not an ordinary 188 | type. 'f' itself is parameterised by some type variable 'a' — 'f a'. 189 | 190 | And in the light of the previous excursion into kinds, we can feel 191 | that 'f' has a more complicated kind than 'Int' (* – star), for 192 | example. 193 | 194 | Indeed, you can check your suspicions by inspecting the kind of the 195 | 'Functor' typeclass in GHCi: 196 | 197 | >>> :k Functor 198 | Functor :: (* -> *) -> Constraint 199 | 200 | Aha! So, we see that Functor works with types of the kind `* -> *`. 201 | This already tells us a lot! For example, you can't implement 202 | "instance Functor Int" (because Int has kind *), but you can implement 203 | "instance Functor Maybe" (because Maybe has kind `* -> *`). 204 | 205 | And, of course, the 'Functor' instance for 'Maybe' exists in the 206 | standard Haskell library. Therefore, you can already use 'fmap' on any 207 | Maybe value: 208 | 209 | >>> fmap not (Just True) 210 | Just False 211 | 212 | Let's examine this one better. Maybe is parameterised by a single type 213 | variable. This means that 'Maybe' can store "Int", "String", list, or 214 | even other 'Maybe' of something inside. Basically, anything can be put 215 | in there until it has the fittable kind. 216 | 217 | In the example above, we use the 'not' function to 'fmap' our 'Maybe 218 | Bool' value. But as 'not' doesn't change the type of Bool, then we get 219 | 'Maybe Bool' as the result of the 'fmap' in the example above. 220 | 221 | That is a nice example, but not that interesting. Let's look at this one: 222 | 223 | >>> fmap show (Just 42) 224 | Just "42" 225 | >>> fmap (replicate 3) (Just True) 226 | Just [True,True,True] 227 | 228 | Notice how the result type changes when we apply 'fmap' to one 229 | argument at a time. Haskell has type inference, so it can infer proper 230 | types and match them with actual types. 231 | 232 | However, if we don't have a value inside, nothing changes. Or does it? 233 | 234 | >>> fmap (replicate 3) Nothing 235 | Nothing 236 | 237 | Let's look at types closer: 238 | 239 | >>> :t fmap 240 | fmap :: Functor f => (a -> b) -> f a -> f b 241 | >>> :t fmap (replicate 3) 242 | fmap (replicate 3) :: Functor f => f a -> f [a] 243 | >>> :t Nothing 244 | Nothing :: Maybe a 245 | >>> :t fmap (replicate 3) Nothing 246 | fmap (replicate 3) Nothing :: Maybe [a] 247 | 248 | 249 | In GHCi we see 'Nothing', but it is not the same 'Nothing' that it's 250 | been before. Its type has changed. 251 | 252 | You can see how the 'Functor' instance for 'Maybe' is implemented. The 253 | implementation uses good old pattern matching. 254 | 255 | @ 256 | instance Functor Maybe where 257 | fmap :: (a -> b) -> Maybe a -> Maybe b 258 | fmap _ Nothing = Nothing 259 | fmap f (Just a) = Just (f a) 260 | @ 261 | 262 | And as you see the underlying type of 'Maybe' changes over the 'fmap'. 263 | That explains how that 'Nothing' changed its type in reality. 264 | 265 | Now you see that Functors are not magical despite having a cryptic 266 | name. 267 | 268 | > QUESTION: Can you understand why the following implementation of the 269 | Functor instance for Maybe doesn't compile? 270 | 271 | @ 272 | instance Functor Maybe where 273 | fmap :: (a -> b) -> Maybe a -> Maybe b 274 | fmap f (Just a) = Just (f a) 275 | fmap _ x = x 276 | @ 277 | -} 278 | 279 | {- | 280 | =⚔️= Task 2 281 | 282 | Implement 'Functor' instance for the "Secret" data type defined 283 | below. 'Secret' is either an unknown trap with something dangerous 284 | inside or a reward with some treasure. You never know what's inside 285 | until opened! But the 'Functor' instance allows changing the reward 286 | inside, so it is quite handy. 287 | -} 288 | data Secret e a 289 | = Trap e 290 | | Reward a 291 | deriving (Show, Eq) 292 | 293 | 294 | {- | 295 | Functor works with types that have kind `* -> *` but our 'Secret' has 296 | kind `* -> * -> *`. What should we do? Don't worry. We can partially 297 | apply 'Secret' to some type variable that will be fixed inside each 298 | method. Yes, similar to how we can partially apply functions. See, how 299 | we can reuse already known concepts (e.g. partial application) from 300 | values and apply them to the type level? 301 | -} 302 | instance Functor (Secret e) where 303 | fmap :: (a -> b) -> Secret e a -> Secret e b 304 | fmap _ (Trap e) = Trap e 305 | fmap f (Reward a) = Reward (f a) 306 | 307 | {- | 308 | =⚔️= Task 3 309 | 310 | Implement Functor instance for the "List" type defined below. This 311 | list type mimics the standard lists in Haskell. But for training 312 | purposes, let's practise our skills on implementing standard 313 | typeclasses for standard data types. 314 | -} 315 | data List a 316 | = Empty 317 | | Cons a (List a) 318 | 319 | instance Functor (List) where 320 | fmap :: (a -> b) -> List a -> List b 321 | fmap _ Empty = Empty 322 | fmap f (Cons x xs) = Cons (f x) (fmap f xs) 323 | 324 | {- | 325 | =🛡= Applicative 326 | 327 | The 'Applicative' typeclass logically continues the 'Functor' 328 | typeclass. Again, let's look at its definition first. 329 | 330 | @ 331 | class Functor f => Applicative f where 332 | pure :: a -> f a 333 | (<*>) :: f (a -> b) -> f a -> f b 334 | @ 335 | 336 | Wow, that's a lot going on again! Where did all these scary creatures 337 | come? But if we look closer, it all looks a bit familiar already. 338 | 339 | We now can spot straightaway, that similar to Functors, only types 340 | with kind `* -> *` can have an Applicative instance (e.g. Maybe, and 341 | not 'Int' or 'Char'), as we see the same 'f a' argument in the 342 | methods' types. 343 | 344 | But we also can notice the constraint in the class declaration. We 345 | know that the constraint is the restriction on data types. And 346 | actually, it works similarly in the typeclass declarations as 347 | well. Putting this all together, it means that first, any data type 348 | that wants to have an Applicative instance needs to implement the 349 | 'Functor' instance. This is what the part "Functor f => Applicative f" 350 | means. 351 | 352 | To be an Applicative, you first need to be "Functor". So, Functor is a 353 | Level 1 creature, and Applicative is a Level 2 creature. Upgraded 354 | Functor if you wish. 355 | 356 | Unlike Functor, Applicative has two methods: 'pure' and operator (<*>) 357 | called cyclops or starship or Sauron Eye. 'pure' puts a value into the 358 | context, and (<*>) extracts function and value from the context, 359 | applies the function to the argument, and puts the result back in the 360 | context. 361 | 362 | And continuing the chest 'Functor' analogy here, we can notice that a 363 | chest is also an 'Applicative'! We can put anything we want inside our 364 | chest, and this is what "pure" does. Or, let's say, we have one chest 365 | with a key inside, and this key opens a very secret box inside another 366 | chest. We can take both chests, extract their content, and apply one 367 | to another (i.e. open the box with the key), extract the content of 368 | the box and put it back to our chest. And this is the (<*>) 369 | operator. So there are valid implementations of both methods of the 370 | Applicative typeclass. 371 | 372 | In pursuance of the above explanation, you probably can see now why it 373 | is necessary to have 'Functor' first before becoming 'Applicative'. If 374 | we can't replace the content of the chest with some other content, how 375 | can we apply some actions to it and put something new inside? 376 | 377 | Now, back to Haskell. The function 'pure' takes a value and puts in 378 | the context 'f'. For 'Maybe' it looks like this: 379 | 380 | >>> pure True :: Maybe Bool 381 | Just True 382 | 383 | The (<*>) operator is much juicier: it takes a function inside the 384 | context, another value in the context, and returns a new value in the 385 | same context. Apparently, the function somehow is extracted from the 386 | context and applied to the extracted value. 387 | 388 | To keep an eye (<*> hehe) on the next example, let's first check that 389 | you still remember that in Haskell we have Higher-Order functions, 390 | right? This means that functions can not only be passed as arguments 391 | to data types but can also be stored inside other containers. Even 392 | 'Maybe' (e.g. "Maybe (Int -> Int)")! 393 | 394 | >>> Just not <*> Just True 395 | Just False 396 | >>> Just (+ 4) <*> Just 7 397 | Just 11 398 | >>> Just (replicate 3) <*> Just 0 399 | Just [0,0,0] 400 | 401 | Can you guess what will happen if we try to apply (<*>) on 'Nothing'? 402 | Exactly — 'Nothing'! Nothing will happen. 403 | 404 | >>> Just not <*> Nothing 405 | Nothing 406 | >>> Nothing <*> Just True 407 | Nothing 408 | 409 | And, finally, we are ready to see how the 'Applicative' instance for 410 | 'Maybe' really looks like: 411 | 412 | @ 413 | instance Applicative Maybe where 414 | pure :: a -> Maybe a 415 | pure = Just 416 | 417 | (<*>) :: Just (a -> b) -> Just a -> Just b 418 | Nothing <*> _ = Nothing 419 | Just f <*> x = fmap f x 420 | @ 421 | 422 | So, even if the Applicative looks menacingly, there is Nothing scary 423 | in it after all! 424 | 425 | The next part might be a bit difficult to comprehend without having a 426 | lot of practice with Applicatives, but it provides some deeper meaning 427 | behind the concept. 428 | 429 | If 'Functor' allows changing values inside the context, 'Applicative' 430 | enables chaining operations. You may reasonably wonder how often we 431 | have functions inside 'Maybe'. But remember, that in Haskell we have 432 | partial application. This means that we can apply binary functions 433 | partially to some values inside a context, and get a function inside 434 | the context that requires only one argument. 435 | 436 | Now, let's think, how can we implement a function that takes two 437 | optional integers, and returns an optional integer with the sum of the 438 | given integers inside? In other words, we want a function with the 439 | following type signature: 440 | 441 | @ 442 | addMaybes :: Maybe Integer -> Maybe Integer -> Maybe Integer 443 | @ 444 | 445 | At this point you probably can implement it using pattern matching, 446 | but can you do it using the 'Applicative' instance for 'Maybe'? It 447 | turns out, this is possible! 448 | 449 | @ 450 | addMaybes m1 m2 = fmap (+) m1 <*> m2 451 | @ 452 | 453 | Let's disenchant this magic spell step-by-step. 454 | 455 | ghci> :t +d (+) 456 | (+) :: Integer -> Integer -> Integer 457 | ghci> :t +d fmap (+) 458 | fmap (+) :: Functor f => f Integer -> f (Integer -> Integer) 459 | ghci> :t +d fmap (+) (Just 3) 460 | fmap (+) (Just 3) :: Maybe (Integer -> Integer) 461 | 462 | You see that by applying 'fmap' to (+) and some 'Maybe' we get a value 463 | of type "Maybe (Integer -> Integer)". And this means that we can use 464 | the '(<*>)' operator to combine it with another "Maybe Integer". 465 | 466 | The beautiful thing about 'Applicative' is that scales over functions 467 | of any number of arguments. The following code is valid, if the 468 | function 'f' takes `x` arguments. 469 | 470 | @ 471 | fmap f m1 <*> m2 <*> m3 <*> ... <*> m_x 472 | @ 473 | 474 | Applicatives can be found in many applications: 475 | 476 | ✦ Chaining operations over optional values 477 | ✦ Parsers 478 | ✦ Input form validation 479 | ✦ Concurrent and parallel execution of tasks 480 | -} 481 | 482 | {- | 483 | =⚔️= Task 4 484 | 485 | Implement the Applicative instance for our 'Secret' data type from before. 486 | -} 487 | instance Applicative (Secret e) where 488 | pure :: a -> Secret e a 489 | pure = Reward 490 | 491 | (<*>) :: Secret e (a -> b) -> Secret e a -> Secret e b 492 | (<*>) (Trap e) _ = Trap e 493 | (<*>) _ (Trap e) = Trap e 494 | (<*>) (Reward f) (Reward a) = Reward (f a) 495 | 496 | {- | 497 | =⚔️= Task 5 498 | 499 | Implement the 'Applicative' instance for our 'List' type. 500 | 501 | 🕯 HINT: in the applicative instance for lists, you have a list of 502 | functions and a list of arguments for those functions. You need to 503 | apply each function to each argument and combine all the results. You 504 | may also need to implement a few useful helper functions for our List 505 | type. 506 | -} 507 | 508 | instance Applicative (List) where 509 | pure :: a -> List a 510 | pure x = Cons x Empty 511 | 512 | (<*>) :: List (a -> b) -> List a -> List b 513 | (<*>) Empty _ = Empty 514 | (<*>) _ Empty = Empty 515 | (<*>) (Cons f fs) (Cons x xs) = Cons (f x) (fs <*> xs) 516 | 517 | {- | 518 | =🛡= Monad 519 | 520 | Now, the Monad Dragon. We've come that far not to give up. If we 521 | managed to fight 'Functor' and 'Applicative', then sure, we can beat 522 | 'Monad', right?💪 523 | 524 | As usual, we need to know with whom we are dealing, so let's inspect 525 | the 'Monad' typeclass definition: 526 | 527 | @ 528 | class Applicative f => Monad f where 529 | (>>=) :: f a -> (a -> f b) -> f b 530 | @ 531 | 532 | Look, it is just a simple typeclass! Also, it looks very familiar to 533 | 'Functor' and 'Applicative'. You can even start thinking that they are 534 | all here just to confuse developers by moving arrows and 'f' letters 535 | from one place to another. 536 | 537 | First of all, to become a 'Monad', a Level 3 creature, a type must be 538 | an 'Applicative' first. Only after that, it can even dare to implement 539 | the (>>=) operator called __bind__. 540 | 541 | *And this is again our reporter from the Chest Analogy show live.* 542 | Turns out, our chest is also a monad! If our chest contains some 543 | gold, we can take all our gold and buy a new chest using our 544 | gold. The remaining gold can be put to the new chest. The amount of 545 | money we have determines the quality of our new chest. And this is 546 | what the monad about — next context can depend on the value in the 547 | current context. 548 | 549 | And to expand a bit more on why you need to have the 'Applicative' 550 | instance before becoming a 'Monad': if you can't put values (stuff) 551 | inside a context (chest) using "pure", then there is no sense in 552 | buying a new chest at all. 553 | 554 | So, our chest is 'Functor', 'Applicative' and 'Monad'. Pure gold example! 555 | 556 | To describe the same in more technical words, The bind (>>=) operator 557 | takes a value of the type 'f a' ('a' in the 'f' context), a function 558 | from 'a' to 'f b' ('b' in the 'f' context), and returns a value of 559 | type 'f b'. So, to understand what it means, let's get back to our 560 | example with 'Maybe'. But first, we need to get somewhere a function 561 | that we would be able to use for the second argument of (>>=) in our 562 | examples. 563 | 564 | Let's implement a "safe" 'half' function, that divides a number by 2, 565 | but only if the number is even. 566 | 567 | -} 568 | half :: Int -> Maybe Int 569 | half n 570 | | even n = Just (div n 2) 571 | | otherwise = Nothing 572 | 573 | {- | 574 | 575 | Now, we can experiment with this function and the 'Monad' instance of 576 | 'Maybe' in GHCi: 577 | 578 | >>> Just 6 >>= half 579 | Just 3 580 | >>> Just 3 >>= half 581 | Nothing 582 | >>> Nothing >>= half 583 | Nothing 584 | 585 | That makes sense — the resulting context depends on the value in the 586 | initial context. 587 | 588 | Let's now see how it is implemented. The instance for 'Maybe' is 589 | rather straightforward: 590 | 591 | @ 592 | instance Monad Maybe where 593 | (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b 594 | Nothing >>= _ = Nothing 595 | Just x >>= f = f x 596 | @ 597 | 598 | Look, it is even simpler than the 'Applicative' instance! I feel 599 | deceived now. I heard legends about merciless Monads, scary stories 600 | when I was a kid, but in reality, there is Nothing special in it! 601 | Could I even name myself a Monad conqueror now? (Of course, you can, 602 | but after you try to implement the instances in the exercises) 603 | 604 | On the general note, you can notice some similarities between the main 605 | methods of all three typeclasses: 606 | 607 | @ 608 | fmap :: (a -> b) -> f a -> f b 609 | (<*>) :: f (a -> b) -> f a -> f b 610 | (=<<) :: (a -> f b) -> f a -> f b 611 | @ 612 | 613 | ♫ NOTE: The (=<<) operator is a version (>>=) with arguments 614 | flipped. It could be implemented via the regular bind operator. 615 | 616 | All type signatures look similar, but they represent different 617 | concepts in the end. 618 | -} 619 | 620 | {- | 621 | =⚔️= Task 6 622 | 623 | Implement the 'Monad' instance for our 'Secret' type. 624 | -} 625 | instance Monad (Secret e) where 626 | (>>=) :: Secret e a -> (a -> Secret e b) -> Secret e b 627 | Trap e >>= _ = Trap e 628 | Reward a >>= f = f a 629 | 630 | {- | 631 | =⚔️= Task 7 632 | 633 | Implement the 'Monad' instance for our lists. 634 | 635 | 🕯 HINT: You probably will need to implement a helper function (or 636 | maybe a few) to flatten lists of lists to a single list. 637 | -} 638 | 639 | (+|+) :: List a -> List a -> List a 640 | (+|+) Empty x = x 641 | (+|+) (Cons x xs) y = Cons x (xs +|+ y) 642 | 643 | instance Monad (List) where 644 | (>>=) :: List a -> (a -> List b) -> List b 645 | (>>=) Empty _ = Empty 646 | (>>=) (Cons x Empty) f = f x 647 | (>>=) (Cons x xs) f = (f x) +|+ (xs >>= f) 648 | 649 | {- | 650 | =💣= Task 8*: Before the Final Boss 651 | 652 | So far we've been talking only about instances and use cases of 653 | different typeclasses. But one more cool thing we haven't discussed 654 | yet is the ability to implement functions polymorphic over the 655 | context. Let's say that you have two boolean values inside some 656 | contexts, and you want to apply the logical AND operation on them. But 657 | you don't know the context! It can be 'Maybe', or 'Either' or 'List', 658 | or anything else! However, this is not a problem in Haskell. You still 659 | can implement a function with the type signature described below. 660 | 661 | Can you implement a monad version of AND, polymorphic over any monad? 662 | 663 | 🕯 HINT: Use "(>>=)", "pure" and anonymous function 664 | -} 665 | andM :: (Monad m) => m Bool -> m Bool -> m Bool 666 | andM a b = a >>= (\a' -> b >>= (\b' -> pure (a' && b'))) 667 | 668 | {- | 669 | =🐉= Task 9*: Final Dungeon Boss 670 | 671 | You did it! You made it to the end of Haskell practice! It probably 672 | was challenging, but you made it nevertheless! This makes us so happy 673 | and proud of you! 674 | 675 | Hope you enjoyed learning functional concepts and will continue it in 676 | the future! 677 | 678 | Let us know how did you like the journey and how we can make it even 679 | more remarkable for you! This will take you a few minutes, but is 680 | precious for us and future journey starters: 681 | 682 | * https://docs.google.com/forms/d/e/1FAIpQLScBVhLxq5CgGnAfIGUE-fCoOUqeGkDY2HXzbT7KV2jjLOsmjQ/viewform 683 | 684 | 685 | Also, challenge your friends with this course and spread the word about us! 686 | We are @kowainik on Twitter. You can also use #Learn4Haskell hashtag to share 687 | your story. 688 | 689 | We also have a Ko-fi page, if you want to buy us a coffee after a long road. 690 | ☕️ https://ko-fi.com/kowainik 691 | 692 | You can also support creators of this course and your proud mentors on GitHub: 693 | ✧ https://github.com/sponsors/vrom911 694 | ✧ https://github.com/sponsors/chshersh 695 | 696 | Now, for the desert, it is time to test your skills on the final boss! 697 | Your task now will be to implement a Binary Tree data structure and 698 | some functions on it. 699 | 700 | Specifically, 701 | 702 | ❃ Implement the polymorphic binary tree type that can store any 703 | elements inside its nodes 704 | ❃ Implement the Functor instance for Tree 705 | ❃ Implement the reverseTree function that reverses the tree and each 706 | subtree of a tree 707 | ❃ Implement the function to convert Tree to list 708 | -} 709 | 710 | data Tree a = End | Branch a (Tree a) (Tree a) 711 | 712 | instance Functor (Tree) where 713 | fmap :: (a -> b) -> Tree a -> Tree b 714 | fmap _ End = End 715 | fmap f (Branch v l r) = Branch (f v) (fmap f l) (fmap f r) 716 | 717 | reverseTree :: Tree a -> Tree a 718 | reverseTree End = End 719 | reverseTree (Branch v l r) = Branch v r l 720 | 721 | treeToList :: Tree a -> [a] 722 | treeToList End = [] 723 | treeToList (Branch v l r) = (treeToList l) ++ [v] ++ (treeToList r) 724 | 725 | {- 726 | You did it! Now it is time to open pull request with your changes 727 | and summon @vrom911 and @chshersh for the review! 728 | -} 729 | -------------------------------------------------------------------------------- /src/Chapter2.hs: -------------------------------------------------------------------------------- 1 | {- 👋 Welcome to Chapter Two, Traveller! 2 | 3 | If you haven't finished Chapter One yet, we encourage you to check it 4 | out, as the knowledge from the previous section is required in order 5 | to pass this training. 6 | 7 | If you already finished Chapter One — congratulations and huge 8 | respect! We are happy to see you with us at the next step. 9 | 10 | A casual reminder of how it works. 11 | 12 | == You are on __Chapter Two__. 13 | 14 | Here we will explore more interesting functional concepts. We are 15 | going to practice our skills on the list type in Haskell. 16 | 17 | In this chapter, you are going to master: 18 | 19 | ✧ The list data type 20 | ✧ Immutability 21 | ✧ Pattern-matching 22 | ✧ Recursion 23 | ✧ Parametric polymorphism 24 | ✧ Lazy evaluation 25 | ✧ Higher-order functions 26 | ✧ Partial function application 27 | ✧ Eta-reduction 28 | 29 | As usual, the explanations are in the Haskell comments of this 30 | module. We are leaving a number of tasks on our path. Your goal is to 31 | solve them all and make the tests for Chapter Two green. 32 | 33 | After finishing the PR, you can choose to summon us, @vrom911 and @chshersh, 34 | to look at your solution in order to give some advice on your 35 | code. This is optional; however, you can ask us for review only if you 36 | want some feedback on your solutions. 37 | 38 | Now, if you are ready, bring it on! 39 | -} 40 | 41 | module Chapter2 where 42 | 43 | {- 44 | =🛡= Imports 45 | 46 | Before we start diving into the FP concepts, let's cover an important 47 | process-development detail. 48 | 49 | Without any additional effort, in Haskell you have access to all 50 | functions defined in the special module called "Prelude". This module 51 | is always imported by default and contains all primitive data types 52 | and useful functions to work with them. However, sometimes you need to 53 | import other modules even from other libraries. 54 | 55 | The Haskell Standard library is called "base". It provides modules to 56 | work with different data types and values. If you want to bring some 57 | additional types and functions in scope, you need to import them 58 | manually. 59 | 60 | For example, to import the list sorting function, you need to write: 61 | 62 | @ 63 | import Data.List (sort) 64 | @ 65 | 66 | All imports should go at the beginning of the module: after the 67 | "module MODULE_NAME where" line and before the first function (or 68 | type) definition. 69 | 70 | ♫ NOTE: you can use Hoogle to explore modules and functions from other 71 | places as well. 72 | 73 | When working with lists, the most practical module will be "Data.List": 74 | 75 | * http://hackage.haskell.org/package/base-4.14.0.0/docs/Data-List.html 76 | -} 77 | 78 | 79 | {- | 80 | =🛡= Lists 81 | 82 | __List__ is a crucial data type in Haskell and functional programming 83 | in general. It represents a collection of elements of the _same type_. 84 | The list type is written as the type of list element in square 85 | brackets. For example, a list of integers will have type '[Int]' and a 86 | list of booleans — '[Bool]'. 87 | 88 | [Interesting fact]: String in Haskell is a list of characters ('Char' 89 | data type in Haskell) and is written as '[Char]'. But Haskell also 90 | provides the "String" alias to '[Char]'. So, in some places, you will 91 | see 'String', and in others, you will see '[Char]', but they mean the 92 | same thing. We will explain better how it works in Chapter Three. For 93 | now, you only need to know that you can use 'String' and '[Char]' 94 | interchangeably. 95 | 96 | To create a list, you need to put elements in square brackets (the 97 | same as used for the list type) and separate them by commas. Such 98 | expressions are called __list literals__. For example, the expression 99 | "[3, 5, 1]" creates a list of three numbers, where the first list 100 | element is number 3, and the last element is number 1. Similarly, you 101 | can create a list of two booleans: [False, True]. A list without 102 | elements is just []. 103 | 104 | You probably noticed that lists could be of any type of 105 | elements. Often you want to write a function that works with lists of 106 | any types (but consistent inside one list). This feature is called 107 | __parametric polymorphism__. It will be explained in more details 108 | later, but when working with lists, you often will see type signatures 109 | like: 110 | 111 | @ 112 | foo :: [a] -> [b] -> [a] 113 | @ 114 | 115 | The above type signature means that this function takes two lists: one 116 | with elements of some type "a" and another with elements of type "b" 117 | (the function doesn't care about the specific types) and returns the 118 | list with elements of the same type as the first list. Such words "a" 119 | and "b" are called __type variables__. 120 | 121 | For comparison, specific types in Haskell start with an uppercase 122 | letter (Int, Bool, Char, etc.), when type variables begin with a 123 | lowercase letter (a, b, el, etc.). This is the way to distinguish 124 | between these types. 125 | 126 | The Haskell standard library already provides a lot of functions to 127 | work with lists. And you will need to operate a lot with standard 128 | functions in the upcoming exercises. Remember, Hoogle is your friend! 129 | -} 130 | 131 | {- | 132 | =⚔️= Task 1 133 | 134 | Explore lists by checking types of various list expressions and 135 | functions in GHCi and insert the corresponding resulting output below: 136 | 137 | List of booleans: 138 | >>> :t [True, False] 139 | [True, False] :: [Bool] 140 | 141 | String is a list of characters: 142 | >>> :t "some string" 143 | "some string" :: [Char] 144 | 145 | Empty list: 146 | >>> :t [] 147 | [] :: [a] 148 | 149 | Append two lists: 150 | >>> :t (++) 151 | (++) :: [a] -> [a] -> [a] 152 | 153 | Prepend an element at the beginning of a list: 154 | >>> :t (:) 155 | (:) :: a -> [a] -> [a] 156 | 157 | Reverse a list: 158 | >>> :t reverse 159 | reverse :: [a] -> [a] 160 | 161 | Take first N elements of a list: 162 | >>> :t take 163 | take :: Int -> [a] -> [a] 164 | 165 | Create list from N same elements: 166 | >>> :t replicate 167 | replicate :: Int -> a -> [a] 168 | 169 | Split a string by line breaks: 170 | >>> :t lines 171 | lines :: String -> [String] 172 | 173 | Join a list of strings with line breaks: 174 | >>> :t unlines 175 | unlines :: [String] -> String 176 | 177 | -} 178 | 179 | {- | 180 | =⚔️= Task 2 181 | 182 | To understand the list type better, it is also beneficial to play with 183 | the list expressions in REPL. 184 | 185 | Evaluate the following expressions in GHCi and insert the answers. Try 186 | to guess first, what you will see. 187 | 188 | >>> [10, 2] ++ [3, 1, 5] 189 | [10,2,3,1,5] 190 | 191 | >>> [] ++ [1, 4] -- [] is an empty list 192 | [1,4] 193 | 194 | >>> 3 : [1, 2] 195 | [3,1,2] 196 | 197 | >>> 4 : 2 : [5, 10] -- prepend multiple elements 198 | [4,2,5,10] 199 | 200 | >>> [1 .. 10] -- list ranges 201 | [1,2,3,4,5,6,7,8,9,10] 202 | 203 | >>> [10 .. 1] 204 | [] 205 | 206 | >>> [10, 9 .. 1] -- backwards list with explicit step 207 | [10,9,8,7,6,5,4,3,2,1] 208 | 209 | >>> length [4, 10, 5] -- list length 210 | 3 211 | 212 | >>> replicate 5 True 213 | [True,True,True,True,True] 214 | 215 | >>> take 5 "Hello, World!" 216 | "Hello" 217 | 218 | >>> drop 5 "Hello, World!" 219 | ", World!" 220 | 221 | >>> zip "abc" [1, 2, 3] -- convert two lists to a single list of pairs 222 | [('a',1),('b',2),('c',3)] 223 | 224 | >>> words "Hello Haskell World!" -- split the string into the list of words 225 | ["Hello","Haskell","World!"] 226 | 227 | 👩‍🔬 Haskell has a lot of syntax sugar. In the case with lists, any 228 | list literal like "[3, 1, 2]" is syntax sugar for prepending elements 229 | at the empty list: "3 : 1 : 2 : []". 230 | 231 | Don't forget that lists are the containers of the same-type 232 | elements. Meaning, you can't combine lists of different types in any 233 | situation. Let's try appending a list of booleans and a string (list 234 | of characters) to see the error message: 235 | 236 | ghci> [True, False] ++ "string" 237 | :4:18: error: 238 | • Couldn't match type ‘Char’ with ‘Bool’ 239 | Expected type: [Bool] 240 | Actual type: [Char] 241 | • In the second argument of ‘(++)’, namely ‘"string"’ 242 | In the expression: [True, False] ++ "string" 243 | In an equation for ‘it’: it = [True, False] ++ "string" 244 | 245 | -} 246 | 247 | {- | 248 | =🛡= Immutability 249 | 250 | At this point in our training, you need to learn that all values in 251 | Haskell are immutable! Woohoo! But what does it mean for us? 252 | 253 | It means that when you apply a function to some variable, the value is 254 | not changed. Instead, you create a new value each time. 255 | 256 | >>> import Data.List (sort) -- sort is not in Prelude 257 | >>> x = [3, 1, 2] -- you can assign values to variables in GHCi 258 | >>> sort x 259 | [1,2,3] 260 | >>> x 261 | [3,1,2] 262 | 263 | The 'sort' function returns a new sorted list. It doesn't change the 264 | original list, so you don't need to worry about accidentally spoiling 265 | values of variables you defined before. 266 | -} 267 | 268 | {- | 269 | =🛡= List implementation 270 | 271 | Let's talk a bit about list implementation details. Lists in Haskell 272 | are implemented as __linked lists__ (or cons-lists). And because 273 | everything in Haskell is immutable, adding elements at the beginning 274 | of the lists is cheap. Haskell doesn't need to allocate new memory and 275 | copy the whole list there; it can just create a new list from a new 276 | element and a pointer to an already existing list. In other words, 277 | tails of lists are shared. 278 | 279 | For these reasons, adding elements to and extracting elements from the 280 | beginning of a list is much cheaper and faster than working with the 281 | end of the list. 282 | 283 | In some sense, lists are similar to trains. Let's look at illustration 284 | of a two-element list: 285 | 286 | . . . . . o o o o o 287 | _________ _________ ____ o 288 | | y | | x | |[]\_n][. 289 | _|________|_o_|________|_o_|__|____)< 290 | oo oo oo oo oo 00-oo\_ 291 | 292 | y : x : [] 293 | 294 | You can see that adding new elements (railway carriages) to the left 295 | is easy: you just need to connect them to the last element in the 296 | chain. 297 | 298 | . . . . . o o o o o 299 | _________ _________ _________ ____ o 300 | | z | | y | | x | |[]\_n][. 301 | _|________|_o_|________|_o_|________|_o_|__|____)< 302 | oo oo oo oo oo oo oo 00-oo\_ 303 | 304 | z : y : x : [] 305 | 306 | But imagine how much difficult it would be to add new carriages to the right? 307 | 308 | . . . . . o o o o o 309 | _________ _________ _________ ____ o 310 | | y | | x | | z | |[]\_n][. 311 | _|________|_o_|________|_o_|________|_o_|__|____)< 312 | oo oo oo oo oo oo oo 00-oo\_ 313 | 314 | y : x : z : [] 315 | 316 | You can't simply attach a new carriage anymore. You need to detach the 317 | locomotive, maybe move trains around the railway a bit for the proper 318 | position, and only then attach everything back again. The same thing 319 | with adding elements to the end of the list — it is a slow and costly 320 | process. 321 | 322 | -} 323 | 324 | {- | 325 | =⚔️= Task 3 326 | 327 | Let's write our first function to process lists in Haskell! Your first 328 | implementation task is to write a function that returns all elements 329 | of a list between two given positions inclusive (starting from zero). 330 | 331 | Remember that each function returns a new list. 332 | 333 | >>> subList 3 5 [1 .. 10] 334 | [4,5,6] 335 | >>> subList 3 0 [True, False, False, True, False] 336 | [] 337 | 338 | ♫ NOTE: When implementing, think about various corner cases. You 339 | should return an empty list when given numbers are negative. 340 | 341 | And also don't forget to check the 'Data.List' module. It is full of 342 | yummy functions. 343 | 344 | Don't forget that you can load this module in GHCi to call functions 345 | from it! 346 | 347 | ghci> :l src/Chapter2.hs 348 | -} 349 | subList :: Int -> Int -> [a] -> [a] 350 | subList start stop xs 351 | | start < 0 || stop < 0 = [] 352 | | otherwise = 353 | let amount = stop - start + 1 354 | headless = drop start xs 355 | in 356 | take amount headless 357 | 358 | 359 | {- | 360 | =⚔️= Task 4 361 | 362 | Implement a function that returns only the first half of a given list. 363 | 364 | >>> firstHalf [3, 4, 1, 2] 365 | [3,4] 366 | >>> firstHalf "bca" 367 | "b" 368 | -} 369 | -- PUT THE FUNCTION TYPE IN HERE 370 | firstHalf l = 371 | let len = length l 372 | to_take = len `div` 2 373 | in 374 | take to_take l 375 | 376 | 377 | {- | 378 | =🛡= Pattern matching 379 | 380 | One of the coolest and most powerful features of Functional 381 | Programming is __pattern matching__. This feature allows you to match 382 | on different values of a type and produce results based on 383 | patterns. The syntax of using pattern matching is similar to defining 384 | ordinary functions, but instead of using variable names, you use the 385 | values. 386 | 387 | For example, the "not" function that returns "the other" boolean is 388 | implemented like this: 389 | 390 | @ 391 | not :: Bool -> Bool 392 | not True = False 393 | not False = True 394 | @ 395 | 396 | To perform pattern-matching, repeat a function name as many times as 397 | many patterns you want to cover. The cool thing about Haskell is that 398 | the compiler warns you if you forget to cover some cases. So you 399 | always can be sure that your patterns are exhaustive! 400 | 401 | Note that you can pattern match on a variable too! Variable is like a 402 | pattern that matches any value and gives it a name. You can think of 403 | variables in function definitions as special cases of pattern 404 | matching. 405 | 406 | You can pattern match on numbers as well! For example, if you want to 407 | write a function that checks whether the given number is zero, you can 408 | write it in the following way: 409 | 410 | @ 411 | isZero :: Int -> Bool 412 | isZero 0 = True 413 | isZero n = False 414 | @ 415 | 416 | Instead of "isZero n = False" you can write "isZero _ = False". The 417 | symbol "_" (underscore) is called __hole__, and it is used when we 418 | don't care about the value of a variable. It is like a pattern that 419 | always matches (the same as a variable), but we don't use its value. 420 | 421 | 👩‍🔬 Unlike 'switch' and 'case' in other languages, that try to go 422 | through each switch and perform all actions in there until it reaches 423 | the breakpoint, pattern matching on function parameters in Haskell 424 | always returns only a single expression for a single branch. You can 425 | think of this process as trying to match all patterns from the first 426 | one to the last one and returning the expression on the right side 427 | of "=" only for the pattern that matches first. This is a helpful 428 | thing to keep in mind, especially when you have overlapping patterns. 429 | Also note that, if no pattern matches the value, the function fails 430 | in runtime. 431 | 432 | 433 | In addition to pattern matching in the function definition, you can 434 | also use the __case-of__ expression. With case-of, you specify the 435 | patterns to match and expressions to return depending on a pattern 436 | inside the function body. The main difference between 'case-of' and 437 | top-level pattern matching is the fact that 'case' uses arrows (->) 438 | instead of "=" for branch results. The "case" is often helpful when 439 | function names are long, or pattern-matching on functions is awkward. 440 | 441 | To understand case-of, let's look at a function that takes two numbers 442 | and a symbol, representing a math operation on these symbols. 443 | 444 | @ 445 | evalOperation :: Char -> Int -> Int -> Int 446 | evalOperation op x y = case op of 447 | '+' -> x + y 448 | '-' -> x - y 449 | '*' -> x * y 450 | '/' -> div x y 451 | _ -> 0 452 | @ 453 | 454 | ♫ NOTE: Each branch with a pattern should have the same alignment! 455 | Remember that Haskell is a _layout-sensitive_ language. Also, note 456 | that in the last line, "_" goes directly under the single quote to 457 | have the same indentation 🔍. You can try copying the function and 458 | change the indentation to see the parser error (which is not that 459 | clever to identify the indentation errors). 460 | 461 | Since we are talking about lists in this chapter, let's see how we can 462 | use pattern-matching on them! It turns out, pattern matching on lists 463 | is an effective and inevitable technique. 464 | 465 | We can pattern-match on list literals directly: 466 | 467 | @ 468 | isEmpty :: [a] -> Bool 469 | isEmpty [] = True 470 | isEmpty _ = False 471 | 472 | sumThree :: [Int] -> Int 473 | sumThree [x, y, z] = x + y + z 474 | sumThree _ = 0 475 | 476 | onlyTwoElements :: [a] -> Bool 477 | onlyTwoElements [_, _] = True 478 | onlyTwoElements _ = False 479 | @ 480 | 481 | Remember the ":" operator to add elements at the beginning of a list? 482 | Turns out, in case of lists this operator can be used for pattern 483 | matching as well! Isn't this cool? For example: 484 | 485 | @ 486 | -- return the first element of the list or default if the list is empty 487 | headOrDef :: a -> [a] -> a 488 | headOrDef def [] = def 489 | headOrDef _ (x:_) = x 490 | 491 | -- check if the first list element is zero 492 | firstIsZero :: [Int] -> Bool 493 | firstIsZero (0:_) = True 494 | firstIsZero _ = False 495 | 496 | -- check that a list has at least two elements 497 | atLeastTwo :: [a] -> Bool 498 | atLeastTwo (_ : _ : _) = True 499 | atLeastTwo _ = False 500 | @ 501 | 502 | When matching on the ":" pattern, the first element of the list goes 503 | to the left side of ':' and the tail of the list goes to the right 504 | side. You can have even nested patterns (as in the last example 505 | above). In other words, when writing a pattern like "(x:y:xs)", it is 506 | the same as writing "(x:(y:xs))". 507 | 508 | ♫ NOTE: Often, pattern matching can be replaced with conditional 509 | checks (if-then-else, guards) and vice versa. In some cases 510 | pattern-matching can be more efficient; in other cases, it can produce 511 | cleaner code or even more maintainable code due to pattern coverage 512 | checker from the Haskell compiler. 513 | -} 514 | 515 | {- | 516 | =⚔️= Task 5 517 | 518 | Implement a function that checks whether the third element of a list 519 | is the number 42. 520 | 521 | >>> isThird42 [1, 2, 42, 10] 522 | True 523 | >>> isThird42 [42, 42, 0, 42] 524 | False 525 | -} 526 | isThird42 :: [Int] -> Bool 527 | isThird42 (_ : _ : 42 : _) = True 528 | isThird42 _ = False 529 | 530 | 531 | {- | 532 | =🛡= Recursion 533 | 534 | Often, when writing in a functional style, you end up implementing the 535 | __recursive__ functions. Recursive functions are nothing more than 536 | calling the function itself from the body of the same function. 537 | 538 | Of course, you need some stopping conditions to exit the function 539 | eventually, and you need to think carefully whether your function ever 540 | reaches the stop condition. However, having pattern-matching in our 541 | arsenal of skills significantly increases our chances of writing 542 | correct functions. Nevertheless, you should think mindfully on how 543 | your recursive function behaves on different corner-cases. 544 | 545 | A simple recursive function can divide a number by 2 until it reaches 546 | zero: 547 | 548 | @ 549 | divToZero :: Int -> Int 550 | divToZero 0 = 0 551 | divToZero n = divToZero (div n 2) 552 | @ 553 | 554 | But as you can see, the function is not that helpful per se. Often you 555 | implement a helper function with some accumulator in order to collect 556 | some information during recursive calls. 557 | 558 | For example, we can patch the previous function to count the number of 559 | steps we need to take in order to reduce the number to zero. 560 | 561 | 🤔 Blitz question: can you guess what this number represents? 562 | 563 | @ 564 | divToZero :: Int -> Int 565 | divToZero n = go 0 n 566 | where 567 | go :: Int -> Int -> Int 568 | go acc 0 = acc 569 | go acc n = go (acc + 1) (div n 2) 570 | @ 571 | 572 | 👩‍🔬 The pattern of having a recursive helper function is called "Recursive go": 573 | 574 | * https://kowainik.github.io/posts/haskell-mini-patterns#recursive-go 575 | 576 | One of the most useful capabilities of pattern matching on lists is 577 | the ability to implement recursive functions with them as well! 578 | 579 | ♫ NOTE: The canonical naming scheme for such patterns is `(x:xs)` 580 | where x is the first element of the list, and xs — rest of the list 581 | (which is a list as well that even could be empty). 582 | 583 | @ 584 | -- list length 585 | len :: [a] -> Int 586 | len [] = 0 587 | len (_:xs) = 1 + len xs 588 | 589 | -- add 10 to every number of a list 590 | addEvery10 :: [Int] -> [Int] 591 | addEvery10 [] = [] 592 | addEvery10 (x:xs) = (x + 10) : addEvery10 xs 593 | @ 594 | 595 | When writing such functions, we usually handle two cases: empty list 596 | and non-empty list (list with at least one element in the beginning) 597 | and we decide what to do in each case. 598 | 599 | Most of the time, the case with a non-empty list uses the recursive 600 | call to the function itself. 601 | 602 | An example of a standard Haskell function is 'concat' that takes a 603 | list of lists and returns a flat list, appending all intermediate 604 | lists: 605 | 606 | @ 607 | concat :: [[a]] -> [a] 608 | concat [] = [] 609 | concat (x:xs) = x ++ concat xs 610 | @ 611 | 612 | And it works like this: 613 | 614 | >>> concat [[2, 1, 3], [1, 2, 3, 4], [0, 5]] 615 | [2,1,3,1,2,3,4,0,5] 616 | -} 617 | 618 | 619 | {- | 620 | =⚔️= Task 6 621 | 622 | Implement a function that duplicates each element of the list 623 | 624 | 🕯 HINT: Use recursion and pattern matching on the list. 625 | 626 | >>> duplicate [3, 1, 2] 627 | [3,3,1,1,2,2] 628 | >>> duplicate "abac" 629 | "aabbaacc" 630 | 631 | -} 632 | duplicate :: [a] -> [a] 633 | duplicate [] = [] 634 | duplicate (x : xs) = x : x : duplicate xs 635 | 636 | 637 | {- | 638 | =⚔️= Task 7 639 | Write a function that takes elements of a list only on even positions. 640 | 641 | 🕯 HINT: You need to write a recursive function that pattern matches 642 | on the list structure. Your function will have several cases and 643 | probably needs to use nested pattern matching on lists of size at 644 | least 2. Alternatively, you can use the "Recursive go" pattern. 645 | 646 | >>> takeEven [2, 1, 3, 5, 4] 647 | [2,3,4] 648 | -} 649 | takeEven (x : _ : xs) = x : takeEven xs 650 | takeEven [x] = [x] 651 | takeEven [] = [] 652 | 653 | {- | 654 | =🛡= Higher-order functions 655 | 656 | Some functions can take other functions as arguments. Such functions 657 | are called __higher-order functions__ (HOFs). Check the types of some 658 | common HOF list functions: 659 | 660 | >>> :t filter 661 | filter :: (a -> Bool) -> [a] -> [a] 662 | >>> :t map 663 | map :: (a -> b) -> [a] -> [b] 664 | 665 | And few usage examples of those functions: 666 | 667 | >>> filter even [1..10] -- keep only even elements in the list 668 | [2,4,6,8,10] 669 | >>> map not [True, False, True] -- maps the 'not' function over each element of the given list 670 | [False,True,False] 671 | 672 | Having HOFs in your language means that functions can be treated in 673 | the same way as any other values and expressions: 674 | 675 | ✲ You can pass functions as arguments 676 | ✲ You can return functions as results 677 | ✲ You can compose functions easily to create new functions 678 | ✲ You can have lists of functions 679 | ✲ And much more! 680 | 681 | The ability to create __lambdas__ (or anonymous functions) nicely 682 | complements the concept of HOF. For example, we can easily add 683 | number 3 to each element of the list by introducing a lambda function: 684 | 685 | >>> map (\x -> x + 3) [0..5] 686 | [3,4,5,6,7,8] 687 | 688 | The syntax of the lambda functions is somewhat similar to normal ones, 689 | except for you don't need to think about its name, which is 690 | awesome. To establish the start of the lambda function, you should 691 | write "\" which is a bit similar to the lambda symbol — λ. Then you 692 | specify space-separated arguments. Instead of the "=" in the ordinary 693 | function body, you should write "->" and then you can use these 694 | arguments and all variables in scope inside the lambda-body. 695 | 696 | These are equal: 697 | 698 | @ 699 | foo a b = a + b 700 | --and 701 | \a b -> a + b 702 | @ 703 | 704 | What's even cooler is the ability to __apply functions partially__ 705 | This means that you can provide only some arguments to a function and 706 | treat the result as a function itself! You already know the 'div' 707 | function: it takes two numbers and returns the result of the integral 708 | division of those numbers. But if we apply 'div' to a number 10 709 | partially, we will get a new function that takes only one number and 710 | returns the result of dividing 10 by that number. You can check the 711 | difference by inspecting the types of corresponding expressions in 712 | GHCi: 713 | 714 | >>> :t +d div 715 | div :: Integer -> Integer -> Integer 716 | >>> :t +d div 10 717 | div 10 :: Integer -> Integer 718 | 719 | 720 | This fact can be used to pass partial applications of some functions 721 | directly to other functions. 722 | 723 | >>> map (div 10) [1 .. 10] 724 | [10,5,3,2,2,1,1,1,1,1] 725 | 726 | You can apply operators partially too! 727 | 728 | >>> filter (< 3) [2, 1, 3, 4, 0, 5] 729 | [2,1,0] 730 | >>> map (* 2) [1..5] 731 | [2,4,6,8,10] 732 | 733 | The implementation of the "map" function is pretty straightforward if 734 | you are already familiar with function application, recursion and 735 | pattern matching. 736 | 737 | @ 738 | map :: (a -> b) -> [a] -> [b] 739 | map _ [] = [] 740 | map f (x:xs) = f x : map f xs 741 | @ 742 | 743 | Now you can see that there is nothing magic in HOFs in the end! 744 | -} 745 | 746 | {- | 747 | =⚔️= Task 8 748 | 749 | Implement a function that repeats each element so many times as the 750 | value of the element itself 751 | 752 | >>> smartReplicate [3, 1, 2] 753 | [3,3,3,1,2,2] 754 | 755 | 🕯 HINT: Use combination of 'map' and 'replicate' 756 | -} 757 | smartReplicate :: [Int] -> [Int] 758 | smartReplicate = concatMap (\x -> replicate x x) 759 | 760 | {- | 761 | =⚔️= Task 9 762 | 763 | Implement a function that takes a number, a list of lists and returns 764 | the list with only those lists that contain a passed element. 765 | 766 | >>> contains 3 [[1, 2, 3, 4, 5], [2, 0], [3, 4]] 767 | [[1,2,3,4,5],[3,4]] 768 | 769 | 🕯 HINT: Use the 'elem' function to check whether an element belongs to a list 770 | -} 771 | contains e = filter (elem e) 772 | 773 | 774 | {- | 775 | =🛡= Eta-reduction 776 | 777 | Another consequence of the HOFs and partial application is 778 | __eta-reduction__. This term is used to call the simplification of 779 | functions over their arguments. Specifically, if we have `foo x = bar 780 | 10 x`, this precisely means that `foo` is a partially applied `bar 781 | 10`. And we can write it like `foo = bar 10`. 782 | 783 | This concept can be used to write functions as well. 784 | 785 | For example, 786 | 787 | @ 788 | nextInt :: Int -> Int 789 | nextInt n = add 1 n 790 | @ 791 | 792 | Could be written with the eta-reduced form: 793 | 794 | @ 795 | nextInt :: Int -> Int 796 | nextInt = add 1 797 | @ 798 | 799 | ♫ NOTE: See that the initial type of the function is not changed and 800 | it works absolutely the same. We just can skip the last argument and 801 | amend its usage in the function body. 802 | -} 803 | 804 | {- | 805 | =⚔️= Task 10 806 | 807 | Let's now try to eta-reduce some of the functions and ensure that we 808 | mastered the skill of eta-reducing. 809 | -} 810 | divideTenBy :: Int -> Int 811 | divideTenBy = div 10 812 | 813 | -- TODO: type ;) 814 | listElementsLessThan x = filter (< x) 815 | 816 | -- Can you eta-reduce this one??? 817 | pairMul = zipWith (*) 818 | 819 | {- | 820 | =🛡= Lazy evaluation 821 | 822 | Another unique Haskell feature is __lazy evaluation__. Haskell is lazy 823 | by default, which means that it doesn't evaluate expressions when not 824 | needed. The lazy evaluation has many benefits: avoid doing redundant 825 | work, provide more composable interfaces. And in this section, we will 826 | focus on Haskell ability to create infinite data structures and work 827 | with them! 828 | 829 | For instance, the Haskell standard library has the 'repeat' function 830 | that returns an infinite list created from a given element. Of course, 831 | we can't print an infinite list to our terminal; it will take an 832 | infinite amount of time! But we can work with parts of it due to lazy 833 | evaluation: 834 | 835 | >>> take 5 (repeat 0) 836 | [0,0,0,0,0] 837 | 838 | Another useful construction is an infinite list of all numbers! 839 | 840 | >>> take 4 [0 ..] 841 | [0,1,2,3] 842 | 843 | Isn't this awesome?! Now we can unleash the real power of the 844 | Infinity Stone! 845 | 846 | ♫ NOTE: Infinite lists bring great power, but with great power comes 847 | great responsibility. Functions like 'length' hang when called on 848 | infinite lists. So make sure you think about such corner cases in the 849 | implementations of your functions if you expect them to work on the 850 | infinite lists. 851 | -} 852 | 853 | {- | 854 | =⚔️= Task 11 855 | 856 | Rotating a list by a single element is the process of moving the first 857 | element of the list to the end. 858 | 859 | Implement a function to rotate a given finite list by N elements. Try 860 | to do it more efficiently than rotating by a single element N times. 861 | 862 | On invalid input (negative rotation coefficient) it should return an empty 863 | list. 864 | 865 | >>> rotate 1 [1,2,3,4] 866 | [2,3,4,1] 867 | >>> rotate 3 [1,2,3,4] 868 | [4,1,2,3] 869 | 870 | 🕯 HINT: Use the 'cycle' function 871 | -} 872 | rotate n xs 873 | | n < 0 = [] 874 | | otherwise = take l $ drop n $ cycle xs 875 | where l = length xs 876 | 877 | {- | 878 | =💣= Task 12* 879 | 880 | Now you should be ready for the final boss at the end of this chapter! 881 | To defeat the boss, implement the reversing function that takes a list 882 | and reverses it. 883 | 884 | >>> rewind [1 .. 5] 885 | [5,4,3,2,1] 886 | 887 | ♫ NOTE: The Haskell standard library already provides the "reverse" 888 | function, but in this task, you need to implement it manually. No 889 | cheating! 890 | -} 891 | rewind [] = [] 892 | rewind xs = go xs [] 893 | where 894 | go :: [a] -> [a] -> [a] 895 | go [] xs = xs 896 | go (x:xs) ys = go xs (x:ys) 897 | 898 | 899 | {- 900 | You did it! Now it is time to open pull request with your changes 901 | and summon @vrom911 and @chshersh for the review! 902 | -} 903 | -------------------------------------------------------------------------------- /src/Chapter3.hs: -------------------------------------------------------------------------------- 1 | {- 👋 Welcome to Chapter Three of our journey, Courageous Knight! 2 | 3 | Glad to see you back for more challenges. You fight great for the glory of the 4 | Functional Programming in the previous Chapters. We are grateful that you are 5 | continuing to walk this road with us. 6 | 7 | This Chapter requires the knowledge from the previous modules, so it is highly 8 | encouraged to at least look through the material of those chapters. 9 | 10 | Let's refresh how the training works. 11 | 12 | == You are on __Chapter Three__. 13 | 14 | At this step, we are going to learn more about types in Haskell and explore 15 | typeclasses. You will need to create a lot of types, so we rely on your 16 | creativity, as you will be given the opportunity to create new worlds out of 17 | Haskell code. 18 | 19 | Specifically, in this chapter, you are going to practice: 20 | 21 | ✧ Types in Haskell 22 | ✧ ADTs: Algebraic Data Types 23 | ✧ Type aliases vs Data types vs Newtypes 24 | ✧ Parametric polymorphism 25 | ✧ Typeclasses 26 | ✧ Ad-hoc polymorphism 27 | 28 | As usual, the explanations are in the Haskell comments of this module. We are 29 | leaving a number of tasks on our path. Your goal is to solve them all. 30 | 31 | After finishing the PR, you can choose to summon us, @vrom911 and @chshersh, to 32 | look at your solution in order to give some advice on your code. This is 33 | optional; however, you can ask us for review only if you want some feedback on 34 | your solutions. 35 | 36 | Okay. Ready? Set. Go! 37 | -} 38 | 39 | {- 40 | =⚗️= Language extensions* 41 | 42 | Some Haskell features are not enabled by default. They can be enabled by turning 43 | on corresponding extensions (the language feature) using the LANGUAGE pragma at 44 | the top of the file before the "module" declaration. The fact that they are not 45 | enabled by default doesn't mean they are experimental. This is just the way 46 | Haskell works. 47 | 48 | In this module, we enable the "InstanceSigs" feature that allows writing type 49 | signatures in places where you can't by default. We believe it's helpful to 50 | provide more top-level type signatures, especially when learning Haskell. 51 | -} 52 | {-# LANGUAGE InstanceSigs #-} 53 | 54 | module Chapter3 where 55 | 56 | import Data.Maybe (isJust) 57 | 58 | {- 59 | =🛡= Types in Haskell 60 | 61 | Let's talk about types. 62 | In the previous chapters, we have already worked a lot with them. And we bet, 63 | you can name a few at this point. 64 | 65 | But we mostly were operating on primitive types, like 'Int' or 'Bool'. We saw 66 | the function (->) a lot! We briefly touched tuples, but we learned that lists 67 | are far more complicated! 68 | 69 | Haskell has several different ways to create entirely new data types. Let's talk 70 | about them all and master our skill of data types construction. 71 | -} 72 | 73 | {- | 74 | =🛡= Type aliases 75 | 76 | The simplest way to introduce a new type in Haskell is __type aliases__. Type 77 | aliases are nothing more than just other names to already existing types. 78 | A few examples: 79 | 80 | @ 81 | type IntList = [Int] 82 | type IntPair = (Int, Int) 83 | @ 84 | 85 | Type aliases are just syntactic sugar and would be replaced by the real types 86 | during compilation, so they don't bring too much to the table of data types. 87 | However, they may make the life of a developer easier in some places. 88 | 89 | One of the most common type aliases is 'String' that we already mentioned in the 90 | List section of the previous chapter. And it is defined in the following way: 91 | 92 | @ 93 | type String = [Char] 94 | @ 95 | 96 | Now it makes much more sense of why 'String' is related to lists. You can see 97 | that any list function could be used on 'String's as well. 98 | 99 | 👩‍🔬 Due to the implementation details of lists, such representation of String 100 | is highly inefficient. It is unfortunate that the list of characters is the 101 | default String type. Experienced Haskellers use more efficient string types 102 | 'Text' and 'ByteString' from Haskell libraries: 'text' and 'bytestring' 103 | correspondingly. But, for the simplicity of this training, we are using 104 | 'String'. 105 | 106 | Another common type alias in the standard library is "FilePath", which is the 107 | same as 'String' (which is the same as '[Char]'): 108 | 109 | @ 110 | type FilePath = String 111 | @ 112 | 113 | Usually, you are encouraged to introduce new data types instead of using aliases 114 | for existing types. But it is still good to know about type aliases. They have a 115 | few use-cases, and you can meet them in various Haskell libraries as well. 116 | -} 117 | 118 | {- | 119 | =🛡= ADT 120 | 121 | Let's not limit ourselves with just type aliases and define some real new data 122 | structures. Are we the creators of new worlds or what? 123 | 124 | Type in Haskell is like a box of information description that the object of that 125 | type should contain. 126 | 127 | Haskell uses Algebraic Data Types (ADT) system of types. That means that there 128 | are two types of types: product and sum types. 129 | 130 | To give you some basic understanding of the difference between these two, let's 131 | go to the book shop. A book in there represents a product type: each book has 132 | the name, author, cover, pages, etc. And all of these properties are mandatory 133 | and come with the book. Bookshelf, in its turn, is the sum type. Each book in a 134 | shelf is a different type, and you can choose one of them at once (there is no 135 | such book where two or more physical books are sewed together). 136 | 137 | We will show you an example now, just to illustrate all the above and then will 138 | explain each concept separately. Note, this is not real syntax. 139 | 140 | @ 141 | -- Product type 142 | Book: 143 | book name 144 | AND book author 145 | AND book cover 146 | AND book pages 147 | 148 | 149 | -- Sum type 150 | BookShelf: 151 | Good book 1 : {Book} 152 | OR Good book 2 : {Book} 153 | OR Cheap book 3 : {Book} 154 | @ 155 | 156 | 👩‍🔬 We use AND in product types to represent the notion of having all fields at 157 | the same time. In contrast, for the sum types, we use OR to tell only about a 158 | single possibility. AND in logic corresponds to multiplication in math, and OR 159 | corresponds to addition. You see that there is some math theory behind the 160 | concept of data types in Haskell, and that's why they are called Algebraic Data 161 | Types. 162 | -} 163 | 164 | {- | 165 | =🛡= Product type 166 | 167 | Let's now see how the product data types look like in Haskell. 168 | 169 | Product type should have a type name, one type constructor (the function that 170 | lets you create the value of the type later) and the description of the fields 171 | it consists of in the view of types. 172 | 173 | When defining a custom type in Haskell, you use the __"data"__ keyword, write 174 | the __type name__ you come up with after it, and then after the "=" sign you 175 | specify the __constructor name__ followed by the __fields__. 176 | 177 | When in action, a custom data type could be used in the following use case. 178 | A definition of a data type for a knight with a name and number of victories 179 | can look like this: 180 | 181 | @ 182 | ┌─ type name 183 | │ 184 | │ ┌─ constructor name (or constructor tag) 185 | │ │ 186 | data Knight = MkKnight String Int 187 | │ │ │ 188 | │ └────┴─── types of fields 189 | │ 190 | └ "data" keyword 191 | @ 192 | 193 | ♫ NOTE: The constructor can have the same name as the type itself. And in most 194 | cases, they are indeed named identically. This is not a problem for Haskell, 195 | because types live in the types namespace, and constructors live in the value 196 | namespace. So there won't be any collisions and misunderstandings from the 197 | compiler side. The names are unambiguous. 198 | 199 | You can use the constructor name, to create a value of the "Knight" type. 200 | 201 | @ 202 | arthur :: Knight 203 | arthur = MkKnight "Arthur" 100 204 | @ 205 | 206 | A constructor is just a function from fields to the type! You can verify this in GHCi: 207 | 208 | ghci> :t MkKnight 209 | MkNight :: String -> Int -> Knight 210 | 211 | As in a regular function, you need to provide a 'String' and an 'Int' to the 212 | 'MkNight' constructor in order to get the full-fledged 'Knight'. 213 | 214 | Also, you can write a function that takes a Knight and returns its name. 215 | It is convenient to use pattern matching for that: 216 | 217 | @ 218 | knightName :: Knight -> String 219 | knightName (MkKnight name _) = name 220 | @ 221 | 222 | And you can extract the name in GHCi: 223 | 224 | ghci> knightName arthur 225 | "Arthur" 226 | 227 | It is comfy to have such getters for all types, so Haskell provides a syntax for 228 | defining __records__ — named parameters for the product data type fields. 229 | 230 | Records have similar syntax for defining in Haskell as (unnamed) ordinary 231 | product types, but fields are specified in the {} separated by a comma. Each 232 | field should have a name and a type in this form: 'fieldName :: FieldType'. 233 | 234 | The same definition of 'Knight' but with records should be written in the 235 | following way: 236 | 237 | @ 238 | data Knight = MkKnight 239 | { knightName :: String 240 | , knightVictories :: Int 241 | } 242 | @ 243 | 244 | The above type definition is equivalent to the one we had before. We just gave 245 | names to our fields for clarity. In addition, we also automatically get 246 | functions "knightName :: Knight -> String" and "knightVictories :: Knight -> 247 | Int". This is what records bring us — free getters! 248 | 249 | The pattern matching on constructors of such records can stay the same. Besides, 250 | you can use field names as getters. 251 | 252 | 👩‍🔬 We are using a particular naming scheme of record field names in record 253 | types in order to avoid name collisions. We add the data type name prefix in 254 | front of each usual field name for that. As we saw, records create getters for 255 | us, which are actually top-level functions. In Haskell, all functions defined at 256 | top-level are available in the whole scope within a module. But Haskell forbids 257 | creating multiple functions with the same name. The Haskell ecosystem has 258 | numerous ways of solving this so-called "record" problem. Still, for simplicity 259 | reasons, we are going to use disambiguation by a prefix which is one of the 260 | standard resolutions for the scope problem. 261 | 262 | In addition to getting top-level getters automatically, you get the following 263 | features for free when using records over unnamed type fields: 264 | 265 | 1. Specify names during constructions — additional visual help for code. 266 | 2. Record-update syntax. 267 | 268 | By default, all functions and constructors work with positional arguments 269 | (unnamed, that are identified by its position in the type declaration). You 270 | write a function or a constructor name first, and then you pass arguments 271 | separated by space. But once we declare a product type as a record, we can use 272 | field names for specifying constructor values. That means that the position 273 | doesn't matter anymore as long as we specify the names. So it is like using 274 | named arguments but only for constructors with records. 275 | This is an alternative way of defining values of custom records. 276 | 277 | Let's introduce Sir Arthur properly! 278 | 279 | @ 280 | arthur :: Knight 281 | arthur = MkKnight 282 | { knightName = "Arthur" 283 | , knightVictories = 100 284 | } 285 | @ 286 | 287 | After we created our custom types and defined some values, we may want to change 288 | some fields of our values. But we can't actually change anything! Remember that 289 | all values in Haskell are immutable, and you can't just change a field of some 290 | type. You need to create a new value! Fortunately, for records, we can use the 291 | __record update syntax__. Record update syntax allows creating new objects of a 292 | record type by assigning new values to the fields of some existing record. 293 | 294 | The syntax for record update is the following: 295 | 296 | @ 297 | lancelot :: Knight 298 | lancelot = arthur { knightName = "Lancelot" } 299 | @ 300 | 301 | Without records, we had to write a custom setter function for each field of each 302 | data type. 303 | 304 | @ 305 | setKnightName :: String -> Knight -> Knight 306 | setKnightName newName (MkKnight _ victories) = 307 | MkKnight newName victories 308 | @ 309 | 310 | ♫ NOTE: By default, GHCi doesn't know how to display values of custom types. If 311 | you want to explore custom data types in REPL, you need to add a magical 312 | "deriving (Show)" line (will be explained later in this chapter) at the end of a 313 | record. Like so: 314 | 315 | @ 316 | data Knight = MkKnight 317 | { knightName :: String 318 | , knightVictories :: Int 319 | } deriving (Show) 320 | @ 321 | 322 | Now GHCi should be able to show the values of your types! Try playing with our 323 | knights in GHCi to get the idea behind records. 324 | 325 | 🕯 HINT: At this point, you may want to be able to enter multi-line strings in 326 | GHCi. Start multi-line blocks by typing the ":{" command, and close such blocks 327 | using the ":}" command. 328 | 329 | ghci> :{ 330 | ghci| data Knight = MkKnight 331 | ghci| { knightName :: String 332 | ghci| , knightVictories :: Int 333 | ghci| } deriving (Show) 334 | ghci| :} 335 | ghci> 336 | 337 | Although, it may be easier to define data types in the module, and load it 338 | afterwards. 339 | -} 340 | 341 | {- | 342 | =⚔️= Task 1 343 | 344 | Define the Book product data type. You can take inspiration from our description 345 | of a book, but you are not limited only by the book properties we described. 346 | Create your own book type of your dreams! 347 | -} 348 | data Book = MkBook 349 | { bookTitle :: String 350 | , bookAuthor :: String 351 | , bookPages :: Int 352 | } deriving (Show) 353 | 354 | {- | 355 | =⚔️= Task 2 356 | 357 | Prepare to defend the honour of our kingdom! A monster attacks our brave knight. 358 | Help him to fight this creature! 359 | 360 | Define data types for Knights and Monsters, and write the "fight" function. 361 | 362 | Both a knight and a monster have the following properties: 363 | 364 | ✦ Health (the number of health points) 365 | ✦ Attack (the number of attack units) 366 | ✦ Gold (the number of coins) 367 | 368 | When a monster fights a knight, the knight hits first, and the monster hits back 369 | only if it survives (health is bigger than zero). A hit decreases the amount of 370 | health by the number represented in the "attack" field. 371 | 372 | Implement the "fight" function, that takes a monster and a knight, performs the 373 | fight following the above rules and returns the amount of gold the knight has 374 | after the fight. The battle has the following possible outcomes: 375 | 376 | ⊛ Knight wins and takes the loot from the monster and adds it to their own 377 | earned treasured 378 | ⊛ Monster defeats the knight. In that case return -1 379 | ⊛ Neither the knight nor the monster wins. On such an occasion, the knight 380 | doesn't earn any money and keeps what they had before. 381 | 382 | -} 383 | 384 | data Knight = MkKnight 385 | { knightHealth :: Int 386 | , knightAttack :: Int 387 | , knightGold :: Int 388 | } deriving (Show) 389 | 390 | data Monster = MkMonster 391 | { monsterHealth :: Int 392 | , monsterAttack :: Int 393 | , monsterGold :: Int 394 | } deriving (Show) 395 | 396 | fight :: Monster -> Knight -> Int 397 | fight monster knight = 398 | let monsterHealth' = monsterHealth monster - knightAttack knight 399 | knightHealth' = knightHealth knight - monsterAttack monster 400 | in 401 | if monsterHealth' > 0 then 402 | if knightHealth' < 0 then -1 else knightGold knight 403 | else 404 | knightGold knight + monsterGold monster 405 | 406 | {- | 407 | =🛡= Sum types 408 | 409 | Another powerful ambassador of ADT is __sum type__. Unlike ordinary records 410 | (product types) that always have all the fields you wrote, sum types represent 411 | alternatives of choices. Sum types can be seen as "one-of" data structures. They 412 | contain many product types (described in the previous section) as alternatives. 413 | 414 | To define a sum type, you have to specify all possible constructors separated 415 | by "|". Each constructor on its own could have an ADT, that describes 416 | this branch of the alternative. 417 | 418 | There is at least one famous sum type that you have already seen — 'Bool' — the 419 | simplest example of a sum type. 420 | 421 | @ 422 | data Bool = False | True 423 | @ 424 | 425 | 'Bool' is a representer of so-called __enumeration__ — a special case of sum 426 | types, a sum of nullary constructors (constructors without fields). 427 | Sum types can have much more than two constructors (but don't abuse this)! 428 | 429 | Look at this one. We need more than two constructors to mirror the "Magic Type". 430 | And none of the magic streams needs any fields. Just pure magic ;) 431 | 432 | @ 433 | data MagicType 434 | = DarkMagic 435 | | LightMagic 436 | | NeutralMagic 437 | @ 438 | 439 | However, the real power of sum types unleashes when you combine them with 440 | fields. As we mentioned, each "|" case in the sum type could be an ADT, so, 441 | naturally, you can have constructors with fields, which are product types from 442 | the previous section. If you think about it, the enumeration also contains a 443 | product type, as it is absolutely legal to create a data type with one 444 | constructor and without any fields: `data Emptiness = TotalVoid`. 445 | 446 | To showcase such sum type, let's represent a possible loot from successfully 447 | completing an adventure: 448 | 449 | @ 450 | data Loot 451 | = Sword Int -- attack 452 | | Shield Int -- defence 453 | | WizardStaff Power SpellLevel 454 | @ 455 | 456 | You can create values of the sum types by using different constructors: 457 | 458 | @ 459 | woodenSword :: Loot 460 | woodenSword = Sword 2 461 | 462 | adamantiumShield :: Loot 463 | adamantiumShield = Shield 3000 464 | @ 465 | 466 | And you can pattern match on different constructors as well. 467 | 468 | @ 469 | acceptLoot :: Loot -> String 470 | acceptLoot loot = case loot of 471 | Sword _ -> "Thanks! That's a great sword!" 472 | Shield _ -> "I'll accept this shield as a reward!" 473 | WizardStaff _ _ -> "What?! I'm not a wizard, take it back!" 474 | @ 475 | 476 | 477 | To sum up all the above, a data type in Haskell can have zero or more 478 | constructors, and each constructor can have zero or more fields. This altogether 479 | gives us product types (records with fields) and sum types (alternatives). The 480 | concept of product types and sum types is called __Algebraic Data Type__. They 481 | allow you to model your domain precisely, make illegal states unrepresentable 482 | and provide more flexibility when working with data types. 483 | -} 484 | 485 | {- | 486 | =⚔️= Task 3 487 | 488 | Create a simple enumeration for the meal types (e.g. breakfast). The one who 489 | comes up with the most number of names wins the challenge. Use your creativity! 490 | -} 491 | 492 | data Meal = Breakfast | Lunch | Dinner | Snack | Fika 493 | 494 | {- | 495 | =⚔️= Task 4 496 | 497 | Define types to represent a magical city in the world! A typical city has: 498 | 499 | ⍟ Optional castle with a __name__ (as 'String') 500 | ⍟ Wall, but only if the city has a castle 501 | ⍟ Church or library but not both 502 | ⍟ Any number of houses. Each house has one, two, three or four __people__ inside. 503 | 504 | After defining the city, implement the following functions: 505 | 506 | ✦ buildCastle — build a castle in the city. If the city already has a castle, 507 | the old castle is destroyed, and the new castle with the __new name__ is built 508 | ✦ buildHouse — add a new living house 509 | ✦ buildWalls — build walls in the city. But since building walls is a 510 | complicated task, walls can be built only if the city has a castle 511 | and at least 10 living __people__ inside in all houses of the city totally. 512 | -} 513 | 514 | data Castle = MkCastle String deriving (Show) 515 | data Facility = Church | Library deriving (Show) 516 | data Inhabitants = One | Two | Three | Four deriving (Show) 517 | newtype House = MkHouse Inhabitants deriving (Show) 518 | 519 | inhabitantsPerHouse :: House -> Int 520 | inhabitantsPerHouse (MkHouse One) = 1 521 | inhabitantsPerHouse (MkHouse Two) = 2 522 | inhabitantsPerHouse (MkHouse Three) = 3 523 | inhabitantsPerHouse (MkHouse Four) = 4 524 | 525 | inhabitantsPerCity :: [House] -> Int 526 | inhabitantsPerCity xs = sum $ map inhabitantsPerHouse xs 527 | 528 | data CityDefense = Undefended | Castled Castle | Walled Castle deriving (Show) 529 | 530 | data City = MkCity 531 | { cityDefense :: CityDefense 532 | , cityFacility :: Facility 533 | , cityHouses :: [House] 534 | } deriving (Show) 535 | 536 | buildCastle :: City -> String -> City 537 | buildCastle city castleName = 538 | let castle = MkCastle castleName in 539 | case cityDefense city of 540 | Undefended -> city { cityDefense = Castled castle } 541 | Castled _ -> city { cityDefense = Castled castle } 542 | Walled _ -> city { cityDefense = Walled castle } 543 | 544 | buildHouse :: City -> House -> City 545 | buildHouse city house = 546 | let houses = house : cityHouses city in 547 | city { cityHouses = houses } 548 | 549 | buildWalls :: City -> City 550 | buildWalls city = 551 | let inhabitants = inhabitantsPerCity (cityHouses city) in 552 | if inhabitants >= 10 then 553 | case cityDefense city of 554 | Undefended -> city 555 | Castled castle -> city { cityDefense = Walled castle } 556 | Walled castle -> city 557 | else 558 | city 559 | 560 | {- 561 | =🛡= Newtypes 562 | 563 | There is one more way to create a custom structure in Haskell. Let's see what 564 | that is and how it differs from others. 565 | 566 | __Newtype__ is a way to create a lightweight wrapper around an existing type. 567 | Unlike type aliases, newtypes make an entirely new type for the compiler point 568 | of view (as the name suggests). However, such data types don't have additional 569 | runtime overhead, which means that it would work as fast as the underlying type 570 | without the wrapper. 571 | 572 | You can declare a data type as a newtype only if it has __exactly one 573 | constructor__ with __exactly one field__. It is a compiler error if you try to 574 | define a newtype with another number of constructors or fields. 575 | 576 | The syntax is similar to defining an ordinary data type, but you use the 577 | "newtype" keyword instead of the "data" keyword. "newtype" is a product type. 578 | 579 | @ 580 | newtype Attack = MkAttack Int 581 | @ 582 | 583 | The same rule about names fields as for any data types applies to newtypes 584 | as well. Meaning you can write the above type as follows: 585 | 586 | @ 587 | newtype Attack = MkAttack 588 | { unAttack :: Int 589 | } 590 | @ 591 | 592 | You can use the "MkAttack" constructor to create values of the "Attack" type, 593 | and you can pattern match on "MkAttack" as on ordinary data types. When using 594 | newtypes, you pay an extra development cost of writing extra wrappers and 595 | unwrappers, but at the same time, you get additional compile-time guarantees of 596 | not mixing types. 597 | 598 | Newtypes serve the purpose of creating safer and more maintainable interfaces. 599 | Let's prove that. 600 | 601 | Say, we have a function to get a BMI. 602 | 603 | @ 604 | myBMI :: Double -> Double -> Double 605 | myBMI height weight = ... 606 | @ 607 | 608 | And I can use it to calculate my BMI: 609 | 610 | ghci> myBMI 200 70 611 | 612 | Imagine how terrifying it could be if one accidentally messes with the order of 613 | height and weight. 😱 614 | 615 | ghci> myBMI 70 200 616 | 617 | However, this could be avoided if our function would look like this: 618 | 619 | @ 620 | myBMI :: Height -> Weight -> Double 621 | -- | ╰╴ newtype 622 | -- ╰╴newtype 623 | myBMI height weight = ... 624 | @ 625 | 626 | And to run it you won't be able to mess arguments: 627 | 628 | ghci> myBMI (Height 200) (Weight 70) 629 | -} 630 | 631 | {- 632 | =⚔️= Task 5 633 | 634 | Improve the following code (types definition and function implementations) by 635 | introducing extra newtypes. 636 | 637 | 🕯 HINT: if you complete this task properly, you don't need to change the 638 | implementation of the "hitPlayer" function at all! 639 | -} 640 | 641 | newtype Health = Health Int 642 | newtype Armor = Armor Int 643 | newtype Attack = Attack Int 644 | newtype Dexterity = Dexterity Int 645 | newtype Strength = Strength Int 646 | newtype Damage = Damage Int 647 | newtype Defense = Defense Int 648 | 649 | data Player = Player 650 | { playerHealth :: Health 651 | , playerArmor :: Armor 652 | , playerAttack :: Attack 653 | , playerDexterity :: Dexterity 654 | , playerStrength :: Strength 655 | } 656 | 657 | calculatePlayerDamage :: Attack -> Strength -> Damage 658 | calculatePlayerDamage (Attack attack) (Strength strength) = Damage (attack + strength) 659 | 660 | calculatePlayerDefense :: Armor -> Dexterity -> Defense 661 | calculatePlayerDefense (Armor armor) (Dexterity dexterity) = Defense (armor * dexterity) 662 | 663 | calculatePlayerHit :: Damage -> Defense -> Health -> Health 664 | calculatePlayerHit (Damage damage) (Defense defense) (Health health) = Health (health + defense - damage) 665 | 666 | -- The second player hits first player and the new first player is returned 667 | hitPlayer :: Player -> Player -> Player 668 | hitPlayer player1 player2 = 669 | let damage = calculatePlayerDamage 670 | (playerAttack player2) 671 | (playerStrength player2) 672 | defense = calculatePlayerDefense 673 | (playerArmor player1) 674 | (playerDexterity player1) 675 | newHealth = calculatePlayerHit 676 | damage 677 | defense 678 | (playerHealth player1) 679 | in player1 { playerHealth = newHealth } 680 | 681 | {- | 682 | =🛡= Polymorphic data types 683 | 684 | Similar to functions, data types in Haskell can be __polymorphic__. This means 685 | that they can use some type variables as placeholders, representing general 686 | types. You can either reason about data types in terms of such variables (and 687 | don't worry about the specific types), or substitute variables with some 688 | particular types. 689 | Such polymorphism in Haskell is an example of the __parametric polymorphism__. 690 | 691 | The process of defining a polymorphic type is akin to the ordinary data type 692 | definition. The only difference is that all the type variables should go after 693 | the type name so that you can reuse them in the constructor fields later. 694 | 695 | For example, 696 | 697 | @ 698 | data Foo a = MkFoo a 699 | @ 700 | 701 | Note that both product and sum types can be parameterised. 702 | 703 | > Actually, we've already seen a polymorphic data type! Remember Lists from Chapter Two? 704 | 705 | To give an example of a custom polymorphic type, let's implement a 706 | "TreasureChest" data type. Our treasure chest is flexible, and it can store some 707 | amount of gold. Additionally there is some space for one more arbitrary 708 | treasure. But that could be any treasure, and we don't know what it is 709 | beforehand. 710 | 711 | In Haskell words, the data type can be defined like this: 712 | 713 | @ 714 | data TreasureChest x = TreasureChest 715 | { treasureChestGold :: Int 716 | , treasureChestLoot :: x 717 | } 718 | @ 719 | 720 | You can see that a treasure chest can store any treasure, indeed! We call it 721 | treasure 'x'. 722 | 723 | And when writing functions involving the "TreasureChest" type, we don't always 724 | need to know what kind of treasure is inside besides gold. 725 | We can either use a type variable in our type signature: 726 | 727 | @ 728 | howMuchGoldIsInMyChest :: TreasureChest x -> Int 729 | @ 730 | 731 | or we can specify a concrete type: 732 | 733 | @ 734 | isEnoughDiamonds :: TreasureChest Diamond -> Bool 735 | @ 736 | 737 | In the same spirit, we can implement a function that creates treasure with some 738 | predefined amount of gold and a given treasure: 739 | 740 | @ 741 | mkMehChest :: x -> TreasureChest x 742 | mkMehChest treasure = TreasureChest 743 | { treasureChestGold = 50 744 | , treasureChestLoot = treasure 745 | } 746 | @ 747 | 748 | 749 | Polymorphic Algebraic Data Types are a great deal! One of the most common and 750 | useful standard polymorphic types is __"Maybe"__. It represents the notion of 751 | optional value (maybe the value is there, or maybe it is not). 752 | "Maybe" is defined in the standard library in the following way: 753 | 754 | @ 755 | data Maybe a 756 | = Nothing 757 | | Just a 758 | @ 759 | 760 | Haskell doesn't have a concept of "null" values. If you want to work with 761 | potentially absent values, use the "Maybe" type explicitly. 762 | 763 | > Is there a good way to avoid null-pointer bugs? Maybe. © Jasper Van der Jeught 764 | 765 | Another standard polymorphic data type is "Either". It stores either the value 766 | of one type or a value of another. 767 | 768 | @ 769 | data Either a b 770 | = Left a 771 | | Right b 772 | @ 773 | 774 | ♫ NOTE: It can help to explore types of constructors "Nothing", "Just", "Left" 775 | and "Right". Let's stretch our fingers and blow off the dust from our GHCi and 776 | check that! 777 | 778 | You can pattern match on values of the "Either" type as well as on any other 779 | custom data type. 780 | 781 | @ 782 | showEither :: Either String Int -> String 783 | showEither (Left msg) = "Left with string: " ++ msg 784 | showEither (Right n) = "Right with number: " ++ show n 785 | @ 786 | 787 | Now, after we covered polymorphic types, you are finally ready to learn how 788 | lists are actually defined in the Haskell world. Behold the might list type! 789 | 790 | @ 791 | data [] a 792 | = [] 793 | | a : [a] 794 | @ 795 | 796 | Immediately we know what all of that means! 797 | The ":" is simply the constructor name for the list. Constructors in Haskell can 798 | be defined as infix operators as well (i.e. be written after the first argument, 799 | the same way we write `1 + 2` and not `+ 1 2`), but only if they start with a 800 | colon ":". The ":" is taken by lists. Now you see why we were able to pattern 801 | match on it? 802 | 803 | The type name uses built-in syntax to reserve the square brackets [] exclusively 804 | for lists but, otherwise, is a simple polymorphic recursive sum type. 805 | 806 | If you rename some constructor and type names, the list type could look quite 807 | simple, as any of us could have written it: 808 | 809 | @ 810 | data List a 811 | = Empty 812 | | Cons a (List a) 813 | @ 814 | 815 | ♫ NOTE: We use () to group "List" with "a" type variable in the second field of 816 | the "Cons" constructor. This is done to tell the compiler that "List" and "a" 817 | should go together as one type. 818 | -} 819 | 820 | {- | 821 | =⚔️= Task 6 822 | 823 | Before entering the real world of adventures and glorious victories, we should 824 | prepare for different things in this world. It is always a good idea to 825 | understand the whole context before going for a quest. And, before fighting a 826 | dragon, it makes sense to prepare for different unexpected things. So let's 827 | define data types describing a Dragon Lair! 828 | 829 | ⍟ A lair has a dragon and possibly a treasure chest (as described in the 830 | previous section). A lair also may not contain any treasures, but we'll never 831 | know until we explore the cave! 832 | ⍟ A dragon can have a unique magical power. But it can be literally anything! 833 | And we don't know in advance what power it has. 834 | 835 | Create data types that describe such Dragon Lair! Use polymorphism to 836 | parametrise data types in places where values can be of any general type. 837 | 838 | 🕯 HINT: 'Maybe' that some standard types we mentioned above are useful for 839 | maybe-treasure ;) 840 | -} 841 | data TreasureChest x = TreasureChest 842 | { treasureChestGold :: Int 843 | , treasureChestLoot :: x 844 | } 845 | 846 | data Dragon x = MkDragon 847 | { name :: String 848 | , magicalPower :: x 849 | } 850 | 851 | data DragonLair a b = MkDragonLair 852 | { dragon :: Dragon a 853 | , treasureChest :: Maybe (TreasureChest b) 854 | } 855 | 856 | {- 857 | =🛡= Typeclasses 858 | 859 | __Typeclass__ is a regularly used way to express common characteristics of the 860 | different data types. In some sense, a typeclass describes the interface of some 861 | value without telling you the implementation details. 862 | 863 | __Instance__ is a representation of the typeclass ↔︎️ data type relationships. In 864 | order to show that the data type obeys the typeclasses rules and to use the 865 | methods of the typeclass on the data values, you need to provide the work 866 | instructions under this particular typeclass. And that is the instance of the 867 | data type for the specific typeclass. 868 | 869 | Let’s consolidate the typeclasses and instances concepts on the analogues from 870 | our fantasy world. 871 | 872 | Many lovely princesses need to be rescued. And those processes are usually 873 | alike: find a path to the particular castle (differs from princess to princess), 874 | defeat the monster (also unique for each princess) and rescue the beloved 875 | beauty. So we can see that the "Rescue Mission Plan" is the typeclass, and each 876 | princess has its own instance for that. If you are a prince on a white horse, 877 | you'd better check the particular instance for your princess to get into the 878 | salvation journey. 879 | 880 | Next, let’s look at one code example for the better illustration of the 881 | instance-typeclass relationship. We can define a typeclass that would tell us 882 | one's arch enemy. 883 | 884 | The syntax is as follows: you need to use the "class" keyword, then you need to 885 | specify the typeclass name. Typeclasses should start with the upper letter. 886 | After that, the type parameter should be identified, which represents the data 887 | types that would have instances of this typeclass. And, finally, the "where" 888 | keyword. After, you can specify methods of the typeclass – functions that should 889 | work with the type parameter. 890 | 891 | @ 892 | ┌─ typeclass name 893 | │ 894 | │ ┌─ type parameter 895 | │ │ ┌───── "where" keyword 896 | │ │ │ 897 | class ArchEnemy a where 898 | │ getArchEnemy :: a -> String 899 | │ │ │ 900 | │ │ └─── the same type parameter 901 | │ └───── method name 902 | │ 903 | └ "class" keyword 904 | @ 905 | 906 | And that 'getArchEnemy' method could be used with a lot of data types: Bool, 907 | Double,… name them all! Let’s have our first instances to show how it works: 908 | 909 | The syntax is simple and consistent with the typeclass declaration, but instead 910 | of the "class" keyword you need to have the "instance" keyword. Of course, 911 | instead of the type parameter, you have to specify the concrete type, for which 912 | you are implementing the instance. And all the necessary methods should have its 913 | implementation for the particular data type. 914 | 915 | @ 916 | instance ArchEnemy Bool where 917 | getArchEnemy :: Bool -> String 918 | getArchEnemy True = "False" 919 | getArchEnemy False = "True" 920 | 921 | 922 | instance ArchEnemy Int where 923 | getArchEnemy :: Int -> String 924 | getArchEnemy i = case i of 925 | 0 -> "Division" 926 | _ -> "Derivative" 927 | 928 | instance ArchEnemy Double where 929 | getArchEnemy :: Double -> String 930 | getArchEnemy n 931 | | isNaN n = "Infinity" 932 | | isInfinite n = "Limit" 933 | | otherwise = "NaN" 934 | @ 935 | 936 | And then you can write polymorphic functions and not worry about which specific 937 | type is underhood until it has the instance of the desired typeclass. For that 938 | we are using __constraints__ in Haskell. It is the identification of affiliation 939 | to the typeclass. The constraints should go after the "::" sign in the function 940 | type declaration. You can specify one or many constraints. If more than one they 941 | should be in parenthesis and comma-separated. The end of constraints is 942 | determined with the "=>" arrow, and the function type could be written as usual. 943 | 944 | @ 945 | revealArchEnemy :: (ArchEnemy a, Show a) => a -> String 946 | revealArchEnemy x = 947 | "The arch-enemy of " ++ show x ++ " is " ++ getArchEnemy x 948 | @ 949 | 950 | The behaviour of this polymorphic function depends on the data type used with 951 | this function. Such dependency is called __ad-hoc polymorphism__. 952 | 953 | This is how it works in action: 954 | 955 | ghci> revealArchEnemy (42 :: Int) 956 | "The arch-enemy of 42 is Derivative" 957 | 958 | However, if we try to use this function with something that doesn’t implement an 959 | instance of our typeclass, we will get the corresponding compiler error, that 960 | would warn us precisely about that: 961 | 962 | ghci> revealArchEnemy "An adorable string that has no enemies (✿◠ω◠)" 963 | 964 | :21:1: error: 965 | • No instance for (ArchEnemy String) 966 | arising from a use of revealArchEnemy 967 | • In the expression: revealArchEnemy "An adorable string that has no enemies (✿◠ω◠)" 968 | In an equation for 'it': it = revealArchEnemy "An adorable string that has no enemies (✿◠ω◠)" 969 | 970 | 971 | Interestingly, it is possible to reuse existing instances of data types in the 972 | same typeclass instances as well. And we also can reuse the __constraints__ in 973 | the instance declaration for that! 974 | 975 | This gives us the ability to specify the instances for polymorphic data types 976 | with some conditions (constraints). 977 | 978 | Let's see it in the example of the 'ArchEnemy' typeclass instance for the 979 | "Maybe" something data type. 980 | 981 | @ 982 | instance (ArchEnemy a) => ArchEnemy (Maybe a) where 983 | getArchEnemy :: Maybe a -> String 984 | getArchEnemy (Just x) = getArchEnemy x 985 | getArchEnemy Nothing = "NullPointerException" 986 | @ 987 | 988 | This instance is suitable for any Maybe as long as the instance of the inside 989 | type exists. You can see how we reuse the fact that the underlying type has this 990 | instance and apply this typeclass method to it. 991 | -} 992 | 993 | {- | 994 | =⚔️= Task 7 995 | 996 | Often we want to combine several values of a type and get a single value of the 997 | exact same type. We can combine different things: treasures, adventures, groups 998 | of heroes, etc.. So it makes sense to implement a typeclass for such a concept 999 | and define helpful instances. 1000 | 1001 | We will call such a typeclass "Append". You can find its definition below. 1002 | 1003 | Implement instances of "Append" for the following types: 1004 | 1005 | ✧ The "Gold" newtype where append is the addition 1006 | ✧ "List" where append is list concatenation 1007 | ✧ *(Challenge): "Maybe" where append is appending of values inside "Just" constructors 1008 | 1009 | -} 1010 | class Append a where 1011 | append :: a -> a -> a 1012 | 1013 | newtype Gold = Gold Int 1014 | 1015 | instance Append Gold where 1016 | append :: Gold -> Gold -> Gold 1017 | append (Gold a) (Gold b) = Gold (a + b) 1018 | 1019 | instance Append [a] where 1020 | append :: [a] -> [a] -> [a] 1021 | append = (++) 1022 | 1023 | instance (Append a) => Append (Maybe a) where 1024 | append :: (Append a) => Maybe a -> Maybe a -> Maybe a 1025 | append Nothing Nothing = Nothing 1026 | append Nothing (Just a) = Just a 1027 | append (Just a) Nothing = Just a 1028 | append (Just a) (Just b) = Just (append a b) 1029 | 1030 | {- 1031 | =🛡= Standard Typeclasses and Deriving 1032 | 1033 | As many useful data types, standard library in Haskell also comes with some very 1034 | essential typeclasses: 1035 | 1036 | ﹡ 'Show' — show the data type value as a 'String'. This helps us to print into 1037 | the terminal in GHCi. 1038 | ﹡ 'Read' — inverse to the 'Show'. By a given String, it is possible to parse 1039 | it into the data type. 1040 | ﹡ 'Eq' — determine if two values of the same data types are equal. 1041 | ﹡ 'Ord' — compare values of the same data type (requires the data type to have 1042 | the instance of 'Eq'). 1043 | ﹡ 'Bounded' — specify the lowest and highest value of the data type 1044 | ﹡ 'Enum' — operate with enumeration types. 1045 | 1046 | You can use Hoogle to check what methods these classes have. Additionally, the 1047 | documentation there shows for which types there are already instances of these 1048 | typeclasses. 1049 | 1050 | Alternatively, you can use the ":i" command in GHCi (short for ":info") to see 1051 | the typeclass methods and its instances for the standard data types. 1052 | 1053 | As these typeclasses are way too useful, and it is relatively straightforward to 1054 | implement instances for such classes, GHC provides a nice feature: __deriving__. 1055 | Deriving is the feature of the Haskell compiler, that auto generates the 1056 | instances of typeclasses for the specified data types. The code would be 1057 | produced internally during the compilation, so no need to litter your code with 1058 | the boilerplate implementation of instances. 1059 | 1060 | The deriving syntax is the following. It should be attached to the data type 1061 | declaration. It consists of the "deriving" keyword, and then typeclass names in 1062 | parenthesis comma-separated. 1063 | 1064 | @ 1065 | data Princess = Princess 1066 | { princessName :: String 1067 | } deriving (Show, Read, Eq, Ord) 1068 | @ 1069 | 1070 | And it is ready to be used! 1071 | 1072 | ghci> show (Princess "Anna") 1073 | Princess {princessName = "Anna"} 1074 | 1075 | -} 1076 | 1077 | {- 1078 | =⚔️= Task 8 1079 | 1080 | Create an enumeration type to represent days of the week. After defining a type, 1081 | implement the following functions: 1082 | 1083 | ✦ isWeekend — True if the weekday is weekend 1084 | ✦ nextDay — returns the next weekday 1085 | ✦ daysToParty — number of the days to the next Friday 1086 | 1087 | 🕯 HINT: to implement this task, derive some standard typeclasses 1088 | -} 1089 | 1090 | data Weekday = Mon | Tue | Wed | Thu | Fri | Sat | Sun deriving (Show, Eq, Ord, Enum, Bounded) 1091 | 1092 | isWeekend :: Weekday -> Bool 1093 | isWeekend day = day >= Sat 1094 | 1095 | nextDay :: Weekday -> Weekday 1096 | nextDay day 1097 | | day == maxBound = minBound 1098 | | otherwise = succ day 1099 | 1100 | daysToParty :: Weekday -> Int 1101 | daysToParty day 1102 | | day == Fri = 0 1103 | | otherwise = 1 + daysToParty (nextDay day) 1104 | 1105 | {- 1106 | =💣= Task 9* 1107 | 1108 | You got to the end of Chapter Three, Brave Warrior! I hope you are ready for the 1109 | final Boss. We talked a lot about data types and typeclasses in this chapter. 1110 | Now it is time to show what we learned! 1111 | 1112 | To defeat the final boss in this chapter, you need to implement your own data 1113 | types, typeclasses and instances, describing the world, and write polymorphic 1114 | functions using custom types and typeclasses. 1115 | 1116 | The task: 1117 | When two fighters engage in a battle (knight fights the monster, duel of 1118 | knights, monsters fighting for a lair, etc.), both of them can perform different 1119 | actions. They do their activities in turns, i.e. one fighter goes first, then 1120 | the other goes second, then the first again, and so on, until one of them wins. 1121 | 1122 | Both knight and monster have a sequence of actions they can do. A knight can 1123 | attack, drink a health potion, cast a spell to increase their defence. A monster 1124 | can only attack or run away. Each fighter starts with some list of actions they 1125 | can do, performs them in sequence one after another, and when the sequence ends, 1126 | the process starts from the beginning. 1127 | 1128 | Monsters have only health and attack, while knights also have a defence. So when 1129 | knights are attacked, their health is decreased less, if they have more defence. 1130 | The fight ends when the health of one fighter becomes zero or less. 1131 | 1132 | As you can see, both monster and knight have similar characteristics, but they 1133 | also have some differences. So it is possible to describe their common 1134 | properties using typeclasses, but they are different data types in the end. 1135 | 1136 | Implement data types and typeclasses, describing such a battle between two 1137 | contestants, and write a function that decides the outcome of a fight! 1138 | -} 1139 | 1140 | data KAction = KAttack | KPotion | KSpell 1141 | 1142 | data K = MkK 1143 | { kHealth :: Health 1144 | , kAttack :: Attack 1145 | , kDefense :: Defense 1146 | , kActions :: [KAction] 1147 | , usedSpell :: Bool 1148 | , usedPotion :: Bool 1149 | } 1150 | 1151 | mkK :: Health -> Attack -> Defense -> [KAction] -> K 1152 | mkK h a d k = 1153 | MkK { kHealth = h 1154 | , kAttack = a 1155 | , kDefense = d 1156 | , kActions = cycle k 1157 | , usedSpell = False 1158 | , usedPotion = False 1159 | } 1160 | 1161 | data MAction = MAttack | MFlee 1162 | data M = MkM 1163 | { mHealth :: Health 1164 | , mAttack :: Attack 1165 | , mActions :: [MAction] 1166 | } 1167 | 1168 | mkM :: Health -> Attack -> [MAction] -> M 1169 | mkM h a m = 1170 | MkM { mHealth = h 1171 | , mAttack = a 1172 | , mActions = cycle m 1173 | } 1174 | 1175 | data Turn = TAttack Damage | TIdle | TFlee 1176 | 1177 | class Fighter a where 1178 | damage :: a -> Damage 1179 | getDamaged :: a -> Damage -> a 1180 | isDead :: a -> Bool 1181 | nextAction :: a -> (Turn, a) 1182 | 1183 | instance Fighter K where 1184 | damage :: K -> Damage 1185 | damage k = Damage (a + (if usedSpell k then 1 else 0)) 1186 | where Attack a = kAttack k 1187 | 1188 | getDamaged :: K -> Damage -> K 1189 | getDamaged k (Damage d) = 1190 | let Health kHealth' = kHealth k in 1191 | let Defense kDefense' = kDefense k in 1192 | k { kHealth = Health (min (kHealth' + kDefense' + (if usedPotion k then 1 else 0) - d) kHealth') } 1193 | 1194 | isDead :: K -> Bool 1195 | isDead k = h <= 0 1196 | where Health h = kHealth k 1197 | 1198 | nextAction :: K -> (Turn, K) 1199 | nextAction k = 1200 | let action = head $ kActions k 1201 | remaining = drop 1 $ kActions k 1202 | (action', k') = case action of 1203 | KAttack -> (TAttack (damage k), k) 1204 | KPotion -> (TIdle, k { usedPotion = True }) 1205 | KSpell -> (TIdle, k { usedSpell = True }) 1206 | in 1207 | (action', k' { kActions = remaining }) 1208 | 1209 | instance Fighter M where 1210 | damage :: M -> Damage 1211 | damage m = Damage a 1212 | where Attack a = mAttack m 1213 | 1214 | getDamaged m (Damage d) = 1215 | let Health mHealth' = mHealth m in 1216 | m { mHealth = Health (mHealth' - d) } 1217 | 1218 | isDead :: M -> Bool 1219 | isDead m = h <= 0 1220 | where Health h = mHealth m 1221 | 1222 | nextAction :: M -> (Turn, M) 1223 | nextAction m = 1224 | let action = head $ mActions m 1225 | remaining = drop 1 $ mActions m 1226 | action' = case action of 1227 | MAttack -> TAttack (damage m) 1228 | MFlee -> TFlee 1229 | in 1230 | (action', m { mActions = remaining }) 1231 | 1232 | 1233 | winner :: (Fighter a, Fighter b) => a -> b -> Either a b 1234 | winner a b 1235 | | isDead a = Right b 1236 | | isDead b = Left a 1237 | | otherwise = 1238 | let (turnA, a') = nextAction a 1239 | (turnB, b') = nextAction b 1240 | in 1241 | case (turnA, turnB) of 1242 | (TFlee, _) -> Right b' 1243 | (_, TFlee) -> Left a' 1244 | (TIdle, TIdle) -> 1245 | winner a' b' 1246 | (TIdle, TAttack dmg) -> 1247 | winner (getDamaged a' dmg) b' 1248 | (TAttack dmg, TIdle) -> 1249 | winner a' (getDamaged b' dmg) 1250 | (TAttack dmgByA, TAttack dmgByB) -> 1251 | winner (getDamaged a' dmgByB) (getDamaged b' dmgByA) 1252 | 1253 | {- 1254 | You did it! Now it is time to open pull request with your changes 1255 | and summon @vrom911 and @chshersh for the review! 1256 | -} 1257 | 1258 | {- 1259 | =📜= Additional resources 1260 | Deriving: https://kowainik.github.io/posts/deriving 1261 | Extensions: https://kowainik.github.io/posts/extensions 1262 | -} 1263 | --------------------------------------------------------------------------------