├── .github ├── CODEOWNERS ├── dependabot.yml ├── pull_request_template.md └── workflows │ └── ci.yml ├── .gitignore ├── .gitpod.yml ├── .stylish-haskell.yaml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Makefile ├── README.md ├── images ├── HacktoberFest2020-Learn4Haskell-Banner.png ├── HacktoberFest2021-Learn4Haskell-Banner.png └── Learn4Haskell.png ├── learn4haskell.cabal ├── src ├── Chapter1.hs ├── Chapter2.hs ├── Chapter3.hs └── Chapter4.hs └── test ├── DoctestChapter1.hs ├── DoctestChapter2.hs ├── DoctestChapter3.hs ├── DoctestChapter4.hs ├── Spec.hs └── Test ├── Chapter1.hs ├── Chapter2.hs ├── Chapter3.hs └── Chapter4.hs /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @vrom911 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | commit-message: 8 | prefix: "GA" 9 | include: "scope" 10 | labels: 11 | - "CI" 12 | - "library :books:" 13 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Solutions for Chapter {NUMBER} 2 | 3 | cc @vrom911 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | types: [synchronize, opened, reopened] 6 | push: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | name: Build Learn4Haskell 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | cabal: ["3.8"] 17 | ghc: ["9.4.4"] 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | 22 | - uses: haskell/actions/setup@v2 23 | name: Setup Haskell 24 | with: 25 | ghc-version: ${{ matrix.ghc }} 26 | cabal-version: ${{ matrix.cabal }} 27 | 28 | - uses: actions/cache@v3 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-latest 41 | strategy: 42 | matrix: 43 | cabal: ["3.8"] 44 | ghc: ["9.4.4"] 45 | steps: 46 | - uses: actions/checkout@v3 47 | 48 | - uses: haskell/actions/setup@v2 49 | name: Setup Haskell 50 | with: 51 | ghc-version: ${{ matrix.ghc }} 52 | cabal-version: ${{ matrix.cabal }} 53 | 54 | - uses: actions/cache@v3 55 | name: Cache ~/.cabal/store 56 | with: 57 | path: ~/.cabal/store 58 | key: ${{ runner.os }}-${{ matrix.ghc }}-cabal 59 | 60 | - name: Chapter 1 - Doctest 61 | run: | 62 | cabal v2-test doctest-chapter1 --enable-tests --test-show-details=direct 63 | 64 | - name: Chapter 1 - Tests 65 | run: | 66 | cabal run learn4haskell-test --enable-tests -- -m "Chapter1Normal" 67 | 68 | - name: Chapter 1 - Tests - Advanced 69 | continue-on-error: true 70 | run: | 71 | cabal run learn4haskell-test --enable-tests -- -m "Chapter1Advanced" 72 | 73 | chapter2: 74 | name: Chapter Two 75 | runs-on: ubuntu-latest 76 | strategy: 77 | matrix: 78 | cabal: ["3.8"] 79 | ghc: ["9.4.4"] 80 | steps: 81 | - uses: actions/checkout@v3 82 | 83 | - uses: haskell/actions/setup@v2 84 | name: Setup Haskell 85 | with: 86 | ghc-version: ${{ matrix.ghc }} 87 | cabal-version: ${{ matrix.cabal }} 88 | 89 | - uses: actions/cache@v3 90 | name: Cache ~/.cabal/store 91 | with: 92 | path: ~/.cabal/store 93 | key: ${{ runner.os }}-${{ matrix.ghc }}-cabal 94 | 95 | - name: Chapter 2 - Doctest 96 | run: | 97 | cabal v2-test doctest-chapter2 --enable-tests --test-show-details=direct 98 | 99 | - name: Chapter 2 - Tests 100 | run: | 101 | cabal run learn4haskell-test --enable-tests -- -m "Chapter2Normal" 102 | 103 | - name: Chapter 2 - Tests - Advanced 104 | continue-on-error: true 105 | run: | 106 | cabal run learn4haskell-test --enable-tests -- -m "Chapter2Advanced" 107 | 108 | chapter3: 109 | name: Chapter Three 110 | runs-on: ubuntu-latest 111 | strategy: 112 | matrix: 113 | cabal: ["3.8"] 114 | ghc: ["9.4.4"] 115 | steps: 116 | - uses: actions/checkout@v3 117 | 118 | - uses: haskell/actions/setup@v2 119 | name: Setup Haskell 120 | with: 121 | ghc-version: ${{ matrix.ghc }} 122 | cabal-version: ${{ matrix.cabal }} 123 | 124 | - uses: actions/cache@v3 125 | name: Cache ~/.cabal/store 126 | with: 127 | path: ~/.cabal/store 128 | key: ${{ runner.os }}-${{ matrix.ghc }}-cabal 129 | 130 | - name: Chapter 3 - Doctest 131 | run: | 132 | cabal v2-test doctest-chapter3 --enable-tests --test-show-details=direct 133 | 134 | chapter4: 135 | name: Chapter Four 136 | runs-on: ubuntu-latest 137 | strategy: 138 | matrix: 139 | cabal: ["3.8"] 140 | ghc: ["9.4.4"] 141 | steps: 142 | - uses: actions/checkout@v3 143 | 144 | - uses: haskell/actions/setup@v2 145 | name: Setup Haskell 146 | with: 147 | ghc-version: ${{ matrix.ghc }} 148 | cabal-version: ${{ matrix.cabal }} 149 | 150 | - uses: actions/cache@v3 151 | name: Cache ~/.cabal/store 152 | with: 153 | path: ~/.cabal/store 154 | key: ${{ runner.os }}-${{ matrix.ghc }}-cabal 155 | 156 | - name: Chapter 4 - Doctest 157 | run: | 158 | cabal v2-test doctest-chapter4 --enable-tests --test-show-details=direct 159 | 160 | - name: Chapter 4 - Tests 161 | run: | 162 | cabal run learn4haskell-test --enable-tests -- -m "Chapter4Normal" 163 | 164 | - name: Chapter 4 - Tests - Advanced 165 | continue-on-error: true 166 | run: | 167 | cabal run learn4haskell-test --enable-tests -- -m "Chapter4Advanced" 168 | -------------------------------------------------------------------------------- /.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 | .env 57 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # from https://github.com/haskell/haskell-language-server/blob/master/.gitpod.yml 2 | tasks: 3 | - before: | 4 | # Only the /workspace folder is persistent 5 | export XDG_DATA_HOME=/workspace/.local/share 6 | export XDG_CONFIG_HOME=/workspace/.local/config 7 | export XDG_STATE_HOME=/workspace/.local/state 8 | export XDG_CACHE_HOME=/workspace/.cache 9 | export CABAL_DIR=/workspace/.cabal 10 | export STACK_ROOT=/workspace/.stack 11 | 12 | # install ghcup, ghc and cabal 13 | export GHCUP_INSTALL_BASE_PREFIX=/workspace 14 | export BOOTSTRAP_HASKELL_NONINTERACTIVE=1 15 | export BOOTSTRAP_HASKELL_MINIMAL=1 16 | curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh 17 | /workspace/.ghcup/bin/ghcup install ghc --set 18 | /workspace/.ghcup/bin/ghcup install cabal 19 | 20 | # Add ghcup binaries to the PATH since VSCode does not see 'source .ghcup/env' 21 | pushd /usr/local/bin 22 | sudo ln -s /workspace/.ghcup/bin/* /usr/local/bin 23 | popd 24 | 25 | # Fix the Cabal dir since VSCode does not see CABAL_DIR 26 | cabal update 27 | echo "Symlinking /workspace/.cabal to ~/.cabal" 28 | ln -s /workspace/.cabal ~ 29 | init: | 30 | cabal update 31 | 32 | vscode: 33 | extensions: 34 | - haskell.haskell 35 | - justusadam.language-haskell 36 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 to 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 | to 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # learn4haskell 2 | 3 | ![Learn4Haskell](/images/HacktoberFest2021-Learn4Haskell-Banner.png) 4 | 5 | 🚨 __Important notice: This course continues BAU even after Hacktoberfest__ 🚨 6 | 7 | You can find the results of Hacktoberfest 2020 for [Learn4Haskell](https://github.com/kowainik/learn4haskell) in the following blog post: 8 | 9 | * [Brave New Hacktoberfest](https://kowainik.github.io/posts/hacktoberfest2020) 10 | 11 |
12 | 13 | It's the time of the year when thousand pull requests are starting to float in 14 | the air like a leaf on the wind 🍃 15 | 16 | It's Hacktoberfest! And we are happy to be part of this fantastic event. 17 | 18 | Usually, people contribute to projects within the communities they spend most of their time 19 | already and don't try to go out of those boundaries. 20 | But why not use this time to learn something challenging, something fresh, 21 | something that you never had time for? 22 | 23 | You can get the benefits of Hacktoberfest while learning something new 24 | fascinating concepts – Functional Programming with Haskell. 25 | 26 | And we're here to help! 27 | 28 | * 4 Pull Request to get the T-Shirt or plant a tree as stands in the Hacktoberfest rules. 29 | * 4 Pull Request to learn to program in Haskell. 30 | * 4 Pull Request to blow your mind. 31 | 32 | ## Table of Contents 33 | 34 | * [What is Learn4Haskell](#what-is-learn4haskell) 35 | * [Course Plan](#course-plan) 36 | * [Goals](#goals) 37 | * [Who can participate](#who-can-participate) 38 | * [What you will get from this course](#what-you-will-get-from-this-course) 39 | * [How to get started](#how-to-get-started) 40 | * [Installing Haskell](#installing-haskell) 41 | * [Haskell IDE](#haskell-ide) 42 | * [How to develop](#how-to-develop) 43 | * [Who we are](#who-we-are) 44 | * [How can you help](#how-can-you-help) 45 | 46 | ## What is Learn4Haskell 47 | 48 | Learn4Haskell is a GitHub-located course that will get you into the Haskell 49 | Functional Programming world in just 4 Pull Requests. 50 | 51 | This course is organised as a coding project. So you can complete 52 | the course without needing to exit your editor. 53 | 54 | This works in the following way. When you decide to start the project, all you 55 | need to do is to fork the project. We have prepared 4 separate modules — chapters. 56 | Each part contains educational material and lots of examples that we provide in 57 | a simple form that doesn't require you to know anything about functional programming beforehand. 58 | Also, each chapter contains several exercises on everything that is 59 | explained by us. You can solve the tasks on your way and at the end open a PR to 60 | your fork with this chapter's solution and summon us (by shouting out our 61 | nicknames there). We would be happy to give you feedback on your progress, 62 | explain problematic concepts or just support you mentally! 63 | 64 | Each chapter contains unique information and covers different topics. We suggest 65 | going through them in order. However, if you think that some of the chapters 66 | are already familiar to you, feel free to skip onto the next one. 67 | If you would like to talk to us, you can even rely on PRs for the chapter you 68 | have questions about. 69 | 70 | Chapters are stuffed with information but are aimed to be completed 71 | without additional resources. You may spend an evening per chapter, but we swear 72 | it's worth it! 73 | 74 | At the end of the course, you should be able to independently create and read 75 | basic Haskell code and understand Monads and other famous concepts of Functional 76 | Programming. 77 | 78 | ### Course Plan 79 | 80 | Here is a more concrete plan of the mystical 4 Chapters we prepared for 81 | you. These are the highlights of each part. 82 | 83 | * __Chapter One__ – What is Haskell, what are its particularities, basic Haskell 84 | syntax, functions, types, expressions. 85 | * __Chapter Two__ – FP concepts in the language, immutability, pattern matching, 86 | recursion, polymorphism, laziness, Higher-ordered functions, partial 87 | applications, eta-reduction. 88 | * __Chapter Three__ – Focus on Types. Type aliases, ADTs, Product types and 89 | Records, Sum types and Enumerations, Newtypes, Typeclasses. 90 | * __Chapter Four__ – Kinds. Three monsters of functional programming: Functor, Applicative, 91 | Monad. 92 | 93 | ## Goals 94 | 95 | We created the Learn4Haskell project in pursuit of the following goals: 96 | 97 | * Help others to learn Haskell 98 | * Give a beginner-friendly and self-consistent course with theory and practice 99 | in the same place 100 | * Explain Haskell topics before each task, but strive to be concise and useful 101 | at the same time. It's a tough balance! 102 | * Help people who want to participate in Hacktoberfest and Open-Source, but also 103 | want to learn new things during this process 104 | * Provide review and feedback on solutions, so people are never alone in this 105 | challenging yet exciting journey! 106 | * Give people who completed this course all the necessary understandings to 107 | be able to work with basic projects that use standard features. We also intend 108 | that you have a strong basis on what they should do to be able to continue their functional programming 109 | studies. 110 | 111 | ## Who can participate 112 | 113 | Everyone! 114 | 115 | We welcome everyone and would be happy to assist you in this journey! 116 | 117 | The course is intended for people who don't know Haskell or know only language 118 | basics, though. 119 | 120 | If you are already an experienced Haskell developer and have come here for learning 121 | advanced topics, this course might not be that for you. But you still can help us! 122 | Your feedback and suggestions would be helpful for us as well as for the 123 | language newcomers who decide to work with this course. 124 | 125 | ## What you will get from this course 126 | 127 | This course has many benefits upon completion. Check them out to be sure that it fits 128 | your expectations! 129 | 130 | Participation in this course would give you: 131 | 132 | * 4 Pull Requests required for Hacktoberfest completion 133 | * Basic knowledge of the most functional programming language 134 | * Understanding of the functional programming concepts that you would be able to use in your 135 | day-to-day life afterwards 136 | * On-the-fly feedback and help from experienced Haskell developers and educators 137 | * Interesting challenges 138 | * Fun! 139 | 140 | Honestly, this seems like a pretty rad deal! 141 | 142 | ## How to get started 143 | 144 | Starting to learn Haskell with Learn4Haskell is a piece of cake! 145 | 146 | 1. [Fork this repository](https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/fork-a-repo). 147 | 2. :warning: Add the `hacktoberfest` topic to your fork. Otherwise, [your PRs won't count](https://hacktoberfest.digitalocean.com/hacktoberfest-update). 148 | 3. Enable GitHub Actions for your forked repository. 149 | * Visit: https://github.com//learn4haskell/actions 150 | 4. [Install the Haskell compiler](#installing-haskell). 151 | 5. Open the `src/Chapter1.hs` file, and start learning and solving tasks! 152 | 6. After you finish the first chapter (or any other chapter, or even if you are 153 | stuck in the middle), open 154 | [Pull Request](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request) 155 | __to your fork__ with the solution and mention @vrom911 and I 156 | would be on our way for the review. 157 | 158 | > Note, that you should open a PR for your fork of this repo, not this repo. 159 | > Everyone has their solutions to the included tasks, and they don't mix together 160 | > well in one repo 🙂 161 | 162 | > However, if you find some bugs or problems in this repo, you can 163 | > open a PR to Learn4Haskell directly. We appreciate any help and feedback! 164 | 165 | Learn4Haskell has 4 chapters that you can walk through and submit 4 pull requests to 166 | complete the Hacktoberfest event (or just for knowledge and your enjoyment). 167 | 168 | So, you can start right now with forking. Following this we'll describe how you can 169 | install all the necessary items to be able to run this course locally. 170 | 171 | ### Installing Haskell 172 | 173 | If you're on Windows, install the `haskell-dev` and `make` packages [using Chocolatey](https://chocolatey.org/install). 174 | 175 | ```shell 176 | choco install haskell-dev make 177 | refreshenv 178 | ``` 179 | 180 | If you're on Linux or macOS, then the process is easy: 181 | 182 | 1. Install [ghcup](https://www.haskell.org/ghcup/) and follow `ghcup` 183 | instructions for successful installation (remember to restart your terminal afterwards to avoid an `unknown ghcup command` error on the next step). 184 | 2. Install the latest version of the Haskell compiler — GHC — and the 185 | [Cabal](https://www.haskell.org/cabal/) build tool. After you install 186 | `ghcup`, it is easy to install the rest with a few commands from your 187 | terminal 188 | 189 | ```shell 190 | ghcup install ghc 9.2.4 191 | ghcup set ghc 9.2.4 192 | ghcup install cabal 3.2.0.0 193 | ``` 194 | 3. Run `cabal update` to fetch the latest info about Haskell packages. 195 | 196 | ### Haskell IDE 197 | 198 | If you don't have any IDE preferences, we recommend installing 199 | [Visual Studio Code](https://code.visualstudio.com/download) with the 200 | [Haskell plugin](https://marketplace.visualstudio.com/items?itemName=haskell.haskell). 201 | The mentioned plugin would give you everything required to immediately start coding with Haskell. 202 | 203 | ### Gitpod 204 | 205 | [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/kowainik/learn4haskell) 206 | 207 | [Gitpod](https://www.gitpod.io/) is a VSCode-based Web IDE. 208 | With it, you can get a Haskell environment out-of-the-box. 209 | It's free to use up to 50 hours per month. 210 | 211 | Just prepend `gitpod.io#` to your repo URL and you are ready to go. 212 | It will take some time to initialize the workspace for the first time it opens. 213 | It only keeps changes under `/workspace`, and it will be deleted after a period of inactivity unless it's pinned. 214 | 215 | ### How to develop 216 | 217 | The course assumes that you install Haskell tooling (GHC and Cabal), edit code 218 | in the corresponding chapters, run GHCi (Haskell interpreter, explained in the 219 | course) from the root of this project and load your chapters to check your code. 220 | Don't worry, each chapter explains all the needed information! 221 | 222 | We also provide a Makefile with commands to test your solutions locally with the included 223 | prepared test-suite. We have also configured the CI using GitHub 224 | Actions on Learn4Haskell to check your answers at GitHub automatically! 225 | 226 | To run all tests for Chapter One: 227 | 228 | ```shell 229 | make test-chapter1 230 | ``` 231 | 232 | To run tests only for basic tasks for Chapter One (without the advanced tasks): 233 | 234 | ```shell 235 | make test-chapter1-basic 236 | ``` 237 | 238 | Similar commands are provided for all chapters from One to Four. 239 | 240 | ## Who we are 241 | 242 | I am [Veronika (@vrom911)](https://vrom911.github.io/) and I drive this open source organisation — 243 | [Kowainik](https://kowainik.github.io/). We have a lot of open source projects 244 | and libraries in Haskell that are used in the Haskell community. We are also 245 | working on a lot of tutorials and guides in Haskell and mentoring people who are 246 | keen to learn Haskell as well. 247 | 248 | We are passionate about Functional Programming and Haskell in particular. But at 249 | the same time, we understand how difficult it can be to get into all these 250 | ideas on your own. That is why we've decided to start this course to help 251 | newcomers. With the interactive learning process and live discussions we've included, Haskell 252 | will not be that scary. We will do our best so that it especially won't be the case 253 | for you or any others participating here! 254 | 255 | ## How can you help 256 | 257 | You can help us by supporting us on Ko-Fi or via GitHub sponsorship program: 258 | 259 | * [Kowainik Ko-Fi](https://ko-fi.com/kowainik) 260 | * [Veronika Romashkina via GitHub](https://github.com/sponsors/vrom911) 261 | 262 | 263 | We also appreciate any feedback on our course a lot! You can submit your 264 | feedback using the following form: 265 | * [Feedback Form](https://docs.google.com/forms/d/e/1FAIpQLScBVhLxq5CgGnAfIGUE-fCoOUqeGkDY2HXzbT7KV2jjLOsmjQ/viewform) 266 | -------------------------------------------------------------------------------- /images/HacktoberFest2020-Learn4Haskell-Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kowainik/learn4haskell/cfc69fa36399c44a6885f3981a18f0fae4e51caf/images/HacktoberFest2020-Learn4Haskell-Banner.png -------------------------------------------------------------------------------- /images/HacktoberFest2021-Learn4Haskell-Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kowainik/learn4haskell/cfc69fa36399c44a6885f3981a18f0fae4e51caf/images/HacktoberFest2021-Learn4Haskell-Banner.png -------------------------------------------------------------------------------- /images/Learn4Haskell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kowainik/learn4haskell/cfc69fa36399c44a6885f3981a18f0fae4e51caf/images/Learn4Haskell.png -------------------------------------------------------------------------------- /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-2022 Kowainik 15 | build-type: Simple 16 | extra-doc-files: README.md 17 | CHANGELOG.md 18 | tested-with: GHC == 8.10.4 19 | GHC == 9.0.2 20 | GHC == 9.2.7 21 | GHC == 9.4.4 22 | 23 | source-repository head 24 | type: git 25 | location: https://github.com/kowainik/learn4haskell.git 26 | 27 | common common-options 28 | build-depends: base >= 4.14.0.0 && < 4.18 29 | 30 | ghc-options: -Wall 31 | -Wcompat 32 | -Widentities 33 | -Wincomplete-uni-patterns 34 | -Wincomplete-record-updates 35 | -Wredundant-constraints 36 | if impl(ghc >= 8.2) 37 | ghc-options: -fhide-source-paths 38 | if impl(ghc >= 8.4) 39 | ghc-options: -Wpartial-fields 40 | if impl(ghc >= 8.10) 41 | ghc-options: -Wunused-packages 42 | if impl(ghc >= 9.0) 43 | ghc-options: -Winvalid-haddock 44 | if impl(ghc >= 9.2) 45 | ghc-options: -Woperator-whitespace 46 | -Wredundant-bang-patterns 47 | if impl(ghc >= 9.4) 48 | ghc-options: -Wredundant-strictness-flags 49 | -Wforall-identifier 50 | 51 | default-language: Haskell2010 52 | 53 | common common-doctest 54 | import: common-options 55 | hs-source-dirs: test 56 | build-depends: doctest >= 0.17 && < 0.22 57 | ghc-options: -threaded 58 | 59 | library 60 | import: common-options 61 | hs-source-dirs: src 62 | exposed-modules: Chapter1 63 | Chapter2 64 | Chapter3 65 | Chapter4 66 | 67 | test-suite learn4haskell-test 68 | import: common-options 69 | type: exitcode-stdio-1.0 70 | hs-source-dirs: test 71 | main-is: Spec.hs 72 | other-modules: Test.Chapter1 73 | Test.Chapter2 74 | Test.Chapter3 75 | Test.Chapter4 76 | build-depends: learn4haskell 77 | , hspec >= 2.7.4 && < 2.11 78 | , hspec-hedgehog 79 | , hedgehog >= 1.0.2 && < 2 80 | ghc-options: -threaded 81 | -rtsopts 82 | -with-rtsopts=-N 83 | 84 | test-suite doctest-chapter1 85 | import: common-doctest 86 | type: exitcode-stdio-1.0 87 | main-is: DoctestChapter1.hs 88 | 89 | test-suite doctest-chapter2 90 | import: common-doctest 91 | type: exitcode-stdio-1.0 92 | main-is: DoctestChapter2.hs 93 | 94 | test-suite doctest-chapter3 95 | import: common-doctest 96 | type: exitcode-stdio-1.0 97 | main-is: DoctestChapter3.hs 98 | 99 | test-suite doctest-chapter4 100 | import: common-doctest 101 | type: exitcode-stdio-1.0 102 | main-is: DoctestChapter4.hs 103 | -------------------------------------------------------------------------------- /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 the tests for the Chapter you are working on 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 me, @vrom911, 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 a 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 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. In 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 this 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 the 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 the 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 the 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 | 213 | >>> :t 'a' 214 | 215 | >>> :t 42 216 | 217 | 218 | A pair of boolean and char: 219 | >>> :t (True, 'x') 220 | 221 | 222 | Boolean negation: 223 | >>> :t not 224 | 225 | 226 | Boolean 'and' operator: 227 | >>> :t (&&) 228 | 229 | 230 | Addition of two numbers: 231 | >>> :t (+) 232 | 233 | 234 | Maximum of two values: 235 | >>> :t max 236 | 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 | parentheses. 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 __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 type, including 'Ints, 'Doubles, 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 the types of 301 | functions and operators first. Remember this from the previous task? ;) 302 | 303 | >>> 1 + 2 304 | 305 | 306 | >>> 10 - 15 307 | 308 | 309 | >>> 10 - (-5) -- negative constants require () 310 | 311 | 312 | >>> (3 + 5) < 10 313 | 314 | 315 | >>> True && False 316 | 317 | 318 | >>> 10 < 20 || 20 < 5 319 | 320 | 321 | >>> 2 ^ 10 -- power 322 | 323 | 324 | >>> not False 325 | 326 | 327 | >>> div 20 3 -- integral division 328 | 329 | 330 | >>> mod 20 3 -- integral division remainder 331 | 332 | 333 | >>> max 4 10 334 | 335 | 336 | >>> min 5 (max 1 2) 337 | 338 | 339 | >>> max (min 1 10) (min 5 7) 340 | 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 can 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 an __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 the 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 the 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 x y = (x + y) * (x + y) 433 | 434 | 435 | {- | 436 | =⚔️= Task 4 437 | 438 | Implement the function that takes an integer value and returns the next 'Int'. 439 | 440 | >>> next 10 441 | 11 442 | >>> next (-4) 443 | -3 444 | 445 | ♫ NOTE: The current function body is defined using a special function called 446 | "error". Don't panic, it is not broken. 'error' is like a placeholder, that 447 | evaluates to an exception if you try evaluating it. And it also magically fits 448 | every type 。.☆.*。. No need to worry much about "error" here, just replace the 449 | function body with the proper implementation. 450 | -} 451 | next :: Int -> Int 452 | next x = error "next: not implemented!" 453 | 454 | {- | 455 | After you've implemented the function (or even during the implementation), you 456 | can run it in GHCi with your input. To do so, first, you need to load the module 457 | with the function using the ":l" (short for ":load") command. 458 | 459 | ghci> :l src/Chapter1.hs 460 | 461 | After that, you can call the 'next' function as you already know how to do that. 462 | Or any other function defined in this module! But remember, that you need to 463 | reload the module again after you change the file's content. You can reload the 464 | last loaded module by merely typing the ":r" command (no need to specify the 465 | name again). 466 | 467 | ghci> :r 468 | 469 | A typical workflow looks like this: you load the module once using the ":l" 470 | command, and then you should reload it using the ":r" command each time you 471 | change it and want to check your changes. 472 | -} 473 | 474 | {- | 475 | =⚔️= Task 5 476 | 477 | Implement a function that returns the last digit of a given number. 478 | 479 | >>> lastDigit 42 480 | 2 481 | 482 | 🕯 HINT: use the `mod` function 483 | 484 | ♫ NOTE: You can discover possible functions to use via Hoogle: 485 | https://hoogle.haskell.org/ 486 | 487 | Hoogle lets you search Haskell functions either by name or by type. You can 488 | enter the type you expect a function to have, and Hoogle will output relevant 489 | results. Or you can try to guess the function name, search for it and check 490 | whether it works for you! 491 | -} 492 | -- DON'T FORGET TO SPECIFY THE TYPE IN HERE 493 | lastDigit n = error "lastDigit: Not implemented!" 494 | 495 | 496 | {- | 497 | =⚔️= Task 6 498 | 499 | Implement a function, that takes two numbers and returns the one closer to zero: 500 | 501 | >>> closestToZero 10 5 502 | 5 503 | >>> closestToZero (-7) 3 504 | 3 505 | 506 | 507 | 🕯 HINT: You can use the 'abs' function and the __if-then-else__ Haskell syntax 508 | for this task. 509 | 510 | 'if-then-else' is a language construct for an expression that returns only one 511 | branch depending on the checked condition. For example: 512 | 513 | >>> if even 10 then 0 else 1 514 | 0 515 | 516 | The 'if-then-else' constructs must always have both __then__ and __else__ 517 | branches because it is an expression and it must always return some value. 518 | 519 | 👩‍🔬 Due to lazy evaluation in Haskell, only the expression from the branch 520 | satisfying the check will be returned and, therefore, evaluated. 521 | -} 522 | closestToZero :: Int -> Int -> Int 523 | closestToZero x y = error "closestToZero: not implemented!" 524 | 525 | 526 | {- | 527 | =⚔️= Task 7 528 | Write a function that returns the middle number among three given numbers. 529 | 530 | >>> mid 3 1 2 531 | 2 532 | 533 | 🕯 HINT: When checking multiple conditions, it is more convenient to use the 534 | language construct called "guards" instead of multiple nested 'if-then-else' 535 | expressions. The syntax of guards is the following: 536 | 537 | @ 538 | sign :: Int -> Int 539 | sign n 540 | | n < 0 = (-1) 541 | | n == 0 = 0 542 | | otherwise = 1 543 | @ 544 | 545 | You define different conditions in different branches, started by the '|' 546 | symbol. And the functions check them from top to bottom, returning the first 547 | value after "=" where the condition is true. 548 | 549 | ♫ NOTE: The "=" sign goes after each branch, respectively. 550 | 551 | ♫ NOTE: It is essential to have the same indentation before each branch "|"! 552 | Remember, that Haskell is an indentation- and layout-sensitive language. 553 | 554 | Casual reminder about adding top-level type signatures for all functions :) 555 | -} 556 | 557 | mid x y z = error "mid: not implemented!" 558 | 559 | {- | 560 | =⚔️= Task 8 561 | 562 | Implement a function that checks whether a given character is a vowel. 563 | 564 | 🕯 HINT: use guards 565 | 566 | >>> isVowel 'a' 567 | True 568 | >>> isVowel 'x' 569 | False 570 | -} 571 | isVowel c = error "isVowel: not implemented!" 572 | 573 | 574 | {- | 575 | == Local variables and functions 576 | 577 | So far, we've been playing only with simple expressions and function 578 | definitions. However, in some cases, expressions may become complicated, and it 579 | could make sense to introduce some helper variables. 580 | 581 | You can use the let-in construct in Haskell to define variables. 582 | Here goes an example: 583 | 584 | @ 585 | half :: Int -> Int 586 | half n = let halfN = div n 2 in halfN 587 | @ 588 | 589 | ♫ NOTE: __let-in__ is also an expression! You can't just define variables; you 590 | also need to return some expression that may use defined variables. 591 | 592 | The syntax for defining multiple variables requires to care about indentation 593 | more, but there is nothing special in it as well: 594 | 595 | @ 596 | halfAndTwice :: Int -> (Int, Int) 597 | halfAndTwice n = 598 | let halfN = div n 2 599 | twiceN = n * 2 600 | in (halfN, twiceN) 601 | @ 602 | 603 | In addition to let-in (or sometimes even alternatively to let-in) you can use 604 | the __where__ construct to define local variables and functions. 605 | And, again, the example: 606 | 607 | @ 608 | pythagoras :: Double -> Double -> Double 609 | pythagoras a b = square a + square b 610 | where 611 | square :: Double -> Double 612 | square x = x ^ 2 613 | @ 614 | 615 | You can define multiple functions inside __where__! 616 | Just remember to keep proper indentation. 617 | -} 618 | 619 | {- | 620 | =⚔️= Task 9 621 | 622 | Implement a function that returns the sum of the last two digits of a number. 623 | 624 | >>> sumLast2 42 625 | 6 626 | >>> sumLast2 134 627 | 7 628 | >>> sumLast2 1 629 | 1 630 | 631 | Try to introduce variables in this task (either with let-in or where) to avoid 632 | specifying complex expressions. 633 | -} 634 | 635 | sumLast2 n = error "sumLast2: Not implemented!" 636 | 637 | 638 | {- | 639 | =💣= Task 10* 640 | 641 | You did it! You've passed all the challenges in your first training! 642 | Congratulations! 643 | Now, are you ready for the boss at the end of this training??? 644 | 645 | Implement a function that returns the first digit of a given number. 646 | 647 | >>> firstDigit 230 648 | 2 649 | >>> firstDigit 5623 650 | 5 651 | 652 | You need to use recursion in this task. Feel free to return to it later, if you 653 | aren't ready for this boss yet! 654 | -} 655 | 656 | firstDigit n = error "firstDigit: Not implemented!" 657 | 658 | 659 | {- 660 | You did it! Now it is time to open a pull request with your changes 661 | and summon @vrom911 for the review! 662 | -} 663 | 664 | {- 665 | =📜= Additional resources 666 | 667 | Modules: http://learnyouahaskell.com/modules 668 | Let vs where: https://wiki.haskell.org/Let_vs._Where 669 | Packages and modules in Haskell: https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/packages.html 670 | -} 671 | -------------------------------------------------------------------------------- /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 me, @vrom911, 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 | * https://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 type (but consistent inside one list). This feature is called 107 | __parametric polymorphism__. It will be explained in more detail 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.), where 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 | 140 | 141 | String is a list of characters: 142 | >>> :t "some string" 143 | 144 | 145 | Empty list: 146 | >>> :t [] 147 | 148 | 149 | Append two lists: 150 | >>> :t (++) 151 | 152 | 153 | Prepend an element at the beginning of a list: 154 | >>> :t (:) 155 | 156 | 157 | Reverse a list: 158 | >>> :t reverse 159 | 160 | 161 | Take first N elements of a list: 162 | >>> :t take 163 | 164 | 165 | Create a list from N same elements: 166 | >>> :t replicate 167 | 168 | 169 | Split a string by line breaks: 170 | >>> :t lines 171 | 172 | 173 | Join a list of strings with line breaks: 174 | >>> :t unlines 175 | 176 | 177 | -} 178 | 179 | {- | 180 | =⚔️= Task 2 181 | 182 | To understand the list type better, it is also beneficial to play with 183 | 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 | 190 | >>> [] ++ [1, 4] -- [] is an empty list 191 | 192 | >>> 3 : [1, 2] 193 | 194 | >>> 4 : 2 : [5, 10] -- prepend multiple elements 195 | 196 | >>> [1 .. 10] -- list ranges 197 | 198 | >>> [10 .. 1] 199 | 200 | >>> [10, 9 .. 1] -- backwards list with explicit step 201 | 202 | >>> length [4, 10, 5] -- list length 203 | 204 | >>> replicate 5 True 205 | 206 | >>> take 5 "Hello, World!" 207 | 208 | >>> drop 5 "Hello, World!" 209 | 210 | >>> zip "abc" [1, 2, 3] -- convert two lists to a single list of pairs 211 | 212 | >>> words "Hello Haskell World!" -- split the string into the list of words 213 | 214 | 215 | 216 | 👩‍🔬 Haskell has a lot of syntax sugar. In the case with lists, any 217 | list literal like "[3, 1, 2]" is syntax sugar for prepending elements 218 | at the empty list: "3 : 1 : 2 : []". 219 | 220 | Don't forget that lists are the containers of the same-type 221 | elements. Meaning, you can't combine lists of different types in any 222 | situation. Let's try appending a list of booleans and a string (list 223 | of characters) to see the error message: 224 | 225 | ghci> [True, False] ++ "string" 226 | :4:18: error: 227 | • Couldn't match type ‘Char’ with ‘Bool’ 228 | Expected type: [Bool] 229 | Actual type: [Char] 230 | • In the second argument of ‘(++)’, namely ‘"string"’ 231 | In the expression: [True, False] ++ "string" 232 | In an equation for ‘it’: it = [True, False] ++ "string" 233 | 234 | -} 235 | 236 | {- | 237 | =🛡= Immutability 238 | 239 | At this point in our training, you need to learn that all values in 240 | Haskell are immutable! Woohoo! But what does it mean for us? 241 | 242 | It means that when you apply a function to some variable, the value is 243 | not changed. Instead, you create a new value each time. 244 | 245 | >>> import Data.List (sort) -- sort is not in Prelude 246 | >>> x = [3, 1, 2] -- you can assign values to variables in GHCi 247 | >>> sort x 248 | [1,2,3] 249 | >>> x 250 | [3,1,2] 251 | 252 | The 'sort' function returns a new sorted list. It doesn't change the 253 | original list, so you don't need to worry about accidentally spoiling 254 | values of variables you defined before. 255 | -} 256 | 257 | {- | 258 | =🛡= List implementation 259 | 260 | Let's talk a bit about list implementation details. Lists in Haskell 261 | are implemented as __linked lists__ (or cons-lists). And because 262 | everything in Haskell is immutable, adding elements at the beginning 263 | of the lists is cheap. Haskell doesn't need to allocate new memory and 264 | copy the whole list there; it can just create a new list from a new 265 | element and a pointer to an already existing list. In other words, 266 | tails of lists are shared. 267 | 268 | For these reasons, adding elements to and extracting elements from the 269 | beginning of a list is much cheaper and faster than working with the 270 | end of the list. 271 | 272 | In some sense, lists are similar to trains. Let's look at an illustration 273 | of a two-element list: 274 | 275 | . . . . . o o o o o 276 | _________ _________ ____ o 277 | | y | | x | |[]\_n][. 278 | _|________|_o_|________|_o_|__|____)< 279 | oo oo oo oo oo 00-oo\_ 280 | 281 | y : x : [] 282 | 283 | You can see that adding new elements (railway carriages) to the left 284 | is easy: you just need to connect them to the last element in the 285 | chain. 286 | 287 | . . . . . o o o o o 288 | _________ _________ _________ ____ o 289 | | z | | y | | x | |[]\_n][. 290 | _|________|_o_|________|_o_|________|_o_|__|____)< 291 | oo oo oo oo oo oo oo 00-oo\_ 292 | 293 | z : y : x : [] 294 | 295 | But imagine how much difficult it would be to add new carriages to the right? 296 | 297 | . . . . . o o o o o 298 | _________ _________ _________ ____ o 299 | | y | | x | | z | |[]\_n][. 300 | _|________|_o_|________|_o_|________|_o_|__|____)< 301 | oo oo oo oo oo oo oo 00-oo\_ 302 | 303 | y : x : z : [] 304 | 305 | You can't simply attach a new carriage anymore. You need to detach the 306 | locomotive, maybe move trains around the railway a bit for the proper 307 | position, and only then attach everything back again. The same thing 308 | with adding elements to the end of the list — it is a slow and costly 309 | process. 310 | 311 | -} 312 | 313 | {- | 314 | =⚔️= Task 3 315 | 316 | Let's write our first function to process lists in Haskell! Your first 317 | implementation task is to write a function that returns all elements 318 | of a list between two given positions inclusive (starting from zero). 319 | 320 | Remember that each function returns a new list. 321 | 322 | >>> subList 3 5 [1 .. 10] 323 | [4,5,6] 324 | >>> subList 3 0 [True, False, False, True, False] 325 | [] 326 | 327 | ♫ NOTE: When implementing, think about various corner cases. You 328 | should return an empty list when given numbers that are negative. 329 | 330 | And also don't forget to check the 'Data.List' module. It is full of 331 | yummy functions. 332 | 333 | Don't forget that you can load this module in GHCi to call functions 334 | from it! 335 | 336 | ghci> :l src/Chapter2.hs 337 | -} 338 | subList :: Int -> Int -> [a] -> [a] 339 | subList = error "subList: Not implemented!" 340 | 341 | {- | 342 | =⚔️= Task 4 343 | 344 | Implement a function that returns only the first half of a given list. 345 | 346 | >>> firstHalf [3, 4, 1, 2] 347 | [3,4] 348 | >>> firstHalf "bca" 349 | "b" 350 | -} 351 | -- PUT THE FUNCTION TYPE IN HERE 352 | firstHalf l = error "firstHalf: Not implemented!" 353 | 354 | 355 | {- | 356 | =🛡= Pattern matching 357 | 358 | One of the coolest and most powerful features of Functional 359 | Programming is __pattern matching__. This feature allows you to match 360 | on different values of a type and produce results based on 361 | patterns. The syntax of using pattern matching is similar to defining 362 | ordinary functions, but instead of using variable names, you use the 363 | values. 364 | 365 | For example, the "not" function that returns "the other" boolean is 366 | implemented like this: 367 | 368 | @ 369 | not :: Bool -> Bool 370 | not True = False 371 | not False = True 372 | @ 373 | 374 | To perform pattern-matching, repeat a function name as many times as 375 | many patterns you want to cover. The cool thing about Haskell is that 376 | the compiler warns you if you forget to cover some cases. So you 377 | always can be sure that your patterns are exhaustive! 378 | 379 | Note that you can pattern match on a variable too! Variable is like a 380 | pattern that matches any value and gives it a name. You can think of 381 | variables in function definitions as special cases of pattern 382 | matching. 383 | 384 | You can pattern match on numbers as well! For example, if you want to 385 | write a function that checks whether the given number is zero, you can 386 | write it in the following way: 387 | 388 | @ 389 | isZero :: Int -> Bool 390 | isZero 0 = True 391 | isZero n = False 392 | @ 393 | 394 | Instead of "isZero n = False" you can write "isZero _ = False". The 395 | symbol "_" (underscore) is called __hole__, and it is used when we 396 | don't care about the value of a variable. It is like a pattern that 397 | always matches (the same as a variable), but we don't use its value. 398 | 399 | 👩‍🔬 Unlike 'switch' and 'case' in other languages, that try to go 400 | through each switch and perform all actions in there until it reaches 401 | the breakpoint, pattern matching on function parameters in Haskell 402 | always returns only a single expression for a single branch. You can 403 | think of this process as trying to match all patterns from the first 404 | one to the last one and returning the expression on the right side 405 | of "=" only for the pattern that matches first. This is a helpful 406 | thing to keep in mind, especially when you have overlapping patterns. 407 | Also note that, if no pattern matches the value, the function fails 408 | at runtime. 409 | 410 | 411 | In addition to pattern matching in the function definition, you can 412 | also use the __case-of__ expression. With case-of, you specify the 413 | patterns to match and expressions to return depending on a pattern 414 | inside the function body. The main difference between 'case-of' and 415 | top-level pattern matching is the fact that 'case' uses arrows (->) 416 | instead of "=" for branch results. The "case" is often helpful when 417 | function names are long, or pattern-matching on functions is awkward. 418 | 419 | To understand case-of, let's look at a function that takes two numbers 420 | and a symbol, representing a math operation on these symbols. 421 | 422 | @ 423 | evalOperation :: Char -> Int -> Int -> Int 424 | evalOperation op x y = case op of 425 | '+' -> x + y 426 | '-' -> x - y 427 | '*' -> x * y 428 | '/' -> div x y 429 | _ -> 0 430 | @ 431 | 432 | ♫ NOTE: Each branch with a pattern should have the same alignment! 433 | Remember that Haskell is a _layout-sensitive_ language. Also, note 434 | that in the last line, "_" goes directly under the single quote to 435 | have the same indentation 🔍. You can try copying the function and 436 | change the indentation to see the parser error (which is not that 437 | clever to identify the indentation errors). 438 | 439 | Since we are talking about lists in this chapter, let's see how we can 440 | use pattern-matching on them! It turns out, pattern matching on lists 441 | is an effective and inevitable technique. 442 | 443 | We can pattern-match on list literals directly: 444 | 445 | @ 446 | isEmpty :: [a] -> Bool 447 | isEmpty [] = True 448 | isEmpty _ = False 449 | 450 | sumThree :: [Int] -> Int 451 | sumThree [x, y, z] = x + y + z 452 | sumThree _ = 0 453 | 454 | onlyTwoElements :: [a] -> Bool 455 | onlyTwoElements [_, _] = True 456 | onlyTwoElements _ = False 457 | @ 458 | 459 | Remember the ":" operator to add elements at the beginning of a list? 460 | Turns out, in case of lists this operator can be used for pattern 461 | matching as well! Isn't this cool? For example: 462 | 463 | @ 464 | -- return the first element of the list or default if the list is empty 465 | headOrDef :: a -> [a] -> a 466 | headOrDef def [] = def 467 | headOrDef _ (x:_) = x 468 | 469 | -- check if the first list element is zero 470 | firstIsZero :: [Int] -> Bool 471 | firstIsZero (0:_) = True 472 | firstIsZero _ = False 473 | 474 | -- check that a list has at least two elements 475 | atLeastTwo :: [a] -> Bool 476 | atLeastTwo (_ : _ : _) = True 477 | atLeastTwo _ = False 478 | @ 479 | 480 | When matching on the ":" pattern, the first element of the list goes 481 | to the left side of ':' and the tail of the list goes to the right 482 | side. You can have even nested patterns (as in the last example 483 | above). In other words, when writing a pattern like "(x:y:xs)", it is 484 | the same as writing "(x:(y:xs))". 485 | 486 | ♫ NOTE: Often, pattern matching can be replaced with conditional 487 | checks (if-then-else, guards) and vice versa. In some cases 488 | pattern-matching can be more efficient; in other cases, it can produce 489 | cleaner code or even more maintainable code due to pattern coverage 490 | checker from the Haskell compiler. 491 | -} 492 | 493 | {- | 494 | =⚔️= Task 5 495 | 496 | Implement a function that checks whether the third element of a list 497 | is the number 42. 498 | 499 | >>> isThird42 [1, 2, 42, 10] 500 | True 501 | >>> isThird42 [42, 42, 0, 42] 502 | False 503 | -} 504 | isThird42 = error "isThird42: Not implemented!" 505 | 506 | 507 | {- | 508 | =🛡= Recursion 509 | 510 | Often, when writing in a functional style, you end up implementing 511 | __recursive__ functions. Recursive functions are nothing more than 512 | calling the function itself from the body of the same function. 513 | 514 | Of course, you need some stopping conditions to exit the function 515 | eventually, and you need to think carefully whether your function ever 516 | reaches the stop condition. However, having pattern-matching in our 517 | arsenal of skills significantly increases our chances of writing 518 | correct functions. Nevertheless, you should think mindfully on how 519 | your recursive function behaves on different corner-cases. 520 | 521 | A simple recursive function can divide a number by 2 until it reaches 522 | zero: 523 | 524 | @ 525 | divToZero :: Int -> Int 526 | divToZero 0 = 0 527 | divToZero n = divToZero (div n 2) 528 | @ 529 | 530 | But as you can see, the function is not that helpful per se. Often you 531 | implement a helper function with some accumulator in order to collect 532 | some information during recursive calls. 533 | 534 | For example, we can patch the previous function to count the number of 535 | steps we need to take in order to reduce the number to zero. 536 | 537 | 🤔 Blitz question: can you guess what this number represents? 538 | 539 | @ 540 | divToZero :: Int -> Int 541 | divToZero n = go 0 n 542 | where 543 | go :: Int -> Int -> Int 544 | go acc 0 = acc 545 | go acc n = go (acc + 1) (div n 2) 546 | @ 547 | 548 | 👩‍🔬 The pattern of having a recursive helper function is called "Recursive go": 549 | 550 | * https://kowainik.github.io/posts/haskell-mini-patterns#recursive-go 551 | 552 | One of the most useful capabilities of pattern matching on lists is 553 | the ability to implement recursive functions with them as well! 554 | 555 | ♫ NOTE: The canonical naming scheme for such patterns is `(x:xs)` 556 | where x is the first element of the list, and xs — rest of the list 557 | (which is a list as well that even could be empty). 558 | 559 | @ 560 | -- list length 561 | len :: [a] -> Int 562 | len [] = 0 563 | len (_:xs) = 1 + len xs 564 | 565 | -- add 10 to every number of a list 566 | addEvery10 :: [Int] -> [Int] 567 | addEvery10 [] = [] 568 | addEvery10 (x:xs) = (x + 10) : addEvery10 xs 569 | @ 570 | 571 | When writing such functions, we usually handle two cases: empty list 572 | and non-empty list (list with at least one element in the beginning) 573 | and we decide what to do in each case. 574 | 575 | Most of the time, the case with a non-empty list uses the recursive 576 | call to the function itself. 577 | 578 | An example of a standard Haskell function is 'concat' that takes a 579 | list of lists and returns a flat list, appending all intermediate 580 | lists: 581 | 582 | @ 583 | concat :: [[a]] -> [a] 584 | concat [] = [] 585 | concat (x:xs) = x ++ concat xs 586 | @ 587 | 588 | And it works like this: 589 | 590 | >>> concat [[2, 1, 3], [1, 2, 3, 4], [0, 5]] 591 | [2,1,3,1,2,3,4,0,5] 592 | -} 593 | 594 | 595 | {- | 596 | =⚔️= Task 6 597 | 598 | Implement a function that duplicates each element of the list 599 | 600 | 🕯 HINT: Use recursion and pattern matching on the list. 601 | 602 | >>> duplicate [3, 1, 2] 603 | [3,3,1,1,2,2] 604 | >>> duplicate "abac" 605 | "aabbaacc" 606 | 607 | -} 608 | duplicate :: [a] -> [a] 609 | duplicate = error "duplicate: Not implemented!" 610 | 611 | 612 | {- | 613 | =⚔️= Task 7 614 | Write a function that takes elements of a list only in even positions. 615 | 616 | 🕯 HINT: You need to write a recursive function that pattern matches 617 | on the list structure. Your function will have several cases and 618 | probably needs to use nested pattern matching on lists of size at 619 | least 2. Alternatively, you can use the "Recursive go" pattern. 620 | 621 | >>> takeEven [2, 1, 3, 5, 4] 622 | [2,3,4] 623 | -} 624 | takeEven = error "takeEven: Not implemented!" 625 | 626 | {- | 627 | =🛡= Higher-order functions 628 | 629 | Some functions can take other functions as arguments. Such functions 630 | are called __higher-order functions__ (HOFs). Check the types of some 631 | common HOF list functions: 632 | 633 | >>> :t filter 634 | filter :: (a -> Bool) -> [a] -> [a] 635 | >>> :t map 636 | map :: (a -> b) -> [a] -> [b] 637 | 638 | And few usage examples of those functions: 639 | 640 | >>> filter even [1..10] -- keep only even elements in the list 641 | [2,4,6,8,10] 642 | >>> map not [True, False, True] -- maps the 'not' function over each element of the given list 643 | [False,True,False] 644 | 645 | Having HOFs in your language means that functions can be treated in 646 | the same way as any other values and expressions: 647 | 648 | ✲ You can pass functions as arguments 649 | ✲ You can return functions as results 650 | ✲ You can compose functions easily to create new functions 651 | ✲ You can have lists of functions 652 | ✲ And much more! 653 | 654 | The ability to create __lambdas__ (or anonymous functions) nicely 655 | complements the concept of HOF. For example, we can easily add 656 | number 3 to each element of the list by introducing a lambda function: 657 | 658 | >>> map (\x -> x + 3) [0..5] 659 | [3,4,5,6,7,8] 660 | 661 | The syntax of lambda functions is somewhat similar to normal ones, 662 | except for you don't need to think about its name, which is 663 | awesome. To establish the start of the lambda function, you should 664 | write "\" which is a bit similar to the lambda symbol — λ. Then you 665 | specify space-separated arguments. Instead of the "=" in the ordinary 666 | function body, you should write "->" and then you can use these 667 | arguments and all variables in scope inside the lambda-body. 668 | 669 | These are equal: 670 | 671 | @ 672 | foo a b = a + b 673 | --and 674 | \a b -> a + b 675 | @ 676 | 677 | What's even cooler is the ability to __apply functions partially__ 678 | This means that you can provide only some arguments to a function and 679 | treat the result as a function itself! You already know the 'div' 680 | function: it takes two numbers and returns the result of the integral 681 | division of those numbers. But if we apply 'div' to a number 10 682 | partially, we will get a new function that takes only one number and 683 | returns the result of dividing 10 by that number. You can check the 684 | difference by inspecting the types of corresponding expressions in 685 | GHCi: 686 | 687 | >>> :t +d div 688 | div :: Integer -> Integer -> Integer 689 | >>> :t +d div 10 690 | div 10 :: Integer -> Integer 691 | 692 | 693 | This fact can be used to pass partial applications of some functions 694 | directly to other functions. 695 | 696 | >>> map (div 10) [1 .. 10] 697 | [10,5,3,2,2,1,1,1,1,1] 698 | 699 | You can apply operators partially too! 700 | 701 | >>> filter (< 3) [2, 1, 3, 4, 0, 5] 702 | [2,1,0] 703 | >>> map (* 2) [1..5] 704 | [2,4,6,8,10] 705 | 706 | The implementation of the "map" function is pretty straightforward if 707 | you are already familiar with function application, recursion and 708 | pattern matching. 709 | 710 | @ 711 | map :: (a -> b) -> [a] -> [b] 712 | map _ [] = [] 713 | map f (x:xs) = f x : map f xs 714 | @ 715 | 716 | Now you can see that there is nothing magic in HOFs in the end! 717 | -} 718 | 719 | {- | 720 | =⚔️= Task 8 721 | 722 | Implement a function that repeats each element as many times as the 723 | value of the element itself 724 | 725 | >>> smartReplicate [3, 1, 2] 726 | [3,3,3,1,2,2] 727 | 728 | 🕯 HINT: Use combination of 'map' and 'replicate' 729 | -} 730 | smartReplicate :: [Int] -> [Int] 731 | smartReplicate l = error "smartReplicate: Not implemented!" 732 | 733 | {- | 734 | =⚔️= Task 9 735 | 736 | Implement a function that takes a number, a list of lists and returns 737 | the list with only those lists that contain a passed element. 738 | 739 | >>> contains 3 [[1, 2, 3, 4, 5], [2, 0], [3, 4]] 740 | [[1,2,3,4,5],[3,4]] 741 | 742 | 🕯 HINT: Use the 'elem' function to check whether an element belongs to a list 743 | -} 744 | contains = error "contains: Not implemented!" 745 | 746 | 747 | {- | 748 | =🛡= Eta-reduction 749 | 750 | Another consequence of the HOFs and partial application is 751 | __eta-reduction__. This term is used to call the simplification of 752 | functions over their arguments. Specifically, if we have `foo x = bar 753 | 10 x`, this precisely means that `foo` is a partially applied `bar 754 | 10`. And we can write it like `foo = bar 10`. 755 | 756 | This concept can be used to write functions as well. 757 | 758 | For example, 759 | 760 | @ 761 | nextInt :: Int -> Int 762 | nextInt n = add 1 n 763 | @ 764 | 765 | Could be written with the eta-reduced form: 766 | 767 | @ 768 | nextInt :: Int -> Int 769 | nextInt = add 1 770 | @ 771 | 772 | ♫ NOTE: See that the initial type of the function is not changed and 773 | it works absolutely the same. We just can skip the last argument and 774 | amend its usage in the function body. 775 | -} 776 | 777 | {- | 778 | =⚔️= Task 10 779 | 780 | Let's now try to eta-reduce some of the functions and ensure that we 781 | mastered the skill of eta-reducing. 782 | -} 783 | divideTenBy :: Int -> Int 784 | divideTenBy x = div 10 x 785 | 786 | -- TODO: type ;) 787 | listElementsLessThan x l = filter (< x) l 788 | 789 | -- Can you eta-reduce this one??? 790 | pairMul xs ys = zipWith (*) xs ys 791 | 792 | {- | 793 | =🛡= Lazy evaluation 794 | 795 | Another unique Haskell feature is __lazy evaluation__. Haskell is lazy 796 | by default, which means that it doesn't evaluate expressions when not 797 | needed. The lazy evaluation has many benefits: avoid doing redundant 798 | work, provide more composable interfaces. And in this section, we will 799 | focus on Haskell's ability to create infinite data structures and work 800 | with them! 801 | 802 | For instance, the Haskell standard library has the 'repeat' function 803 | that returns an infinite list created from a given element. Of course, 804 | we can't print an infinite list to our terminal; it will take an 805 | infinite amount of time! But we can work with parts of it due to lazy 806 | evaluation: 807 | 808 | >>> take 5 (repeat 0) 809 | [0,0,0,0,0] 810 | 811 | Another useful construction is an infinite list of all numbers! 812 | 813 | >>> take 4 [0 ..] 814 | [0,1,2,3] 815 | 816 | Isn't this awesome?! Now we can unleash the real power of the 817 | Infinity Stone! 818 | 819 | ♫ NOTE: Infinite lists bring great power, but with great power comes 820 | great responsibility. Functions like 'length' hang when called on 821 | infinite lists. So make sure you think about such corner cases in the 822 | implementations of your functions if you expect them to work on the 823 | infinite lists. 824 | -} 825 | 826 | {- | 827 | =⚔️= Task 11 828 | 829 | Rotating a list by a single element is the process of moving the first 830 | element of the list to the end. 831 | 832 | Implement a function to rotate a given finite list by N elements. Try 833 | to do it more efficiently than rotating by a single element N times. 834 | 835 | On invalid input (negative rotation coefficient) it should return an empty 836 | list. 837 | 838 | >>> rotate 1 [1,2,3,4] 839 | [2,3,4,1] 840 | >>> rotate 3 [1,2,3,4] 841 | [4,1,2,3] 842 | 843 | 🕯 HINT: Use the 'cycle' function 844 | -} 845 | rotate = error "rotate: Not implemented!" 846 | 847 | {- | 848 | =💣= Task 12* 849 | 850 | Now you should be ready for the final boss at the end of this chapter! 851 | To defeat the boss, implement the reversing function that takes a list 852 | and reverses it. 853 | 854 | >>> rewind [1 .. 5] 855 | [5,4,3,2,1] 856 | 857 | ♫ NOTE: The Haskell standard library already provides the "reverse" 858 | function, but in this task, you need to implement it manually. No 859 | cheating! 860 | -} 861 | rewind = error "rewind: Not Implemented!" 862 | 863 | 864 | {- 865 | You did it! Now it is time to open pull request with your changes 866 | and summon @vrom911 for the review! 867 | -} 868 | -------------------------------------------------------------------------------- /src/Chapter3.hs: -------------------------------------------------------------------------------- 1 | {- 👋 Welcome to Chapter Three of our journey, Courageous Knight! 2 | 3 | Glad to see you back for more challenges. You fought 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 me, @vrom911, 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 | {- 57 | =🛡= Types in Haskell 58 | 59 | Let's talk about types. 60 | In the previous chapters, we have already worked a lot with them. And we bet, 61 | you can name a few at this point. 62 | 63 | But we mostly were operating on primitive types, like 'Int' or 'Bool'. We saw 64 | the function (->) a lot! We briefly touched tuples, but we learned that lists 65 | are far more complicated! 66 | 67 | Haskell has several different ways to create entirely new data types. Let's talk 68 | about them all and master our skill of data types construction. 69 | -} 70 | 71 | {- | 72 | =🛡= Type aliases 73 | 74 | The simplest way to introduce a new type in Haskell is __type aliases__. Type 75 | aliases are nothing more than just other names to already existing types. 76 | A few examples: 77 | 78 | @ 79 | type IntList = [Int] 80 | type IntPair = (Int, Int) 81 | @ 82 | 83 | Type aliases are just syntactic sugar and would be replaced by the real types 84 | during compilation, so they don't bring too much to the table of data types. 85 | However, they may make the life of a developer easier in some places. 86 | 87 | One of the most common type aliases is 'String' that we already mentioned in the 88 | List section of the previous chapter. And it is defined in the following way: 89 | 90 | @ 91 | type String = [Char] 92 | @ 93 | 94 | Now it makes much more sense of why 'String' is related to lists. You can see 95 | that any list function could be used on 'String's as well. 96 | 97 | 👩‍🔬 Due to the implementation details of lists, such representation of String 98 | is highly inefficient. It is unfortunate that the list of characters is the 99 | default String type. Experienced Haskellers use more efficient string types 100 | 'Text' and 'ByteString' from Haskell libraries: 'text' and 'bytestring' 101 | correspondingly. But, for the simplicity of this training, we are using 102 | 'String'. 103 | 104 | Another common type alias in the standard library is "FilePath", which is the 105 | same as 'String' (which is the same as '[Char]'): 106 | 107 | @ 108 | type FilePath = String 109 | @ 110 | 111 | Usually, you are encouraged to introduce new data types instead of using aliases 112 | for existing types. But it is still good to know about type aliases. They have a 113 | few use-cases, and you can meet them in various Haskell libraries as well. 114 | -} 115 | 116 | {- | 117 | =🛡= ADT 118 | 119 | Let's not limit ourselves with just type aliases and define some real new data 120 | structures. Are we the creators of new worlds or what? 121 | 122 | Type in Haskell is like a box of information description that the object of that 123 | type should contain. 124 | 125 | Haskell uses Algebraic Data Types (ADT) system of types. That means that there 126 | are two types of types: product and sum types. 127 | 128 | To give you some basic understanding of the difference between these two, let's 129 | go to the book shop. A book in there represents a product type: each book has 130 | the name, author, cover, pages, etc. And all of these properties are mandatory 131 | and come with the book. Bookshelf, in its turn, is a sum type. Each book in a 132 | shelf is a different type, and you can choose one of them at once (there is no 133 | such book where two or more physical books are sewed together). 134 | 135 | We will show you an example now, just to illustrate all the above and then will 136 | explain each concept separately. Note, this is not real syntax. 137 | 138 | @ 139 | -- Product type 140 | Book: 141 | book name 142 | AND book author 143 | AND book cover 144 | AND book pages 145 | 146 | 147 | -- Sum type 148 | BookShelf: 149 | Good book 1 : {Book} 150 | OR Good book 2 : {Book} 151 | OR Cheap book 3 : {Book} 152 | @ 153 | 154 | 👩‍🔬 We use AND in product types to represent the notion of having all fields at 155 | the same time. In contrast, for the sum types, we use OR to tell only about a 156 | single possibility. AND in logic corresponds to multiplication in math, and OR 157 | corresponds to addition. You see that there is some math theory behind the 158 | concept of data types in Haskell, and that's why they are called Algebraic Data 159 | Types. 160 | -} 161 | 162 | {- | 163 | =🛡= Product type 164 | 165 | Let's now see how product data types look like in Haskell. 166 | 167 | Product type should have a type name, one type constructor (the function that 168 | lets you create the value of the type later) and the description of the fields 169 | it consists of in the view of types. 170 | 171 | When defining a custom type in Haskell, you use the __"data"__ keyword, write 172 | the __type name__ you come up with after it, and then after the "=" sign you 173 | specify the __constructor name__ followed by the __fields__. 174 | 175 | When in action, a custom data type could be used in the following use case. 176 | A definition of a data type for a knight with a name and number of victories 177 | can look like this: 178 | 179 | @ 180 | ┌─ type name 181 | │ 182 | │ ┌─ constructor name (or constructor tag) 183 | │ │ 184 | data Knight = MkKnight String Int 185 | │ │ │ 186 | │ └────┴─── types of fields 187 | │ 188 | └ "data" keyword 189 | @ 190 | 191 | ♫ NOTE: The constructor can have the same name as the type itself. And in most 192 | cases, they are indeed named identically. This is not a problem for Haskell, 193 | because types live in the types namespace, and constructors live in the value 194 | namespace. So there won't be any collisions and misunderstandings from the 195 | compiler side. The names are unambiguous. 196 | 197 | You can use the constructor name, to create a value of the "Knight" type. 198 | 199 | @ 200 | arthur :: Knight 201 | arthur = MkKnight "Arthur" 100 202 | @ 203 | 204 | A constructor is just a function from fields to the type! You can verify this in GHCi: 205 | 206 | ghci> :t MkKnight 207 | MkKnight :: String -> Int -> Knight 208 | 209 | As in a regular function, you need to provide a 'String' and an 'Int' to the 210 | 'MkKnight' constructor in order to get the full-fledged 'Knight'. 211 | 212 | Also, you can write a function that takes a Knight and returns its name. 213 | It is convenient to use pattern matching for that: 214 | 215 | @ 216 | knightName :: Knight -> String 217 | knightName (MkKnight name _) = name 218 | @ 219 | 220 | And you can extract the name in GHCi: 221 | 222 | ghci> knightName arthur 223 | "Arthur" 224 | 225 | It is comfy to have such getters for all types, so Haskell provides a syntax for 226 | defining __records__ — named parameters for the product data type fields. 227 | 228 | Records have a similar syntax for defining in Haskell as (unnamed) ordinary 229 | product types, but fields are specified in the {} separated by a comma. Each 230 | field should have a name and a type in this form: 'fieldName :: FieldType'. 231 | 232 | The same definition of 'Knight' but with records should be written in the 233 | following way: 234 | 235 | @ 236 | data Knight = MkKnight 237 | { knightName :: String 238 | , knightVictories :: Int 239 | } 240 | @ 241 | 242 | The above type definition is equivalent to the one we had before. We just gave 243 | names to our fields for clarity. In addition, we also automatically get 244 | functions "knightName :: Knight -> String" and "knightVictories :: Knight -> 245 | Int". This is what records bring us — free getters! 246 | 247 | The pattern matching on constructors of such records can stay the same. Besides, 248 | you can use field names as getters. 249 | 250 | 👩‍🔬 We are using a particular naming scheme of record field names in record 251 | types in order to avoid name collisions. We add the data type name prefix in 252 | front of each usual field name for that. As we saw, records create getters for 253 | us, which are actually top-level functions. In Haskell, all functions defined at 254 | top-level are available in the whole scope within a module. But Haskell forbids 255 | creating multiple functions with the same name. The Haskell ecosystem has 256 | numerous ways of solving this so-called "record" problem. Still, for simplicity 257 | reasons, we are going to use disambiguation by a prefix which is one of the 258 | standard resolutions for the scope problem. 259 | 260 | In addition to getting top-level getters automatically, you get the following 261 | features for free when using records over unnamed type fields: 262 | 263 | 1. Specify names during constructions — additional visual help for code. 264 | 2. Record-update syntax. 265 | 266 | By default, all functions and constructors work with positional arguments 267 | (unnamed, that are identified by its position in the type declaration). You 268 | write a function or a constructor name first, and then you pass arguments 269 | separated by spaces. But once we declare a product type as a record, we can use 270 | field names for specifying constructor values. That means that the position 271 | doesn't matter anymore as long as we specify the names. So it is like using 272 | named arguments but only for constructors with records. 273 | This is an alternative way of defining values of custom records. 274 | 275 | Let's introduce Sir Arthur properly! 276 | 277 | @ 278 | arthur :: Knight 279 | arthur = MkKnight 280 | { knightName = "Arthur" 281 | , knightVictories = 100 282 | } 283 | @ 284 | 285 | After we created our custom types and defined some values, we may want to change 286 | some fields of our values. But we can't actually change anything! Remember that 287 | all values in Haskell are immutable, and you can't just change a field of some 288 | type. You need to create a new value! Fortunately, for records, we can use the 289 | __record update syntax__. Record update syntax allows creating new objects of a 290 | record type by assigning new values to the fields of some existing record. 291 | 292 | The syntax for record update is the following: 293 | 294 | @ 295 | lancelot :: Knight 296 | lancelot = arthur { knightName = "Lancelot" } 297 | @ 298 | 299 | Without records, we had to write a custom setter function for each field of each 300 | data type. 301 | 302 | @ 303 | setKnightName :: String -> Knight -> Knight 304 | setKnightName newName (MkKnight _ victories) = 305 | MkKnight newName victories 306 | @ 307 | 308 | ♫ NOTE: By default, GHCi doesn't know how to display values of custom types. If 309 | you want to explore custom data types in the REPL, you need to add a magical 310 | "deriving (Show)" line (will be explained later in this chapter) at the end of a 311 | record. Like so: 312 | 313 | @ 314 | data Knight = MkKnight 315 | { knightName :: String 316 | , knightVictories :: Int 317 | } deriving (Show) 318 | @ 319 | 320 | Now GHCi should be able to show the values of your types! Try playing with our 321 | knights in GHCi to get the idea behind records. 322 | 323 | 🕯 HINT: At this point, you may want to be able to enter multi-line strings in 324 | GHCi. Start multi-line blocks by typing the ":{" command, and close such blocks 325 | using the ":}" command. 326 | 327 | ghci> :{ 328 | ghci| data Knight = MkKnight 329 | ghci| { knightName :: String 330 | ghci| , knightVictories :: Int 331 | ghci| } deriving (Show) 332 | ghci| :} 333 | ghci> 334 | 335 | Although, it may be easier to define data types in the module, and load it 336 | afterwards. 337 | -} 338 | 339 | {- | 340 | =⚔️= Task 1 341 | 342 | Define the Book product data type. You can take inspiration from our description 343 | of a book, but you are not limited only by the book properties we described. 344 | Create your own book type of your dreams! 345 | -} 346 | 347 | {- | 348 | =⚔️= Task 2 349 | 350 | Prepare to defend the honour of our kingdom! A monster attacks our brave knight. 351 | Help him to fight this creature! 352 | 353 | Define data types for Knights and Monsters, and write the "fight" function. 354 | 355 | Both a knight and a monster have the following properties: 356 | 357 | ✦ Health (the number of health points) 358 | ✦ Attack (the number of attack units) 359 | ✦ Gold (the number of coins) 360 | 361 | When a monster fights a knight, the knight hits first, and the monster hits back 362 | only if it survives (health is bigger than zero). A hit decreases the amount of 363 | health by the number represented in the "attack" field. 364 | 365 | Implement the "fight" function, that takes a monster and a knight, performs the 366 | fight following the above rules and returns the amount of gold the knight has 367 | after the fight. The battle has the following possible outcomes: 368 | 369 | ⊛ Knight wins and takes the loot from the monster and adds it to their own 370 | earned treasure 371 | ⊛ Monster defeats the knight. In that case return -1 372 | ⊛ Neither the knight nor the monster wins. On such an occasion, the knight 373 | doesn't earn any money and keeps what they had before. 374 | 375 | ♫ NOTE: In this task, you need to implement only a single round of the fight. 376 | 377 | -} 378 | 379 | {- | 380 | =🛡= Sum types 381 | 382 | Another powerful ambassador of ADTs is the __sum type__. Unlike ordinary records 383 | (product types) that always have all the fields you wrote, sum types represent 384 | alternatives of choices. Sum types can be seen as "one-of" data structures. They 385 | contain many product types (described in the previous section) as alternatives. 386 | 387 | To define a sum type, you have to specify all possible constructors separated 388 | by "|". Each constructor on its own could have an ADT, that describes 389 | this branch of the alternative. 390 | 391 | There is at least one famous sum type that you have already seen — 'Bool' — the 392 | simplest example of a sum type. 393 | 394 | @ 395 | data Bool = False | True 396 | @ 397 | 398 | 'Bool' is a representer of so-called __enumeration__ — a special case of sum 399 | types, a sum of nullary constructors (constructors without fields). 400 | Sum types can have much more than two constructors (but don't abuse this)! 401 | 402 | Look at this one. We need more than two constructors to mirror the "Magic Type". 403 | And none of the magic streams needs any fields. Just pure magic ;) 404 | 405 | @ 406 | data MagicType 407 | = DarkMagic 408 | | LightMagic 409 | | NeutralMagic 410 | @ 411 | 412 | However, the real power of sum types unleashes when you combine them with 413 | fields. As we mentioned, each "|" case in the sum type could be an ADT, so, 414 | naturally, you can have constructors with fields, which are product types from 415 | the previous section. If you think about it, the enumeration also contains a 416 | product type, as it is absolutely legal to create a data type with one 417 | constructor and without any fields: `data Emptiness = TotalVoid`. 418 | 419 | To showcase such sum type, let's represent a possible loot from successfully 420 | completing an adventure: 421 | 422 | @ 423 | data Loot 424 | = Sword Int -- attack 425 | | Shield Int -- defence 426 | | WizardStaff Power SpellLevel 427 | @ 428 | 429 | You can create values of the sum types by using different constructors: 430 | 431 | @ 432 | woodenSword :: Loot 433 | woodenSword = Sword 2 434 | 435 | adamantiumShield :: Loot 436 | adamantiumShield = Shield 3000 437 | @ 438 | 439 | And you can pattern match on different constructors as well. 440 | 441 | @ 442 | acceptLoot :: Loot -> String 443 | acceptLoot loot = case loot of 444 | Sword _ -> "Thanks! That's a great sword!" 445 | Shield _ -> "I'll accept this shield as a reward!" 446 | WizardStaff _ _ -> "What?! I'm not a wizard, take it back!" 447 | @ 448 | 449 | 450 | To sum up all the above, a data type in Haskell can have zero or more 451 | constructors, and each constructor can have zero or more fields. This altogether 452 | gives us product types (records with fields) and sum types (alternatives). The 453 | concept of product types and sum types is called __Algebraic Data Type__. They 454 | allow you to model your domain precisely, make illegal states unrepresentable 455 | and provide more flexibility when working with data types. 456 | -} 457 | 458 | {- | 459 | =⚔️= Task 3 460 | 461 | Create a simple enumeration for the meal types (e.g. breakfast). The one who 462 | comes up with the most number of names wins the challenge. Use your creativity! 463 | -} 464 | 465 | {- | 466 | =⚔️= Task 4 467 | 468 | Define types to represent a magical city in the world! A typical city has: 469 | 470 | ⍟ Optional castle with a __name__ (as 'String') 471 | ⍟ Wall, but only if the city has a castle 472 | ⍟ Church or library but not both 473 | ⍟ Any number of houses. Each house has one, two, three or four __people__ inside. 474 | 475 | After defining the city, implement the following functions: 476 | 477 | ✦ buildCastle — build a castle in the city. If the city already has a castle, 478 | the old castle is destroyed, and the new castle with the __new name__ is built 479 | ✦ buildHouse — add a new living house 480 | ✦ buildWalls — build walls in the city. But since building walls is a 481 | complicated task, walls can be built only if the city has a castle 482 | and at least 10 living __people__ inside in all houses of the city in total. 483 | -} 484 | 485 | {- 486 | =🛡= Newtypes 487 | 488 | There is one more way to create a custom structure in Haskell. Let's see what 489 | that is and how it differs from others. 490 | 491 | __Newtype__ is a way to create a lightweight wrapper around an existing type. 492 | Unlike type aliases, newtypes make an entirely new type from the compiler's point 493 | of view (as the name suggests). However, such data types don't have additional 494 | runtime overhead, which means that it would work as fast as the underlying type 495 | without the wrapper. 496 | 497 | You can declare a data type as a newtype only if it has __exactly one 498 | constructor__ with __exactly one field__. It is a compiler error if you try to 499 | define a newtype with another number of constructors or fields. 500 | 501 | The syntax is similar to defining an ordinary data type, but you use the 502 | "newtype" keyword instead of the "data" keyword. "newtype" is a product type. 503 | 504 | @ 505 | newtype Attack = MkAttack Int 506 | @ 507 | 508 | The same rule about names fields as for any data types applies to newtypes 509 | as well. Meaning you can write the above type as follows: 510 | 511 | @ 512 | newtype Attack = MkAttack 513 | { unAttack :: Int 514 | } 515 | @ 516 | 517 | You can use the "MkAttack" constructor to create values of the "Attack" type, 518 | and you can pattern match on "MkAttack" as on ordinary data types. When using 519 | newtypes, you pay an extra development cost of writing extra wrappers and 520 | unwrappers, but at the same time, you get additional compile-time guarantees of 521 | not mixing types. 522 | 523 | Newtypes serve the purpose of creating safer and more maintainable interfaces. 524 | Let's prove that. 525 | 526 | Say, we have a function to get a BMI. 527 | 528 | @ 529 | myBMI :: Double -> Double -> Double 530 | myBMI height weight = ... 531 | @ 532 | 533 | And I can use it to calculate my BMI: 534 | 535 | ghci> myBMI 200 70 536 | 537 | Imagine how terrifying it could be if one accidentally messes with the order of 538 | height and weight. 😱 539 | 540 | ghci> myBMI 70 200 541 | 542 | However, this could be avoided if our function would look like this: 543 | 544 | @ 545 | myBMI :: Height -> Weight -> Double 546 | -- | ╰╴ newtype 547 | -- ╰╴newtype 548 | myBMI height weight = ... 549 | @ 550 | 551 | And to run it you won't be able to mess arguments: 552 | 553 | ghci> myBMI (Height 200) (Weight 70) 554 | -} 555 | 556 | {- 557 | =⚔️= Task 5 558 | 559 | Improve the following code (types definition and function implementations) by 560 | introducing extra newtypes. 561 | 562 | 🕯 HINT: if you complete this task properly, you don't need to change the 563 | implementation of the "hitPlayer" function at all! 564 | -} 565 | data Player = Player 566 | { playerHealth :: Int 567 | , playerArmor :: Int 568 | , playerAttack :: Int 569 | , playerDexterity :: Int 570 | , playerStrength :: Int 571 | } 572 | 573 | calculatePlayerDamage :: Int -> Int -> Int 574 | calculatePlayerDamage attack strength = attack + strength 575 | 576 | calculatePlayerDefense :: Int -> Int -> Int 577 | calculatePlayerDefense armor dexterity = armor * dexterity 578 | 579 | calculatePlayerHit :: Int -> Int -> Int -> Int 580 | calculatePlayerHit damage defense health = health + defense - damage 581 | 582 | -- The second player hits first player and the new first player is returned 583 | hitPlayer :: Player -> Player -> Player 584 | hitPlayer player1 player2 = 585 | let damage = calculatePlayerDamage 586 | (playerAttack player2) 587 | (playerStrength player2) 588 | defense = calculatePlayerDefense 589 | (playerArmor player1) 590 | (playerDexterity player1) 591 | newHealth = calculatePlayerHit 592 | damage 593 | defense 594 | (playerHealth player1) 595 | in player1 { playerHealth = newHealth } 596 | 597 | {- | 598 | =🛡= Polymorphic data types 599 | 600 | Similar to functions, data types in Haskell can be __polymorphic__. This means 601 | that they can use some type variables as placeholders, representing general 602 | types. You can either reason about data types in terms of such variables (and 603 | don't worry about the specific types), or substitute variables with some 604 | particular types. 605 | Such polymorphism in Haskell is an example of __parametric polymorphism__. 606 | 607 | The process of defining a polymorphic type is akin to an ordinary data type 608 | definition. The only difference is that all the type variables should go after 609 | the type name so that you can reuse them in the constructor fields later. 610 | 611 | For example, 612 | 613 | @ 614 | data Foo a = MkFoo a 615 | @ 616 | 617 | Note that both product and sum types can be parameterised. 618 | 619 | > Actually, we've already seen a polymorphic data type! Remember Lists from Chapter Two? 620 | 621 | To give an example of a custom polymorphic type, let's implement a 622 | "TreasureChest" data type. Our treasure chest is flexible, and it can store some 623 | amount of gold. Additionally there is some space for one more arbitrary 624 | treasure. But that could be any treasure, and we don't know what it is 625 | beforehand. 626 | 627 | In Haskell words, the data type can be defined like this: 628 | 629 | @ 630 | data TreasureChest x = TreasureChest 631 | { treasureChestGold :: Int 632 | , treasureChestLoot :: x 633 | } 634 | @ 635 | 636 | You can see that a treasure chest can store any treasure, indeed! We call it 637 | treasure 'x'. 638 | 639 | And when writing functions involving the "TreasureChest" type, we don't always 640 | need to know what kind of treasure is inside besides gold. 641 | We can either use a type variable in our type signature: 642 | 643 | @ 644 | howMuchGoldIsInMyChest :: TreasureChest x -> Int 645 | @ 646 | 647 | or we can specify a concrete type: 648 | 649 | @ 650 | isEnoughDiamonds :: TreasureChest Diamond -> Bool 651 | @ 652 | 653 | In the same spirit, we can implement a function that creates a treasure chest with some 654 | predefined amount of gold and a given treasure: 655 | 656 | @ 657 | mkMehChest :: x -> TreasureChest x 658 | mkMehChest treasure = TreasureChest 659 | { treasureChestGold = 50 660 | , treasureChestLoot = treasure 661 | } 662 | @ 663 | 664 | 665 | Polymorphic Algebraic Data Types are a great deal! One of the most common and 666 | useful standard polymorphic types is __"Maybe"__. It represents the notion of 667 | optional value (maybe the value is there, or maybe it is not). 668 | "Maybe" is defined in the standard library in the following way: 669 | 670 | @ 671 | data Maybe a 672 | = Nothing 673 | | Just a 674 | @ 675 | 676 | Haskell doesn't have a concept of "null" values. If you want to work with 677 | potentially absent values, use the "Maybe" type explicitly. 678 | 679 | > Is there a good way to avoid null-pointer bugs? Maybe. © Jasper Van der Jeugt 680 | 681 | Another standard polymorphic data type is "Either". It stores either the value 682 | of one type or a value of another. 683 | 684 | @ 685 | data Either a b 686 | = Left a 687 | | Right b 688 | @ 689 | 690 | ♫ NOTE: It can help to explore types of constructors "Nothing", "Just", "Left" 691 | and "Right". Let's stretch our fingers and blow off the dust from our GHCi and 692 | check that! 693 | 694 | You can pattern match on values of the "Either" type as well as on any other 695 | custom data type. 696 | 697 | @ 698 | showEither :: Either String Int -> String 699 | showEither (Left msg) = "Left with string: " ++ msg 700 | showEither (Right n) = "Right with number: " ++ show n 701 | @ 702 | 703 | Now, after we covered polymorphic types, you are finally ready to learn how 704 | lists are actually defined in the Haskell world. Behold the mighty list type! 705 | 706 | @ 707 | data [] a 708 | = [] 709 | | a : [a] 710 | @ 711 | 712 | Immediately we know what all of that means! 713 | The ":" is simply the constructor name for the list. Constructors in Haskell can 714 | be defined as infix operators as well (i.e. be written after the first argument, 715 | the same way we write `1 + 2` and not `+ 1 2`), but only if they start with a 716 | colon ":". The ":" is taken by lists. Now you see why we were able to pattern 717 | match on it? 718 | 719 | The type name uses built-in syntax to reserve the square brackets [] exclusively 720 | for lists but, otherwise, is a simple polymorphic recursive sum type. 721 | 722 | If you rename some constructor and type names, the list type could look quite 723 | simple, as any of us could have written it: 724 | 725 | @ 726 | data List a 727 | = Empty 728 | | Cons a (List a) 729 | @ 730 | 731 | ♫ NOTE: We use () to group "List" with "a" type variable in the second field of 732 | the "Cons" constructor. This is done to tell the compiler that "List" and "a" 733 | should go together as one type. 734 | -} 735 | 736 | {- | 737 | =⚔️= Task 6 738 | 739 | Before entering the real world of adventures and glorious victories, we should 740 | prepare for different things in this world. It is always a good idea to 741 | understand the whole context before going on a quest. And, before fighting a 742 | dragon, it makes sense to prepare for different unexpected things. So let's 743 | define data types describing a Dragon Lair! 744 | 745 | ⍟ A lair has a dragon and possibly a treasure chest (as described in the 746 | previous section). A lair also may not contain any treasures, but we'll never 747 | know until we explore the cave! 748 | ⍟ A dragon can have a unique magical power. But it can be literally anything! 749 | And we don't know in advance what power it has. 750 | 751 | Create data types that describe such Dragon Lair! Use polymorphism to 752 | parametrise data types in places where values can be of any general type. 753 | 754 | 🕯 HINT: 'Maybe' that some standard types we mentioned above are useful for 755 | maybe-treasure ;) 756 | -} 757 | 758 | {- 759 | =🛡= Typeclasses 760 | 761 | __Typeclass__ is a regularly used way to express common characteristics of 762 | different data types. In some sense, a typeclass describes the interface of some 763 | value without telling you the implementation details. 764 | 765 | __Instance__ is a representation of the typeclass ↔︎️ data type relationships. In 766 | order to show that the data type obeys the typeclasses rules and to use the 767 | methods of the typeclass on the data values, you need to provide the work 768 | instructions under this particular typeclass. And that is the instance of the 769 | data type for the specific typeclass. 770 | 771 | Let’s consolidate the typeclasses and instances concepts on the analogues from 772 | our fantasy world. 773 | 774 | Many lovely princesses need to be rescued. And those processes are usually 775 | alike: find a path to the particular castle (differs from princess to princess), 776 | defeat the monster (also unique for each princess) and rescue the beloved 777 | beauty. So we can see that the "Rescue Mission Plan" is the typeclass, and each 778 | princess has its own instance for that. If you are a prince on a white horse, 779 | you'd better check the particular instance for your princess to get on the 780 | salvation journey. 781 | 782 | Next, let’s look at one code example for a better illustration of the 783 | instance-typeclass relationship. We can define a typeclass that would tell us 784 | one's arch enemy. 785 | 786 | The syntax is as follows: you need to use the "class" keyword, then you need to 787 | specify the typeclass name. Typeclasses should start with an upper case letter. 788 | After that, the type parameter should be identified, which represents the data 789 | types that would have instances of this typeclass. And, finally, the "where" 790 | keyword. After, you can specify methods of the typeclass – functions that should 791 | work with the type parameter. 792 | 793 | @ 794 | ┌─ typeclass name 795 | │ 796 | │ ┌─ type parameter 797 | │ │ ┌───── "where" keyword 798 | │ │ │ 799 | class ArchEnemy a where 800 | │ getArchEnemy :: a -> String 801 | │ │ │ 802 | │ │ └─── the same type parameter 803 | │ └───── method name 804 | │ 805 | └ "class" keyword 806 | @ 807 | 808 | And that 'getArchEnemy' method could be used with a lot of data types: Bool, 809 | Double,… name them all! Let’s have our first instances to show how it works: 810 | 811 | The syntax is simple and consistent with the typeclass declaration, but instead 812 | of the "class" keyword you need to have the "instance" keyword. Of course, 813 | instead of the type parameter, you have to specify the concrete type, for which 814 | you are implementing the instance. And all the necessary methods should have its 815 | implementation for the particular data type. 816 | 817 | @ 818 | instance ArchEnemy Bool where 819 | getArchEnemy :: Bool -> String 820 | getArchEnemy True = "False" 821 | getArchEnemy False = "True" 822 | 823 | 824 | instance ArchEnemy Int where 825 | getArchEnemy :: Int -> String 826 | getArchEnemy i = case i of 827 | 0 -> "Division" 828 | _ -> "Derivative" 829 | 830 | instance ArchEnemy Double where 831 | getArchEnemy :: Double -> String 832 | getArchEnemy n 833 | | isNaN n = "Infinity" 834 | | isInfinite n = "Limit" 835 | | otherwise = "NaN" 836 | @ 837 | 838 | And then you can write polymorphic functions and not worry about which specific 839 | type is under the hood until it has the instance of the desired typeclass. For that 840 | we are using __constraints__ in Haskell. It is the identification of affiliation 841 | to the typeclass. The constraints should go after the "::" sign in the function 842 | type declaration. You can specify one or many constraints. If more than one they 843 | should be in parentheses and comma-separated. The end of constraints is 844 | determined with the "=>" arrow, and the function type could be written as usual. 845 | 846 | @ 847 | revealArchEnemy :: (ArchEnemy a, Show a) => a -> String 848 | revealArchEnemy x = 849 | "The arch-enemy of " ++ show x ++ " is " ++ getArchEnemy x 850 | @ 851 | 852 | The behaviour of this polymorphic function depends on the data type used with 853 | this function. Such dependency is called __ad-hoc polymorphism__. 854 | 855 | This is how it works in action: 856 | 857 | ghci> revealArchEnemy (42 :: Int) 858 | "The arch-enemy of 42 is Derivative" 859 | 860 | However, if we try to use this function with something that doesn’t implement an 861 | instance of our typeclass, we will get the corresponding compiler error, that 862 | would warn us precisely about that: 863 | 864 | ghci> revealArchEnemy "An adorable string that has no enemies (✿◠ω◠)" 865 | 866 | :21:1: error: 867 | • No instance for (ArchEnemy String) 868 | arising from a use of revealArchEnemy 869 | • In the expression: revealArchEnemy "An adorable string that has no enemies (✿◠ω◠)" 870 | In an equation for 'it': it = revealArchEnemy "An adorable string that has no enemies (✿◠ω◠)" 871 | 872 | 873 | Interestingly, it is possible to reuse existing instances of data types in the 874 | same typeclass instances as well. And we also can reuse the __constraints__ in 875 | the instance declaration for that! 876 | 877 | This gives us the ability to specify the instances for polymorphic data types 878 | with some conditions (constraints). 879 | 880 | Let's see it in the example of the 'ArchEnemy' typeclass instance for the 881 | "Maybe" something data type. 882 | 883 | @ 884 | instance (ArchEnemy a) => ArchEnemy (Maybe a) where 885 | getArchEnemy :: Maybe a -> String 886 | getArchEnemy (Just x) = getArchEnemy x 887 | getArchEnemy Nothing = "NullPointerException" 888 | @ 889 | 890 | This instance is suitable for any Maybe as long as the instance of the inside 891 | type exists. You can see how we reuse the fact that the underlying type has this 892 | instance and apply this typeclass method to it. 893 | -} 894 | 895 | {- | 896 | =⚔️= Task 7 897 | 898 | Often we want to combine several values of a type and get a single value of the 899 | exact same type. We can combine different things: treasures, adventures, groups 900 | of heroes, etc.. So it makes sense to implement a typeclass for such a concept 901 | and define helpful instances. 902 | 903 | We will call such a typeclass "Append". You can find its definition below. 904 | 905 | Implement instances of "Append" for the following types: 906 | 907 | ✧ The "Gold" newtype where append is the addition 908 | ✧ "List" where append is list concatenation 909 | ✧ *(Challenge): "Maybe" where append is appending of values inside "Just" constructors 910 | 911 | -} 912 | class Append a where 913 | append :: a -> a -> a 914 | 915 | 916 | {- 917 | =🛡= Standard Typeclasses and Deriving 918 | 919 | As well as many useful data types, the standard library in Haskell also comes with some very 920 | essential typeclasses: 921 | 922 | ﹡ 'Show' — show the data type value as a 'String'. This helps us to print to 923 | the terminal in GHCi. 924 | ﹡ 'Read' — inverse to the 'Show'. By a given String, it is possible to parse 925 | it into the data type. 926 | ﹡ 'Eq' — determine if two values of the same data types are equal. 927 | ﹡ 'Ord' — compare values of the same data type (requires the data type to have 928 | the instance of 'Eq'). 929 | ﹡ 'Bounded' — specify the lowest and highest value of the data type 930 | ﹡ 'Enum' — operate with enumeration types. 931 | 932 | You can use Hoogle to check what methods these classes have. Additionally, the 933 | documentation there shows for which types there are already instances of these 934 | typeclasses. 935 | 936 | Alternatively, you can use the ":i" command in GHCi (short for ":info") to see 937 | the typeclass methods and its instances for the standard data types. 938 | 939 | As these typeclasses are way too useful, and it is relatively straightforward to 940 | implement instances for such classes, GHC provides a nice feature: __deriving__. 941 | Deriving is the feature of the Haskell compiler, that auto generates the 942 | instances of typeclasses for the specified data types. The code would be 943 | produced internally during the compilation, so no need to litter your code with 944 | the boilerplate implementation of instances. 945 | 946 | The deriving syntax is the following. It should be attached to the data type 947 | declaration. It consists of the "deriving" keyword, and then typeclass names in 948 | parentheses comma-separated. 949 | 950 | @ 951 | data Princess = Princess 952 | { princessName :: String 953 | } deriving (Show, Read, Eq, Ord) 954 | @ 955 | 956 | And it is ready to be used! 957 | 958 | ghci> show (Princess "Anna") 959 | Princess {princessName = "Anna"} 960 | 961 | -} 962 | 963 | {- 964 | =⚔️= Task 8 965 | 966 | Create an enumeration type to represent days of the week. After defining a type, 967 | implement the following functions: 968 | 969 | ✦ isWeekend — True if the weekday is weekend 970 | ✦ nextDay — returns the next weekday 971 | ✦ daysToParty — number of the days to the next Friday 972 | 973 | 🕯 HINT: to implement this task, derive some standard typeclasses 974 | -} 975 | 976 | {- 977 | =💣= Task 9* 978 | 979 | You got to the end of Chapter Three, Brave Warrior! I hope you are ready for the 980 | final Boss. We talked a lot about data types and typeclasses in this chapter. 981 | Now it is time to show what we learned! 982 | 983 | To defeat the final boss in this chapter, you need to implement your own data 984 | types, typeclasses and instances, describing the world, and write polymorphic 985 | functions using custom types and typeclasses. 986 | 987 | The task: 988 | When two fighters engage in a battle (knight fights the monster, duel of 989 | knights, monsters fighting for a lair, etc.), both of them can perform different 990 | actions. They do their activities in turns, i.e. one fighter goes first, then 991 | the other goes second, then the first again, and so on, until one of them wins. 992 | 993 | Both knight and monster have a sequence of actions they can do. A knight can 994 | attack, drink a health potion, cast a spell to increase their defence. A monster 995 | can only attack or run away. Each fighter starts with some list of actions they 996 | can do, performs them in sequence one after another, and when the sequence ends, 997 | the process starts from the beginning. 998 | 999 | Monsters have only health and attack, while knights also have a defence. So when 1000 | knights are attacked, their health is decreased less, if they have more defence. 1001 | The fight ends when the health of one fighter becomes zero or less. 1002 | 1003 | As you can see, both monster and knight have similar characteristics, but they 1004 | also have some differences. So it is possible to describe their common 1005 | properties using typeclasses, but they are different data types in the end. 1006 | 1007 | Implement data types and typeclasses, describing such a battle between two 1008 | contestants, and write a function that decides the outcome of a fight! 1009 | -} 1010 | 1011 | 1012 | {- 1013 | You did it! Now it is time to open pull request with your changes 1014 | and summon @vrom911 for the review! 1015 | -} 1016 | 1017 | {- 1018 | =📜= Additional resources 1019 | Deriving: https://kowainik.github.io/posts/deriving 1020 | Extensions: https://kowainik.github.io/posts/extensions 1021 | -} 1022 | -------------------------------------------------------------------------------- /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 me, @vrom911, to look at your 33 | solution in order to give some advice on your code. This is optional; however, 34 | you can ask us for review only if you want some feedback on your solutions. 35 | 36 | Perfect. Let's crush this! 37 | -} 38 | 39 | {-# LANGUAGE ConstraintKinds #-} 40 | {-# LANGUAGE InstanceSigs #-} 41 | 42 | module Chapter4 where 43 | 44 | {- | 45 | =🛡= Kinds 46 | 47 | Before we attempt to defeat the mighty and scary Monad Dragon, we need 48 | to talk about __kinds__. Understanding kinds is vital for the 49 | wholesome perception of the Haskell type system. 50 | 51 | All values in Haskell have types. But it turns out, types themselves 52 | also have their own "types". Such "type of a type" is called __kind__. 53 | 54 | Kinds describe the shape of a type. And kinds are much simpler than 55 | types. Primitive types like 'Int' have kind * (star). You can check 56 | information about kinds in GHCi using the ":k" command (short for ":kind"): 57 | 58 | >>> :k Int 59 | Int :: * 60 | 61 | ♫ NOTE: An alternative name for the star * kind is "Type". GHC is 62 | slowly replacing the naming of kind as * with "Type". 63 | 64 | As data types have different types, in the same manner, there are more 65 | than one kind of types. For example, data types like "Maybe" (that 66 | have an additional type parameter) have a different kind. "Maybe" is 67 | parameterised by a type variable. So, "Maybe" by itself doesn't have a 68 | kind *. We call "Maybe" a __type constructor__. There are no values of 69 | type "Maybe" without any parameters specified. As a consequence, you 70 | can't write the following function: 71 | 72 | @ 73 | foo :: Maybe -> Int 74 | @ 75 | 76 | It is invalid, because "Maybe" needs some type parameters. In some 77 | sense, you can look at "Maybe" as a function on type-level from types 78 | to types: you give "Maybe" some type, and you get a concrete type in 79 | the end. And that is the accurate description. Its kind is indeed a 80 | function of two stars. You can check the kind of 'Maybe' in GHCi as 81 | well: 82 | 83 | >>> :k Maybe 84 | Maybe :: * -> * 85 | >>> :k Maybe Int 86 | Maybe Int :: * 87 | 88 | You can think of types with kind * as types in their complete final 89 | form. Types of kind * have values. Types of any other kind don't have 90 | values. 91 | 92 | Haskell has one more standard kind — __Constraint__. This is the kind 93 | for typeclasses. You can see ordinary types of kind * on both sides of 94 | the function arrow (->) in type signatures. And you can see 95 | constraints to the left side of the context arrow (=>). 96 | 97 | You can check the kind of some standard typeclass like "Eq" to verify this: 98 | 99 | >>> :k Eq 100 | Eq :: * -> Constraint 101 | 102 | We hope kinds will become your kind friends by the end of this chapter :) 103 | -} 104 | 105 | {- | 106 | =⚔️= Task 1 107 | 108 | Prepare for the first task! To complete the first challenge, you need 109 | to explore kinds of different types in GHCi and insert the GHCi output 110 | after each command. 111 | 112 | As always, try to guess the output first! And don't forget to insert 113 | the output in here: 114 | 115 | >>> :k Char 116 | 117 | >>> :k Bool 118 | 119 | >>> :k [Int] 120 | 121 | >>> :k [] 122 | 123 | >>> :k (->) 124 | 125 | >>> :k Either 126 | 127 | >>> data Trinity a b c = MkTrinity a b c 128 | >>> :k Trinity 129 | 130 | >>> data IntBox f = MkIntBox (f Int) 131 | >>> :k IntBox 132 | 133 | -} 134 | 135 | {- | 136 | =🛡= Functor 137 | 138 | Let's continue our journey of exploring challenging concepts by 139 | meeting 'Functor'. You may have heard of it and been told or imagined 140 | that this is some Mathematical word, and there is nothing to do with 141 | Functional Programming. But it is simpler. 142 | 143 | 'Functor' is just a typeclass. If we think about it from this 144 | perspective, we would be able to objectively see what it could bring 145 | to the FP world and us specifically, and stop labelling it Math 146 | because of the name only. 147 | 148 | And maybe we can start by looking at its definition? 149 | 150 | @ 151 | class Functor f where 152 | fmap :: (a -> b) -> f a -> f b 153 | @ 154 | 155 | As you can see, this is a class definition, but with a few more 156 | challenging moments, we want to highlight in there. 157 | 158 | First of all, you can see that 'Functor' has one method called 'fmap', 159 | and it is a higher-order function — it takes a function from 'a' to 160 | 'b' as an argument, then something called 'f a', and returns 'f b'. 161 | 162 | Before we dive into the details of 'Functor' implementation in 163 | Haskell, let's try to see the meaning of this typeclass and its 164 | method. And providing an analogy could help us with that. 165 | 166 | 'Functor' allows changing the value (and even its type) in some 167 | context; we know this from the method's type signature. Let's say that 168 | a chest is our context, and its content is the value inside that 169 | context. You can replace the chest content with something else: you 170 | can fill it with gold, you can take all the coins out the chest and 171 | buy clothes using that money, and put back clothes in the chest 172 | instead. Basically, you can replace the content of a chest with 173 | anything else. This process of take-change-put is exactly what 'fmap' 174 | describes! 175 | 176 | That should make more sense now. But let's go back to Haskell. Functor 177 | as a typeclass is defined for a type variable called 'f'. But from the 178 | definition of 'fmap' you can notice that 'f' is not an ordinary 179 | type. 'f' itself is parameterised by some type variable 'a' — 'f a'. 180 | 181 | And in the light of the previous excursion into kinds, we can feel 182 | that 'f' has a more complicated kind than 'Int' (* – star), for 183 | example. 184 | 185 | Indeed, you can check your suspicions by inspecting the kind of the 186 | 'Functor' typeclass in GHCi: 187 | 188 | >>> :k Functor 189 | Functor :: (* -> *) -> Constraint 190 | 191 | Aha! So, we see that Functor works with types of the kind `* -> *`. 192 | This already tells us a lot! For example, you can't implement 193 | "instance Functor Int" (because Int has kind *), but you can implement 194 | "instance Functor Maybe" (because Maybe has kind `* -> *`). 195 | 196 | And, of course, the 'Functor' instance for 'Maybe' exists in the 197 | standard Haskell library. Therefore, you can already use 'fmap' on any 198 | Maybe value: 199 | 200 | >>> fmap not (Just True) 201 | Just False 202 | 203 | Let's examine this one better. Maybe is parameterised by a single type 204 | variable. This means that 'Maybe' can store "Int", "String", list, or 205 | even other 'Maybe' of something inside. Basically, anything can be put 206 | in there until it has the fittable kind. 207 | 208 | In the example above, we use the 'not' function to 'fmap' our 'Maybe 209 | Bool' value. But as 'not' doesn't change the type of Bool, then we get 210 | 'Maybe Bool' as the result of the 'fmap' in the example above. 211 | 212 | That is a nice example, but not that interesting. Let's look at this one: 213 | 214 | >>> fmap show (Just 42) 215 | Just "42" 216 | >>> fmap (replicate 3) (Just True) 217 | Just [True,True,True] 218 | 219 | Notice how the result type changes when we apply 'fmap' to one 220 | argument at a time. Haskell has type inference, so it can infer proper 221 | types and match them with actual types. 222 | 223 | However, if we don't have a value inside, nothing changes. Or does it? 224 | 225 | >>> fmap (replicate 3) Nothing 226 | Nothing 227 | 228 | Let's look at types closer: 229 | 230 | >>> :t fmap 231 | fmap :: Functor f => (a -> b) -> f a -> f b 232 | >>> :t fmap (replicate 3) 233 | fmap (replicate 3) :: Functor f => f a -> f [a] 234 | >>> :t Nothing 235 | Nothing :: Maybe a 236 | >>> :t fmap (replicate 3) Nothing 237 | fmap (replicate 3) Nothing :: Maybe [a] 238 | 239 | 240 | In GHCi we see 'Nothing', but it is not the same 'Nothing' that it's 241 | been before. Its type has changed. 242 | 243 | You can see how the 'Functor' instance for 'Maybe' is implemented. The 244 | implementation uses good old pattern matching. 245 | 246 | @ 247 | instance Functor Maybe where 248 | fmap :: (a -> b) -> Maybe a -> Maybe b 249 | fmap _ Nothing = Nothing 250 | fmap f (Just a) = Just (f a) 251 | @ 252 | 253 | And as you see the underlying type of 'Maybe' changes over the 'fmap'. 254 | That explains how that 'Nothing' changed its type in reality. 255 | 256 | Now you see that Functors are not magical despite having a cryptic 257 | name. 258 | 259 | > QUESTION: Can you understand why the following implementation of the 260 | Functor instance for Maybe doesn't compile? 261 | 262 | @ 263 | instance Functor Maybe where 264 | fmap :: (a -> b) -> Maybe a -> Maybe b 265 | fmap f (Just a) = Just (f a) 266 | fmap _ x = x 267 | @ 268 | -} 269 | 270 | {- | 271 | =⚔️= Task 2 272 | 273 | Implement 'Functor' instance for the "Secret" data type defined 274 | below. 'Secret' is either an unknown trap with something dangerous 275 | inside or a reward with some treasure. You never know what's inside 276 | until opened! But the 'Functor' instance allows changing the reward 277 | inside, so it is quite handy. 278 | -} 279 | data Secret e a 280 | = Trap e 281 | | Reward a 282 | deriving (Show, Eq) 283 | 284 | 285 | {- | 286 | Functor works with types that have kind `* -> *` but our 'Secret' has 287 | kind `* -> * -> *`. What should we do? Don't worry. We can partially 288 | apply 'Secret' to some type variable that will be fixed inside each 289 | method. Yes, similar to how we can partially apply functions. See, how 290 | we can reuse already known concepts (e.g. partial application) from 291 | values and apply them to the type level? 292 | -} 293 | instance Functor (Secret e) where 294 | fmap :: (a -> b) -> Secret e a -> Secret e b 295 | fmap = error "fmap for Box: not implemented!" 296 | 297 | {- | 298 | =⚔️= Task 3 299 | 300 | Implement Functor instance for the "List" type defined below. This 301 | list type mimics the standard lists in Haskell. But for training 302 | purposes, let's practise our skills on implementing standard 303 | typeclasses for standard data types. 304 | -} 305 | data List a 306 | = Empty 307 | | Cons a (List a) 308 | 309 | {- | 310 | =🛡= Applicative 311 | 312 | The 'Applicative' typeclass logically continues the 'Functor' 313 | typeclass. Again, let's look at its definition first. 314 | 315 | @ 316 | class Functor f => Applicative f where 317 | pure :: a -> f a 318 | (<*>) :: f (a -> b) -> f a -> f b 319 | @ 320 | 321 | Wow, that's a lot going on again! Where did all these scary creatures 322 | come? But if we look closer, it all looks a bit familiar already. 323 | 324 | We now can spot straightaway, that similar to Functors, only types 325 | with kind `* -> *` can have an Applicative instance (e.g. Maybe, and 326 | not 'Int' or 'Char'), as we see the same 'f a' argument in the 327 | methods' types. 328 | 329 | But we also can notice the constraint in the class declaration. We 330 | know that the constraint is the restriction on data types. And 331 | actually, it works similarly in the typeclass declarations as 332 | well. Putting this all together, it means that first, any data type 333 | that wants to have an Applicative instance needs to implement the 334 | 'Functor' instance. This is what the part "Functor f => Applicative f" 335 | means. 336 | 337 | To be an Applicative, you first need to be "Functor". So, Functor is a 338 | Level 1 creature, and Applicative is a Level 2 creature. Upgraded 339 | Functor if you wish. 340 | 341 | Unlike Functor, Applicative has two methods: 'pure' and operator (<*>) 342 | called cyclops or starship or Sauron Eye. 'pure' puts a value into the 343 | context, and (<*>) extracts function and value from the context, 344 | applies the function to the argument, and puts the result back in the 345 | context. 346 | 347 | And continuing the chest 'Functor' analogy here, we can notice that a 348 | chest is also an 'Applicative'! We can put anything we want inside our 349 | chest, and this is what "pure" does. Or, let's say, we have one chest 350 | with a key inside, and this key opens a very secret box inside another 351 | chest. We can take both chests, extract their content, and apply one 352 | to another (i.e. open the box with the key), extract the content of 353 | the box and put it back to our chest. And this is the (<*>) 354 | operator. So there are valid implementations of both methods of the 355 | Applicative typeclass. 356 | 357 | In pursuance of the above explanation, you probably can see now why it 358 | is necessary to have 'Functor' first before becoming 'Applicative'. If 359 | we can't replace the content of the chest with some other content, how 360 | can we apply some actions to it and put something new inside? 361 | 362 | Now, back to Haskell. The function 'pure' takes a value and puts in 363 | the context 'f'. For 'Maybe' it looks like this: 364 | 365 | >>> pure True :: Maybe Bool 366 | Just True 367 | 368 | The (<*>) operator is much juicier: it takes a function inside the 369 | context, another value in the context, and returns a new value in the 370 | same context. Apparently, the function somehow is extracted from the 371 | context and applied to the extracted value. 372 | 373 | To keep an eye (<*> hehe) on the next example, let's first check that 374 | you still remember that in Haskell we have Higher-Order functions, 375 | right? This means that functions can not only be passed as arguments 376 | to data types but can also be stored inside other containers. Even 377 | 'Maybe' (e.g. "Maybe (Int -> Int)")! 378 | 379 | >>> Just not <*> Just True 380 | Just False 381 | >>> Just (+ 4) <*> Just 7 382 | Just 11 383 | >>> Just (replicate 3) <*> Just 0 384 | Just [0,0,0] 385 | 386 | Can you guess what will happen if we try to apply (<*>) on 'Nothing'? 387 | Exactly — 'Nothing'! Nothing will happen. 388 | 389 | >>> Just not <*> Nothing 390 | Nothing 391 | >>> Nothing <*> Just True 392 | Nothing 393 | 394 | And, finally, we are ready to see how the 'Applicative' instance for 395 | 'Maybe' really looks like: 396 | 397 | @ 398 | instance Applicative Maybe where 399 | pure :: a -> Maybe a 400 | pure = Just 401 | 402 | (<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b 403 | Nothing <*> _ = Nothing 404 | Just f <*> x = fmap f x 405 | @ 406 | 407 | So, even if the Applicative looks menacingly, there is Nothing scary 408 | in it after all! 409 | 410 | The next part might be a bit difficult to comprehend without having a 411 | lot of practice with Applicatives, but it provides some deeper meaning 412 | behind the concept. 413 | 414 | If 'Functor' allows changing values inside the context, 'Applicative' 415 | enables chaining operations. You may reasonably wonder how often we 416 | have functions inside 'Maybe'. But remember, that in Haskell we have 417 | partial application. This means that we can apply binary functions 418 | partially to some values inside a context, and get a function inside 419 | the context that requires only one argument. 420 | 421 | Now, let's think, how can we implement a function that takes two 422 | optional integers, and returns an optional integer with the sum of the 423 | given integers inside? In other words, we want a function with the 424 | following type signature: 425 | 426 | @ 427 | addMaybes :: Maybe Integer -> Maybe Integer -> Maybe Integer 428 | @ 429 | 430 | At this point you probably can implement it using pattern matching, 431 | but can you do it using the 'Applicative' instance for 'Maybe'? It 432 | turns out, this is possible! 433 | 434 | @ 435 | addMaybes m1 m2 = fmap (+) m1 <*> m2 436 | @ 437 | 438 | Let's disenchant this magic spell step-by-step. 439 | 440 | ghci> :t +d (+) 441 | (+) :: Integer -> Integer -> Integer 442 | ghci> :t +d fmap (+) 443 | fmap (+) :: Functor f => f Integer -> f (Integer -> Integer) 444 | ghci> :t +d fmap (+) (Just 3) 445 | fmap (+) (Just 3) :: Maybe (Integer -> Integer) 446 | 447 | You see that by applying 'fmap' to (+) and some 'Maybe' we get a value 448 | of type "Maybe (Integer -> Integer)". And this means that we can use 449 | the '(<*>)' operator to combine it with another "Maybe Integer". 450 | 451 | The beautiful thing about 'Applicative' is that scales over functions 452 | of any number of arguments. The following code is valid, if the 453 | function 'f' takes `x` arguments. 454 | 455 | @ 456 | fmap f m1 <*> m2 <*> m3 <*> ... <*> m_x 457 | @ 458 | 459 | Applicatives can be found in many applications: 460 | 461 | ✦ Chaining operations over optional values 462 | ✦ Parsers 463 | ✦ Input form validation 464 | ✦ Concurrent and parallel execution of tasks 465 | -} 466 | 467 | {- | 468 | =⚔️= Task 4 469 | 470 | Implement the Applicative instance for our 'Secret' data type from before. 471 | -} 472 | instance Applicative (Secret e) where 473 | pure :: a -> Secret e a 474 | pure = error "pure Secret: Not implemented!" 475 | 476 | (<*>) :: Secret e (a -> b) -> Secret e a -> Secret e b 477 | (<*>) = error "(<*>) Secret: Not implemented!" 478 | 479 | {- | 480 | =⚔️= Task 5 481 | 482 | Implement the 'Applicative' instance for our 'List' type. 483 | 484 | 🕯 HINT: in the applicative instance for lists, you have a list of 485 | functions and a list of arguments for those functions. You need to 486 | apply each function to each argument and combine all the results. You 487 | may also need to implement a few useful helper functions for our List 488 | type. 489 | -} 490 | 491 | 492 | {- | 493 | =🛡= Monad 494 | 495 | Now, the Monad Dragon. We've come that far not to give up. If we 496 | managed to fight 'Functor' and 'Applicative', then sure, we can beat 497 | 'Monad', right?💪 498 | 499 | As usual, we need to know with whom we are dealing, so let's inspect 500 | the 'Monad' typeclass definition: 501 | 502 | @ 503 | class Applicative f => Monad f where 504 | (>>=) :: f a -> (a -> f b) -> f b 505 | @ 506 | 507 | Look, it is just a simple typeclass! Also, it looks very familiar to 508 | 'Functor' and 'Applicative'. You can even start thinking that they are 509 | all here just to confuse developers by moving arrows and 'f' letters 510 | from one place to another. 511 | 512 | First of all, to become a 'Monad', a Level 3 creature, a type must be 513 | an 'Applicative' first. Only after that, it can even dare to implement 514 | the (>>=) operator called __bind__. 515 | 516 | *And this is again our reporter from the Chest Analogy show live.* 517 | Turns out, our chest is also a monad! If our chest contains some 518 | gold, we can take all our gold and buy a new chest using our 519 | gold. The remaining gold can be put to the new chest. The amount of 520 | money we have determines the quality of our new chest. And this is 521 | what monad is about — the next context can depend on the value in the 522 | current context. 523 | 524 | And to expand a bit more on why you need to have the 'Applicative' 525 | instance before becoming a 'Monad': if you can't put values (stuff) 526 | inside a context (chest) using "pure", then there is no sense in 527 | buying a new chest at all. 528 | 529 | So, our chest is 'Functor', 'Applicative' and 'Monad'. Pure gold example! 530 | 531 | To describe the same in more technical words, The bind (>>=) operator 532 | takes a value of the type 'f a' ('a' in the 'f' context), a function 533 | from 'a' to 'f b' ('b' in the 'f' context), and returns a value of 534 | type 'f b'. So, to understand what it means, let's get back to our 535 | example with 'Maybe'. But first, we need to get a function somewhere 536 | that we would be able to use for the second argument of (>>=) in our 537 | examples. 538 | 539 | Let's implement a "safe" 'half' function, that divides a number by 2, 540 | but only if the number is even. 541 | 542 | -} 543 | half :: Int -> Maybe Int 544 | half n 545 | | even n = Just (div n 2) 546 | | otherwise = Nothing 547 | 548 | {- | 549 | 550 | Now, we can experiment with this function and the 'Monad' instance of 551 | 'Maybe' in GHCi: 552 | 553 | >>> Just 6 >>= half 554 | Just 3 555 | >>> Just 3 >>= half 556 | Nothing 557 | >>> Nothing >>= half 558 | Nothing 559 | 560 | That makes sense — the resulting context depends on the value in the 561 | initial context. 562 | 563 | Let's now see how it is implemented. The instance for 'Maybe' is 564 | rather straightforward: 565 | 566 | @ 567 | instance Monad Maybe where 568 | (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b 569 | Nothing >>= _ = Nothing 570 | Just x >>= f = f x 571 | @ 572 | 573 | Look, it is even simpler than the 'Applicative' instance! I feel 574 | deceived now. I heard legends about merciless Monads, scary stories 575 | when I was a kid, but in reality, there is Nothing special in it! 576 | Could I even name myself a Monad conqueror now? (Of course, you can, 577 | but after you try to implement the instances in the exercises) 578 | 579 | On a general note, you can notice some similarities between the main 580 | methods of all three typeclasses: 581 | 582 | @ 583 | fmap :: (a -> b) -> f a -> f b 584 | (<*>) :: f (a -> b) -> f a -> f b 585 | (=<<) :: (a -> f b) -> f a -> f b 586 | @ 587 | 588 | ♫ NOTE: The (=<<) operator is a version (>>=) with arguments 589 | flipped. It could be implemented via the regular bind operator. 590 | 591 | All type signatures look similar, but they represent different 592 | concepts in the end. 593 | -} 594 | 595 | {- | 596 | =⚔️= Task 6 597 | 598 | Implement the 'Monad' instance for our 'Secret' type. 599 | -} 600 | instance Monad (Secret e) where 601 | (>>=) :: Secret e a -> (a -> Secret e b) -> Secret e b 602 | (>>=) = error "bind Secret: Not implemented!" 603 | 604 | {- | 605 | =⚔️= Task 7 606 | 607 | Implement the 'Monad' instance for our lists. 608 | 609 | 🕯 HINT: You probably will need to implement a helper function (or 610 | maybe a few) to flatten lists of lists to a single list. 611 | -} 612 | 613 | 614 | {- | 615 | =💣= Task 8*: Before the Final Boss 616 | 617 | So far we've been talking only about instances and use cases of 618 | different typeclasses. But one more cool thing we haven't discussed 619 | yet is the ability to implement functions polymorphic over the 620 | context. Let's say that you have two boolean values inside some 621 | contexts, and you want to apply the logical AND operation on them. But 622 | you don't know the context! It can be 'Maybe', or 'Either' or 'List', 623 | or anything else! However, this is not a problem in Haskell. You still 624 | can implement a function with the type signature described below. 625 | 626 | Can you implement a monad version of AND, polymorphic over any monad? 627 | 628 | 🕯 HINT: Use "(>>=)", "pure" and anonymous function 629 | -} 630 | andM :: (Monad m) => m Bool -> m Bool -> m Bool 631 | andM = error "andM: Not implemented!" 632 | 633 | {- | 634 | =🐉= Task 9*: Final Dungeon Boss 635 | 636 | You did it! You made it to the end of Haskell practice! It probably 637 | was challenging, but you made it nevertheless! This makes us so happy 638 | and proud of you! 639 | 640 | Hope you enjoyed learning functional concepts and will continue it in 641 | the future! 642 | 643 | Let us know how did you like the journey and how we can make it even 644 | more remarkable for you! This will take you a few minutes, but is 645 | precious for us and future journey starters: 646 | 647 | * https://docs.google.com/forms/d/e/1FAIpQLScBVhLxq5CgGnAfIGUE-fCoOUqeGkDY2HXzbT7KV2jjLOsmjQ/viewform 648 | 649 | 650 | Also, challenge your friends with this course and spread the word about us! 651 | We are @kowainik on Twitter. You can also use #Learn4Haskell hashtag to share 652 | your story. 653 | 654 | We also have a Ko-fi page, if you want to buy us a coffee after a long road. 655 | ☕️ https://ko-fi.com/kowainik 656 | 657 | You can also support creators of this course and your proud mentors on GitHub: 658 | ✧ https://github.com/sponsors/vrom911 659 | 660 | Now, for the dessert, it is time to test your skills on the final boss! 661 | Your task now will be to implement a Binary Tree data structure and 662 | some functions on it. 663 | 664 | Specifically, 665 | 666 | ❃ Implement a polymorphic binary tree type that can store any 667 | elements inside its nodes 668 | ❃ Implement the Functor instance for Tree 669 | ❃ Implement the reverseTree function that reverses the tree and each 670 | subtree of a tree 671 | ❃ Implement the function to convert Tree to list 672 | -} 673 | 674 | 675 | {- 676 | You did it! Now it is time to open pull request with your changes 677 | and summon @vrom911 for the review! 678 | -} 679 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | import Test.Hspec.Hedgehog (hedgehog, (===), forAll) 9 | 10 | import Chapter1 11 | 12 | import qualified Hedgehog.Range as Range (linear) 13 | import qualified Hedgehog.Gen as Gen (int) 14 | 15 | 16 | chapter1 :: Spec 17 | chapter1 = describe "Chapter1" $ do 18 | chapter1normal 19 | chapter1advanced 20 | 21 | reverseInt :: Int -> Int 22 | reverseInt x = (*) (signum x) . read . reverse . show . abs $ x 23 | 24 | chapter1normal :: Spec 25 | chapter1normal = describe "Chapter1Normal" $ do 26 | describe "Task4: next" $ do 27 | it "returns next Int for 42" $ next 42 `shouldBe` 43 28 | it "returns next Int for negative" $ next (-5) `shouldBe` (-4) 29 | it "returns next Int for 0" $ next 0 `shouldBe` 1 30 | describe "Task5: lastDigit" $ do 31 | it "last digit of 0" $ lastDigit 0 `shouldBe` 0 32 | it "last digit of 0 < x < 10" $ lastDigit 5 `shouldBe` 5 33 | it "last digit of 10 < x < 100" $ lastDigit 34 `shouldBe` 4 34 | it "last digit of 100 < x < 1000" $ lastDigit 341 `shouldBe` 1 35 | it "last digit of big num" $ lastDigit 1234789 `shouldBe` 9 36 | it "last digit of negative" $ lastDigit (-12) `shouldBe` 2 37 | describe "Task6: closestToZero" $ do 38 | it "both positive, 1st wins" $ closestToZero 100 200 `shouldBe` 100 39 | it "both positive, 2nd wins" $ closestToZero 200 100 `shouldBe` 100 40 | it "both negative, 2nd wins" $ closestToZero (-200) (-100) `shouldBe` (-100) 41 | it "both negative, 1st wins" $ closestToZero (-100) (-200) `shouldBe` (-100) 42 | it "with 0, 1st wins" $ closestToZero 0 (-200) `shouldBe` 0 43 | it "with 0, 2nd wins" $ closestToZero 10 0 `shouldBe` 0 44 | it "equals" $ closestToZero 42 42 `shouldBe` 42 45 | it "positive, negative, pos wins" $ closestToZero 11 (-12) `shouldBe` 11 46 | it "positive, negative, neg wins" $ closestToZero 12 (-11) `shouldBe` (-11) 47 | describe "Task7: mid" $ do 48 | it "positives up " $ mid 10 20 30 `shouldBe` 20 49 | it "positives down" $ mid 30 20 10 `shouldBe` 20 50 | it "positives mix " $ mid 20 30 10 `shouldBe` 20 51 | it "negatives down" $ mid (-10) (-20) (-30) `shouldBe` (-20) 52 | it "negatives up " $ mid (-30) (-20) (-10) `shouldBe` (-20) 53 | it "negatives mix " $ mid (-20) (-30) (-10) `shouldBe` (-20) 54 | it "all equal" $ mid 1 1 1 `shouldBe` 1 55 | it "all equal, except 1" $ mid 1 1 2 `shouldBe` 1 56 | it "all equal, except 1" $ mid 2 1 2 `shouldBe` 2 57 | it "all equal, except 1" $ mid 1 2 2 `shouldBe` 2 58 | describe "Task8: isVowel" $ do 59 | it "true for vowels" $ all isVowel "aeiou" `shouldBe` True 60 | it "false for non-vowels" $ isVowel 'c' `shouldBe` False 61 | it "false for symbol" $ isVowel '+' `shouldBe` False 62 | describe "Task9: sumLast2" $ do 63 | it "sumLast2 0" $ sumLast2 0 `shouldBe` 0 64 | it "sumLast2 0 < 10" $ sumLast2 9 `shouldBe` 9 65 | it "sumLast2 10 < 100" $ sumLast2 56 `shouldBe` 11 66 | it "sumLast2 100 < 1000" $ sumLast2 987 `shouldBe` 15 67 | it "sumLast2 0 > -10" $ sumLast2 (-9) `shouldBe` 9 68 | it "sumLast2 -10 > -100" $ sumLast2 (-56) `shouldBe` 11 69 | it "sumLast2 -100 > -1000" $ sumLast2 (-987) `shouldBe` 15 70 | 71 | chapter1advanced :: Spec 72 | chapter1advanced = describe "Chapter1Advanced" $ do 73 | describe "Task 10*" $ do 74 | it "first digit 0" $ firstDigit 0 `shouldBe` 0 75 | it "first digit 0 < 10" $ firstDigit 9 `shouldBe` 9 76 | it "first digit 10 < 100" $ firstDigit 58 `shouldBe` 5 77 | it "first digit 100 < 1000" $ firstDigit 158 `shouldBe` 1 78 | it "first digit big" $ firstDigit 467321 `shouldBe` 4 79 | it "first digit 0 > -10" $ firstDigit (-9) `shouldBe` 9 80 | it "first digit -10 > -100" $ firstDigit (-58) `shouldBe` 5 81 | it "first digit -100 > -1000" $ firstDigit (-158) `shouldBe` 1 82 | describe "Task 4 & 5 : first and last digit" $ do 83 | it "last digit is the first digit of the reversed number" $ hedgehog $ do 84 | xGen <- forAll $ Gen.int (Range.linear (-200) 200) 85 | digit <- forAll $ Gen.int (Range.linear 1 9) 86 | let x = if xGen `rem` 10 == 0 then xGen + digit else xGen 87 | (firstDigit x :: Int) === (lastDigit (reverseInt x) :: Int) 88 | -------------------------------------------------------------------------------- /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 | import Test.Hspec.Hedgehog (hedgehog, (===), forAll) 9 | 10 | import Chapter2 11 | 12 | import qualified Hedgehog.Range as Range 13 | import qualified Hedgehog.Gen as Gen 14 | 15 | 16 | chapter2 :: Spec 17 | chapter2 = describe "Chapter2" $ do 18 | chapter2normal 19 | chapter2advanced 20 | 21 | chapter2normal :: Spec 22 | chapter2normal = describe "Chapter2Normal" $ do 23 | describe "Task3: subList" $ do 24 | it "range on empty list" $ subList 1 2 emptyInts `shouldBe` emptyInts 25 | it "range within" $ subList 2 5 [0..7] `shouldBe` [2..5] 26 | it "range 0 .. len" $ subList 0 10 [0..10] `shouldBe` [0..10] 27 | it "range negative" $ subList (-1) 5 [0..5] `shouldBe` [] 28 | it "range overflow" $ subList 0 5 [0, 1] `shouldBe` [0, 1] 29 | it "range x > y" $ subList 5 2 [0..5] `shouldBe` [] 30 | it "range equal" $ subList 0 0 [0..3] `shouldBe` [0] 31 | describe "Task4: firstHalf" $ do 32 | it "empty" $ firstHalf emptyInts `shouldBe` emptyInts 33 | it "even len" $ firstHalf [1,3,5,7] `shouldBe` [1, 3] 34 | it "odd len" $ firstHalf [1,3,5,7, 10] `shouldBe` [1, 3] 35 | describe "Task5: isThird42" $ do 36 | it "empty" $ isThird42 emptyInts `shouldBe` False 37 | it "one elem" $ isThird42 [42] `shouldBe` False 38 | it "two elem" $ isThird42 [1, 42] `shouldBe` False 39 | it "three elem with 42" $ isThird42 [1, 2, 42] `shouldBe` True 40 | it "three elem without 42" $ isThird42 [1, 2, 3] `shouldBe` False 41 | it "more than three elem with 42" $ isThird42 [1, 2, 42, 4, 5, 6] `shouldBe` True 42 | it "more than three elem without 42" $ isThird42 [1..10] `shouldBe` False 43 | describe "Task6: duplicate" $ do 44 | it "empty" $ duplicate emptyInts `shouldBe` emptyInts 45 | it "one elem" $ duplicate [0] `shouldBe` [0, 0] 46 | it "two elems" $ duplicate [-1, 0] `shouldBe` [-1, -1, 0, 0] 47 | it "many elems" $ duplicate [0..5] `shouldBe` [0,0,1,1,2,2,3,3,4,4,5,5] 48 | describe "Task6: duplicate property" $ do 49 | it "length (duplicate xs) = 2 * length xs" $ hedgehog $ do 50 | xs <- forAll $ Gen.list (Range.linear 0 10) Gen.bool 51 | length (duplicate xs) === 2 * length xs 52 | describe "Task7: takeEven" $ do 53 | it "empty" $ takeEven emptyInts `shouldBe` emptyInts 54 | it "one elem" $ takeEven [1] `shouldBe` [1] 55 | it "two elem" $ takeEven [1,2] `shouldBe` [1] 56 | it "many elems" $ takeEven [0 .. 10] `shouldBe` [0, 2 .. 10] 57 | describe "Task8: smartReplicate" $ do 58 | it "empty" $ smartReplicate emptyInts `shouldBe` emptyInts 59 | it "one elem: 0" $ smartReplicate [0] `shouldBe` emptyInts 60 | it "one elem: negative" $ smartReplicate [-5] `shouldBe` emptyInts 61 | it "many positive" $ smartReplicate [0..3] `shouldBe` [1, 2, 2, 3, 3, 3] 62 | it "many negative" $ smartReplicate [0, -1 .. -3] `shouldBe` [] 63 | describe "Task9: contains" $ do 64 | it "empty" $ contains 0 ([] :: [[Int]]) `shouldBe` ([] :: [[Int]]) 65 | it "one with elem" $ contains 0 [[5, 0, 1]] `shouldBe` [[5, 0, 1]] 66 | it "one with elem, one without" $ contains 0 [[5, 0, 1], [1..4]] `shouldBe` [[5, 0, 1]] 67 | it "one with elem, one without" $ contains 0 [[1..4], [5, 0, 1]] `shouldBe` [[5, 0, 1]] 68 | it "all without" $ contains 0 [[1..4], [5,4..1]] `shouldBe` ([] :: [[Int]]) 69 | it "all with" $ contains 5 [[1..5], [6,5..1]] `shouldBe` [[1..5], [6,5..1]] 70 | describe "Task11: rotate" $ do 71 | it "empty list" $ rotate 5 emptyInts `shouldBe` emptyInts 72 | it "empty list with 0" $ rotate 0 emptyInts `shouldBe` emptyInts 73 | it "list rotate 0" $ rotate 0 [1..5] `shouldBe` [1..5] 74 | it "list rotate len" $ rotate 5 [1..5] `shouldBe` [1..5] 75 | it "list rotate n" $ rotate 3 [1..5] `shouldBe` [4,5,1,2,3] 76 | it "list rotate len + n" $ rotate 8 [1..5] `shouldBe` [4,5,1,2,3] 77 | it "empty on negative" $ rotate (-5) [1..5] `shouldBe` emptyInts 78 | 79 | chapter2advanced :: Spec 80 | chapter2advanced = describe "Chapter2Advanced" $ do 81 | describe "Task12*: rewind" $ do 82 | it "empty" $ rewind emptyInts `shouldBe` emptyInts 83 | it "one elem" $ rewind [1] `shouldBe` [1] 84 | it "many elems" $ rewind [1..10] `shouldBe` [10,9..1] 85 | it "many elems random" $ rewind [5,1,9,56,32,7,11] `shouldBe` [11,7,32,56,9,1,5] 86 | describe "Task12*: rewind Properties" $ do 87 | it "rewind == reverse" $ hedgehog $ do 88 | xs <- forAll $ Gen.list (Range.linear 0 10) Gen.bool 89 | rewind xs === reverse xs 90 | it "length rewind == length" $ hedgehog $ do 91 | xs <- forAll $ Gen.list (Range.linear 0 10) Gen.bool 92 | length (rewind xs :: [Bool]) === length xs 93 | 94 | emptyInts :: [Int] 95 | emptyInts = [] 96 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------