forked from kowainik/learn4haskell
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Chapter1.hs
670 lines (490 loc) · 21.3 KB
/
Chapter1.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
{- 👋 Welcome, Brave folks!
Happy to see you here, on the way to the wonderful Functional Programming land
with Haskell! Fight the fierce Monad Dragon and save the globe from despicable
runtime exceptions!
We appreciate your curiosity and will try to provide you with all the necessary
equipment for your training before the battle in the real FP world. Learning
Functional Programming can be challenging. But we designed this training to be
beginner-friendly and helpful to everyone!
Practice your functional skills, and choose your class in the end, who you want
to become: Monad Wizard, Exceptions Crusher, Recursion Priest, Free Fighter,
Composition Warlock, and many more!
Here is how it works:
★ Make sure that you familiarise yourself with this repo's README in order to
understand why and what we are trying to achieve with this.
★ For each Chapter, learn the material that we provide and try to solve each
task proposed to consolidate the results.
★ Create a PR with solutions to as many tasks as you can after each Chapter is
finished.
★ Make sure that the tests for the Chapter you are working on are passing.
★ Receive some feedback and suggestions from us.
★ Merge the PR.
★ Go to the next Chapter.
★ When finished, tell us how you liked it. Share it with your friends. Support
us on Ko-Fi or GitHub sponsorship.
== You are on __Chapter One__.
Here we will give the basic Haskell syntax knowledge and explain other common
concepts on the way. In this chapter, you are going to learn:
✧ Haskell language main particularities
✧ How to use Haskell interactively
✧ Expressions
✧ Types
✧ Basic language syntax: calling functions, if-then-else, defining local
variables
✧ How to write your function from scratch
✧ Some standard Haskell functions
We are leaving a number of tasks on our path. Your goal is to solve them all and
make the test for Chapter One green.
After finishing the PR, you can choose to summon me, @vrom911, to
look at your solution in order to give some advice on your code or help with
understanding and exercise solutions. This is optional; however, you can ask us
for review only if you want some feedback on your solutions.
Now, if you are ready, let's start!
-}
-- Single-line comments in Haskell start with --
{- | This tutorial uses block comments to explain various concepts and provide
task description.
-}
{- All code in Haskell is organised into modules. Each module corresponds to a
single file. Modules then can be combined in a package. But you don't need to
worry about this for now. We already created the package with module hierarchy
for you.
Each Haskell module starts with the "module <MODULE_NAME> where" line.
Modules should have the same name as the corresponding file with
the `.hs` extension.
-}
module Chapter1 where
{- |
In Haskell, we have __expressions__. Expressions can be represented by some
primitive values (numbers: 1, 100; characters: 'a', 'z'; booleans: True, False;
etc.) or by a combination of the primitive values and other expressions using
language syntax constructions (if-then-else, let-in, case-of, etc.) and various
functions (addition — (+), division — div, maximum — max, sorting — sort,
sortBy, sortOn, etc.) and variables. Functions are also expressions as well as
variables.
If an expression is a combination of other values and expressions, it can be
__evaluated__ (or reduced) to a primitive value. The evaluation process is not
immediate, since Haskell is a __lazy language__ and it won't evaluate
expressions unless really necessary. You can see the evaluation results either
by running a Haskell program or by playing with some functions in the
interactive interpreter (explained later).
Haskell is a __strongly-typed__ language, which means that each expression has
a type. Each value and function is associated with some type. You can't change
the value type. You can only pass a value to some function that will do its
work, and maybe produce a value of a different type.
Types can be _specific_ (like `Int`, `Integer`, `Double` or `Bool`) and they
always start with an uppercase letter, or _polymorphic_ (aka general) specified
through the variables – begin with a lowercase letter. The concept of
polymorphism is more sophisticated than working with concrete types, thus we
won't dive too much into it in this chapter and will work with concrete
types for now.
Furthermore, Haskell is a __statically-typed__ language, which means that each
expression has the type known at compile-time, rather than run-time. It allows
the compiler to catch some kinds of bugs in your program early; before you
even run it.
Additionally to static typing, Haskell has __type inference__. This means that
you _don't need_ to specify the type of each expression as it is going to be
found out for each expression and subexpression by the powerful compiler.
However, you are __strongly encouraged to write top-level function type
signatures__ and provide types in different situations where you don't
immediately see what types will be inferred.
-}
{-
Haskell is a __compiled__ language. In the illustration below, you can see the
overall picture of the process from your code to the binary of the written
program:
+-----------+ +-------------+ +-------------+
| | | | | |
| Parsing +------> Compilation +-----> Executable |
| | | | | |
+-----------+ +-------------+ +-------------+
In comparison, such languages as Python, JavaScript, etc. are interpreted
languages. And that could be illustrated in this different workflow:
+-----------+ +----------------+ +------------+
| | | | | |
| Parsing +------> Interpretation +-----> Evaluation |
| | | | | |
+-----------+ +----------------+ +------------+
So, when working with Haskell, you first need to compile the code in order to
run it later.
However, most of the time while working with Haskell (especially when learning
Haskell), you use __GHCi__ to play with functions, see their behaviour and
explore the API of external libraries. GHCi is a __Haskell REPL__
(read-eval-print-loop). It allows calling functions directly in an interactive
shell. GHCi interprets Haskell expressions and statements and prints the
evaluation result of each expression.
As you can see, Haskell supports both paradigms: you can either compile your
code and run the executable, or interpret the code and run it directly.
Assuming that you already have the Haskell compiler installed (we recommend
GHC), you can start GHCi by executing the following command in your terminal
from the root of this project.
$ ghci
> If you don't have Haskell yet, refer to the corresponding section of the
README.
Now, you can evaluate some expressions and see their results immediately.
>>> 1 + 2
3
♫ NOTE: Each time you see a line starting with >>> in this training, it means
that the text after >>> is supposed to be evaluated in GHCi, and the evaluation
result should be printed on the next line if it has any. In your GHCi, you
probably have the string "Prelude> " as your prompt. For some expressions, we
already prefilled the result, but for others, you need to insert it on your own.
We use lovely Haskell tools to test the results of the GHCi evaluation as well,
so you'd better insert exact answers in order to make them pass the testing!
You will also see lines started with "ghci>". They are also supposed to be
evaluated in GHCi, but our testing system doesn't check their output.
They are here just to showcase the different usages of GHCi.
GHCi can do much more than evaluating expressions. It also contains some special
commands starting with a colon. For example, to see the list of all available
commands, type ":?" in your GHCi.
ghci> :?
To quit GHCi, enter the ":q" command (short for ":quit").
ghci> :q
-}
{- |
=⚔️= Task 1
Since types play a crucial role in Haskell, we can start by exploring the types of
some basic expressions. You can inspect the type of expression by using the ":t"
command in GHCi (short for ":type").
For example:
>>> :t False
False :: Bool
"::" in Haskell indicates that the type of the expression before, would be
specified after these symbols.
So, the output in this example means that 'False' has type 'Bool'.
(ノ◕ヮ◕)ノ Your first task! Use GHCi to discover the types of the following
expressions and functions:
> Try to guess first and then compare your expectations with GHCi output
>>> :t True
<INSERT THE RESULT INSTEAD OF THE TEXT>
>>> :t 'a'
<INSERT THE RESULT INSTEAD OF THE TEXT>
>>> :t 42
<INSERT THE RESULT INSTEAD OF THE TEXT>
A pair of boolean and char:
>>> :t (True, 'x')
<INSERT THE RESULT INSTEAD OF THE TEXT>
Boolean negation:
>>> :t not
<INSERT THE RESULT INSTEAD OF THE TEXT>
Boolean 'and' operator:
>>> :t (&&)
<INSERT THE RESULT INSTEAD OF THE TEXT>
Addition of two numbers:
>>> :t (+)
<INSERT THE RESULT INSTEAD OF THE TEXT>
Maximum of two values:
>>> :t max
<INSERT THE RESULT INSTEAD OF THE TEXT>
You might not understand each type at this moment, but don't worry! You've only
started your Haskell journey. Types will become your friends soon.
Primitive types in Haskell include 'Int', 'Bool', 'Double', 'Char' and many
more. You've also seen the arrow "->" which is a function. When you see "A -> B
-> C" you can think that this is a function that takes two arguments of types
"A" and "B" and returns a value of type "C".
-}
{- |
=⚔️= Task 2
After having our first look at the Haskell type system, we can do something more
exciting. Call to arms! In other words, let's call some functions.
When calling a function in Haskell, you type a name of the function first, and
then you specify space-separated function arguments. That's right. No commas, no
parentheses. You only need to use () when grouping arguments (e.g. using other
expressions as arguments).
For example, if the function `foo` takes two arguments, the call of this
function can look like this:
ghci> foo arg1 (fun arg2)
Operators in Haskell are also functions, and you can define your own operators
as well! The important difference between operators and functions is that
functions are specified using alphanumeric symbols, and operators are specified
using "operator" symbols. For example, addition — +, cons — :, list append — ++,
diamond operator — <>. Also, by default, you call operators in __infix__
form (operator goes __after__ the first argument), while ordinary functions are
what-called __prefix__ form (the name goes first, before all arguments).
ghci> :t add
add :: Int -> Int -> Int
ghci> :t (+)
(+) :: Int -> Int -> Int
ghci> add 1 2
3
ghci> 1 + 2
3
♫ NOTE: in reality, the type of the + operator is the following:
>>> :t (+)
(+) :: Num a => a -> a -> a
> It may look scary to you, but we will cover all this 'Num' and "=>" later. For
now, you can think of this as a polymorphic function — in this case, the
operator, that can work with any numeric type, including 'Ints, 'Doubles,
etc. Or you can even pass the "+d" option to the ":t" command to see a simpler
type. In this case, polymorphic types will default to some standard types:
ghci> :t +d (+)
(+) :: Integer -> Integer -> Integer
Get ready for the next task, brave programmer! Evaluate the following
expressions in GHCi
> As in the previous task, try to guess first and then compare your expectations
with the GHCi output.
🕯 HINT: if you are curious, it might be interesting to explore the types of
functions and operators first. Remember this from the previous task? ;)
>>> 1 + 2
<INSERT THE RESULT INSTEAD OF THE TEXT>
>>> 10 - 15
<INSERT THE RESULT INSTEAD OF THE TEXT>
>>> 10 - (-5) -- negative constants require ()
<INSERT THE RESULT INSTEAD OF THE TEXT>
>>> (3 + 5) < 10
<INSERT THE RESULT INSTEAD OF THE TEXT>
>>> True && False
<INSERT THE RESULT INSTEAD OF THE TEXT>
>>> 10 < 20 || 20 < 5
<INSERT THE RESULT INSTEAD OF THE TEXT>
>>> 2 ^ 10 -- power
<INSERT THE RESULT INSTEAD OF THE TEXT>
>>> not False
<INSERT THE RESULT INSTEAD OF THE TEXT>
>>> div 20 3 -- integral division
<INSERT THE RESULT INSTEAD OF THE TEXT>
>>> mod 20 3 -- integral division remainder
<INSERT THE RESULT INSTEAD OF THE TEXT>
>>> max 4 10
<INSERT THE RESULT INSTEAD OF THE TEXT>
>>> min 5 (max 1 2)
<INSERT THE RESULT INSTEAD OF THE TEXT>
>>> max (min 1 10) (min 5 7)
<INSERT THE RESULT INSTEAD OF THE TEXT>
Because Haskell is a __statically-typed__ language, you see an error each time
you try to mix values of different types in situations where you are not
supposed to. Try evaluating the following expressions to see errors:
ghci> not 'a'
ghci> max True 'x'
ghci> 10 + True
This is a gentle way to get familiar with various error messages in Haskell.
In some cases, the error messages can be challenging to decipher and
understand their meaning. Haskell has a bad reputation for having not-so-helpful
error messages in some situations. But, of course, such a small challenge won't
stop you, right? You're a brave warrior, and you can finish all tasks despite
all obstacles! And we are always here to help and to decrypt these ancient
scripts together.
-}
{- |
=🛡= Defining a function
We have already learned how to use different functions and operators in Haskell.
Let's now check how they are defined and whether we can introduce our own.
When defining a function in Haskell, you write its type signature on the first
line, and then its body on the following line(s). The type signature should be
written immediately from the start of a line. Haskell is an __indentation-__ and
__layout-sensitive__ language, so this is important to keep in mind.
For example, here is the type signature of a function that takes a 'Double' and
an 'Int', and then returns an 'Int':
@
roundSubtract :: Double -> Int -> Int
@
We have already seen the "::" sequence of symbols when practising our skills in
GHCi. Now you know that this is the syntax for specifying types in your code as
well.
The following line should be the function definition start line. You write the
function name again and give argument names in the same order as you wrote the types
followed by the "=" sign. And you provide the function implementation after "=".
@
roundSubtract x y = ceiling x - y
@
^ Here x corresponds to the 'Double', and y to 'Int'.
The body of the function can be as big as you want. However, don't forget about
the indentation rules when your body exceeds the definition line.
The same function body can be written on a separate line, minding the
indentation.
@
roundSubtract x y =
ceiling x - y
@
Putting everything together, the complete function definition looks like this:
@
roundSubtract :: Double -> Int -> Int
roundSubtract x y = ceiling x - y
@
Now you are ready for defining your own functions!
-}
{- |
In our training, for some functions types are provided for you. For others, you
need to write types manually to challenge yourself.
Don't forget the main rule:
**Always provide type signatures for top-level functions in Haskell.**
-}
{- |
=⚔️= Task 3
Below you see the function that finds the square of the sum of two integers. Your
task is to specify the type of this function.
>>> squareSum 3 4
49
-}
squareSum x y = (x + y) * (x + y)
{- |
=⚔️= Task 4
Implement the function that takes an integer value and returns the next 'Int'.
>>> next 10
11
>>> next (-4)
-3
♫ NOTE: The current function body is defined using a special function called
"error". Don't panic, it is not broken. 'error' is like a placeholder, that
evaluates to an exception if you try evaluating it. And it also magically fits
every type 。.☆.*。. No need to worry much about "error" here, just replace the
function body with the proper implementation.
-}
next :: Int -> Int
next x = error "next: not implemented!"
{- |
After you've implemented the function (or even during the implementation), you
can run it in GHCi with your input. To do so, first, you need to load the module
with the function using the ":l" (short for ":load") command.
ghci> :l src/Chapter1.hs
After that, you can call the 'next' function as you already know how to do that.
Or any other function defined in this module! But remember, that you need to
reload the module again after you change the file's content. You can reload the
last loaded module by merely typing the ":r" command (no need to specify the
name again).
ghci> :r
A typical workflow looks like this: you load the module once using the ":l"
command, and then you should reload it using the ":r" command each time you
change it and want to check your changes.
-}
{- |
=⚔️= Task 5
Implement a function that returns the last digit of a given number.
>>> lastDigit 42
2
🕯 HINT: use the `mod` function
♫ NOTE: You can discover possible functions to use via Hoogle:
https://hoogle.haskell.org/
Hoogle lets you search Haskell functions either by name or by type. You can
enter the type you expect a function to have, and Hoogle will output relevant
results. Or you can try to guess the function name, search for it and check
whether it works for you!
-}
-- DON'T FORGET TO SPECIFY THE TYPE IN HERE
lastDigit n = error "lastDigit: Not implemented!"
{- |
=⚔️= Task 6
Implement a function, that takes two numbers and returns the one closer to zero:
>>> closestToZero 10 5
5
>>> closestToZero (-7) 3
3
🕯 HINT: You can use the 'abs' function and the __if-then-else__ Haskell syntax
for this task.
'if-then-else' is a language construct for an expression that returns only one
branch depending on the checked condition. For example:
>>> if even 10 then 0 else 1
0
The 'if-then-else' constructs must always have both __then__ and __else__
branches because it is an expression and it must always return some value.
👩🔬 Due to lazy evaluation in Haskell, only the expression from the branch
satisfying the check will be returned and, therefore, evaluated.
-}
closestToZero :: Int -> Int -> Int
closestToZero x y = error "closestToZero: not implemented!"
{- |
=⚔️= Task 7
Write a function that returns the middle number among three given numbers.
>>> mid 3 1 2
2
🕯 HINT: When checking multiple conditions, it is more convenient to use the
language construct called "guards" instead of multiple nested 'if-then-else'
expressions. The syntax of guards is the following:
@
sign :: Int -> Int
sign n
| n < 0 = (-1)
| n == 0 = 0
| otherwise = 1
@
You define different conditions in different branches, started by the '|'
symbol. And the functions check them from top to bottom, returning the first
value after "=" where the condition is true.
♫ NOTE: The "=" sign goes after each branch, respectively.
♫ NOTE: It is essential to have the same indentation before each branch "|"!
Remember, that Haskell is an indentation- and layout-sensitive language.
Casual reminder about adding top-level type signatures for all functions :)
-}
mid x y z = error "mid: not implemented!"
{- |
=⚔️= Task 8
Implement a function that checks whether a given character is a vowel.
🕯 HINT: use guards
>>> isVowel 'a'
True
>>> isVowel 'x'
False
-}
isVowel c = error "isVowel: not implemented!"
{- |
== Local variables and functions
So far, we've been playing only with simple expressions and function
definitions. However, in some cases, expressions may become complicated, and it
could make sense to introduce some helper variables.
You can use the let-in construct in Haskell to define variables.
Here goes an example:
@
half :: Int -> Int
half n = let halfN = div n 2 in halfN
@
♫ NOTE: __let-in__ is also an expression! You can't just define variables; you
also need to return some expression that may use defined variables.
The syntax for defining multiple variables requires to care about indentation
more, but there is nothing special in it as well:
@
halfAndTwice :: Int -> (Int, Int)
halfAndTwice n =
let halfN = div n 2
twiceN = n * 2
in (halfN, twiceN)
@
In addition to let-in (or sometimes even alternatively to let-in) you can use
the __where__ construct to define local variables and functions.
And, again, the example:
@
pythagoras :: Double -> Double -> Double
pythagoras a b = square a + square b
where
square :: Double -> Double
square x = x ^ 2
@
You can define multiple functions inside __where__!
Just remember to keep proper indentation.
-}
{- |
=⚔️= Task 9
Implement a function that returns the sum of the last two digits of a number.
>>> sumLast2 42
6
>>> sumLast2 134
7
>>> sumLast2 1
1
Try to introduce variables in this task (either with let-in or where) to avoid
specifying complex expressions.
-}
sumLast2 n = error "sumLast2: Not implemented!"
{- |
=💣= Task 10*
You did it! You've passed all the challenges in your first training!
Congratulations!
Now, are you ready for the boss at the end of this training???
Implement a function that returns the first digit of a given number.
>>> firstDigit 230
2
>>> firstDigit 5623
5
You need to use recursion in this task. Feel free to return to it later, if you
aren't ready for this boss yet!
-}
firstDigit n = error "firstDigit: Not implemented!"
{-
You did it! Now it is time to open a pull request with your changes
and summon @vrom911 for the review!
-}
{-
=📜= Additional resources
Modules: http://learnyouahaskell.com/modules
Let vs where: https://wiki.haskell.org/Let_vs._Where
Packages and modules in Haskell: https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/packages.html
-}