├── .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 | 
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 | [](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 |
--------------------------------------------------------------------------------