-
Notifications
You must be signed in to change notification settings - Fork 9
/
Basics.hs
1463 lines (1083 loc) · 38.2 KB
/
Basics.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
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{-
---
fulltitle: Haskell Basics
date: September 4, 2024
---
Welcome to Haskell!
-------------------
This material is available in two formats: both
[online](https://www.cis.upenn.edu/~cis5520/current/stub/01-basics/Basics.html),
and embedded in the comments of a Haskell module in the repository
[01-basics](https://github.com/upenn-cis5520/01-basics) on github. We have prepared
instructions on using
[github with CIS 5520](https://www.cis.upenn.edu/~cis5520/current/version.html)
and with installing
[GHC and VSCode](https://www.cis.upenn.edu/~cis5520/current/haskell-vscode.html).
We strongly encourage you to read this module using the VSCode editor so that you
can experiment with it.
This module is an introduction to the basics of the Haskell language. It explains
the components of the language through examples and provides exercises
to test your understanding.
First, every Haskell file begins with the name of the module (this name must
start with a capital letter and be the same as the name of the file, without the `.hs`).
After that, the next lines specify definitions that are imported from other modules.
These import lines must be at the beginning of the file, before any other definitions.
-}
module Basics where
-- library imports must come at the beginning of the module
-- these come from the HUnit library for unit testing
import Test.HUnit
( Counts,
Test (TestList),
runTestTT,
(~?),
(~?=),
)
import Prelude hiding (const, or, sum, tail, take)
{-
Haskell supports two kinds of comments: single line comments start with `--`
and block comments that begin and end with `{-` and `-}` respectively.
What does it mean to understand a Haskell program?
--------------------------------------------------
Our goal in this class is not just for you to be able to write programs. We
also want you to be able to reason about them both precisely and abstractly,
so that you understand exactly what they mean.
We can do this because Haskell is a functional programming language, and
\*functional* programming means that the semantics of a program can be described
mathematically. One principle of mathematics *Leibniz* equality: in any context, we
can replace any object with anything that is equal to it. In other words, if
we know that some property `P` is true about some object `A`, and `A` is equal
to `B`, then we know that the property `P` *also* holds for `B`.
Therefore, in Haskell, we reason about computation by reasoning about *equality* of sub-expressions.
For example, if we want to know what value an arithmetic expression computes
to, we only need to find some number that is equal to it.
3 * (4 + 5)
{ 4+5 is equal to 9 by addition }
== 3 * 9
{ by multiplication }
== 27
That's it!
Furthermore, we can ask VSCode to compute the value of an expression for us with a
special form of comment (i.e. a single line comment that starts with '>>>').
If you are working in VSCode, try clicking on "Evaluate..." below. (You will only
see "Evaluate..." if your IDE is set up correctly.)
-}
-- >>> 3 * (4 + 5)
{-
This sort of reasoning isn't so surprising so far. We can do the same thing in
almost any other language. What makes Haskell different is that we will be able
to use this equational reasoning in more contexts than just arithmetic.
A Haskell module is a list of definitions
-----------------------------------------
A Haskell module (like this one) is a list of *definitions*. These definitions
allow us to give names to Haskell expressions. In a module, all of the definitions
must have unique names. If we try to add another definition of `ex`, we will get
an error.
-}
ex :: Integer
ex = 3 * (4 + 5)
{-
We can ask VSCode to calculate with these names, just as we did above.
-}
-- >>> ex
{-
Haskell definitions do not need to be in order. In a definition for one name, we
we can use any other name in the module, even if it appears later in the file.
-}
ey :: Integer
ey = ex + ez -- refers to `ex` above and `ez` below.
ez :: Integer
ez = ex + 200
-- >>> ey
{-
Whenever we give a name to an expression, it is a good idea to also write down
its type, as we have done so for `ex` above. VSCode can often figure
out this type for you and will suggest that you add it to your file by clicking
on the suggestion. You should do so---as later error messages can be perplexing
without these additional hints.
The `Integer` type is the type of arbitrarily large integers in Haskell.
-}
bigInteger :: Integer
bigInteger = 12345678901234567890
{-
This is in contrast to the `Int` type, for word-sized integers (machine
dependent). Numbers are overloaded in Haskell, so the type annotation tells
the compiler how to interpret this expression. (And, note the warning
issued by the compiler for this out of range number!)
-}
bigInt :: Int
bigInt = 12345678901234567890
{-
Compare the value of a extra-large `Integer`
-}
-- >>> bigInteger
{-
with an `Int`
-}
-- >>> bigInt
{-
Above, we declared the type of an expression separately from giving it a
name. However, if we don't want to give a name to an expression, we can
still annotate it with its type using `::`.
-}
-- >>> 31 * (42 + 56) :: Integer
{-
More generally, the type annotation can be attached to any subexpression, not
just at the top level.
-}
-- >>> (31 :: Integer) * (42 + 56)
{-
It is good style to annotate the type of *every* top-level declaration in a Haskell
program. This helps with error messages, as Haskell operators, like `*`, and
constants like '31', are overloaded.
Elements of Haskell
-------------------
So far, we have have seen the following three properties of Haskell:
\* Haskell code is based on *expressions*
\* Expressions evaluate to *values*, which are equal to expressions
\* Every expression has a *type*, which may influence evaluation
You are probably familiar with expressions in other programming languages,
where they are often used to compute numeric and boolean values. Haskell also
includes these types and operators.
For example, Haskell includes floating point numbers, via the `Double` type,
using the same overloaded syntax.
-}
-- >>> 31 * (42 + 56) :: Double -- double precision floating point
{-
Furthermore, you'll also find characters, strings and boolean values.
-}
-- >>> 'a' :: Char -- characters
-- >>> "abcd" :: String -- strings
-- >>> "cis" ++ "5520" -- string concatenation
-- >>> True :: Bool -- boolean values
-- >>> 1 <= 3 || False && 3 > 2 -- boolean operators, comparisons
{-
What is a little different about Haskell is that everything is an expression,
including conditionals. This means that `if` can be nested inside other
expressions.
-}
-- >>> (if ex > 28 then 1 else 0) + 2 :: Int
{-
Now the last basic type, shown below, is subtle. It is a special constant,
written `()` and called "unit". The type of this constant is written with the
same notation, and also called "unit". You'll need to pay attention to
context to know whether we mean the value or the type. The key fact about
this basic type is that there is only *one* value with type `()`. As a result,
we sometimes say that this type is "trivial" --- if we know that a variable has
type `()`, then we know that its value (if it has one) must be equal to `()`.
-}
-- >>> () :: () -- 'unit' (both value and type have the same syntax)
{-
What is the point of a trivial type? In Haskell, every function is a mathematical function,
so must return some value. However, sometimes there isn't anything interesting to return, so the
function returns `()`. This is similar to the "void" type in other languages.
We'll have more to say about unit later.
What is Abstraction?
--------------------
We're going to talk a lot about *abstraction* in this course, starting from
simple examples and getting dramatically more expressive. But what is
abstraction, exactly?
The first answer is: "Pattern Recognition"
31 * (42 + 56)
70 * (12 + 95)
90 * (68 + 12)
What do these expressions have in common? They all follow the same general
pattern. We can generalize that pattern to a *function* by defining an equation.
-}
pat :: Integer -> Integer -> Integer -> Integer
pat a b c = a * (b + c)
{-
We call functions by providing them with arguments.
-}
-- >>> pat 31 42 56
{-
No parentheses are necessary, unless the argument itself is a compound expression.
-}
-- >>> pat (30 + 1) 42 56
{-
The important question is not "What does this function do?"
but, instead "What does this function mean?" We can reason
about that meaning using what we know about equality.
pat (30 + 1) 42 56
{ function call, replace a b & c in right-hand side of
equation by (30 + 1) 42 and 56 }
== (30 + 1) * (42 + 56)
{ addition (twice) }
== 31 * 98
{ multiplication }
== 3038
Functions, like `pat`, are the core abstraction mechanisms in functional
programming.
There was something subtle about the example above: we put the expression
`(30 + 1)` in for `a` in the body of `pat`. When reasoning in most languages,
it is only sound to do this replacement using values (like 31) instead general
expressions, like `(30 + 1)`. Haskell, supports more flexible
reasoning. We could have also replaced `(30 + 1)` with `31` in the first step above,
before using the equation for the function call, and we would have gotten
the same result.
Function types
--------------
Like all expressions, functions have types.
The type of a function taking an input of type `A` and yielding an output of
type `B` is written as
A -> B
For example, the `pos` function determines whether an `Int` is strictly
greater than zero.
-}
pos :: Int -> Bool
pos x = x > 0
{-
The `pat` function above takes multiple arguments. Therefore, its type has
multiple `->`s, one for each argument.
The type of a function taking inputs of type `A1`, `A2`, and `A3` and
returning a result of type `B` is written as
A1 -> A2 -> A3 -> B
Symbolic vs. alphabetic names
-----------------------------
Symbolic identifiers (i.e. `+` and `*`) are infix by default.
Parentheses around a symbolic name turn it into a regular
name.
For example, if we want to define an alphabetic name for the
addition function, we can do so.
-}
plus :: Int -> Int -> Int
plus = (+)
{-
And we can call operations in parentheses just like "standard" functions, by
writing their arguments afterwards.
-}
p0 :: Int
p0 = (+) 2 4
{-
Likewise we can use alphabetic name in backquotes as infix.
-}
p1 :: Int
p1 = 2 `plus` 2
{-
We can also define new symbolic infix identifiers. For example,
the `>+` operator below is just like `+`, but it adds one to the result.
When giving its type, we put it in parentheses to refer to it outside of
an infix context. However, in its definition, we put the parameters `x` and `y`
on either side of the operator.
-}
(>+) :: Int -> Int -> Int
x >+ y = x + y + 1
-- >>> 2 >+ 3
{-
Laziness is a virtue
--------------------
One major difference between Haskell and other programming languages is that
Haskell uses a "call-by-need" semantics for evaluation, aka "lazy" evaluation.
What this means is that Haskell does not evaluate arguments before calling
functions. Instead, expressions are only evaluated when they are needed.
We can observe this behavior in Haskell by seeing what happens when we use
`error` in a subexpresion. The `error` keyword in Haskell triggers a
\*non-recoverable* runtime exception, aborting any computation in progress.
It is not a good idea to use `error` in production code, but it is convenient
for incremental development and for learning about how Haskell evaluation works.
An `error` can be used in any context and can be given
any type because it does not produce a value.
If an error is triggered, then we know that subexpression was evaluated.
For example, addition always needs to evaluate its arguments, so this
error will trigger.
-}
-- >>> 1 + 2 + 3 + error "Here!"
{-
However, we won't trigger an error that is in dead code, such as in
the non-selected part of an if-expression...
-}
-- >>> if 1 < 3 then 5 else error "Unreachable"
-- 5
{-
..or that was short-circuited when evaluating a boolean expression.
-}
-- >>> True || error "Unreachable"
-- True
{-
In contrast, you can see that if the first argument were `False` instead,
it does not short circuit and does trigger the error.
-}
-- >>> False || error "Here!"
{-
In most languages, `if` and `||` are defined via special constructs because they
include sub-expressions that are not always evaluated. However, in Haskell, these
constructs are less special. For example, you can define your own short-circuiting
version of the `or` operator. Suppose you would like this operator to have a textual
name instead of (infix) symbols:
-}
or :: Bool -> Bool -> Bool
or a b = if a then True else b
{-
Through laziness, this definition short circuits, just like the Prelude version of `||`.
-}
-- >>> or True (error "Unreachable")
{-
More generally, because Haskell is lazy, the language enables more abstraction.
Functions and operators that we define can have nontrivial control behavior.
Laziness is also the reason that we can reason about Haskell programs just by thinking
about equalities. For example, there is a function in the Prelude with the following
definition:
-}
const :: a -> b -> b
const x y = y
{-
In most languages if you see a subexpression like
`const (f 3) 4`, you have to know whether the expression `f 3` produces a normal value
first before you can know that the result is 4. However, in Haskell, you can use
substitution: the pattern above says that with any call to `const`, the result is the value of
the second argument.
Thus:
-}
-- >>> const (error "Here!") 4
{-
Laziness is unique to Haskell, and we'll see more examples of it throughout the semester.
Sometimes we use the word "strictness" to contrast with laziness. If an argument will
always be evaluated before the function is called, we call this argument "strict." In most
languages all functions are strict.
However, in Haskell, functions can be either lazy or strict, and lazy is the default.
For example, both arguments in an addition operation are strict, because we need to know what
the numbers are to sum them together. However, only the first argument in `||` is strict
because it does not evaluate the second argument when the value of the first one is `True`.
Making Haskell DO something
===========================
Programs often interact with the world:
\* Read files
\* Display graphics
\* Broadcast packets
\* Run test cases and print success or failure
They don't *just* compute values.
How does this fit with values & equalities above?
We've gotten pretty far without doing any I/O. That's fairly standard in
Haskell. Working with VSCode means that we can see the answers directly, we
don't need an action to print them out. However, a standalone executable needs
to do *something*, so we demonstrate that next.
The GHC System
--------------
We'll start with a few examples just using the interactive toplevel for the
Haskell language. Although Haskell is a compiled language, the interactive
toplevel, "ghci" is available for experimentation. You can access this
toplevel using any command prompt (i.e. Terminal), as long as you have GHC
installed. The examples below also assume that you have the "stack" tool
available and that you have started the command prompt in the same directory
that contains this source code. [Instructions for installing "stack" and
other tools are available.](https://www.cis.upenn.edu/~cis5520/current/haskell-vscode.html)
First use the terminal to start `ghci` and instruct it to load the `Basics`
module.
sweirich@sixteen 01-basics % stack ghci Basics.hs
Using configuration for cis5520-basics:lib to load /Users/sweirich/552/cis5520-23fa/lectures/01-basics/Basics.lhs
cis5520-basics> configure (lib)
Configuring cis5520-basics-0.1.0.0...
cis5520-basics> initial-build-steps (lib)
Configuring GHCi with the following packages: cis5520-basics
GHCi, version 9.2.5: https://www.haskell.org/ghc/ :? for help
[1 of 1] Compiling Basics ( /Users/sweirich/5520/cis5520-23fa/lectures/01-basics/Basics.lhs, interpreted )
Ok, one module loaded.
Loaded GHCi configuration from /private/var/folders/p3/pkythxvx6rq9q054797y4bb80000gn/T/haskell-stack-ghci/48e82592/ghci-script
*Basics>
Now, with the prompt from the module, you can ask for the values, types and
information of expressions.
*Basics> ex
27
*Basics> :t ex
ex :: Integer
*Basics> :i ex
ex :: Integer -- Defined at Basics.hs:5:1
Obligatory Hello World
----------------------
"IO actions" are a new sort of sort of value that describe an effect
on the world.
IO a -- Type of an action that returns an `a` (a can be anything!)
Actions that *do* something but return nothing have the type `IO ()`.
putStr :: String -> IO ()
So `putStr` takes in a string and returns an *action* that writes the string
to stdout.
Because `putStr` doesn't evaluate to a value that we can print, we can't play with
it using the IDE. Even if you hit `Evaluate...` you will see nothing because the
IDE hides the printing action.
-}
-- >>> putStr "Say what?"
{-
Instead, to observe the printing action, we need to use GHCi. Let's give this action
a name first:
-}
hw :: IO ()
hw = putStr "Hello World!\n"
{-
and then we can give it a try.
*Basics> hw
Hello World!
*Basics>
Just 'do' it
-------------
How can we do many actions? By composing small actions.
The `do` syntax allows us to create a compound action that sequences one
action after another. The definition of `many` below is a compound action
that outputs the three strings in order. (Try it out in ghci!)
-}
many :: IO ()
many = do
putStr "Hello" -- each line in the sequence
putStr " World!" -- must be an IO action
putStr "\n" -- don't forget the newline
{-
Note: white-space is significant here. The `do` notation sequences actions, but
each action in the sequence must start at the same character offset: all of
the `putStr`s must be lined up.
Sometimes people put the `do` on a line by itself and then start the list
of actions on the next line. This saves column width in larger developments.
-}
many' :: IO ()
many' = do
putStr "Hello"
putStr " World!"
putStr "\n"
{-
Observe that we are taking advantage of laziness when we construct these actions.
The `many` action does some printing, but that printing doesn't happen when when
we define `many` --- only later when we run it in ghci.
Example: Input Action
---------------------
Actions can also return a value.
getLine :: IO String
This action reads and returns a line from stdin. We can name the result
as part of a `do` sequence, with this notation, called "bind".
x <- action
Here `x` is a variable that can be used to refer to the result of the
action in later code. For example, below, `n` is new `String` variable
that is initialized by reading from stdin.
-}
query :: IO ()
query = do
putStr "What is your name? "
n <- getLine
putStrLn ("Welcome to CIS 5520 " ++ n)
{-
Any `IO t` action can be used with bind to create a variable of type `t`.
However, when working with type `IO ()` the result is a special value,
written `()` and called "unit". This special value indicates that the IO
action did not have anything interesting to return---similar to a method
that returns "void" in other programming languages.
What is different in Haskell is that we do have a value of type `()`,
and we can name it with bind, as shown in `query'` below. However, we probably
won't do anything with this value.
(Note that this example also demonstrates the Haskell convention to name
variables that are unused with names that start with an underscore.)
-}
query' :: IO ()
query' = do
_m <- putStr "What is your name? "
n <- getLine
putStrLn ("Welcome to CIS 5520 " ++ n)
{-
Note that you cannot name the *last* action in a sequence. Names are there so that
you can use their results later. If you want to return the value instead, the last action
should be a `return`.
-}
query2 :: IO String -- compare this type to `query` above.
query2 = do
putStr "What is your name? "
n <- getLine
return n
{-
Furthermore, there is no need to name a value if it is just going to be
returned right away. This version is also equivalent.
-}
query2' :: IO String
query2' = do
putStr "What is your name? "
getLine
{-
Example: Testing Actions
------------------------
The `hunit` library contains definitions for constructing unit tests for your
programs. You must import this library at the top of your module (with
import Test.HUnit`) before you can access these definitions. This library
defines a `Test` type for test cases.
-}
-- | A test case that, when run, checks that the result of computation
-- matches the expected value 3
t1 :: Test
t1 = (1 + 2 :: Int) ~?= 3
{-
\* Haskell is lazy by default, so these definitions *create* tests, but don't
actually run them yet. We'll do that below.
\* The `(~?=)` operator is overloaded. You can create tests that compare
expressions at many different types. When the expressions themselves are also
overloaded (such as those with numbers), we run into ambiguity---what type of
expressions should this test actually use? We resolve that ambiguity with
an internal typing annotation `(3 :: Int)`.
To run the test case, we use the function `runTestTT`.
runTestTT :: Test -> IO Counts
-}
numTest :: IO Counts
numTest = runTestTT t1
{-
This expression is an action that runs the test case(s) and returns a data
structure (of type `Counts`) recording how many tests cases were run and how
many failed or produced errors. A failing test case is caused by a false
assertion, for example `True ~?= False`. An erroroneous testcase is one that
produced a run-time error, such as division by zero.
Although we can evaluate `numTest` in the IDE, we get more information about
failing and erroroneous tests using `ghci`.
*Basics> runTestTT (True ~?= False)
### Failure:
<interactive>:16
expected: False
but got: True
Cases: 1 Tried: 1 Errors: 0 Failures: 1
Counts {cases = 1, tried = 1, errors = 0, failures = 1}
*Basics> runTestTT ( 1 `div` 0 ~?= (1 :: Int))
### Error:
divide by zero
Cases: 1 Tried: 1 Errors: 1 Failures: 0
Counts {cases = 1, tried = 1, errors = 1, failures = 0}
Structured Data
===============
Tuples
------
An ordered sequence of values is called a *tuple* and its type is written as
(where `A1` ... `An` are the types of the values in the sequence).
(A1, ..., An)
For example,
-}
tup1 :: (Char, Int)
tup1 = ('a', 5)
tup2 :: (Char, Double, Int)
tup2 = ('a', 5.2, 7)
tup3 :: ((Int, Double), Bool)
tup3 = ((7, 5.2), True)
{-
There can be any number of elements in a tuple, but the structure must match
the type.
\*Pattern Matching* extracts values from tuples.
A function that takes a tuple as an argument *looks* like it has multiple
arguments, but in reality, it has just one. We use a *pattern* to name the
three components of the tuple for use in the function.
-}
tpat :: (Int, Int, Int) -> Int
tpat (a, b, c) = a * (b + c)
{-
We can put *anything* in a tuple
--------------------------------
We can have tuples of tuples. These three values below have three different
types. Look closely! (We use the word 'pair' to describe a tuple with two
values.)
-}
tup4 :: ((Int, Int), Int)
tup4 = ((1, 2), 3) -- a pair of a pair and a number
tup5 :: (Int, (Int, Int))
tup5 = (1, (2, 3)) -- a pair of a number and a pair
{-
-}
tup6 :: (Int, Int, Int)
tup6 = (1, 2, 3) -- a three-tuple, or triple
{-
Note that the pattern that names the variables must match the structure of the
type *exactly*.
-}
pat4 :: ((Int, Int), Int) -> Int
pat4 ((a, b), c) = a * (b + c)
-- >>> pat4 tup4
pat5 :: (Int, (Int, Int)) -> Int
pat5 (a, (b, c)) = a * (b + c)
-- >>> pat5 tup5
pat6 :: (Int, Int, Int) -> Int
pat6 (a, b, c) = a * (b + c)
-- >>> pat6 tup6
{-
We can stick anything in tuples, even IO actions.
-}
act2 :: (IO (), IO ())
act2 = (putStr "Hello", putStr "Hello")
{-
This doesn't actually run both actions, it just creates a pair holding two IO
computations. Haskell doesn't evaluate the components of a tuple when it is
constructed. It waits until you actually need it.
Compare the difference between these definitions in ghci. Try to predict what
they will do.
-}
runAct2 :: IO ()
runAct2 = do
let (x, y) = act2 -- pattern match in `do` sequences using `let`
x -- run the first action
y -- then run the second
runAct2' :: IO ()
runAct2' = do
let (x, y) = act2 -- pattern match
y -- run the second action
x -- then run the first
runAct2'' :: IO ()
runAct2'' = do
let (x, y) = act2 -- pattern match
x -- run the first action
x -- then run it again!
{-
Optional values
---------------
The type of "optional" or "partial" values is written
Maybe A
This type describes either a value of type `A`, or else nothing.
-}
m1 :: Maybe Int
m1 = Just 2 -- the 'Just' tag tells the compiler that we have a value
m2 :: Maybe Int
m2 = Nothing -- the 'Nothing' tag means there is no value
{-
The `Nothing` value of the `Maybe` type often plays the role that `null` does
in other languages. But note that `Nothing` is not a value of type `Int` or
`String` or any other type. It is a value of type `Maybe Int`. This means that
we can't accidentally use `Nothing` where we need an `Int`. This is a good
thing, because it means that we can't be surprised by `null` --- the types tell
us exactly when a value could be `Nothing`.
'Maybe' is useful for partial functions
---------------------------------------
A common use of the `Maybe` type is for functions that don't always have a value
to return. For example, suppose we know the location of some, but not all, classes
in the CIS department. For the ones that we have no information about, we can
return `Nothing`.
-}
location :: String -> Maybe String
location "cis5010" = Just "Wu & Chen"
location "cis5020" = Just "Heilmeier"
location "cis5200" = Just "Wu & Chen"
location "cis5520" = Just "3401 Walnut, 401B"
location _ = Nothing -- wildcard pattern, matches anything
{-
Extracting values from 'Maybe's
--------------------------------
Pattern matching extracts values from `Maybe`s; we need a pattern for each
case.
-}
pat'' :: Maybe Int -> Int
pat'' (Just x) = x
pat'' Nothing = 2
{-
Patterns can be nested, too.
-}
jn :: Maybe (Maybe a) -> Maybe a
jn (Just (Just x)) = Just x
jn (Just Nothing) = Nothing
jn Nothing = Nothing
{-
\**Quiz**: See if you can come up with a slightly simpler way to write `jn` using two
patterns instead of three. The `undefined` expression is one that produces a run-time
error if it is ever evaluated.
-}
jn' :: Maybe (Maybe a) -> Maybe a
jn' = undefined
{-
Lists
=====
The type of a list of values, each of type `A` is written
[A]
A list is a sequence of values of the same type. There is no limit to
the number of values that can be stored in a list. We notate lists as
a sequence of comma-separated values inside square brackets.
-}
l1 :: [Double]
l1 = [1.0, 2.0, 3.0, 4.0]
l2 :: [Int]
l2 = undefined -- make a list of numbers
{-
Lists can contain structured data...
-}
l3 :: [(Int, Bool)]
l3 = [(1, True), (2, False)]
{-
...and can be nested:
-}
l4 :: [[Int]]
l4 = undefined -- make a list of lists
{-
List elements *must* have the same type.
-}
-- l5 :: [Int]
-- l5 = [ 1 , True ] -- doesn't type check
{-
(Observe the type error that results when you uncomment the definition above.)
The empty list is written `[]` and pronounced "nil".
-}
l6 :: [a]
l6 = []
{-
\*Note*: `String` is just another name for a list of characters (`[Char]`).
-}
l7 :: String
l7 = ['h', 'e', 'l', 'l', 'o', ' ', '5', '5', '2', '0', '!']
{-
What is the value of l7?
-}
-- >>> l7
--
{-
"Cons"tructing Lists
---------------------
The infix operator `:` constructs a new list, by adding a new element to the
front of an existing list. (Note, the existing list is not modified.)
We call this operator `cons`.
(The `a` in the type of `cons` below means that this function works for lists
containing *any* type of element. In other words, we say that this function
is *polymorphic*. More on this later.)
-}
cons :: a -> [a] -> [a]
cons = (:)
c1 :: [Bool]
c1 = True : [False, False]
c2 :: [Int]
c2 = 1 : []
{-
Try evaluating `c1` and `c2`.
-}
-- >>> c1
--
-- >>> c2
--
{-
And check out the type of `c3`.
-}
c3 = [] : []