This commit is contained in:
Yann Esposito (Yogsototh) 2019-12-25 16:35:56 +01:00
parent 383b69bc75
commit 7784f02483
Signed by untrusted user who does not match committer: yogsototh
GPG key ID: 7B19A4C650D59646
17 changed files with 400 additions and 371 deletions

View file

@ -213,12 +213,6 @@ figure, .figure {
.notes { .notes {
padding: 5px 10px; padding: 5px 10px;
} }
.notes::before {
content: "☞";
float: left;
display: inline-block;
width: 1.5em;
}
.underline { .underline {
text-decoration: underline; text-decoration: underline;
} }
@ -376,12 +370,21 @@ pre::after,pre::before,hr:after,
nav a, nav a:visited, .main nav a,.main nav a:visited { nav a, nav a:visited, .main nav a,.main nav a:visited {
color: var(--fg2); color: var(--fg2);
} }
#labels label:hover, a:hover, a:active, a:focus, .main a:hover,.main a:active,.main a:focus, #labels label:hover,
nav a:focus, nav a:hover, .main nav a:focus,.main nav a:hover { a:hover,
a:hover *,
.main a:hover,
.main a:hover *,
nav a:hover,
.main nav a:hover {
color: var(--l-fg); color: var(--l-fg);
background: var(--l-bg); background: var(--l-bg);
} }
abbr { border-bottom: dashed 1px;
display: inline-block;
}
thead, .main thead, tr:hover, .main tr:hover { thead, .main thead, tr:hover, .main tr:hover {
background: var(--rbg); background: var(--rbg);
color: var(--rfg); color: var(--rfg);
@ -428,6 +431,7 @@ blockquote:after, .main blockquote:after {
.notes, .main .notes { .notes, .main .notes {
background: var(--rbg); background: var(--rbg);
color: var(--rfg); color: var(--rfg);
margin: 1em 0;
} }
/* ---- SYNTAX HIGHLIGHTING ---- */ /* ---- SYNTAX HIGHLIGHTING ---- */
.org-rainbow-delimiters-depth-1, .org-rainbow-delimiters-depth-9, .org-rainbow-delimiters-depth-1, .org-rainbow-delimiters-depth-9,

View file

@ -0,0 +1,10 @@
-- Version 1
evenSum :: [Integer] -> Integer
evenSum l = accumSum 0 l
accumSum n l = if l == []
then n
else let x = head l
xs = tail l
in if even x
then accumSum (n+x) xs
else accumSum n xs

View file

@ -0,0 +1,6 @@
-- Version 10
import Data.List (foldl')
sum' :: (Num a) => [a] -> a
sum' = foldl' (+) 0
evenSum :: Integral a => [a] -> a
evenSum = sum' . (filter even)

View file

@ -0,0 +1,11 @@
-- Version 2
evenSum :: Integral a => [a] -> a
evenSum l = accumSum 0 l
where accumSum n l =
if l == []
then n
else let x = head l
xs = tail l
in if even x
then accumSum (n+x) xs
else accumSum n xs

View file

@ -0,0 +1,8 @@
-- Version 3
evenSum l = accumSum 0 l
where
accumSum n [] = n
accumSum n (x:xs) =
if even x
then accumSum (n+x) xs
else accumSum n xs

View file

@ -0,0 +1,9 @@
-- Version 4
evenSum :: Integral a => [a] -> a
evenSum = accumSum 0
where
accumSum n [] = n
accumSum n (x:xs) =
if even x
then accumSum (n+x) xs
else accumSum n xs

View file

@ -0,0 +1,5 @@
-- Version 5
evenSum l = mysum 0 (filter even l)
where
mysum n [] = n
mysum n (x:xs) = mysum (n+x) xs

View file

@ -0,0 +1,6 @@
-- Version 6
-- foldl' isn't accessible by default
-- we need to import it from the module Data.List
import Data.List
evenSum l = foldl' mysum 0 (filter even l)
where mysum acc value = acc + value

View file

@ -0,0 +1,5 @@
-- Version 7
-- Generally it is considered a good practice
-- to import only the necessary function(s)
import Data.List (foldl')
evenSum l = foldl' (\x y -> x+y) 0 (filter even l)

View file

@ -0,0 +1,4 @@
-- Version 8
import Data.List (foldl')
evenSum :: Integral a => [a] -> a
evenSum l = foldl' (+) 0 (filter even l)

View file

@ -0,0 +1,4 @@
-- Version 9
import Data.List (foldl')
evenSum :: Integral a => [a] -> a
evenSum = (foldl' (+) 0) . (filter even)

View file

@ -0,0 +1,25 @@
square :: Num a => a -> a
square x = x^2
square' x = (^) x 2
square'' x = (^2) x
square''' = (^2)
absolute :: (Ord a, Num a) => a -> a
absolute x = if x >= 0 then x else -x
absolute' x
| x >= 0 = x
| otherwise = -x
main = do
print $ square 10
print $ square' 10
print $ square'' 10
print $ square''' 10
print $ absolute 10
print $ absolute (-10)
print $ absolute' 10
print $ absolute' (-10)

View file

@ -161,7 +161,7 @@ The article contains five parts:
- More on infinite tree; a more math oriented discussion about - More on infinite tree; a more math oriented discussion about
infinite trees infinite trees
** Helpers :noexport: ** Helpers :noexport:
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: helpers :CUSTOM_ID: helpers
:END: :END:
@ -230,16 +230,32 @@ Congratulations you should be ready to start now.
#+begin_notes #+begin_notes
- There are multiple ways to install Haskell and I don't think there is a - There are multiple ways to install Haskell and I don't think there is a
full consensus between developer about what is the best method. If you full consensus between developer about what is the best method.
whish to use another method take a look at [[http://haskell.org][haskell.org]]. If you whish to use another method take a look at [[http://haskell.org][haskell.org]].
- This install method is only suitable for using as a playground and - This install method is only suitable for using as a playground and I
perfect for this tutorial. I don't think it is suitable for serious think perfectly adapted to run code example from this article.
development. I do not recommend it for serious development.
- =nix= is a generic package manager and goes beyond Haskell. One great - =nix= is a generic package manager and goes beyond Haskell.
good point is that it does not only manage Haskell packages but really a One great good point is that it does not only manage Haskell packages but
lot of other kind of packages. This can be quite helpful if you need to really a lot of other kind of packages.
depends on a Haskell package that itself depends on a system library, for This can be quite helpful if you need to depends on a Haskell package that
example =ncurses=. itself depends on a system library, for example =ncurses=.
- I use [[http://nixos.org/nix][=nix=]] for other projects unrelated to Haskell.
For example, I use the nix-shell bang pattern for shell script for which
I can assume the executable I want are present.
#+end_notes
#+begin_notes
*BONUS*: use [[https://direnv.net][=direnv=]]
#+begin_src
~ cd hsenv
~ echo "use nix" > .envrc
~ direnv allow
#+end_src
Now each time you'll cd into your hsenv directory you'll get the
environment set for you.
#+end_notes #+end_notes
** Don't be afraid ** Don't be afraid
@ -338,7 +354,7 @@ of new things.
Hopefully many of these new concepts will help you to program even in Hopefully many of these new concepts will help you to program even in
imperative languages. imperative languages.
/Smart Static Typing/ /Advanced Static Typing/
Instead of being in your way like in =C=, =C++= or =Java=, the type system Instead of being in your way like in =C=, =C++= or =Java=, the type system
is here to help you. is here to help you.
@ -455,7 +471,7 @@ We declare the type using =::=
#+END_SRC #+END_SRC
#+BEGIN_EXAMPLE #+BEGIN_EXAMPLE
[nix-shell:~/tmp/hsenv]$ runghc basic.hs [nix-shell:~/hsenv]$ runghc basic.hs
13 13
#+END_EXAMPLE #+END_EXAMPLE
@ -472,7 +488,7 @@ Now try
You should get this error: You should get this error:
#+BEGIN_EXAMPLE #+BEGIN_EXAMPLE
[nix-shell:~/tmp/hsenv]$ runghc error_basic.hs [nix-shell:~/hsenv]$ runghc error_basic.hs
error_basic.hs:4:17: error: error_basic.hs:4:17: error:
• No instance for (Fractional Int) arising from the literal 2.3 • No instance for (Fractional Int) arising from the literal 2.3
@ -497,7 +513,7 @@ infer the most general type for us:
#+END_SRC #+END_SRC
#+begin_example #+begin_example
[nix-shell:~/tmp/hsenv]$ runghc float_basic.hs [nix-shell:~/hsenv]$ runghc float_basic.hs
22.93 22.93
#+end_example #+end_example
@ -823,7 +839,7 @@ But it is considered a good practice to do so.
/Infix notation/ /Infix notation/
#+BEGIN_SRC haskell #+BEGIN_SRC haskell :tangle functions.hs
square :: Num a => a -> a square :: Num a => a -> a
square x = x^2 square x = x^2
#+END_SRC #+END_SRC
@ -832,7 +848,7 @@ Note =^= uses infix notation.
For each infix operator there its associated prefix notation. For each infix operator there its associated prefix notation.
You just have to put it inside parenthesis. You just have to put it inside parenthesis.
#+BEGIN_SRC haskell #+BEGIN_SRC haskell :tangle functions.hs
square' x = (^) x 2 square' x = (^) x 2
square'' x = (^2) x square'' x = (^2) x
@ -841,7 +857,7 @@ You just have to put it inside parenthesis.
We can remove =x= in the left and right side! We can remove =x= in the left and right side!
It's called η-reduction. It's called η-reduction.
#+BEGIN_SRC haskell #+BEGIN_SRC haskell :tangle functions.hs
square''' = (^2) square''' = (^2)
#+END_SRC #+END_SRC
@ -852,11 +868,18 @@ Here:
=square==square'==square''==square'''= =square==square'==square''==square'''=
#+END_QUOTE #+END_QUOTE
Note for each prefix notation you can transform it to infix notation with
=`= like this:
#+begin_example
foo x y ↔ x `foo` y
#+end_example
/Tests/ /Tests/
An implementation of the absolute function. An implementation of the absolute function.
#+BEGIN_SRC haskell #+BEGIN_SRC haskell :tangle functions.hs
absolute :: (Ord a, Num a) => a -> a absolute :: (Ord a, Num a) => a -> a
absolute x = if x >= 0 then x else -x absolute x = if x >= 0 then x else -x
#+END_SRC #+END_SRC
@ -867,7 +890,7 @@ You cannot forget the =else=.
Another equivalent version: Another equivalent version:
#+BEGIN_SRC haskell #+BEGIN_SRC haskell :tangle functions.hs
absolute' x absolute' x
| x >= 0 = x | x >= 0 = x
| otherwise = -x | otherwise = -x
@ -878,7 +901,8 @@ Notation warning: indentation is /important/ in Haskell.
Like in Python, bad indentation can break your code! Like in Python, bad indentation can break your code!
#+END_QUOTE #+END_QUOTE
#+BEGIN_SRC haskell {{{lnk(functions.hs)}}}
#+BEGIN_SRC haskell :tangle functions.hs
main = do main = do
print $ square 10 print $ square 10
print $ square' 10 print $ square' 10
@ -890,13 +914,22 @@ Like in Python, bad indentation can break your code!
print $ absolute' (-10) print $ absolute' (-10)
#+END_SRC #+END_SRC
* Difficulty: Normal #+begin_example
~/t/hsenv> runghc functions.hs
100
100
100
100
10
10
10
10
#+end_example
* Difficulty: First steps
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: hard-part :CUSTOM_ID: difficulty--first-steps
:END: :END:
The hard part can now begin.
** Functional style ** Functional style
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: functional-style :CUSTOM_ID: functional-style
@ -920,7 +953,7 @@ example: =[1,2,3,4,5] ⇒ 2 + 4 ⇒ 6=
#+END_QUOTE #+END_QUOTE
To show differences between functional and imperative approaches, I'll To show differences between functional and imperative approaches, I'll
start by providing an imperative solution (in JavaScript): start by providing an imperative solution (in javascript):
#+BEGIN_SRC javascript #+BEGIN_SRC javascript
function evenSum(list) { function evenSum(list) {
@ -1010,12 +1043,11 @@ Note that for any non empty list =l=, =l ⇔ (head l):(tail l)=
The first Haskell solution. The first Haskell solution.
The function =evenSum= returns the sum of all even numbers in a list: The function =evenSum= returns the sum of all even numbers in a list:
#+BEGIN_SRC haskell {{{lnk(evenSum_v1.hs)}}}
#+BEGIN_SRC haskell :tangle evenSum_v1.hs
-- Version 1 -- Version 1
evenSum :: [Integer] -> Integer evenSum :: [Integer] -> Integer
evenSum l = accumSum 0 l evenSum l = accumSum 0 l
accumSum n l = if l == [] accumSum n l = if l == []
then n then n
else let x = head l else let x = head l
@ -1027,39 +1059,36 @@ The function =evenSum= returns the sum of all even numbers in a list:
To test a function you can use =ghci=: To test a function you can use =ghci=:
#+BEGIN_HTML #+begin_example
% ghci ~/t/hsenv> ghci
GHCi, version 7.0.3: http://www.haskell.org/ghc/ :? for help GHCi, version 8.6.5: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done. Prelude> :l evenSum_v1.hs
Loading package integer-gmp ... linking ... done. [1 of 1] Compiling Main ( evenSum_v1.hs, interpreted )
Loading package base ... linking ... done. Ok, one module loaded.
Prelude> :load 11_Functions.lhs *Main> evenSum [1..5]
[1 of 1] Compiling Main ( 11_Functions.lhs, interpreted ) 6
Ok, modules loaded: Main. #+end_example
*Main> evenSum [1..5]
6
#+END_HTML
Here is an example of execution[fn:2]: Here is an example of execution[fn:2]:
#+BEGIN_HTML #+begin_example
*Main> evenSum [1..5] *Main> evenSum [1..5]
accumSum 0 [1,2,3,4,5] accumSum 0 [1,2,3,4,5]
1 is odd 1 is odd
accumSum 0 [2,3,4,5] accumSum 0 [2,3,4,5]
2 is even 2 is even
accumSum (0+2) [3,4,5] accumSum (0+2) [3,4,5]
3 is odd 3 is odd
accumSum (0+2) [4,5] accumSum (0+2) [4,5]
2 is even 2 is even
accumSum (0+2+4) [5] accumSum (0+2+4) [5]
5 is odd 5 is odd
accumSum (0+2+4) [] accumSum (0+2+4) []
l == [] l == []
0+2+4 0+2+4
0+6 0+6
6 6
#+END_HTML #+end_example
Coming from an imperative language all should seem right. Coming from an imperative language all should seem right.
In fact, many things can be improved here. In fact, many things can be improved here.
@ -1069,17 +1098,14 @@ First, we can generalize the type.
evenSum :: Integral a => [a] -> a evenSum :: Integral a => [a] -> a
#+END_SRC #+END_SRC
#+BEGIN_SRC haskell
main = do print $ evenSum [1..10]
#+END_SRC
Next, we can use sub functions using =where= or =let=. Next, we can use sub functions using =where= or =let=.
This way our =accumSum= function won't pollute the namespace of our module. This way our =accumSum= function will not pollute the namespace of our
module.
#+BEGIN_SRC haskell {{{lnk(evenSum_v2.hs)}}}
#+BEGIN_SRC haskell :tangle evenSum_v2.hs
-- Version 2 -- Version 2
evenSum :: Integral a => [a] -> a evenSum :: Integral a => [a] -> a
evenSum l = accumSum 0 l evenSum l = accumSum 0 l
where accumSum n l = where accumSum n l =
if l == [] if l == []
@ -1091,15 +1117,10 @@ This way our =accumSum= function won't pollute the namespace of our module.
else accumSum n xs else accumSum n xs
#+END_SRC #+END_SRC
#+BEGIN_SRC haskell
main = print $ evenSum [1..10]
#+END_SRC
-----
Next, we can use pattern matching. Next, we can use pattern matching.
#+BEGIN_SRC haskell {{{lnk(evenSum_v3.hs)}}}
#+BEGIN_SRC haskell :tangle evenSum_v3.hs
-- Version 3 -- Version 3
evenSum l = accumSum 0 l evenSum l = accumSum 0 l
where where
@ -1143,10 +1164,6 @@ with
This is a very useful feature. This is a very useful feature.
It makes our code both terser and easier to read. It makes our code both terser and easier to read.
#+BEGIN_SRC haskell
main = print $ evenSum [1..10]
#+END_SRC
In Haskell you can simplify function definitions by η-reducing them. In Haskell you can simplify function definitions by η-reducing them.
For example, instead of writing: For example, instead of writing:
@ -1157,15 +1174,15 @@ For example, instead of writing:
you can simply write you can simply write
#+BEGIN_SRC haskell #+BEGIN_SRC haskell
f = some expression f = (some expression)
#+END_SRC #+END_SRC
We use this method to remove the =l=: We use this method to remove the =l=:
#+BEGIN_SRC haskell {{{lnk(evenSum_v4.hs)}}}
#+BEGIN_SRC haskell :tangle evenSum_v4.hs
-- Version 4 -- Version 4
evenSum :: Integral a => [a] -> a evenSum :: Integral a => [a] -> a
evenSum = accumSum 0 evenSum = accumSum 0
where where
accumSum n [] = n accumSum n [] = n
@ -1175,10 +1192,6 @@ We use this method to remove the =l=:
else accumSum n xs else accumSum n xs
#+END_SRC #+END_SRC
#+BEGIN_SRC haskell
main = print $ evenSum [1..10]
#+END_SRC
*** Higher Order Functions *** Higher Order Functions
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: higher-order-functions :CUSTOM_ID: higher-order-functions
@ -1201,7 +1214,8 @@ Here are some examples:
Let's proceed by small steps. Let's proceed by small steps.
#+BEGIN_SRC haskell {{{lnk(evenSum_v5.hs)}}}
#+BEGIN_SRC haskell :tangle evenSum_v5.hs
-- Version 5 -- Version 5
evenSum l = mysum 0 (filter even l) evenSum l = mysum 0 (filter even l)
where where
@ -1218,7 +1232,7 @@ where
The function =filter= takes a function of type (=a -> Bool=) and a list of The function =filter= takes a function of type (=a -> Bool=) and a list of
type =[a]=. type =[a]=.
It returns a list containing only elements for which the function returned It returns a list containing only elements for which the function returned
=true=. =True=.
Our next step is to use another technique to accomplish the same thing as a Our next step is to use another technique to accomplish the same thing as a
loop. loop.
@ -1260,7 +1274,8 @@ follow the code as if =foldl= and =foldl'= were identical.
Now our new version of =evenSum= becomes: Now our new version of =evenSum= becomes:
#+BEGIN_SRC haskell {{{lnk(evenSum_v6.hs)}}}
#+BEGIN_SRC haskell :tangle evenSum_v6.hs
-- Version 6 -- Version 6
-- foldl' isn't accessible by default -- foldl' isn't accessible by default
-- we need to import it from the module Data.List -- we need to import it from the module Data.List
@ -1272,7 +1287,8 @@ Now our new version of =evenSum= becomes:
We can also simplify this by using directly a lambda notation. We can also simplify this by using directly a lambda notation.
This way we don't have to create the temporary name =mysum=. This way we don't have to create the temporary name =mysum=.
#+BEGIN_SRC haskell {{{lnk(evenSum_v7.hs)}}}
#+BEGIN_SRC haskell :tangle evenSum_v7.hs
-- Version 7 -- Version 7
-- Generally it is considered a good practice -- Generally it is considered a good practice
-- to import only the necessary function(s) -- to import only the necessary function(s)
@ -1286,15 +1302,10 @@ And of course, we note that
(\x y -> x+y) ⇔ (+) (\x y -> x+y) ⇔ (+)
#+END_SRC #+END_SRC
#+BEGIN_SRC haskell
main = print $ evenSum [1..10]
#+END_SRC
-----
Finally Finally
#+BEGIN_SRC haskell {{{lnk(evenSum_v8.hs)}}}
#+BEGIN_SRC haskell :tangle evenSum_v8.hs
-- Version 8 -- Version 8
import Data.List (foldl') import Data.List (foldl')
evenSum :: Integral a => [a] -> a evenSum :: Integral a => [a] -> a
@ -1327,7 +1338,8 @@ The =(.)= function corresponds to mathematical composition.
We can take advantage of this operator to η-reduce our function: We can take advantage of this operator to η-reduce our function:
#+BEGIN_SRC haskell {{{lnk(evenSum_v9.hs)}}}
#+BEGIN_SRC haskell :tangle evenSum_v9.hs
-- Version 9 -- Version 9
import Data.List (foldl') import Data.List (foldl')
evenSum :: Integral a => [a] -> a evenSum :: Integral a => [a] -> a
@ -1336,7 +1348,8 @@ We can take advantage of this operator to η-reduce our function:
Also, we could rename some parts to make it clearer: Also, we could rename some parts to make it clearer:
#+BEGIN_SRC haskell {{{lnk(evenSum_v10.hs)}}}
#+BEGIN_SRC haskell :tangle evenSum_v10.hs
-- Version 10 -- Version 10
import Data.List (foldl') import Data.List (foldl')
sum' :: (Num a) => [a] -> a sum' :: (Num a) => [a] -> a
@ -1365,7 +1378,7 @@ Updating version 10 is extremely easy:
squareEvenSum' = evenSum . (map (^2)) squareEvenSum' = evenSum . (map (^2))
#+END_SRC #+END_SRC
We just had to add another "transformation function"[^0216]. We just had to add another "transformation function".
#+BEGIN_EXAMPLE #+BEGIN_EXAMPLE
map (^2) [1,2,3,4] ⇔ [1,4,9,16] map (^2) [1,2,3,4] ⇔ [1,4,9,16]
@ -1375,9 +1388,9 @@ The =map= function simply applies a function to all the elements of a list.
We didn't have to modify anything /inside/ the function definition. We didn't have to modify anything /inside/ the function definition.
This makes the code more modular. This makes the code more modular.
But in addition you can think more mathematically about your function. But in addition you can think more mathematically about your functions.
You can also use your function interchangably with others, as needed. You can also use your functions interchangeably with others, as needed.
That is, you can compose, map, fold, filter using your new function. That is, you can /compose/, map, fold, filter using your new function.
Modifying version 1 is left as an exercise to the reader ☺. Modifying version 1 is left as an exercise to the reader ☺.
@ -1394,7 +1407,7 @@ Unfortunately, using pure functional programming isn't well suited to all
usages. usages.
Or at least such a language hasn't been found yet. Or at least such a language hasn't been found yet.
One of the great powers of Haskell is the ability to create DSLs (Domain One of the great powers of Haskell is the ability to create DSL (Domain
Specific Language) making it easy to change the programming paradigm. Specific Language) making it easy to change the programming paradigm.
In fact, Haskell is also great when you want to write imperative style In fact, Haskell is also great when you want to write imperative style
@ -1409,10 +1422,6 @@ to understand when and how to use it.
But before talking about this Haskell super-power, we must talk about But before talking about this Haskell super-power, we must talk about
another essential aspect of Haskell: /Types/. another essential aspect of Haskell: /Types/.
#+BEGIN_SRC haskell
main = print $ evenSum [1..10]
#+END_SRC
** Types ** Types
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: types :CUSTOM_ID: types
@ -1421,8 +1430,10 @@ another essential aspect of Haskell: /Types/.
#+CAPTION: Dali, the madonna of port Lligat #+CAPTION: Dali, the madonna of port Lligat
[[./salvador-dali-the-madonna-of-port-lligat.jpg]] [[./salvador-dali-the-madonna-of-port-lligat.jpg]]
#+MACRO: tldr @@html:<abbr title="too long; didn't read">tl;dr:</abbr> @@
#+BEGIN_QUOTE #+BEGIN_QUOTE
%tldr {{{tldr}}}
- =type Name = AnotherType= is just an alias and the compiler doesn't - =type Name = AnotherType= is just an alias and the compiler doesn't
mark any difference between =Name= and =AnotherType=. mark any difference between =Name= and =AnotherType=.
@ -1436,8 +1447,8 @@ In Haskell, types are strong and static.
Why is this important? Why is this important?
It will help you /greatly/ to avoid mistakes. It will help you /greatly/ to avoid mistakes.
In Haskell, most bugs are caught during the compilation of your program. In Haskell, most bugs are caught during the compilation of your program.
And the main reason is because of the type inference during compilation. And the main reason is because of the type checking during compilation.
Type inference makes it easy to detect where you used the wrong parameter Type checking makes it easy to detect where you used the wrong parameter
at the wrong place, for example. at the wrong place, for example.
*** Type inference *** Type inference
@ -1461,15 +1472,13 @@ You can provide =square= with an =Int=, an =Integer=, a =Float= a
Proof by example: Proof by example:
#+BEGIN_EXAMPLE #+BEGIN_EXAMPLE
% ghci ~/t/hsenv> ghci
GHCi, version 7.0.4: GHCi, version 8.6.5: http://www.haskell.org/ghc/ :? for help
... Prelude> let square x = x * x
Prelude> let square x = x*x
Prelude> square 2 Prelude> square 2
4 4
Prelude> square 2.1 Prelude> square 2.1
4.41 4.41
Prelude> -- load the Data.Complex module
Prelude> :m Data.Complex Prelude> :m Data.Complex
Prelude Data.Complex> square (2 :+ 1) Prelude Data.Complex> square (2 :+ 1)
3.0 :+ 4.0 3.0 :+ 4.0
@ -1481,15 +1490,12 @@ Now compare with the amount of code necessary in C:
#+BEGIN_SRC C #+BEGIN_SRC C
int int_square(int x) { return x*x; } int int_square(int x) { return x*x; }
float float_square(float x) {return x*x; } float float_square(float x) {return x*x; }
complex complex_square (complex z) { complex complex_square (complex z) {
complex tmp; complex tmp;
tmp.real = z.real * z.real - z.img * z.img; tmp.real = z.real * z.real - z.img * z.img;
tmp.img = 2 * z.img * z.real; tmp.img = 2 * z.img * z.real;
} }
complex x,y; complex x,y;
y = complex_square(x); y = complex_square(x);
#+END_SRC #+END_SRC
@ -1524,8 +1530,8 @@ int main() {
#+END_SRC #+END_SRC
C++ does a far better job than C in this regard. C++ does a far better job than C in this regard.
But for more complex functions the syntax can be hard to follow: see [[http://bartoszmilewski.com/2009/10/21/what-does-haskell-have-to-do-with-c/][this But for more complex functions the syntax can be hard to follow: see
article]] for example. [[http://bartoszmilewski.com/2009/10/21/what-does-haskell-have-to-do-with-c/][this article]] for example.
In C++ you must declare that a function can work with different types. In C++ you must declare that a function can work with different types.
In Haskell, the opposite is the case. In Haskell, the opposite is the case.
@ -1549,7 +1555,8 @@ Generally, in Haskell:
You can construct your own types. You can construct your own types.
First, you can use aliases or type synonyms. First, you can use aliases or type synonyms.
#+BEGIN_SRC haskell {{{lnk(type_constr_1.hs)}}}
#+BEGIN_SRC haskell :tangle type_constr_1.hs
type Name = String type Name = String
type Color = String type Color = String
@ -1591,7 +1598,7 @@ Another method is to create your own types using the keyword =data=.
Now if you switch parameters of =showInfos=, the compiler complains! Now if you switch parameters of =showInfos=, the compiler complains!
So this is a potential mistake you will never make again and the only price So this is a potential mistake you will never make again and the only price
is to be more verbose. is to be a bit more verbose.
Also notice that constructors are functions: Also notice that constructors are functions:
@ -1666,7 +1673,7 @@ If you want to be able to print (=Show=), read (=Read=), test equality
(=Eq=) and compare (=Ord=) your new data structure you can tell Haskell to (=Eq=) and compare (=Ord=) your new data structure you can tell Haskell to
derive the appropriate functions for you. derive the appropriate functions for you.
#+BEGIN_SRC haskell #+BEGIN_SRC haskell :tangle list.hs
infixr 5 ::: infixr 5 :::
data List a = Nil | a ::: (List a) data List a = Nil | a ::: (List a)
deriving (Show,Read,Eq,Ord) deriving (Show,Read,Eq,Ord)
@ -1676,12 +1683,12 @@ When you add =deriving (Show)= to your data declaration, Haskell creates a
=show= function for you. =show= function for you.
We'll see soon how you can use your own =show= function. We'll see soon how you can use your own =show= function.
#+BEGIN_SRC haskell #+BEGIN_SRC haskell :tangle list.hs
convertList [] = Nil convertList [] = Nil
convertList (x:xs) = x ::: convertList xs convertList (x:xs) = x ::: convertList xs
#+END_SRC #+END_SRC
#+BEGIN_SRC haskell #+BEGIN_SRC haskell :tangle list.hs
main = do main = do
print (0 ::: 1 ::: Nil) print (0 ::: 1 ::: Nil)
print (convertList [0,1]) print (convertList [0,1])
@ -1704,9 +1711,7 @@ This prints:
We'll just give another standard example: binary trees. We'll just give another standard example: binary trees.
#+BEGIN_SRC haskell #+BEGIN_SRC haskell :tangle tree.hs
import Data.List
data BinTree a = Empty data BinTree a = Empty
| Node a (BinTree a) (BinTree a) | Node a (BinTree a) (BinTree a)
deriving (Show) deriving (Show)
@ -1715,7 +1720,7 @@ We'll just give another standard example: binary trees.
We will also create a function which turns a list into an ordered binary We will also create a function which turns a list into an ordered binary
tree. tree.
#+BEGIN_SRC haskell #+BEGIN_SRC haskell :tangle tree.hs
treeFromList :: (Ord a) => [a] -> BinTree a treeFromList :: (Ord a) => [a] -> BinTree a
treeFromList [] = Empty treeFromList [] = Empty
treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs)) treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
@ -1733,7 +1738,7 @@ Look at how elegant this function is. In plain English:
- the right subtree is the tree created from members of the list =xs= - the right subtree is the tree created from members of the list =xs=
which are strictly superior to =x=. which are strictly superior to =x=.
#+BEGIN_SRC haskell #+BEGIN_SRC haskell :tangle tree.hs
main = print $ treeFromList [7,2,4,8] main = print $ treeFromList [7,2,4,8]
#+END_SRC #+END_SRC
@ -1745,212 +1750,106 @@ Node 7 (Node 2 Empty (Node 4 Empty Empty)) (Node 8 Empty Empty)
This is an informative but quite unpleasant representation of our tree. This is an informative but quite unpleasant representation of our tree.
Just for fun, let's code a better display for our trees. I've added the =containers= package in the =shell.nix= file, it is time to
I simply had fun making a nice function to display trees in a general way. use this library which contain an helper to show trees.
You can safely skip this part if you find it too difficult to follow.
We have a few changes to make. #+BEGIN_SRC haskell :tangle pretty_tree.hs
We remove the =deriving (Show)= from the declaration of our =BinTree= type. import Data.Tree (Tree,Forest(..))
And it might also be useful to make our BinTree an instance of (=Eq= and import qualified Data.Tree as Tree
=Ord=) so we will be able to test equality and compare trees.
#+BEGIN_SRC haskell data BinTree a = Empty
data BinTree a = Empty | Node a (BinTree a) (BinTree a)
| Node a (BinTree a) (BinTree a) deriving (Eq,Ord,Show)
deriving (Eq,Ord)
treeFromList :: (Ord a) => [a] -> BinTree a
treeFromList [] = Empty
treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
(treeFromList (filter (>x) xs))
-- | Function to transform our internal BinTree type to the
-- type of Tree declared in Data.Tree (from containers package)
-- so that the function Tree.drawForest can use
binTreeToForestString :: (Show a) => BinTree a -> Forest String
binTreeToForestString Empty = []
binTreeToForestString (Node x left right) =
[Tree.Node (show x) ((binTreeToForestString left) ++ (binTreeToForestString right))]
-- | Function that given a BinTree print a representation of it in the console
prettyPrintTree :: (Show a) => BinTree a -> IO ()
prettyPrintTree = putStrLn . Tree.drawForest . binTreeToForestString
main = do
putStrLn "Int binary tree:"
prettyPrintTree $ treeFromList [7,2,4,8,1,3,6,21,12,23]
putStrLn "\nNote we could also use another type\n"
putStrLn "String binary tree:"
prettyPrintTree $
treeFromList ["foo","bar","baz","gor","yog"]
putStrLn "\nAs we can test equality and order trees, we can make tree of trees!\n"
putStrLn "\nBinary tree of Char binary trees:"
prettyPrintTree (treeFromList
(map treeFromList ["foo","bar","zara","baz","foo"]))
#+END_SRC #+END_SRC
Without the =deriving (Show)=, Haskell doesn't create a =show= method for #+begin_example
us. ~/t/hsenv> runghc pretty_tree.hs
We will create our own version of =show=.
To achieve this, we must declare that our newly created type =BinTree a= is
an instance of the type class =Show=.
The general syntax is:
#+BEGIN_SRC haskell
instance Show (BinTree a) where
show t = ... -- You declare your function here
#+END_SRC
Here is my version of how to show a binary tree.
Don't worry about the apparent complexity.
I made a lot of improvements in order to display even stranger objects.
#+BEGIN_SRC haskell
-- declare BinTree a to be an instance of Show
instance (Show a) => Show (BinTree a) where
-- will start by a '<' before the root
-- and put a : a begining of line
show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
where
-- treeshow pref Tree
-- shows a tree and starts each line with pref
-- We don't display the Empty tree
treeshow pref Empty = ""
-- Leaf
treeshow pref (Node x Empty Empty) =
(pshow pref x)
-- Right branch is empty
treeshow pref (Node x left Empty) =
(pshow pref x) ++ "\n" ++
(showSon pref "`--" " " left)
-- Left branch is empty
treeshow pref (Node x Empty right) =
(pshow pref x) ++ "\n" ++
(showSon pref "`--" " " right)
-- Tree with left and right children non empty
treeshow pref (Node x left right) =
(pshow pref x) ++ "\n" ++
(showSon pref "|--" "| " left) ++ "\n" ++
(showSon pref "`--" " " right)
-- shows a tree using some prefixes to make it nice
showSon pref before next t =
pref ++ before ++ treeshow (pref ++ next) t
-- pshow replaces "\n" by "\n"++pref
pshow pref x = replace '\n' ("\n"++pref) (show x)
-- replaces one char by another string
replace c new string =
concatMap (change c new) string
where
change c new x
| x == c = new
| otherwise = x:[] -- "x"
#+END_SRC
The =treeFromList= method remains identical.
#+BEGIN_SRC haskell
treeFromList :: (Ord a) => [a] -> BinTree a
treeFromList [] = Empty
treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
(treeFromList (filter (>x) xs))
#+END_SRC
And now, we can play:
#+BEGIN_SRC haskell
main = do
putStrLn "Int binary tree:"
print $ treeFromList [7,2,4,8,1,3,6,21,12,23]
#+END_SRC
#+BEGIN_EXAMPLE
Int binary tree: Int binary tree:
< 7 7
: |--2 |
: | |--1 +- 2
: | `--4 | |
: | |--3 | +- 1
: | `--6 | |
: `--8 | `- 4
: `--21 | |
: |--12 | +- 3
: `--23 | |
#+END_EXAMPLE | `- 6
|
`- 8
|
`- 21
|
+- 12
|
`- 23
Now it is far better!
The root is shown by starting the line with the =<= character.
And each following line starts with a =:=.
But we could also use another type.
#+BEGIN_SRC haskell
putStrLn "\nString binary tree:"
print $ treeFromList ["foo","bar","baz","gor","yog"]
#+END_SRC
#+BEGIN_EXAMPLE Note we could also use another type
String binary tree: String binary tree:
< "foo" "foo"
: |--"bar" |
: | `--"baz" +- "bar"
: `--"gor" | |
: `--"yog" | `- "baz"
#+END_EXAMPLE |
`- "gor"
|
`- "yog"
As we can test equality and order trees, we can make tree of trees! As we can test equality and order trees, we can make tree of trees!
#+BEGIN_SRC haskell
putStrLn "\nBinary tree of Char binary trees:"
print ( treeFromList
(map treeFromList ["baz","zara","bar"]))
#+END_SRC
#+BEGIN_EXAMPLE
Binary tree of Char binary trees: Binary tree of Char binary trees:
< < 'b' Node 'f' Empty (Node 'o' Empty Empty)
: : |--'a' |
: : `--'z' +- Node 'b' (Node 'a' Empty Empty) (Node 'r' Empty Empty)
: |--< 'b' | |
: | : |--'a' | `- Node 'b' (Node 'a' Empty Empty) (Node 'z' Empty Empty)
: | : `--'r' |
: `--< 'z' `- Node 'z' (Node 'a' Empty (Node 'r' Empty Empty)) Empty
: : `--'a' #+end_example
: : `--'r'
#+END_EXAMPLE
This is why I chose to prefix each line of tree display by =:= (except Notice how duplicate elements aren't inserted in trees.
for the root). For exemple the Char BinTree constructed from the list =foo= is just =f ->
o=.
#+CAPTION: Yo Dawg Tree When =o= is inserted another time the second =o= is not duplicated.
[[./yo_dawg_tree.jpg]] But more importantly it works also for our own =BinTree= notice how the
tree for =foo= is inserted only once.
#+BEGIN_SRC haskell
putStrLn "\nTree of Binary trees of Char binary trees:"
print $ (treeFromList . map (treeFromList . map treeFromList))
[ ["YO","DAWG"]
, ["I","HEARD"]
, ["I","HEARD"]
, ["YOU","LIKE","TREES"] ]
#+END_SRC
Which is equivalent to
#+BEGIN_SRC haskell
print ( treeFromList (
map treeFromList
[ map treeFromList ["YO","DAWG"]
, map treeFromList ["I","HEARD"]
, map treeFromList ["I","HEARD"]
, map treeFromList ["YOU","LIKE","TREES"] ]))
#+END_SRC
and gives:
#+BEGIN_EXAMPLE
Binary tree of Binary trees of Char binary trees:
< < < 'Y'
: : : `--'O'
: : `--< 'D'
: : : |--'A'
: : : `--'W'
: : : `--'G'
: |--< < 'I'
: | : `--< 'H'
: | : : |--'E'
: | : : | `--'A'
: | : : | `--'D'
: | : : `--'R'
: `--< < 'Y'
: : : `--'O'
: : : `--'U'
: : `--< 'L'
: : : `--'I'
: : : |--'E'
: : : `--'K'
: : `--< 'T'
: : : `--'R'
: : : |--'E'
: : : `--'S'
#+END_EXAMPLE
Notice how duplicate trees aren't inserted; there is only one tree
corresponding to ="I","HEARD"=.
We have this for (almost) free, because we have declared Tree to be an We have this for (almost) free, because we have declared Tree to be an
instance of =Eq=. instance of =Eq=.
@ -2023,46 +1922,6 @@ This code is mostly the same as the previous one.
deriving (Eq,Ord) deriving (Eq,Ord)
#+END_SRC #+END_SRC
#+BEGIN_SRC haskell
-- declare BinTree a to be an instance of Show
instance (Show a) => Show (BinTree a) where
-- will start by a '<' before the root
-- and put a : a begining of line
show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
where
treeshow pref Empty = ""
treeshow pref (Node x Empty Empty) =
(pshow pref x)
treeshow pref (Node x left Empty) =
(pshow pref x) ++ "\n" ++
(showSon pref "`--" " " left)
treeshow pref (Node x Empty right) =
(pshow pref x) ++ "\n" ++
(showSon pref "`--" " " right)
treeshow pref (Node x left right) =
(pshow pref x) ++ "\n" ++
(showSon pref "|--" "| " left) ++ "\n" ++
(showSon pref "`--" " " right)
-- show a tree using some prefixes to make it nice
showSon pref before next t =
pref ++ before ++ treeshow (pref ++ next) t
-- pshow replace "\n" by "\n"++pref
pshow pref x = replace '\n' ("\n"++pref) (" " ++ show x)
-- replace on char by another string
replace c new string =
concatMap (change c new) string
where
change c new x
| x == c = new
| otherwise = x:[] -- "x"
#+END_SRC
Suppose we don't mind having an ordered binary tree. Suppose we don't mind having an ordered binary tree.
Here is an infinite binary tree: Here is an infinite binary tree:
@ -2178,7 +2037,7 @@ Look at the result for
print $ treeTakeDepth 4 infTreeTwo print $ treeTakeDepth 4 infTreeTwo
#+END_SRC #+END_SRC
* Difficulty: Nightmare * Difficulty: Hard
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: hell-difficulty-part :CUSTOM_ID: hell-difficulty-part
:END: :END:
@ -3219,7 +3078,7 @@ In particular, monads are very useful for:
If you have followed me until here, then you've done it! You know If you have followed me until here, then you've done it! You know
monads[fn:7]! monads[fn:7]!
* Difficulty: Hell * Difficulty: Nightmarish
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: difficulty--hell :CUSTOM_ID: difficulty--hell
:END: :END:
@ -3249,6 +3108,13 @@ Haskell at least two or three times before it really clicked for them.
:CUSTOM_ID: web-application :CUSTOM_ID: web-application
:END: :END:
* Difficulty: Hell
:PROPERTIES:
:CUSTOM_ID: difficulty--hell-be9a
:END:
This part will be for advanced Haskell code.
* Appendix * Appendix
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: appendix :CUSTOM_ID: appendix

View file

@ -0,0 +1,10 @@
infixr 5 :::
data List a = Nil | a ::: (List a)
deriving (Show,Read,Eq,Ord)
convertList [] = Nil
convertList (x:xs) = x ::: convertList xs
main = do
print (0 ::: 1 ::: Nil)
print (convertList [0,1])

View file

@ -0,0 +1,35 @@
import Data.Tree (Tree,Forest(..))
import qualified Data.Tree as Tree
data BinTree a = Empty
| Node a (BinTree a) (BinTree a)
deriving (Eq,Ord,Show)
treeFromList :: (Ord a) => [a] -> BinTree a
treeFromList [] = Empty
treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
(treeFromList (filter (>x) xs))
-- | Function to transform our internal BinTree type to the
-- type of Tree declared in Data.Tree (from containers package)
-- so that the function Tree.drawForest can use
binTreeToForestString :: (Show a) => BinTree a -> Forest String
binTreeToForestString Empty = []
binTreeToForestString (Node x left right) =
[Tree.Node (show x) ((binTreeToForestString left) ++ (binTreeToForestString right))]
-- | Function that given a BinTree print a representation of it in the console
prettyPrintTree :: (Show a) => BinTree a -> IO ()
prettyPrintTree = putStrLn . Tree.drawForest . binTreeToForestString
main = do
putStrLn "Int binary tree:"
prettyPrintTree $ treeFromList [7,2,4,8,1,3,6,21,12,23]
putStrLn "\nNote we could also use another type\n"
putStrLn "String binary tree:"
prettyPrintTree $
treeFromList ["foo","bar","baz","gor","yog"]
putStrLn "\nAs we can test equality and order trees, we can make tree of trees!\n"
putStrLn "\nBinary tree of Char binary trees:"
prettyPrintTree (treeFromList
(map treeFromList ["foo","bar","zara","baz","foo"]))

View file

@ -0,0 +1,10 @@
data BinTree a = Empty
| Node a (BinTree a) (BinTree a)
deriving (Show)
treeFromList :: (Ord a) => [a] -> BinTree a
treeFromList [] = Empty
treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
(treeFromList (filter (>x) xs))
main = print $ treeFromList [7,2,4,8]

View file

@ -0,0 +1,11 @@
type Name = String
type Color = String
showInfos :: Name -> Color -> String
showInfos name color = "Name: " ++ name
++ ", Color: " ++ color
name :: Name
name = "Robin"
color :: Color
color = "Blue"
main = putStrLn $ showInfos name color