3773 lines
100 KiB
Org Mode
3773 lines
100 KiB
Org Mode
|
#+TITLE: Learn Haskell Fast and Hard
|
|||
|
#+AUTHOR: Yann Esposito
|
|||
|
#+KEYWORDS: Haskell, programming, functional, tutorial
|
|||
|
#+OPTIONS: auto-id:t
|
|||
|
#+PROPERTY: eval no
|
|||
|
|
|||
|
blogimage("magritte_pleasure_principle.jpg","Magritte pleasure
|
|||
|
principle")
|
|||
|
|
|||
|
%tldr A very short and dense tutorial for learning Haskell.
|
|||
|
|
|||
|
Thanks to:
|
|||
|
|
|||
|
- [[https://plus.google.com/u/0/113751420744109290534][Oleg Taykalo]]
|
|||
|
you can find a Russian translation here:
|
|||
|
[[http://habrahabr.ru/post/152889/][Part 1]] /&/
|
|||
|
[[http://habrahabr.ru/post/153383/][Part 2]],
|
|||
|
- [[http://silly-bytes.blogspot.fr][Daniel Campoverde]] for the Spanish
|
|||
|
translation here:
|
|||
|
[[http://silly-bytes.blogspot.fr/2016/06/aprende-haskell-rapido-y-dificil_29.html][Aprende
|
|||
|
Haskell rápido y difícil]],
|
|||
|
- [[http://github.com/joom][Joomy Korkut]] for the Turkish translation
|
|||
|
here: [[https://github.com/joom/zor-yoldan-haskell][Zor Yoldan
|
|||
|
Haskell]].
|
|||
|
|
|||
|
I really believe all developers should learn Haskell. I don't think
|
|||
|
everyone needs to be super Haskell ninjas, but they should at least
|
|||
|
discover what Haskell has to offer. Learning Haskell opens your mind.
|
|||
|
|
|||
|
Mainstream languages share the same foundations:
|
|||
|
|
|||
|
- variables
|
|||
|
- loops
|
|||
|
- pointers[fn:1]
|
|||
|
- data structures, objects and classes (for most)
|
|||
|
|
|||
|
Haskell is very different. The language uses a lot of concepts I had
|
|||
|
never heard about before. Many of those concepts will help you become a
|
|||
|
better programmer.
|
|||
|
|
|||
|
But learning Haskell can be hard. It was for me. In this article I try
|
|||
|
to provide what I lacked during my learning.
|
|||
|
|
|||
|
This article will certainly be hard to follow. This is on purpose. There
|
|||
|
is no shortcut to learning Haskell. It is hard and challenging. But I
|
|||
|
believe this is a good thing. It is because it is hard that Haskell is
|
|||
|
interesting.
|
|||
|
|
|||
|
The conventional method to learning Haskell is to read two books. First
|
|||
|
[[http://learnyouahaskell.com]["Learn You a Haskell"]] and just after
|
|||
|
[[http://www.realworldhaskell.org]["Real World Haskell"]]. I also
|
|||
|
believe this is the right way to go. But to learn what Haskell is all
|
|||
|
about, you'll have to read them in detail.
|
|||
|
|
|||
|
In contrast, this article is a very brief and dense overview of all
|
|||
|
major aspects of Haskell. I also added some information I lacked while I
|
|||
|
learned Haskell.
|
|||
|
|
|||
|
The article contains five parts:
|
|||
|
|
|||
|
- Introduction: a short example to show Haskell can be friendly.
|
|||
|
- Basic Haskell: Haskell syntax, and some essential notions.
|
|||
|
- Hard Difficulty Part:
|
|||
|
|
|||
|
- Functional style; a progressive example, from imperative to
|
|||
|
functional style
|
|||
|
- Types; types and a standard binary tree example
|
|||
|
- Infinite Structure; manipulate an infinite binary tree!
|
|||
|
|
|||
|
- Hell Difficulty Part:
|
|||
|
|
|||
|
- Deal with IO; A very minimal example
|
|||
|
- IO trick explained; the hidden detail I lacked to understand IO
|
|||
|
- Monads; incredible how we can generalize
|
|||
|
|
|||
|
- Appendix:
|
|||
|
|
|||
|
- More on infinite tree; a more math oriented discussion about
|
|||
|
infinite trees
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
Note: Each time you see a separator with a filename ending in =.lhs=
|
|||
|
you can click the filename to get this file. If you save the file as
|
|||
|
=filename.lhs=, you can run it with
|
|||
|
|
|||
|
#+BEGIN_SRC bash
|
|||
|
runhaskell filename.lhs
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Some might not work, but most will. You should see a link just below.
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
* Introduction
|
|||
|
:PROPERTIES:
|
|||
|
:CUSTOM_ID: introduction
|
|||
|
:END:
|
|||
|
|
|||
|
** Install
|
|||
|
:PROPERTIES:
|
|||
|
:CUSTOM_ID: install
|
|||
|
:END:
|
|||
|
|
|||
|
blogimage("Haskell-logo.png", "Haskell logo")
|
|||
|
|
|||
|
There are different way to install Haskell, I would recommend to use
|
|||
|
[[https://haskellstack.org][=stack=]].
|
|||
|
|
|||
|
There are other way to install Haskell on your system you could visit,
|
|||
|
you can learn more about it by visiting
|
|||
|
[[https://haskell.org][haskell.org]] or
|
|||
|
[[https://haskell-lang.org][haskell-lang.org]]
|
|||
|
|
|||
|
Tools:
|
|||
|
|
|||
|
- =ghc=: Compiler similar to gcc for =C=.
|
|||
|
- =ghci=: Interactive Haskell (REPL)
|
|||
|
- =runhaskell=: Execute a program without compiling it. Convenient but
|
|||
|
very slow compared to compiled programs.
|
|||
|
|
|||
|
** Don't be afraid
|
|||
|
:PROPERTIES:
|
|||
|
:CUSTOM_ID: don't-be-afraid
|
|||
|
:END:
|
|||
|
|
|||
|
blogimage("munch_TheScream.jpg","The Scream")
|
|||
|
|
|||
|
Many books/articles about Haskell start by introducing some esoteric
|
|||
|
formula (quick sort, Fibonacci, etc...). I will do the exact opposite.
|
|||
|
At first I won't show you any Haskell super power. I will start with
|
|||
|
similarities between Haskell and other programming languages. Let's jump
|
|||
|
to the mandatory "Hello World".
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main = putStrLn "Hello World!"
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
To run it, you can save this code in a =hello.hs= and:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
~ runhaskell ./hello.hs
|
|||
|
Hello World!
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
or if you use =stack= first run =stack setup= and then:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
~ stack runhaskell ./hello.hs
|
|||
|
Hello World!
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
You could also download the literate Haskell source. You should see a
|
|||
|
link just above the introduction title. Download this file as
|
|||
|
=00_hello_world.lhs= and:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
~ runhaskell 00_hello_world.lhs
|
|||
|
Hello World!
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
01_basic/10_Introduction/00_hello_world.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
01_basic/10_Introduction/10_hello_you.lhs
|
|||
|
|
|||
|
Now, a program asking your name and replying "Hello" using the name you
|
|||
|
entered:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main = do
|
|||
|
print "What is your name?"
|
|||
|
name <- getLine
|
|||
|
print ("Hello " ++ name ++ "!")
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
First, let us compare this with similar programs in a few imperative
|
|||
|
languages:
|
|||
|
|
|||
|
#+BEGIN_SRC python
|
|||
|
# Python
|
|||
|
print "What is your name?"
|
|||
|
name = raw_input()
|
|||
|
print "Hello %s!" % name
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_SRC ruby
|
|||
|
# Ruby
|
|||
|
puts "What is your name?"
|
|||
|
name = gets.chomp
|
|||
|
puts "Hello #{name}!"
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_SRC C
|
|||
|
// In C
|
|||
|
#include <stdio.h>
|
|||
|
int main (int argc, char **argv) {
|
|||
|
char name[666]; // <- An Evil Number!
|
|||
|
// What if my name is more than 665 character long?
|
|||
|
printf("What is your name?\n");
|
|||
|
scanf("%s", name);
|
|||
|
printf("Hello %s!\n", name);
|
|||
|
return 0;
|
|||
|
}
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
The structure is the same, but there are some syntax differences. The
|
|||
|
main part of this tutorial will be dedicated to explaining why.
|
|||
|
|
|||
|
In Haskell there is a =main= function and every object has a type. The
|
|||
|
type of =main= is =IO ()=. This means =main= will cause side effects.
|
|||
|
|
|||
|
Just remember that Haskell can look a lot like mainstream imperative
|
|||
|
languages.
|
|||
|
|
|||
|
01_basic/10_Introduction/10_hello_you.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
01_basic/10_Introduction/20_very_basic.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h3 id="very-basic-haskell">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Very basic Haskell
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h3>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
blogimage("picasso_owl.jpg","Picasso minimal owl")
|
|||
|
|
|||
|
Before continuing you need to be warned about some essential properties
|
|||
|
of Haskell.
|
|||
|
|
|||
|
/Functional/
|
|||
|
|
|||
|
Haskell is a functional language. If you have an imperative language
|
|||
|
background, you'll have to learn a lot of new things. Hopefully many of
|
|||
|
these new concepts will help you to program even in imperative
|
|||
|
languages.
|
|||
|
|
|||
|
/Smart Static Typing/
|
|||
|
|
|||
|
Instead of being in your way like in =C=, =C++= or =Java=, the type
|
|||
|
system is here to help you.
|
|||
|
|
|||
|
/Purity/
|
|||
|
|
|||
|
Generally your functions won't modify anything in the outside world.
|
|||
|
This means they can't modify the value of a variable, can't get user
|
|||
|
input, can't write on the screen, can't launch a missile. On the other
|
|||
|
hand, parallelism will be very easy to achieve. Haskell makes it clear
|
|||
|
where effects occur and where your code is pure. Also, it will be far
|
|||
|
easier to reason about your program. Most bugs will be prevented in the
|
|||
|
pure parts of your program.
|
|||
|
|
|||
|
Furthermore, pure functions follow a fundamental law in Haskell:
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
Applying a function with the same parameters always returns the same
|
|||
|
value.
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
/Laziness/
|
|||
|
|
|||
|
Laziness by default is a very uncommon language design. By default,
|
|||
|
Haskell evaluates something only when it is needed. In consequence, it
|
|||
|
provides a very elegant way to manipulate infinite structures, for
|
|||
|
example.
|
|||
|
|
|||
|
A last warning about how you should read Haskell code. For me, it is
|
|||
|
like reading scientific papers. Some parts are very clear, but when you
|
|||
|
see a formula, just focus and read slower. Also, while learning Haskell,
|
|||
|
it /really/ doesn't matter much if you don't understand syntax details.
|
|||
|
If you meet a =>>==, =<$>=, =<-= or any other weird symbol, just ignore
|
|||
|
them and follows the flow of the code.
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h4 id="function-declaration">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Function declaration
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h4>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
You might be used to declaring functions like this:
|
|||
|
|
|||
|
In =C=:
|
|||
|
|
|||
|
#+BEGIN_SRC C
|
|||
|
int f(int x, int y) {
|
|||
|
return x*x + y*y;
|
|||
|
}
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
In JavaScript:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
function f(x,y) {
|
|||
|
return x*x + y*y;
|
|||
|
}
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
in Python:
|
|||
|
|
|||
|
#+BEGIN_SRC python
|
|||
|
def f(x,y):
|
|||
|
return x*x + y*y
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
in Ruby:
|
|||
|
|
|||
|
#+BEGIN_SRC ruby
|
|||
|
def f(x,y)
|
|||
|
x*x + y*y
|
|||
|
end
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
In Scheme:
|
|||
|
|
|||
|
#+BEGIN_SRC scheme
|
|||
|
(define (f x y)
|
|||
|
(+ (* x x) (* y y)))
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Finally, the Haskell way is:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
f x y = x*x + y*y
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Very clean. No parenthesis, no =def=.
|
|||
|
|
|||
|
Don't forget, Haskell uses functions and types a lot. It is thus very
|
|||
|
easy to define them. The syntax was particularly well thought out for
|
|||
|
these objects.
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h4 id="a-type-example">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
A Type Example
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h4>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Although it is not mandatory, type information for functions is usually
|
|||
|
made explicit. It's not mandatory because the compiler is smart enough
|
|||
|
to discover it for you. It's a good idea because it indicates intent and
|
|||
|
understanding.
|
|||
|
|
|||
|
Let's play a little. We declare the type using =::=
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
f :: Int -> Int -> Int
|
|||
|
f x y = x*x + y*y
|
|||
|
|
|||
|
main = print (f 2 3)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
~ runhaskell 20_very_basic.lhs
|
|||
|
13
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
01_basic/10_Introduction/20_very_basic.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
01_basic/10_Introduction/21_very_basic.lhs
|
|||
|
|
|||
|
Now try
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
f :: Int -> Int -> Int
|
|||
|
f x y = x*x + y*y
|
|||
|
|
|||
|
main = print (f 2.3 4.2)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
You should get this error:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
21_very_basic.lhs:6:23:
|
|||
|
No instance for (Fractional Int)
|
|||
|
arising from the literal `4.2'
|
|||
|
Possible fix: add an instance declaration for (Fractional Int)
|
|||
|
In the second argument of `f', namely `4.2'
|
|||
|
In the first argument of `print', namely `(f 2.3 4.2)'
|
|||
|
In the expression: print (f 2.3 4.2)
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
The problem: =4.2= isn't an Int.
|
|||
|
|
|||
|
01_basic/10_Introduction/21_very_basic.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
01_basic/10_Introduction/22_very_basic.lhs
|
|||
|
|
|||
|
The solution: don't declare a type for =f= for the moment and let
|
|||
|
Haskell infer the most general type for us:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
f x y = x*x + y*y
|
|||
|
|
|||
|
main = print (f 2.3 4.2)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
It works! Luckily, we don't have to declare a new function for every
|
|||
|
single type. For example, in =C=, you'll have to declare a function for
|
|||
|
=int=, for =float=, for =long=, for =double=, etc...
|
|||
|
|
|||
|
But, what type should we declare? To discover the type Haskell has found
|
|||
|
for us, just launch ghci:
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<pre><span class="low">
|
|||
|
%</span> ghci<span class="low"><code>
|
|||
|
GHCi, version 7.0.4: http://www.haskell.org/ghc/ :? for help
|
|||
|
Loading package ghc-prim ... linking ... done.
|
|||
|
Loading package integer-gmp ... linking ... done.
|
|||
|
Loading package base ... linking ... done.
|
|||
|
Loading package ffi-1.0 ... linking ... done.
|
|||
|
Prelude></code></span> let f x y = x*x + y*y
|
|||
|
<span class="low"><code>Prelude></code></span> :type f
|
|||
|
<code>f :: Num a => a -> a -> a</code>
|
|||
|
</pre>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Uh? What is this strange type?
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
Num a => a -> a -> a
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
First, let's focus on the right part =a -> a -> a=. To understand it,
|
|||
|
just look at a list of progressive examples:
|
|||
|
|
|||
|
| The written type | Its meaning |
|
|||
|
|--------------------+---------------------------------------------------------------------------|
|
|||
|
| =Int= | the type =Int= |
|
|||
|
| =Int -> Int= | the type function from =Int= to =Int= |
|
|||
|
| =Float -> Int= | the type function from =Float= to =Int= |
|
|||
|
| =a -> Int= | the type function from any type to =Int= |
|
|||
|
| =a -> a= | the type function from any type =a= to the same type =a= |
|
|||
|
| =a -> a -> a= | the type function of two arguments of any type =a= to the same type =a= |
|
|||
|
|
|||
|
In the type =a -> a -> a=, the letter =a= is a /type variable/. It means
|
|||
|
=f= is a function with two arguments and both arguments and the result
|
|||
|
have the same type. The type variable =a= could take many different type
|
|||
|
values. For example =Int=, =Integer=, =Float=...
|
|||
|
|
|||
|
So instead of having a forced type like in =C= and having to declare a
|
|||
|
function for =int=, =long=, =float=, =double=, etc., we declare only one
|
|||
|
function like in a dynamically typed language.
|
|||
|
|
|||
|
This is sometimes called parametric polymorphism. It's also called
|
|||
|
having your cake and eating it too.
|
|||
|
|
|||
|
Generally =a= can be any type, for example a =String= or an =Int=, but
|
|||
|
also more complex types, like =Trees=, other functions, etc. But here
|
|||
|
our type is prefixed with =Num a =>=.
|
|||
|
|
|||
|
=Num= is a /type class/. A type class can be understood as a set of
|
|||
|
types. =Num= contains only types which behave like numbers. More
|
|||
|
precisely, =Num= is class containing types which implement a specific
|
|||
|
list of functions, and in particular =(+)= and =(*)=.
|
|||
|
|
|||
|
Type classes are a very powerful language construct. We can do some
|
|||
|
incredibly powerful stuff with this. More on this later.
|
|||
|
|
|||
|
Finally, =Num a => a -> a -> a= means:
|
|||
|
|
|||
|
Let =a= be a type belonging to the =Num= type class. This is a function
|
|||
|
from type =a= to (=a -> a=).
|
|||
|
|
|||
|
Yes, strange. In fact, in Haskell no function really has two arguments.
|
|||
|
Instead all functions have only one argument. But we will note that
|
|||
|
taking two arguments is equivalent to taking one argument and returning
|
|||
|
a function taking the second argument as a parameter.
|
|||
|
|
|||
|
More precisely =f 3 4= is equivalent to =(f 3) 4=. Note =f 3= is a
|
|||
|
function:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
f :: Num a => a -> a -> a
|
|||
|
|
|||
|
g :: Num a => a -> a
|
|||
|
g = f 3
|
|||
|
|
|||
|
g y ⇔ 3*3 + y*y
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
Another notation exists for functions. The lambda notation allows us to
|
|||
|
create functions without assigning them a name. We call them anonymous
|
|||
|
functions. We could also have written:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
g = \y -> 3*3 + y*y
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
The =\= is used because it looks like =λ= and is ASCII.
|
|||
|
|
|||
|
If you are not used to functional programming your brain should be
|
|||
|
starting to heat up. It is time to make a real application.
|
|||
|
|
|||
|
01_basic/10_Introduction/22_very_basic.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
01_basic/10_Introduction/23_very_basic.lhs
|
|||
|
|
|||
|
But just before that, we should verify the type system works as
|
|||
|
expected:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
f :: Num a => a -> a -> a
|
|||
|
f x y = x*x + y*y
|
|||
|
|
|||
|
main = print (f 3 2.4)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
It works, because, =3= is a valid representation both for Fractional
|
|||
|
numbers like Float and for Integer. As =2.4= is a Fractional number, =3=
|
|||
|
is then interpreted as being also a Fractional number.
|
|||
|
|
|||
|
01_basic/10_Introduction/23_very_basic.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
01_basic/10_Introduction/24_very_basic.lhs
|
|||
|
|
|||
|
If we force our function to work with different types, it will fail:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
f :: Num a => a -> a -> a
|
|||
|
f x y = x*x + y*y
|
|||
|
|
|||
|
x :: Int
|
|||
|
x = 3
|
|||
|
y :: Float
|
|||
|
y = 2.4
|
|||
|
-- won't work because type x ≠ type y
|
|||
|
main = print (f x y)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
The compiler complains. The two parameters must have the same type.
|
|||
|
|
|||
|
If you believe that this is a bad idea, and that the compiler should
|
|||
|
make the transformation from one type to another for you, you should
|
|||
|
really watch this great (and funny) video:
|
|||
|
[[https://www.destroyallsoftware.com/talks/wat][WAT]]
|
|||
|
|
|||
|
01_basic/10_Introduction/24_very_basic.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h2 id="essential-haskell">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Essential Haskell
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h2>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
blogimage("kandinsky_gugg.jpg","Kandinsky Gugg")
|
|||
|
|
|||
|
I suggest that you skim this part. Think of it as a reference. Haskell
|
|||
|
has a lot of features. A lot of information is missing here. Come back
|
|||
|
here if the notation feels strange.
|
|||
|
|
|||
|
I use the =⇔= symbol to state that two expression are equivalent. It is
|
|||
|
a meta notation, =⇔= does not exists in Haskell. I will also use =⇒= to
|
|||
|
show what the return value of an expression is.
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h3 id="notations">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Notations
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h3>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h5 id="arithmetic">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Arithmetic
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h5>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
3 + 2 * 6 / 3 ⇔ 3 + ((2*6)/3)
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h5 id="logic">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Logic
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h5>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
True || False ⇒ True
|
|||
|
True && False ⇒ False
|
|||
|
True == False ⇒ False
|
|||
|
True /= False ⇒ True (/=) is the operator for different
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h5 id="powers">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Powers
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h5>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
x^n for n an integral (understand Int or Integer)
|
|||
|
x**y for y any kind of number (Float for example)
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
=Integer= has no limit except the capacity of your machine:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
4^103
|
|||
|
102844034832575377634685573909834406561420991602098741459288064
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
Yeah! And also rational numbers FTW! But you need to import the module
|
|||
|
=Data.Ratio=:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
$ ghci
|
|||
|
....
|
|||
|
Prelude> :m Data.Ratio
|
|||
|
Data.Ratio> (11 % 15) * (5 % 3)
|
|||
|
11 % 9
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h5 id="lists">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Lists
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h5>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
[] ⇔ empty list
|
|||
|
[1,2,3] ⇔ List of integral
|
|||
|
["foo","bar","baz"] ⇔ List of String
|
|||
|
1:[2,3] ⇔ [1,2,3], (:) prepend one element
|
|||
|
1:2:[] ⇔ [1,2]
|
|||
|
[1,2] ++ [3,4] ⇔ [1,2,3,4], (++) concatenate
|
|||
|
[1,2,3] ++ ["foo"] ⇔ ERROR String ≠ Integral
|
|||
|
[1..4] ⇔ [1,2,3,4]
|
|||
|
[1,3..10] ⇔ [1,3,5,7,9]
|
|||
|
[2,3,5,7,11..100] ⇔ ERROR! I am not so smart!
|
|||
|
[10,9..1] ⇔ [10,9,8,7,6,5,4,3,2,1]
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h5 id="strings">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Strings
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h5>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
In Haskell strings are list of =Char=.
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
'a' :: Char
|
|||
|
"a" :: [Char]
|
|||
|
"" ⇔ []
|
|||
|
"ab" ⇔ ['a','b'] ⇔ 'a':"b" ⇔ 'a':['b'] ⇔ 'a':'b':[]
|
|||
|
"abc" ⇔ "ab"++"c"
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
/Remark/: In real code you shouldn't use list of char to represent
|
|||
|
text. You should mostly use =Data.Text= instead. If you want to
|
|||
|
represent a stream of ASCII char, you should use =Data.ByteString=.
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h5 id="tuples">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Tuples
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h5>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
The type of couple is =(a,b)=. Elements in a tuple can have different
|
|||
|
types.
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
-- All these tuples are valid
|
|||
|
(2,"foo")
|
|||
|
(3,'a',[2,3])
|
|||
|
((2,"a"),"c",3)
|
|||
|
|
|||
|
fst (x,y) ⇒ x
|
|||
|
snd (x,y) ⇒ y
|
|||
|
|
|||
|
fst (x,y,z) ⇒ ERROR: fst :: (a,b) -> a
|
|||
|
snd (x,y,z) ⇒ ERROR: snd :: (a,b) -> b
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h5 id="deal-with-parentheses">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Deal with parentheses
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h5>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
To remove some parentheses you can use two functions: =($)= and =(.)=.
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
-- By default:
|
|||
|
f g h x ⇔ (((f g) h) x)
|
|||
|
|
|||
|
-- the $ replace parenthesis from the $
|
|||
|
-- to the end of the expression
|
|||
|
f g $ h x ⇔ f g (h x) ⇔ (f g) (h x)
|
|||
|
f $ g h x ⇔ f (g h x) ⇔ f ((g h) x)
|
|||
|
f $ g $ h x ⇔ f (g (h x))
|
|||
|
|
|||
|
-- (.) the composition function
|
|||
|
(f . g) x ⇔ f (g x)
|
|||
|
(f . g . h) x ⇔ f (g (h x))
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
01_basic/20_Essential_Haskell/10a_Functions.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h3 id="useful-notations-for-functions">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Useful notations for functions
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h3>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Just a reminder:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
x :: Int ⇔ x is of type Int
|
|||
|
x :: a ⇔ x can be of any type
|
|||
|
x :: Num a => a ⇔ x can be any type a
|
|||
|
such that a belongs to Num type class
|
|||
|
f :: a -> b ⇔ f is a function from a to b
|
|||
|
f :: a -> b -> c ⇔ f is a function from a to (b→c)
|
|||
|
f :: (a -> b) -> c ⇔ f is a function from (a→b) to c
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
Remember that defining the type of a function before its declaration
|
|||
|
isn't mandatory. Haskell infers the most general type for you. But it is
|
|||
|
considered a good practice to do so.
|
|||
|
|
|||
|
/Infix notation/
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
square :: Num a => a -> a
|
|||
|
square x = x^2
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Note =^= uses infix notation. For each infix operator there its
|
|||
|
associated prefix notation. You just have to put it inside parenthesis.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
square' x = (^) x 2
|
|||
|
|
|||
|
square'' x = (^2) x
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
We can remove =x= in the left and right side! It's called η-reduction.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
square''' = (^2)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Note we can declare functions with ='= in their name. Here:
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
=square= ⇔ =square'= ⇔ =square''= ⇔ =square'''=
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
/Tests/
|
|||
|
|
|||
|
An implementation of the absolute function.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
absolute :: (Ord a, Num a) => a -> a
|
|||
|
absolute x = if x >= 0 then x else -x
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Note: the =if .. then .. else= Haskell notation is more like the =¤?¤:¤=
|
|||
|
C operator. You cannot forget the =else=.
|
|||
|
|
|||
|
Another equivalent version:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
absolute' x
|
|||
|
| x >= 0 = x
|
|||
|
| otherwise = -x
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
Notation warning: indentation is /important/ in Haskell. Like in
|
|||
|
Python, bad indentation can break your code!
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
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)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
01_basic/20_Essential_Haskell/10a_Functions.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h2 id="hard-part">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Hard Part
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h2>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
The hard part can now begin.
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h3 id="functional-style">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Functional style
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h3>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
blogimage("hr_giger_biomechanicallandscape_500.jpg","Biomechanical
|
|||
|
Landscape by H.R. Giger")
|
|||
|
|
|||
|
In this section, I will give a short example of the impressive
|
|||
|
refactoring ability provided by Haskell. We will select a problem and
|
|||
|
solve it in a standard imperative way. Then I will make the code evolve.
|
|||
|
The end result will be both more elegant and easier to adapt.
|
|||
|
|
|||
|
Let's solve the following problem:
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
Given a list of integers, return the sum of the even numbers in the
|
|||
|
list.
|
|||
|
|
|||
|
example: =[1,2,3,4,5] ⇒ 2 + 4 ⇒ 6=
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
To show differences between functional and imperative approaches, I'll
|
|||
|
start by providing an imperative solution (in JavaScript):
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
function evenSum(list) {
|
|||
|
var result = 0;
|
|||
|
for (var i=0; i< list.length ; i++) {
|
|||
|
if (list[i] % 2 ==0) {
|
|||
|
result += list[i];
|
|||
|
}
|
|||
|
}
|
|||
|
return result;
|
|||
|
}
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
In Haskell, by contrast, we don't have variables or a for loop. One
|
|||
|
solution to achieve the same result without loops is to use recursion.
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
/Remark/: Recursion is generally perceived as slow in imperative
|
|||
|
languages. But this is generally not the case in functional
|
|||
|
programming. Most of the time Haskell will handle recursive functions
|
|||
|
efficiently.
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
Here is a =C= version of the recursive function. Note that for
|
|||
|
simplicity I assume the int list ends with the first =0= value.
|
|||
|
|
|||
|
#+BEGIN_SRC C
|
|||
|
int evenSum(int *list) {
|
|||
|
return accumSum(0,list);
|
|||
|
}
|
|||
|
|
|||
|
int accumSum(int n, int *list) {
|
|||
|
int x;
|
|||
|
int *xs;
|
|||
|
if (*list == 0) { // if the list is empty
|
|||
|
return n;
|
|||
|
} else {
|
|||
|
x = list[0]; // let x be the first element of the list
|
|||
|
xs = list+1; // let xs be the list without x
|
|||
|
if ( 0 == (x%2) ) { // if x is even
|
|||
|
return accumSum(n+x, xs);
|
|||
|
} else {
|
|||
|
return accumSum(n, xs);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Keep this code in mind. We will translate it into Haskell. First,
|
|||
|
however, I need to introduce three simple but useful functions we will
|
|||
|
use:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
even :: Integral a => a -> Bool
|
|||
|
head :: [a] -> a
|
|||
|
tail :: [a] -> [a]
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
=even= verifies if a number is even.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
even :: Integral a => a -> Bool
|
|||
|
even 3 ⇒ False
|
|||
|
even 2 ⇒ True
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
=head= returns the first element of a list:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
head :: [a] -> a
|
|||
|
head [1,2,3] ⇒ 1
|
|||
|
head [] ⇒ ERROR
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
=tail= returns all elements of a list, except the first:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
tail :: [a] -> [a]
|
|||
|
tail [1,2,3] ⇒ [2,3]
|
|||
|
tail [3] ⇒ []
|
|||
|
tail [] ⇒ ERROR
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Note that for any non empty list =l=, =l ⇔ (head l):(tail l)=
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
02_Hard_Part/11_Functions.lhs
|
|||
|
|
|||
|
The first Haskell solution. The function =evenSum= returns the sum of
|
|||
|
all even numbers in a list:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
-- 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
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
To test a function you can use =ghci=:
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<pre>
|
|||
|
% ghci
|
|||
|
<span class="low">GHCi, version 7.0.3: http://www.haskell.org/ghc/ :? for help
|
|||
|
Loading package ghc-prim ... linking ... done.
|
|||
|
Loading package integer-gmp ... linking ... done.
|
|||
|
Loading package base ... linking ... done.
|
|||
|
Prelude></span> :load 11_Functions.lhs
|
|||
|
<span class="low">[1 of 1] Compiling Main ( 11_Functions.lhs, interpreted )
|
|||
|
Ok, modules loaded: Main.
|
|||
|
*Main></span> evenSum [1..5]
|
|||
|
6
|
|||
|
</pre>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Here is an example of execution[fn:2]:
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<pre>
|
|||
|
*Main> evenSum [1..5]
|
|||
|
accumSum 0 [1,2,3,4,5]
|
|||
|
<span class="yellow">1 is odd</span>
|
|||
|
accumSum 0 [2,3,4,5]
|
|||
|
<span class="yellow">2 is even</span>
|
|||
|
accumSum (0+2) [3,4,5]
|
|||
|
<span class="yellow">3 is odd</span>
|
|||
|
accumSum (0+2) [4,5]
|
|||
|
<span class="yellow">2 is even</span>
|
|||
|
accumSum (0+2+4) [5]
|
|||
|
<span class="yellow">5 is odd</span>
|
|||
|
accumSum (0+2+4) []
|
|||
|
<span class="yellow">l == []</span>
|
|||
|
0+2+4
|
|||
|
0+6
|
|||
|
6
|
|||
|
</pre>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Coming from an imperative language all should seem right. In fact, many
|
|||
|
things can be improved here. First, we can generalize the type.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
evenSum :: Integral a => [a] -> a
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main = do print $ evenSum [1..10]
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
02_Hard_Part/11_Functions.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
02_Hard_Part/12_Functions.lhs
|
|||
|
|
|||
|
Next, we can use sub functions using =where= or =let=. This way our
|
|||
|
=accumSum= function won't pollute the namespace of our module.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
-- 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
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main = print $ evenSum [1..10]
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
02_Hard_Part/12_Functions.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
02_Hard_Part/13_Functions.lhs
|
|||
|
|
|||
|
Next, we can use pattern matching.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
-- 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
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
What is pattern matching? Use values instead of general parameter
|
|||
|
names[fn:3].
|
|||
|
|
|||
|
Instead of saying: =foo l = if l == [] then <x> else <y>= You simply
|
|||
|
state:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
foo [] = <x>
|
|||
|
foo l = <y>
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
But pattern matching goes even further. It is also able to inspect the
|
|||
|
inner data of a complex value. We can replace
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
foo l = let x = head l
|
|||
|
xs = tail l
|
|||
|
in if even x
|
|||
|
then foo (n+x) xs
|
|||
|
else foo n xs
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
with
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
foo (x:xs) = if even x
|
|||
|
then foo (n+x) xs
|
|||
|
else foo n xs
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
This is a very useful feature. It makes our code both terser and easier
|
|||
|
to read.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main = print $ evenSum [1..10]
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
02_Hard_Part/13_Functions.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
02_Hard_Part/14_Functions.lhs
|
|||
|
|
|||
|
In Haskell you can simplify function definitions by η-reducing them. For
|
|||
|
example, instead of writing:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
f x = (some expresion) x
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
you can simply write
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
f = some expression
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
We use this method to remove the =l=:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
-- 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
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main = print $ evenSum [1..10]
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
02_Hard_Part/14_Functions.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
02_Hard_Part/15_Functions.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h4 id="higher-order-functions">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Higher Order Functions
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h4>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
blogimage("escher_polygon.png","Escher")
|
|||
|
|
|||
|
To make things even better we should use higher order functions. What
|
|||
|
are these beasts? Higher order functions are functions taking functions
|
|||
|
as parameters.
|
|||
|
|
|||
|
Here are some examples:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
filter :: (a -> Bool) -> [a] -> [a]
|
|||
|
map :: (a -> b) -> [a] -> [b]
|
|||
|
foldl :: (a -> b -> a) -> a -> [b] -> a
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Let's proceed by small steps.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
-- Version 5
|
|||
|
evenSum l = mysum 0 (filter even l)
|
|||
|
where
|
|||
|
mysum n [] = n
|
|||
|
mysum n (x:xs) = mysum (n+x) xs
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
where
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
filter even [1..10] ⇔ [2,4,6,8,10]
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
The function =filter= takes a function of type (=a -> Bool=) and a list
|
|||
|
of type =[a]=. It returns a list containing only elements for which the
|
|||
|
function returned =true=.
|
|||
|
|
|||
|
Our next step is to use another technique to accomplish the same thing
|
|||
|
as a loop. We will use the =foldl= function to accumulate a value as we
|
|||
|
pass through the list. The function =foldl= captures a general coding
|
|||
|
pattern:
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<pre>
|
|||
|
myfunc list = foo <span class="blue">initialValue</span> <span class="green">list</span>
|
|||
|
foo accumulated [] = accumulated
|
|||
|
foo tmpValue (x:xs) = foo (<span class="yellow">bar</span> tmpValue x) xs
|
|||
|
</pre>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Which can be replaced by:
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<pre>
|
|||
|
myfunc list = foldl <span class="yellow">bar</span> <span class="blue">initialValue</span> <span class="green">list</span>
|
|||
|
</pre>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
If you really want to know how the magic works, here is the definition
|
|||
|
of =foldl=:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
foldl f z [] = z
|
|||
|
foldl f z (x:xs) = foldl f (f z x) xs
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
foldl f z [x1,...xn]
|
|||
|
⇔ f (... (f (f z x1) x2) ...) xn
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
But as Haskell is lazy, it doesn't evaluate =(f z x)= and simply pushes
|
|||
|
it onto the stack. This is why we generally use =foldl'= instead of
|
|||
|
=foldl=; =foldl'= is a /strict/ version of =foldl=. If you don't
|
|||
|
understand what lazy and strict means, don't worry, just follow the code
|
|||
|
as if =foldl= and =foldl'= were identical.
|
|||
|
|
|||
|
Now our new version of =evenSum= becomes:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
-- 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
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
We can also simplify this by using directly a lambda notation. This way
|
|||
|
we don't have to create the temporary name =mysum=.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
-- 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)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
And of course, we note that
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
(\x y -> x+y) ⇔ (+)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main = print $ evenSum [1..10]
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
02_Hard_Part/15_Functions.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
02_Hard_Part/16_Functions.lhs
|
|||
|
|
|||
|
Finally
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
-- Version 8
|
|||
|
import Data.List (foldl')
|
|||
|
evenSum :: Integral a => [a] -> a
|
|||
|
evenSum l = foldl' (+) 0 (filter even l)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
=foldl'= isn't the easiest function to grasp. If you are not used to it,
|
|||
|
you should study it a bit.
|
|||
|
|
|||
|
To help you understand what's going on here, let's look at a step by
|
|||
|
step evaluation:
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<pre>
|
|||
|
<span class="yellow">evenSum [1,2,3,4]</span>
|
|||
|
⇒ foldl' (+) 0 (<span class="yellow">filter even [1,2,3,4]</span>)
|
|||
|
⇒ <span class="yellow">foldl' (+) 0 <span class="blue">[2,4]</span></span>
|
|||
|
⇒ <span class="blue">foldl' (+) (<span class="yellow">0+2</span>) [4]</span>
|
|||
|
⇒ <span class="yellow">foldl' (+) <span class="blue">2</span> [4]</span>
|
|||
|
⇒ <span class="blue">foldl' (+) (<span class="yellow">2+4</span>) []</span>
|
|||
|
⇒ <span class="yellow">foldl' (+) <span class="blue">6</span> []</span>
|
|||
|
⇒ <span class="blue">6</span>
|
|||
|
</pre>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Another useful higher order function is =(.)=. The =(.)= function
|
|||
|
corresponds to mathematical composition.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
(f . g . h) x ⇔ f ( g (h x))
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
We can take advantage of this operator to η-reduce our function:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
-- Version 9
|
|||
|
import Data.List (foldl')
|
|||
|
evenSum :: Integral a => [a] -> a
|
|||
|
evenSum = (foldl' (+) 0) . (filter even)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Also, we could rename some parts to make it clearer:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
-- Version 10
|
|||
|
import Data.List (foldl')
|
|||
|
sum' :: (Num a) => [a] -> a
|
|||
|
sum' = foldl' (+) 0
|
|||
|
evenSum :: Integral a => [a] -> a
|
|||
|
evenSum = sum' . (filter even)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
It is time to discuss the direction our code has moved as we introduced
|
|||
|
more functional idioms. What did we gain by using higher order
|
|||
|
functions?
|
|||
|
|
|||
|
At first, you might think the main difference is terseness. But in fact,
|
|||
|
it has more to do with better thinking. Suppose we want to modify our
|
|||
|
function slightly, for example, to get the sum of all even squares of
|
|||
|
elements of the list.
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
[1,2,3,4] ▷ [1,4,9,16] ▷ [4,16] ▷ 20
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
Updating version 10 is extremely easy:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
squareEvenSum = sum' . (filter even) . (map (^2))
|
|||
|
squareEvenSum' = evenSum . (map (^2))
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
We just had to add another "transformation function"[^0216].
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
map (^2) [1,2,3,4] ⇔ [1,4,9,16]
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
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. This
|
|||
|
makes the code more modular. But in addition you can think more
|
|||
|
mathematically about your function. You can also use your function
|
|||
|
interchangably with others, as needed. That is, you can compose, map,
|
|||
|
fold, filter using your new function.
|
|||
|
|
|||
|
Modifying version 1 is left as an exercise to the reader ☺.
|
|||
|
|
|||
|
If you believe we have reached the end of generalization, then know you
|
|||
|
are very wrong. For example, there is a way to not only use this
|
|||
|
function on lists but on any recursive type. If you want to know how, I
|
|||
|
suggest you to read this quite fun article:
|
|||
|
[[http://eprints.eemcs.utwente.nl/7281/01/db-utwente-40501F46.pdf][Functional
|
|||
|
Programming with Bananas, Lenses, Envelopes and Barbed Wire by Meijer,
|
|||
|
Fokkinga and Paterson]].
|
|||
|
|
|||
|
This example should show you how great pure functional programming is.
|
|||
|
Unfortunately, using pure functional programming isn't well suited to
|
|||
|
all usages. 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
|
|||
|
Specific Language) making it easy to change the programming paradigm.
|
|||
|
|
|||
|
In fact, Haskell is also great when you want to write imperative style
|
|||
|
programming. Understanding this was really hard for me to grasp when
|
|||
|
first learning Haskell. A lot of effort tends to go into explaining the
|
|||
|
superiority of the functional approach. Then when you start using an
|
|||
|
imperative style with Haskell, it can be hard to understand when and how
|
|||
|
to use it.
|
|||
|
|
|||
|
But before talking about this Haskell super-power, we must talk about
|
|||
|
another essential aspect of Haskell: /Types/.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main = print $ evenSum [1..10]
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
02_Hard_Part/16_Functions.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h3 id="types">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Types
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h3>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
blogimage("salvador-dali-the-madonna-of-port-lligat.jpg","Dali, the
|
|||
|
madonna of port Lligat")
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
%tldr
|
|||
|
|
|||
|
- =type Name = AnotherType= is just an alias and the compiler doesn't
|
|||
|
mark any difference between =Name= and =AnotherType=.
|
|||
|
- =data Name = NameConstructor AnotherType= does mark a difference.
|
|||
|
- =data= can construct structures which can be recursives.
|
|||
|
- =deriving= is magic and creates functions for you.
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
In Haskell, types are strong and static.
|
|||
|
|
|||
|
Why is this important? It will help you /greatly/ to avoid mistakes. In
|
|||
|
Haskell, most bugs are caught during the compilation of your program.
|
|||
|
And the main reason is because of the type inference during compilation.
|
|||
|
Type inference makes it easy to detect where you used the wrong
|
|||
|
parameter at the wrong place, for example.
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h4 id="type-inference">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Type inference
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h4>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Static typing is generally essential for fast execution. But most
|
|||
|
statically typed languages are bad at generalizing concepts. Haskell's
|
|||
|
saving grace is that it can /infer/ types.
|
|||
|
|
|||
|
Here is a simple example, the =square= function in Haskell:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
square x = x * x
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
This function can =square= any Numeral type. You can provide =square=
|
|||
|
with an =Int=, an =Integer=, a =Float= a =Fractional= and even
|
|||
|
=Complex=. Proof by example:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
% ghci
|
|||
|
GHCi, version 7.0.4:
|
|||
|
...
|
|||
|
Prelude> let square x = x*x
|
|||
|
Prelude> square 2
|
|||
|
4
|
|||
|
Prelude> square 2.1
|
|||
|
4.41
|
|||
|
Prelude> -- load the Data.Complex module
|
|||
|
Prelude> :m Data.Complex
|
|||
|
Prelude Data.Complex> square (2 :+ 1)
|
|||
|
3.0 :+ 4.0
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
=x :+ y= is the notation for the complex (x + iy).
|
|||
|
|
|||
|
Now compare with the amount of code necessary in C:
|
|||
|
|
|||
|
#+BEGIN_SRC C
|
|||
|
int int_square(int x) { return x*x; }
|
|||
|
|
|||
|
float float_square(float x) {return x*x; }
|
|||
|
|
|||
|
complex complex_square (complex z) {
|
|||
|
complex tmp;
|
|||
|
tmp.real = z.real * z.real - z.img * z.img;
|
|||
|
tmp.img = 2 * z.img * z.real;
|
|||
|
}
|
|||
|
|
|||
|
complex x,y;
|
|||
|
y = complex_square(x);
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
For each type, you need to write a new function. The only way to work
|
|||
|
around this problem is to use some meta-programming trick, for example
|
|||
|
using the pre-processor. In C++ there is a better way, C++ templates:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
#include <iostream>
|
|||
|
#include <complex>
|
|||
|
using namespace std;
|
|||
|
|
|||
|
template<typename T>
|
|||
|
T square(T x)
|
|||
|
{
|
|||
|
return x*x;
|
|||
|
}
|
|||
|
|
|||
|
int main() {
|
|||
|
// int
|
|||
|
int sqr_of_five = square(5);
|
|||
|
cout << sqr_of_five << endl;
|
|||
|
// double
|
|||
|
cout << (double)square(5.3) << endl;
|
|||
|
// complex
|
|||
|
cout << square( complex<double>(5,3) )
|
|||
|
<< endl;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
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
|
|||
|
article]] for example.
|
|||
|
|
|||
|
In C++ you must declare that a function can work with different types.
|
|||
|
In Haskell, the opposite is the case. The function will be as general as
|
|||
|
possible by default.
|
|||
|
|
|||
|
Type inference gives Haskell the feeling of freedom that dynamically
|
|||
|
typed languages provide. But unlike dynamically typed languages, most
|
|||
|
errors are caught before run time. Generally, in Haskell:
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
"if it compiles it certainly does what you intended"
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
02_Hard_Part/21_Types.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h4 id="type-construction">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Type construction
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h4>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
You can construct your own types. First, you can use aliases or type
|
|||
|
synonyms.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
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
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
02_Hard_Part/21_Types.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
02_Hard_Part/22_Types.lhs
|
|||
|
|
|||
|
But it doesn't protect you much. Try to swap the two parameter of
|
|||
|
=showInfos= and run the program:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
putStrLn $ showInfos color name
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
It will compile and execute. In fact you can replace Name, Color and
|
|||
|
String everywhere. The compiler will treat them as completely identical.
|
|||
|
|
|||
|
Another method is to create your own types using the keyword =data=.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
data Name = NameConstr String
|
|||
|
data Color = ColorConstr String
|
|||
|
|
|||
|
showInfos :: Name -> Color -> String
|
|||
|
showInfos (NameConstr name) (ColorConstr color) =
|
|||
|
"Name: " ++ name ++ ", Color: " ++ color
|
|||
|
|
|||
|
name = NameConstr "Robin"
|
|||
|
color = ColorConstr "Blue"
|
|||
|
main = putStrLn $ showInfos name color
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
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
|
|||
|
is to be more verbose.
|
|||
|
|
|||
|
Also notice that constructors are functions:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
NameConstr :: String -> Name
|
|||
|
ColorConstr :: String -> Color
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
The syntax of =data= is mainly:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
data TypeName = ConstructorName [types]
|
|||
|
| ConstructorName2 [types]
|
|||
|
| ...
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Generally the usage is to use the same name for the DataTypeName and
|
|||
|
DataTypeConstructor.
|
|||
|
|
|||
|
Example:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
data Complex a = Num a => Complex a a
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Also you can use the record syntax:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
data DataTypeName = DataConstructor {
|
|||
|
field1 :: [type of field1]
|
|||
|
, field2 :: [type of field2]
|
|||
|
...
|
|||
|
, fieldn :: [type of fieldn] }
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
And many accessors are made for you. Furthermore you can use another
|
|||
|
order when setting values.
|
|||
|
|
|||
|
Example:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
data Complex a = Num a => Complex { real :: a, img :: a}
|
|||
|
c = Complex 1.0 2.0
|
|||
|
z = Complex { real = 3, img = 4 }
|
|||
|
real c ⇒ 1.0
|
|||
|
img z ⇒ 4
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
02_Hard_Part/22_Types.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
02_Hard_Part/23_Types.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h4 id="recursive-type">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Recursive type
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h4>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
You already encountered a recursive type: lists. You can re-create
|
|||
|
lists, but with a more verbose syntax:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
data List a = Empty | Cons a (List a)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
If you really want to use an easier syntax you can use an infix name for
|
|||
|
constructors.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
infixr 5 :::
|
|||
|
data List a = Nil | a ::: (List a)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
The number after =infixr= gives the precedence.
|
|||
|
|
|||
|
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 derive the appropriate functions for you.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
infixr 5 :::
|
|||
|
data List a = Nil | a ::: (List a)
|
|||
|
deriving (Show,Read,Eq,Ord)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
When you add =deriving (Show)= to your data declaration, Haskell creates
|
|||
|
a =show= function for you. We'll see soon how you can use your own
|
|||
|
=show= function.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
convertList [] = Nil
|
|||
|
convertList (x:xs) = x ::: convertList xs
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main = do
|
|||
|
print (0 ::: 1 ::: Nil)
|
|||
|
print (convertList [0,1])
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
This prints:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
0 ::: (1 ::: Nil)
|
|||
|
0 ::: (1 ::: Nil)
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
02_Hard_Part/23_Types.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
02_Hard_Part/30_Trees.lhs
|
|||
|
|
|||
|
*** Trees
|
|||
|
:PROPERTIES:
|
|||
|
:CUSTOM_ID: trees
|
|||
|
:END:
|
|||
|
|
|||
|
blogimage("magritte-l-arbre.jpg","Magritte, l'Arbre")
|
|||
|
|
|||
|
We'll just give another standard example: binary trees.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
import Data.List
|
|||
|
|
|||
|
data BinTree a = Empty
|
|||
|
| Node a (BinTree a) (BinTree a)
|
|||
|
deriving (Show)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
We will also create a function which turns a list into an ordered binary
|
|||
|
tree.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
treeFromList :: (Ord a) => [a] -> BinTree a
|
|||
|
treeFromList [] = Empty
|
|||
|
treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
|
|||
|
(treeFromList (filter (>x) xs))
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Look at how elegant this function is. In plain English:
|
|||
|
|
|||
|
- an empty list will be converted to an empty tree.
|
|||
|
- a list =(x:xs)= will be converted to a tree where:
|
|||
|
|
|||
|
- The root is =x=
|
|||
|
- Its left subtree is the tree created from members of the list =xs=
|
|||
|
which are strictly inferior to =x= and
|
|||
|
- the right subtree is the tree created from members of the list =xs=
|
|||
|
which are strictly superior to =x=.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main = print $ treeFromList [7,2,4,8]
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
You should obtain the following:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
Node 7 (Node 2 Empty (Node 4 Empty Empty)) (Node 8 Empty Empty)
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
This is an informative but quite unpleasant representation of our tree.
|
|||
|
|
|||
|
02_Hard_Part/30_Trees.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
02_Hard_Part/31_Trees.lhs
|
|||
|
|
|||
|
Just for fun, let's code a better display for our trees. I simply had
|
|||
|
fun making a nice function to display trees in a general way. You can
|
|||
|
safely skip this part if you find it too difficult to follow.
|
|||
|
|
|||
|
We have a few changes to make. We remove the =deriving (Show)= from the
|
|||
|
declaration of our =BinTree= type. And it might also be useful to make
|
|||
|
our BinTree an instance of (=Eq= and =Ord=) so we will be able to test
|
|||
|
equality and compare trees.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
data BinTree a = Empty
|
|||
|
| Node a (BinTree a) (BinTree a)
|
|||
|
deriving (Eq,Ord)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Without the =deriving (Show)=, Haskell doesn't create a =show= method
|
|||
|
for us. 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 :eval never-export
|
|||
|
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 :eval never-export
|
|||
|
-- 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 :eval never-export
|
|||
|
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 :eval never-export
|
|||
|
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:
|
|||
|
< 7
|
|||
|
: |--2
|
|||
|
: | |--1
|
|||
|
: | `--4
|
|||
|
: | |--3
|
|||
|
: | `--6
|
|||
|
: `--8
|
|||
|
: `--21
|
|||
|
: |--12
|
|||
|
: `--23
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
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 :eval never-export
|
|||
|
putStrLn "\nString binary tree:"
|
|||
|
print $ treeFromList ["foo","bar","baz","gor","yog"]
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
String binary tree:
|
|||
|
< "foo"
|
|||
|
: |--"bar"
|
|||
|
: | `--"baz"
|
|||
|
: `--"gor"
|
|||
|
: `--"yog"
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
As we can test equality and order trees, we can make tree of trees!
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
putStrLn "\nBinary tree of Char binary trees:"
|
|||
|
print ( treeFromList
|
|||
|
(map treeFromList ["baz","zara","bar"]))
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
Binary tree of Char binary trees:
|
|||
|
< < 'b'
|
|||
|
: : |--'a'
|
|||
|
: : `--'z'
|
|||
|
: |--< 'b'
|
|||
|
: | : |--'a'
|
|||
|
: | : `--'r'
|
|||
|
: `--< 'z'
|
|||
|
: : `--'a'
|
|||
|
: : `--'r'
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
This is why I chose to prefix each line of tree display by =:= (except
|
|||
|
for the root).
|
|||
|
|
|||
|
blogimage("yo_dawg_tree.jpg","Yo Dawg Tree")
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
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 :eval never-export
|
|||
|
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 instance of =Eq=.
|
|||
|
|
|||
|
See how awesome this structure is: We can make trees containing not only
|
|||
|
integers, strings and chars, but also other trees. And we can even make
|
|||
|
a tree containing a tree of trees!
|
|||
|
|
|||
|
02_Hard_Part/31_Trees.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
02_Hard_Part/40_Infinites_Structures.lhs
|
|||
|
|
|||
|
** Infinite Structures
|
|||
|
:PROPERTIES:
|
|||
|
:CUSTOM_ID: infinite-structures
|
|||
|
:END:
|
|||
|
|
|||
|
blogimage("escher_infinite_lizards.jpg","Escher")
|
|||
|
|
|||
|
It is often said that Haskell is /lazy/.
|
|||
|
|
|||
|
In fact, if you are a bit pedantic, you should say that
|
|||
|
[[http://www.haskell.org/haskellwiki/Lazy_vs._non-strict][Haskell is
|
|||
|
/non-strict/]]. Laziness is just a common implementation for non-strict
|
|||
|
languages.
|
|||
|
|
|||
|
Then what does "not-strict" mean? From the Haskell wiki:
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
Reduction (the mathematical term for evaluation) proceeds from the
|
|||
|
outside in.
|
|||
|
|
|||
|
so if you have =(a+(b*c))= then you first reduce =+= first, then you
|
|||
|
reduce the inner =(b*c)=
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
For example in Haskell you can do:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
-- numbers = [1,2,..]
|
|||
|
numbers :: [Integer]
|
|||
|
numbers = 0:map (1+) numbers
|
|||
|
|
|||
|
take' n [] = []
|
|||
|
take' 0 l = []
|
|||
|
take' n (x:xs) = x:take' (n-1) xs
|
|||
|
|
|||
|
main = print $ take' 10 numbers
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
And it stops.
|
|||
|
|
|||
|
How?
|
|||
|
|
|||
|
Instead of trying to evaluate =numbers= entirely, it evaluates elements
|
|||
|
only when needed.
|
|||
|
|
|||
|
Also, note in Haskell there is a notation for infinite lists
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
[1..] ⇔ [1,2,3,4...]
|
|||
|
[1,3..] ⇔ [1,3,5,7,9,11...]
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
and most functions will work with them. Also, there is a built-in
|
|||
|
function =take= which is equivalent to our =take'=.
|
|||
|
|
|||
|
02_Hard_Part/40_Infinites_Structures.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
02_Hard_Part/41_Infinites_Structures.lhs
|
|||
|
|
|||
|
This code is mostly the same as the previous one.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
import Debug.Trace (trace)
|
|||
|
import Data.List
|
|||
|
data BinTree a = Empty
|
|||
|
| Node a (BinTree a) (BinTree a)
|
|||
|
deriving (Eq,Ord)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
-- 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. Here is an infinite
|
|||
|
binary tree:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
nullTree = Node 0 nullTree nullTree
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
A complete binary tree where each node is equal to 0. Now I will prove
|
|||
|
you can manipulate this object using the following function:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
-- take all element of a BinTree
|
|||
|
-- up to some depth
|
|||
|
treeTakeDepth _ Empty = Empty
|
|||
|
treeTakeDepth 0 _ = Empty
|
|||
|
treeTakeDepth n (Node x left right) = let
|
|||
|
nl = treeTakeDepth (n-1) left
|
|||
|
nr = treeTakeDepth (n-1) right
|
|||
|
in
|
|||
|
Node x nl nr
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
See what occurs for this program:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main = print $ treeTakeDepth 4 nullTree
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
This code compiles, runs and stops giving the following result:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
< 0
|
|||
|
: |-- 0
|
|||
|
: | |-- 0
|
|||
|
: | | |-- 0
|
|||
|
: | | `-- 0
|
|||
|
: | `-- 0
|
|||
|
: | |-- 0
|
|||
|
: | `-- 0
|
|||
|
: `-- 0
|
|||
|
: |-- 0
|
|||
|
: | |-- 0
|
|||
|
: | `-- 0
|
|||
|
: `-- 0
|
|||
|
: |-- 0
|
|||
|
: `-- 0
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
Just to heat up your neurones a bit more, let's make a slightly more
|
|||
|
interesting tree:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
iTree = Node 0 (dec iTree) (inc iTree)
|
|||
|
where
|
|||
|
dec (Node x l r) = Node (x-1) (dec l) (dec r)
|
|||
|
inc (Node x l r) = Node (x+1) (inc l) (inc r)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Another way to create this tree is to use a higher order function. This
|
|||
|
function should be similar to =map=, but should work on =BinTree=
|
|||
|
instead of list. Here is such a function:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
-- apply a function to each node of Tree
|
|||
|
treeMap :: (a -> b) -> BinTree a -> BinTree b
|
|||
|
treeMap f Empty = Empty
|
|||
|
treeMap f (Node x left right) = Node (f x)
|
|||
|
(treeMap f left)
|
|||
|
(treeMap f right)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
/Hint/: I won't talk more about this here. If you are interested in the
|
|||
|
generalization of =map= to other data structures, search for functor and
|
|||
|
=fmap=.
|
|||
|
|
|||
|
Our definition is now:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
infTreeTwo :: BinTree Int
|
|||
|
infTreeTwo = Node 0 (treeMap (\x -> x-1) infTreeTwo)
|
|||
|
(treeMap (\x -> x+1) infTreeTwo)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Look at the result for
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main = print $ treeTakeDepth 4 infTreeTwo
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
< 0
|
|||
|
: |-- -1
|
|||
|
: | |-- -2
|
|||
|
: | | |-- -3
|
|||
|
: | | `-- -1
|
|||
|
: | `-- 0
|
|||
|
: | |-- -1
|
|||
|
: | `-- 1
|
|||
|
: `-- 1
|
|||
|
: |-- 0
|
|||
|
: | |-- -1
|
|||
|
: | `-- 1
|
|||
|
: `-- 2
|
|||
|
: |-- 1
|
|||
|
: `-- 3
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main = do
|
|||
|
print $ treeTakeDepth 4 nullTree
|
|||
|
print $ treeTakeDepth 4 infTreeTwo
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
02_Hard_Part/41_Infinites_Structures.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h2 id="hell-difficulty-part">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Hell Difficulty Part
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h2>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Congratulations for getting so far! Now, some of the really hardcore
|
|||
|
stuff can start.
|
|||
|
|
|||
|
If you are like me, you should get the functional style. You should also
|
|||
|
understand a bit more the advantages of laziness by default. But you
|
|||
|
also don't really understand where to start in order to make a real
|
|||
|
program. And in particular:
|
|||
|
|
|||
|
- How do you deal with effects?
|
|||
|
- Why is there a strange imperative-like notation for dealing with IO?
|
|||
|
|
|||
|
Be prepared, the answers might be complex. But they are all very
|
|||
|
rewarding.
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
03_Hell/01_IO/01_progressive_io_example.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h3 id="deal-with-io">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Deal With IO
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h3>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
blogimage("magritte_carte_blanche.jpg","Magritte, Carte blanche")
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
%tldr
|
|||
|
|
|||
|
A typical function doing =IO= looks a lot like an imperative program:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
f :: IO a
|
|||
|
f = do
|
|||
|
x <- action1
|
|||
|
action2 x
|
|||
|
y <- action3
|
|||
|
action4 x y
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
- To set a value to an object we use =<-= .
|
|||
|
- The type of each line is =IO *=; in this example:
|
|||
|
|
|||
|
- =action1 :: IO b=
|
|||
|
- =action2 x :: IO ()=
|
|||
|
- =action3 :: IO c=
|
|||
|
- =action4 x y :: IO a=
|
|||
|
- =x :: b=, =y :: c=
|
|||
|
|
|||
|
- Few objects have the type =IO a=, this should help you choose. In
|
|||
|
particular you cannot use pure functions directly here. To use pure
|
|||
|
functions you could do =action2 (purefunction x)= for example.
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
In this section, I will explain how to use IO, not how it works. You'll
|
|||
|
see how Haskell separates the pure from the impure parts of the program.
|
|||
|
|
|||
|
Don't stop because you're trying to understand the details of the
|
|||
|
syntax. Answers will come in the next section.
|
|||
|
|
|||
|
What to achieve?
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
Ask a user to enter a list of numbers. Print the sum of the numbers
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
toList :: String -> [Integer]
|
|||
|
toList input = read ("[" ++ input ++ "]")
|
|||
|
|
|||
|
main = do
|
|||
|
putStrLn "Enter a list of numbers (separated by comma):"
|
|||
|
input <- getLine
|
|||
|
print $ sum (toList input)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
It should be straightforward to understand the behavior of this program.
|
|||
|
Let's analyze the types in more detail.
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
putStrLn :: String -> IO ()
|
|||
|
getLine :: IO String
|
|||
|
print :: Show a => a -> IO ()
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
Or more interestingly, we note that each expression in the =do= block
|
|||
|
has a type of =IO a=.
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<pre>
|
|||
|
main = do
|
|||
|
putStrLn "Enter ... " :: <span class="high">IO ()</span>
|
|||
|
getLine :: <span class="high">IO String</span>
|
|||
|
print Something :: <span class="high">IO ()</span>
|
|||
|
</pre>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
We should also pay attention to the effect of the =<-= symbol.
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
do
|
|||
|
x <- something
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
If =something :: IO a= then =x :: a=.
|
|||
|
|
|||
|
Another important note about using =IO=: All lines in a do block must be
|
|||
|
of one of the two forms:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
action1 :: IO a
|
|||
|
-- in this case, generally a = ()
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
ou
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
value <- action2 -- where
|
|||
|
-- action2 :: IO b
|
|||
|
-- value :: b
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
These two kinds of line will correspond to two different ways of
|
|||
|
sequencing actions. The meaning of this sentence should be clearer by
|
|||
|
the end of the next section.
|
|||
|
|
|||
|
03_Hell/01_IO/01_progressive_io_example.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
03_Hell/01_IO/02_progressive_io_example.lhs
|
|||
|
|
|||
|
Now let's see how this program behaves. For example, what happens if the
|
|||
|
user enters something strange? Let's try:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
% runghc 02_progressive_io_example.lhs
|
|||
|
Enter a list of numbers (separated by comma):
|
|||
|
foo
|
|||
|
Prelude.read: no parse
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
Argh! An evil error message and a crash! Our first improvement will
|
|||
|
simply be to answer with a more friendly message.
|
|||
|
|
|||
|
In order to do this, we must detect that something went wrong. Here is
|
|||
|
one way to do this: use the type =Maybe=. This is a very common type in
|
|||
|
Haskell.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
import Data.Maybe
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
What is this thing? =Maybe= is a type which takes one parameter. Its
|
|||
|
definition is:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
data Maybe a = Nothing | Just a
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
This is a nice way to tell there was an error while trying to
|
|||
|
create/compute a value. The =maybeRead= function is a great example of
|
|||
|
this. This is a function similar to the function =read=[fn:4], but if
|
|||
|
something goes wrong the returned value is =Nothing=. If the value is
|
|||
|
right, it returns =Just <the value>=. Don't try to understand too much
|
|||
|
of this function. I use a lower level function than =read=: =reads=.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
maybeRead :: Read a => String -> Maybe a
|
|||
|
maybeRead s = case reads s of
|
|||
|
[(x,"")] -> Just x
|
|||
|
_ -> Nothing
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Now to be a bit more readable, we define a function which goes like
|
|||
|
this: If the string has the wrong format, it will return =Nothing=.
|
|||
|
Otherwise, for example for "1,2,3", it will return =Just [1,2,3]=.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
getListFromString :: String -> Maybe [Integer]
|
|||
|
getListFromString str = maybeRead $ "[" ++ str ++ "]"
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
We simply have to test the value in our main function.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main :: IO ()
|
|||
|
main = do
|
|||
|
putStrLn "Enter a list of numbers (separated by comma):"
|
|||
|
input <- getLine
|
|||
|
let maybeList = getListFromString input in
|
|||
|
case maybeList of
|
|||
|
Just l -> print (sum l)
|
|||
|
Nothing -> error "Bad format. Good Bye."
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
In case of error, we display a nice error message.
|
|||
|
|
|||
|
Note that the type of each expression in the main's =do= block remains
|
|||
|
of the form =IO a=. The only strange construction is =error=. I'll just
|
|||
|
say here that =error msg= takes the needed type (here =IO ()=).
|
|||
|
|
|||
|
One very important thing to note is the type of all the functions
|
|||
|
defined so far. There is only one function which contains =IO= in its
|
|||
|
type: =main=. This means main is impure. But main uses
|
|||
|
=getListFromString= which is pure. So it's clear just by looking at
|
|||
|
declared types which functions are pure and which are impure.
|
|||
|
|
|||
|
Why does purity matter? Among the many advantages, here are three:
|
|||
|
|
|||
|
- It is far easier to think about pure code than impure code.
|
|||
|
- Purity protects you from all the hard-to-reproduce bugs that are due
|
|||
|
to side effects.
|
|||
|
- You can evaluate pure functions in any order or in parallel without
|
|||
|
risk.
|
|||
|
|
|||
|
This is why you should generally put as most code as possible inside
|
|||
|
pure functions.
|
|||
|
|
|||
|
03_Hell/01_IO/02_progressive_io_example.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
03_Hell/01_IO/03_progressive_io_example.lhs
|
|||
|
|
|||
|
Our next iteration will be to prompt the user again and again until she
|
|||
|
enters a valid answer.
|
|||
|
|
|||
|
We keep the first part:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
import Data.Maybe
|
|||
|
|
|||
|
maybeRead :: Read a => String -> Maybe a
|
|||
|
maybeRead s = case reads s of
|
|||
|
[(x,"")] -> Just x
|
|||
|
_ -> Nothing
|
|||
|
getListFromString :: String -> Maybe [Integer]
|
|||
|
getListFromString str = maybeRead $ "[" ++ str ++ "]"
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Now we create a function which will ask the user for an list of integers
|
|||
|
until the input is right.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
askUser :: IO [Integer]
|
|||
|
askUser = do
|
|||
|
putStrLn "Enter a list of numbers (separated by comma):"
|
|||
|
input <- getLine
|
|||
|
let maybeList = getListFromString input in
|
|||
|
case maybeList of
|
|||
|
Just l -> return l
|
|||
|
Nothing -> askUser
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
This function is of type =IO [Integer]=. Such a type means that we
|
|||
|
retrieved a value of type =[Integer]= through some IO actions. Some
|
|||
|
people might explain while waving their hands:
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
«This is an =[Integer]= inside an =IO=»
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
If you want to understand the details behind all of this, you'll have to
|
|||
|
read the next section. But really, if you just want to /use/ IO just
|
|||
|
practice a little and remember to think about the type.
|
|||
|
|
|||
|
Finally our main function is much simpler:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main :: IO ()
|
|||
|
main = do
|
|||
|
list <- askUser
|
|||
|
print $ sum list
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
We have finished with our introduction to =IO=. This was quite fast.
|
|||
|
Here are the main things to remember:
|
|||
|
|
|||
|
- in the =do= block, each expression must have the type =IO a=. You are
|
|||
|
then limited with regard to the range of expressions available. For
|
|||
|
example, =getLine=, =print=, =putStrLn=, etc...
|
|||
|
- Try to externalize the pure functions as much as possible.
|
|||
|
- the =IO a= type means: an IO /action/ which returns an element of type
|
|||
|
=a=. =IO= represents actions; under the hood, =IO a= is the type of a
|
|||
|
function. Read the next section if you are curious.
|
|||
|
|
|||
|
If you practice a bit, you should be able to /use/ =IO=.
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
/Exercises/:
|
|||
|
|
|||
|
- Make a program that sums all of its arguments. Hint: use the
|
|||
|
function =getArgs=.
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
03_Hell/01_IO/03_progressive_io_example.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h3 id="io-trick-explained">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
IO trick explained
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h3>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
blogimage("magritte_pipe.jpg","Magritte, ceci n'est pas une pipe")
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
Here is a %tldr for this section.
|
|||
|
|
|||
|
To separate pure and impure parts, =main= is defined as a function
|
|||
|
which modifies the state of the world.
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
main :: World -> World
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
A function is guaranteed to have side effects only if it has this
|
|||
|
type. But look at a typical main function:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
|
|||
|
main w0 =
|
|||
|
let (v1,w1) = action1 w0 in
|
|||
|
let (v2,w2) = action2 v1 w1 in
|
|||
|
let (v3,w3) = action3 v2 w2 in
|
|||
|
action4 v3 w3
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
We have a lot of temporary elements (here =w1=, =w2= and =w3=) which
|
|||
|
must be passed on to the next action.
|
|||
|
|
|||
|
We create a function =bind= or =(>>=)=. With =bind= we don't need
|
|||
|
temporary names anymore.
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
main =
|
|||
|
action1 >>= action2 >>= action3 >>= action4
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
Bonus: Haskell has syntactical sugar for us:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
main = do
|
|||
|
v1 <- action1
|
|||
|
v2 <- action2 v1
|
|||
|
v3 <- action3 v2
|
|||
|
action4 v3
|
|||
|
#+END_EXAMPLE
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
Why did we use this strange syntax, and what exactly is this =IO= type?
|
|||
|
It looks a bit like magic.
|
|||
|
|
|||
|
For now let's just forget all about the pure parts of our program, and
|
|||
|
focus on the impure parts:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
askUser :: IO [Integer]
|
|||
|
askUser = do
|
|||
|
putStrLn "Enter a list of numbers (separated by commas):"
|
|||
|
input <- getLine
|
|||
|
let maybeList = getListFromString input in
|
|||
|
case maybeList of
|
|||
|
Just l -> return l
|
|||
|
Nothing -> askUser
|
|||
|
|
|||
|
main :: IO ()
|
|||
|
main = do
|
|||
|
list <- askUser
|
|||
|
print $ sum list
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
First remark: this looks imperative. Haskell is powerful enough to make
|
|||
|
impure code look imperative. For example, if you wish you could create a
|
|||
|
=while= in Haskell. In fact, for dealing with =IO=, an imperative style
|
|||
|
is generally more appropriate.
|
|||
|
|
|||
|
But you should have noticed that the notation is a bit unusual. Here is
|
|||
|
why, in detail.
|
|||
|
|
|||
|
In an impure language, the state of the world can be seen as a huge
|
|||
|
hidden global variable. This hidden variable is accessible by all
|
|||
|
functions of your language. For example, you can read and write a file
|
|||
|
in any function. Whether a file exists or not is a difference in the
|
|||
|
possible states that the world can take.
|
|||
|
|
|||
|
In Haskell the current state of the world is not hidden. Rather, it is
|
|||
|
/explicitly/ said that =main= is a function that /potentially/ changes
|
|||
|
the state of the world. Its type is then something like:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main :: World -> World
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Not all functions may access this variable. Those which have access to
|
|||
|
this variable are impure. Functions to which the world variable isn't
|
|||
|
provided are pure[fn:5].
|
|||
|
|
|||
|
Haskell considers the state of the world as an input variable to =main=.
|
|||
|
But the real type of main is closer to this one[fn:6]:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main :: World -> ((),World)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
The =()= type is the unit type. Nothing to see here.
|
|||
|
|
|||
|
Now let's rewrite our main function with this in mind:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main w0 =
|
|||
|
let (list,w1) = askUser w0 in
|
|||
|
let (x,w2) = print (sum list,w1) in
|
|||
|
x
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
First, we note that all functions which have side effects must have the
|
|||
|
type:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
World -> (a,World)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
where =a= is the type of the result. For example, a =getChar= function
|
|||
|
should have the type =World -> (Char, World)=.
|
|||
|
|
|||
|
Another thing to note is the trick to fix the order of evaluation. In
|
|||
|
Haskell, in order to evaluate =f a b=, you have many choices:
|
|||
|
|
|||
|
- first eval =a= then =b= then =f a b=
|
|||
|
- first eval =b= then =a= then =f a b=.
|
|||
|
- eval =a= and =b= in parallel then =f a b=
|
|||
|
|
|||
|
This is true because we're working in a pure part of the language.
|
|||
|
|
|||
|
Now, if you look at the main function, it is clear you must eval the
|
|||
|
first line before the second one since to evaluate the second line you
|
|||
|
have to get a parameter given by the evaluation of the first line.
|
|||
|
|
|||
|
This trick works like a charm. The compiler will at each step provide a
|
|||
|
pointer to a new real world id. Under the hood, =print= will evaluate
|
|||
|
as:
|
|||
|
|
|||
|
- print something on the screen
|
|||
|
- modify the id of the world
|
|||
|
- evaluate as =((),new world id)=.
|
|||
|
|
|||
|
Now, if you look at the style of the main function, it is clearly
|
|||
|
awkward. Let's try to do the same to the =askUser= function:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
askUser :: World -> ([Integer],World)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Before:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
askUser :: IO [Integer]
|
|||
|
askUser = do
|
|||
|
putStrLn "Enter a list of numbers:"
|
|||
|
input <- getLine
|
|||
|
let maybeList = getListFromString input in
|
|||
|
case maybeList of
|
|||
|
Just l -> return l
|
|||
|
Nothing -> askUser
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
After:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
askUser w0 =
|
|||
|
let (_,w1) = putStrLn "Enter a list of numbers:" in
|
|||
|
let (input,w2) = getLine w1 in
|
|||
|
let (l,w3) = case getListFromString input of
|
|||
|
Just l -> (l,w2)
|
|||
|
Nothing -> askUser w2
|
|||
|
in
|
|||
|
(l,w3)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
This is similar, but awkward. Look at all these temporary =w?= names.
|
|||
|
|
|||
|
The lesson is: naive IO implementation in Pure functional languages is
|
|||
|
awkward!
|
|||
|
|
|||
|
Fortunately, there is a better way to handle this problem. We see a
|
|||
|
pattern. Each line is of the form:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
let (y,w') = action x w in
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Even if for some lines the first =x= argument isn't needed. The output
|
|||
|
type is a couple, =(answer, newWorldValue)=. Each function =f= must have
|
|||
|
a type similar to:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
f :: World -> (a,World)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Not only this, but we can also note that we always follow the same usage
|
|||
|
pattern:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
let (y,w1) = action1 w0 in
|
|||
|
let (z,w2) = action2 w1 in
|
|||
|
let (t,w3) = action3 w2 in
|
|||
|
...
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Each action can take from 0 to n parameters. And in particular, each
|
|||
|
action can take a parameter from the result of a line above.
|
|||
|
|
|||
|
For example, we could also have:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
let (_,w1) = action1 x w0 in
|
|||
|
let (z,w2) = action2 w1 in
|
|||
|
let (_,w3) = action3 z w2 in
|
|||
|
...
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
With, of course: =actionN w :: (World) -> (a,World)=.
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
IMPORTANT: there are only two important patterns to consider:
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
let (x,w1) = action1 w0 in
|
|||
|
let (y,w2) = action2 x w1 in
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
and
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
let (_,w1) = action1 w0 in
|
|||
|
let (y,w2) = action2 w1 in
|
|||
|
#+END_EXAMPLE
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
leftblogimage("jocker_pencil_trick.jpg","Jocker pencil trick")
|
|||
|
|
|||
|
Now, we will do a magic trick. We will make the temporary world symbols
|
|||
|
"disappear". We will =bind= the two lines. Let's define the =bind=
|
|||
|
function. Its type is quite intimidating at first:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
bind :: (World -> (a,World))
|
|||
|
-> (a -> (World -> (b,World)))
|
|||
|
-> (World -> (b,World))
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
But remember that =(World -> (a,World))= is the type for an IO action.
|
|||
|
Now let's rename it for clarity:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
type IO a = World -> (a, World)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Some examples of functions:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
getLine :: IO String
|
|||
|
print :: Show a => a -> IO ()
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
=getLine= is an IO action which takes world as a parameter and returns a
|
|||
|
couple =(String, World)=. This can be summarized as: =getLine= is of
|
|||
|
type =IO String=, which we also see as an IO action which will return a
|
|||
|
String "embeded inside an IO".
|
|||
|
|
|||
|
The function =print= is also interesting. It takes one argument which
|
|||
|
can be shown. In fact it takes two arguments. The first is the value to
|
|||
|
print and the other is the state of world. It then returns a couple of
|
|||
|
type =((), World)=. This means that it changes the state of the world,
|
|||
|
but doesn't yield any more data.
|
|||
|
|
|||
|
This new =IO a= type helps us simplify the type of =bind=:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
bind :: IO a
|
|||
|
-> (a -> IO b)
|
|||
|
-> IO b
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
It says that =bind= takes two IO actions as parameters and returns
|
|||
|
another IO action.
|
|||
|
|
|||
|
Now, remember the /important/ patterns. The first was:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
pattern1 w0 =
|
|||
|
let (x,w1) = action1 w0 in
|
|||
|
let (y,w2) = action2 x w1 in
|
|||
|
(y,w2)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Look at the types:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
action1 :: IO a
|
|||
|
action2 :: a -> IO b
|
|||
|
pattern1 :: IO b
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Doesn't it seem familiar?
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
(bind action1 action2) w0 =
|
|||
|
let (x, w1) = action1 w0
|
|||
|
(y, w2) = action2 x w1
|
|||
|
in (y, w2)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
The idea is to hide the World argument with this function. Let's go: As
|
|||
|
an example imagine if we wanted to simulate:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
let (line1, w1) = getLine w0 in
|
|||
|
let ((), w2) = print line1 in
|
|||
|
((), w2)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Now, using the =bind= function:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
(res, w2) = (bind getLine print) w0
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
As print is of type =Show a => a -> (World -> ((), World))=, we know
|
|||
|
=res = ()= (=unit= type). If you didn't see what was magic here, let's
|
|||
|
try with three lines this time.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
let (line1,w1) = getLine w0 in
|
|||
|
let (line2,w2) = getLine w1 in
|
|||
|
let ((),w3) = print (line1 ++ line2) in
|
|||
|
((),w3)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Which is equivalent to:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
(res,w3) = (bind getLine (\line1 ->
|
|||
|
(bind getLine (\line2 ->
|
|||
|
print (line1 ++ line2))))) w0
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Didn't you notice something? Yes, no temporary World variables are used
|
|||
|
anywhere! This is /MA/. /GIC/.
|
|||
|
|
|||
|
We can use a better notation. Let's use =(>>=)= instead of =bind=.
|
|||
|
=(>>=)= is an infix function like =(+)=; reminder =3 + 4 ⇔ (+) 3 4=
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
(res,w3) = (getLine >>=
|
|||
|
(\line1 -> getLine >>=
|
|||
|
(\line2 -> print (line1 ++ line2)))) w0
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
fr; Haskell a confectionné du sucre syntaxique pour vous : Ho Ho Ho!
|
|||
|
Merry Christmas Everyone! Haskell has made syntactical sugar for us:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
do
|
|||
|
x <- action1
|
|||
|
y <- action2
|
|||
|
z <- action3
|
|||
|
...
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Is replaced by:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
action1 >>= (\x ->
|
|||
|
action2 >>= (\y ->
|
|||
|
action3 >>= (\z ->
|
|||
|
...
|
|||
|
)))
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Note that you can use =x= in =action2= and =x= and =y= in =action3=.
|
|||
|
|
|||
|
But what about the lines not using the =<-=? Easy, another function
|
|||
|
=blindBind=:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
blindBind :: IO a -> IO b -> IO b
|
|||
|
blindBind action1 action2 w0 =
|
|||
|
bind action (\_ -> action2) w0
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
I didn't simplify this definition for the purposes of clarity. Of
|
|||
|
course, we can use a better notation: we'll use the =(>>)= operator.
|
|||
|
|
|||
|
And
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
do
|
|||
|
action1
|
|||
|
action2
|
|||
|
action3
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Is transformed into
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
action1 >>
|
|||
|
action2 >>
|
|||
|
action3
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Also, another function is quite useful.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
putInIO :: a -> IO a
|
|||
|
putInIO x = IO (\w -> (x,w))
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
This is the general way to put pure values inside the "IO context". The
|
|||
|
general name for =putInIO= is =return=. This is quite a bad name when
|
|||
|
you learn Haskell. =return= is very different from what you might be
|
|||
|
used to.
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
03_Hell/01_IO/21_Detailled_IO.lhs
|
|||
|
|
|||
|
To finish, let's translate our example:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
|
|||
|
askUser :: IO [Integer]
|
|||
|
askUser = do
|
|||
|
putStrLn "Enter a list of numbers (separated by commas):"
|
|||
|
input <- getLine
|
|||
|
let maybeList = getListFromString input in
|
|||
|
case maybeList of
|
|||
|
Just l -> return l
|
|||
|
Nothing -> askUser
|
|||
|
|
|||
|
main :: IO ()
|
|||
|
main = do
|
|||
|
list <- askUser
|
|||
|
print $ sum list
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Is translated into:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
import Data.Maybe
|
|||
|
|
|||
|
maybeRead :: Read a => String -> Maybe a
|
|||
|
maybeRead s = case reads s of
|
|||
|
[(x,"")] -> Just x
|
|||
|
_ -> Nothing
|
|||
|
getListFromString :: String -> Maybe [Integer]
|
|||
|
getListFromString str = maybeRead $ "[" ++ str ++ "]"
|
|||
|
askUser :: IO [Integer]
|
|||
|
askUser =
|
|||
|
putStrLn "Enter a list of numbers (sep. by commas):" >>
|
|||
|
getLine >>= \input ->
|
|||
|
let maybeList = getListFromString input in
|
|||
|
case maybeList of
|
|||
|
Just l -> return l
|
|||
|
Nothing -> askUser
|
|||
|
|
|||
|
main :: IO ()
|
|||
|
main = askUser >>=
|
|||
|
\list -> print $ sum list
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
You can compile this code to verify that it works.
|
|||
|
|
|||
|
Imagine what it would look like without the =(>>)= and =(>>=)=.
|
|||
|
|
|||
|
03_Hell/01_IO/21_Detailled_IO.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
03_Hell/02_Monads/10_Monads.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h3 id="monads">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Monads
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h3>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
blogimage("dali_reve.jpg","Dali, reve. It represents a weapon out of the
|
|||
|
mouth of a tiger, itself out of the mouth of another tiger, itself out
|
|||
|
of the mouth of a fish itself out of a grenade. I could have choosen a
|
|||
|
picture of the Human centipede as it is a very good representation of
|
|||
|
what a monad really is. But just to think about it, I find this
|
|||
|
disgusting and that wasn't the purpose of this document.")
|
|||
|
|
|||
|
Now the secret can be revealed: =IO= is a /monad/. Being a monad means
|
|||
|
you have access to some syntactical sugar with the =do= notation. But
|
|||
|
mainly, you have access to a coding pattern which will ease the flow of
|
|||
|
your code.
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
*Important remarks*:
|
|||
|
|
|||
|
- Monad are not necessarily about effects! There are a lot of /pure/
|
|||
|
monads.
|
|||
|
- Monad are more about sequencing
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
In Haskell, =Monad= is a type class. To be an instance of this type
|
|||
|
class, you must provide the functions =(>>=)= and =return=. The function
|
|||
|
=(>>)= is derived from =(>>=)=. Here is how the type class =Monad= is
|
|||
|
declared (basically):
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
class Monad m where
|
|||
|
(>>=) :: m a -> (a -> m b) -> m b
|
|||
|
return :: a -> m a
|
|||
|
|
|||
|
(>>) :: m a -> m b -> m b
|
|||
|
f >> g = f >>= \_ -> g
|
|||
|
|
|||
|
-- You should generally safely ignore this function
|
|||
|
-- which I believe exists for historical reasons
|
|||
|
fail :: String -> m a
|
|||
|
fail = error
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
Remarks:
|
|||
|
|
|||
|
- the keyword =class= is not your friend. A Haskell class is /not/ a
|
|||
|
class of the kind you will find in object-oriented programming. A
|
|||
|
Haskell class has a lot of similarities with Java interfaces. A
|
|||
|
better word would have been =typeclass=, since that means a set of
|
|||
|
types. For a type to belong to a class, all functions of the class
|
|||
|
must be provided for this type.
|
|||
|
- In this particular example of type class, the type =m= must be a
|
|||
|
type that takes an argument. for example =IO a=, but also =Maybe a=,
|
|||
|
=[a]=, etc...
|
|||
|
- To be a useful monad, your function must obey some rules. If your
|
|||
|
construction does not obey these rules strange things might happens:
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
return a >>= k == k a
|
|||
|
m >>= return == m
|
|||
|
m >>= (\x -> k x >>= h) == (m >>= k) >>= h
|
|||
|
#+END_EXAMPLE
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h4 id="maybe-monad">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Maybe is a monad
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h4>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
There are a lot of different types that are instances of =Monad=. One of
|
|||
|
the easiest to describe is =Maybe=. If you have a sequence of =Maybe=
|
|||
|
values, you can use monads to manipulate them. It is particularly useful
|
|||
|
to remove very deep =if..then..else..= constructions.
|
|||
|
|
|||
|
Imagine a complex bank operation. You are eligible to gain about 700€
|
|||
|
only if you can afford to follow a list of operations without your
|
|||
|
balance dipping below zero.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
deposit value account = account + value
|
|||
|
withdraw value account = account - value
|
|||
|
|
|||
|
eligible :: (Num a,Ord a) => a -> Bool
|
|||
|
eligible account =
|
|||
|
let account1 = deposit 100 account in
|
|||
|
if (account1 < 0)
|
|||
|
then False
|
|||
|
else
|
|||
|
let account2 = withdraw 200 account1 in
|
|||
|
if (account2 < 0)
|
|||
|
then False
|
|||
|
else
|
|||
|
let account3 = deposit 100 account2 in
|
|||
|
if (account3 < 0)
|
|||
|
then False
|
|||
|
else
|
|||
|
let account4 = withdraw 300 account3 in
|
|||
|
if (account4 < 0)
|
|||
|
then False
|
|||
|
else
|
|||
|
let account5 = deposit 1000 account4 in
|
|||
|
if (account5 < 0)
|
|||
|
then False
|
|||
|
else
|
|||
|
True
|
|||
|
|
|||
|
main = do
|
|||
|
print $ eligible 300 -- True
|
|||
|
print $ eligible 299 -- False
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
03_Hell/02_Monads/10_Monads.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
03_Hell/02_Monads/11_Monads.lhs
|
|||
|
|
|||
|
Now, let's make it better using Maybe and the fact that it is a Monad
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
deposit :: (Num a) => a -> a -> Maybe a
|
|||
|
deposit value account = Just (account + value)
|
|||
|
|
|||
|
withdraw :: (Num a,Ord a) => a -> a -> Maybe a
|
|||
|
withdraw value account = if (account < value)
|
|||
|
then Nothing
|
|||
|
else Just (account - value)
|
|||
|
|
|||
|
eligible :: (Num a, Ord a) => a -> Maybe Bool
|
|||
|
eligible account = do
|
|||
|
account1 <- deposit 100 account
|
|||
|
account2 <- withdraw 200 account1
|
|||
|
account3 <- deposit 100 account2
|
|||
|
account4 <- withdraw 300 account3
|
|||
|
account5 <- deposit 1000 account4
|
|||
|
Just True
|
|||
|
|
|||
|
main = do
|
|||
|
print $ eligible 300 -- Just True
|
|||
|
print $ eligible 299 -- Nothing
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
03_Hell/02_Monads/11_Monads.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
03_Hell/02_Monads/12_Monads.lhs
|
|||
|
|
|||
|
Not bad, but we can make it even better:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
deposit :: (Num a) => a -> a -> Maybe a
|
|||
|
deposit value account = Just (account + value)
|
|||
|
|
|||
|
withdraw :: (Num a,Ord a) => a -> a -> Maybe a
|
|||
|
withdraw value account = if (account < value)
|
|||
|
then Nothing
|
|||
|
else Just (account - value)
|
|||
|
|
|||
|
eligible :: (Num a, Ord a) => a -> Maybe Bool
|
|||
|
eligible account =
|
|||
|
deposit 100 account >>=
|
|||
|
withdraw 200 >>=
|
|||
|
deposit 100 >>=
|
|||
|
withdraw 300 >>=
|
|||
|
deposit 1000 >>
|
|||
|
return True
|
|||
|
|
|||
|
main = do
|
|||
|
print $ eligible 300 -- Just True
|
|||
|
print $ eligible 299 -- Nothing
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
We have proven that Monads are a good way to make our code more elegant.
|
|||
|
Note this idea of code organization, in particular for =Maybe= can be
|
|||
|
used in most imperative languages. In fact, this is the kind of
|
|||
|
construction we make naturally.
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
An important remark:
|
|||
|
|
|||
|
The first element in the sequence being evaluated to =Nothing= will
|
|||
|
stop the complete evaluation. This means you don't execute all lines.
|
|||
|
You get this for free, thanks to laziness.
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
You could also replay these example with the definition of =(>>=)= for
|
|||
|
=Maybe= in mind:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
instance Monad Maybe where
|
|||
|
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
|
|||
|
Nothing >>= _ = Nothing
|
|||
|
(Just x) >>= f = f x
|
|||
|
|
|||
|
return x = Just x
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
The =Maybe= monad proved to be useful while being a very simple example.
|
|||
|
We saw the utility of the =IO= monad. But now for a cooler example,
|
|||
|
lists.
|
|||
|
|
|||
|
03_Hell/02_Monads/12_Monads.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
03_Hell/02_Monads/13_Monads.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h4 id="the-list-monad">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
The list monad
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h4>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
blogimage("golconde.jpg","Golconde de Magritte")
|
|||
|
|
|||
|
The list monad helps us to simulate non-deterministic computations. Here
|
|||
|
we go:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
import Control.Monad (guard)
|
|||
|
|
|||
|
allCases = [1..10]
|
|||
|
|
|||
|
resolve :: [(Int,Int,Int)]
|
|||
|
resolve = do
|
|||
|
x <- allCases
|
|||
|
y <- allCases
|
|||
|
z <- allCases
|
|||
|
guard $ 4*x + 2*y < z
|
|||
|
return (x,y,z)
|
|||
|
|
|||
|
main = do
|
|||
|
print resolve
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
MA. GIC. :
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
[(1,1,7),(1,1,8),(1,1,9),(1,1,10),(1,2,9),(1,2,10)]
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
For the list monad, there is also this syntactic sugar:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
print $ [ (x,y,z) | x <- allCases,
|
|||
|
y <- allCases,
|
|||
|
z <- allCases,
|
|||
|
4*x + 2*y < z ]
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
I won't list all the monads, since there are many of them. Using monads
|
|||
|
simplifies the manipulation of several notions in pure languages. In
|
|||
|
particular, monads are very useful for:
|
|||
|
|
|||
|
- IO,
|
|||
|
- non-deterministic computation,
|
|||
|
- generating pseudo random numbers,
|
|||
|
- keeping configuration state,
|
|||
|
- writing state,
|
|||
|
- ...
|
|||
|
|
|||
|
If you have followed me until here, then you've done it! You know
|
|||
|
monads[fn:7]!
|
|||
|
|
|||
|
03_Hell/02_Monads/13_Monads.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h2 id="appendix">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Appendix
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h2>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
This section is not so much about learning Haskell. It is just here to
|
|||
|
discuss some details further.
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
04_Appendice/01_More_on_infinite_trees/10_Infinite_Trees.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<h3 id="more-on-infinite-tree">
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
More on Infinite Tree
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
</h3>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
In the section [[#infinite-structures][Infinite Structures]] we saw some
|
|||
|
simple constructions. Unfortunately we removed two properties from our
|
|||
|
tree:
|
|||
|
|
|||
|
1. no duplicate node value
|
|||
|
2. well ordered tree
|
|||
|
|
|||
|
In this section we will try to keep the first property. Concerning the
|
|||
|
second one, we must relax it but we'll discuss how to keep it as much as
|
|||
|
possible.
|
|||
|
|
|||
|
This code is mostly the same as the one in the [[#trees][tree section]].
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
import Data.List
|
|||
|
data BinTree a = Empty
|
|||
|
| Node a (BinTree a) (BinTree a)
|
|||
|
deriving (Eq,Ord)
|
|||
|
|
|||
|
-- 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
|
|||
|
|
|||
|
Our first step is to create some pseudo-random number list:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
shuffle = map (\x -> (x*3123) `mod` 4331) [1..]
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Just as a reminder, here is the definition of =treeFromList=
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
treeFromList :: (Ord a) => [a] -> BinTree a
|
|||
|
treeFromList [] = Empty
|
|||
|
treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs))
|
|||
|
(treeFromList (filter (>x) xs))
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
and =treeTakeDepth=:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
treeTakeDepth _ Empty = Empty
|
|||
|
treeTakeDepth 0 _ = Empty
|
|||
|
treeTakeDepth n (Node x left right) = let
|
|||
|
nl = treeTakeDepth (n-1) left
|
|||
|
nr = treeTakeDepth (n-1) right
|
|||
|
in
|
|||
|
Node x nl nr
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
See the result of:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main = do
|
|||
|
putStrLn "take 10 shuffle"
|
|||
|
print $ take 10 shuffle
|
|||
|
putStrLn "\ntreeTakeDepth 4 (treeFromList shuffle)"
|
|||
|
print $ treeTakeDepth 4 (treeFromList shuffle)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_EXAMPLE
|
|||
|
% runghc 02_Hard_Part/41_Infinites_Structures.lhs
|
|||
|
take 10 shuffle
|
|||
|
[3123,1915,707,3830,2622,1414,206,3329,2121,913]
|
|||
|
treeTakeDepth 4 (treeFromList shuffle)
|
|||
|
|
|||
|
< 3123
|
|||
|
: |--1915
|
|||
|
: | |--707
|
|||
|
: | | |--206
|
|||
|
: | | `--1414
|
|||
|
: | `--2622
|
|||
|
: | |--2121
|
|||
|
: | `--2828
|
|||
|
: `--3830
|
|||
|
: |--3329
|
|||
|
: | |--3240
|
|||
|
: | `--3535
|
|||
|
: `--4036
|
|||
|
: |--3947
|
|||
|
: `--4242
|
|||
|
#+END_EXAMPLE
|
|||
|
|
|||
|
Yay! It ends! Beware though, it will only work if you always have
|
|||
|
something to put into a branch.
|
|||
|
|
|||
|
For example
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
treeTakeDepth 4 (treeFromList [1..])
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
will loop forever. Simply because it will try to access the head of
|
|||
|
=filter (<1) [2..]=. But =filter= is not smart enought to understand
|
|||
|
that the result is the empty list.
|
|||
|
|
|||
|
Nonetheless, it is still a very cool example of what non strict programs
|
|||
|
have to offer.
|
|||
|
|
|||
|
Left as an exercise to the reader:
|
|||
|
|
|||
|
- Prove the existence of a number =n= so that
|
|||
|
=treeTakeDepth n (treeFromList shuffle)= will enter an infinite loop.
|
|||
|
- Find an upper bound for =n=.
|
|||
|
- Prove there is no =shuffle= list so that, for any depth, the program
|
|||
|
ends.
|
|||
|
|
|||
|
04_Appendice/01_More_on_infinite_trees/10_Infinite_Trees.lhs
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<hr/>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
04_Appendice/01_More_on_infinite_trees/11_Infinite_Trees.lhs
|
|||
|
|
|||
|
This code is mostly the same as the preceding one.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
import Debug.Trace (trace)
|
|||
|
import Data.List
|
|||
|
data BinTree a = Empty
|
|||
|
| Node a (BinTree a) (BinTree a)
|
|||
|
deriving (Eq,Ord)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
-- 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"
|
|||
|
|
|||
|
treeTakeDepth _ Empty = Empty
|
|||
|
treeTakeDepth 0 _ = Empty
|
|||
|
treeTakeDepth n (Node x left right) = let
|
|||
|
nl = treeTakeDepth (n-1) left
|
|||
|
nr = treeTakeDepth (n-1) right
|
|||
|
in
|
|||
|
Node x nl nr
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
In order to resolve these problem we will modify slightly our
|
|||
|
=treeFromList= and =shuffle= function.
|
|||
|
|
|||
|
A first problem, is the lack of infinite different number in our
|
|||
|
implementation of =shuffle=. We generated only =4331= different numbers.
|
|||
|
To resolve this we make a slightly better =shuffle= function.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
shuffle = map rand [1..]
|
|||
|
where
|
|||
|
rand x = ((p x) `mod` (x+c)) - ((x+c) `div` 2)
|
|||
|
p x = m*x^2 + n*x + o -- some polynome
|
|||
|
m = 3123
|
|||
|
n = 31
|
|||
|
o = 7641
|
|||
|
c = 1237
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
This shuffle function has the property (hopefully) not to have an upper
|
|||
|
nor lower bound. But having a better shuffle list isn't enough not to
|
|||
|
enter an infinite loop.
|
|||
|
|
|||
|
Generally, we cannot decide whether =filter (<x) xs= is empty. Then to
|
|||
|
resolve this problem, I'll authorize some error in the creation of our
|
|||
|
binary tree. This new version of code can create binary tree which don't
|
|||
|
have the following property for some of its nodes:
|
|||
|
|
|||
|
#+BEGIN_QUOTE
|
|||
|
Any element of the left (resp. right) branch must all be strictly
|
|||
|
inferior (resp. superior) to the label of the root.
|
|||
|
#+END_QUOTE
|
|||
|
|
|||
|
Remark it will remains /mostly/ an ordered binary tree. Furthermore, by
|
|||
|
construction, each node value is unique in the tree.
|
|||
|
|
|||
|
Here is our new version of =treeFromList=. We simply have replaced
|
|||
|
=filter= by =safefilter=.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
treeFromList :: (Ord a, Show a) => [a] -> BinTree a
|
|||
|
treeFromList [] = Empty
|
|||
|
treeFromList (x:xs) = Node x left right
|
|||
|
where
|
|||
|
left = treeFromList $ safefilter (<x) xs
|
|||
|
right = treeFromList $ safefilter (>x) xs
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
This new function =safefilter= is almost equivalent to =filter= but
|
|||
|
don't enter infinite loop if the result is a finite list. If it cannot
|
|||
|
find an element for which the test is true after 10000 consecutive
|
|||
|
steps, then it considers to be the end of the search.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
safefilter :: (a -> Bool) -> [a] -> [a]
|
|||
|
safefilter f l = safefilter' f l nbTry
|
|||
|
where
|
|||
|
nbTry = 10000
|
|||
|
safefilter' _ _ 0 = []
|
|||
|
safefilter' _ [] _ = []
|
|||
|
safefilter' f (x:xs) n =
|
|||
|
if f x
|
|||
|
then x : safefilter' f xs nbTry
|
|||
|
else safefilter' f xs (n-1)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
Now run the program and be happy:
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
main = do
|
|||
|
putStrLn "take 10 shuffle"
|
|||
|
print $ take 10 shuffle
|
|||
|
putStrLn "\ntreeTakeDepth 8 (treeFromList shuffle)"
|
|||
|
print $ treeTakeDepth 8 (treeFromList $ shuffle)
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
You should realize the time to print each value is different. This is
|
|||
|
because Haskell compute each value when it needs it. And in this case,
|
|||
|
this is when asked to print it on the screen.
|
|||
|
|
|||
|
Impressively enough, try to replace the depth from =8= to =100=. It will
|
|||
|
work without killing your RAM! The flow and the memory management is
|
|||
|
done naturally by Haskell.
|
|||
|
|
|||
|
Left as an exercise to the reader:
|
|||
|
|
|||
|
- Even with large constant value for =deep= and =nbTry=, it seems to
|
|||
|
work nicely. But in the worst case, it can be exponential. Create a
|
|||
|
worst case list to give as parameter to =treeFromList=.\\
|
|||
|
/hint/: think about (=[0,-1,-1,....,-1,1,-1,...,-1,1,...]=).
|
|||
|
- I first tried to implement =safefilter= as follow:
|
|||
|
|
|||
|
#+BEGIN_HTML
|
|||
|
<pre>
|
|||
|
safefilter' f l = if filter f (take 10000 l) == []
|
|||
|
then []
|
|||
|
else filter f l
|
|||
|
</pre>
|
|||
|
#+END_HTML
|
|||
|
|
|||
|
Explain why it doesn't work and can enter into an infinite loop.
|
|||
|
- Suppose that =shuffle= is real random list with growing bounds. If you
|
|||
|
study a bit this structure, you'll discover that with probability 1,
|
|||
|
this structure is finite. Using the following code (suppose we could
|
|||
|
use =safefilter'= directly as if was not in the where of safefilter)
|
|||
|
find a definition of =f= such that with probability =1=,
|
|||
|
=treeFromList' shuffle= is infinite. And prove it. Disclaimer, this is
|
|||
|
only a conjecture.
|
|||
|
|
|||
|
#+BEGIN_SRC haskell :eval never-export
|
|||
|
treeFromList' [] n = Empty
|
|||
|
treeFromList' (x:xs) n = Node x left right
|
|||
|
where
|
|||
|
left = treeFromList' (safefilter' (<x) xs (f n)
|
|||
|
right = treeFromList' (safefilter' (>x) xs (f n)
|
|||
|
f = ???
|
|||
|
#+END_SRC
|
|||
|
|
|||
|
04_Appendice/01_More_on_infinite_trees/11_Infinite_Trees.lhs
|
|||
|
|
|||
|
** Thanks
|
|||
|
:PROPERTIES:
|
|||
|
:CUSTOM_ID: thanks
|
|||
|
:END:
|
|||
|
|
|||
|
Thanks to [[http://reddit.com/r/haskell][=/r/haskell=]] and
|
|||
|
[[http://reddit.com/r/programming][=/r/programming=]]. Your comment were
|
|||
|
most than welcome.
|
|||
|
|
|||
|
Particularly, I want to thank [[https://github.com/Emm][Emm]] a thousand
|
|||
|
times for the time he spent on correcting my English. Thank you man.
|
|||
|
|
|||
|
[fn:1] Even if most recent languages try to hide them, they are present.
|
|||
|
|
|||
|
[fn:2] I know I'm cheating. But I will talk about non-strictness later.
|
|||
|
|
|||
|
[fn:3] For the brave, a more complete explanation of pattern matching
|
|||
|
can be found
|
|||
|
[[http://www.cs.auckland.ac.nz/references/haskell/haskell-intro-html/patterns.html][here]].
|
|||
|
|
|||
|
[fn:4] Which is itself very similar to the javascript =eval= function,
|
|||
|
that is applied to a string containing JSON.
|
|||
|
|
|||
|
[fn:5] There are some /unsafe/ exceptions to this rule. But you
|
|||
|
shouldn't see such use in a real application except maybe for
|
|||
|
debugging purposes.
|
|||
|
|
|||
|
[fn:6] For the curious ones, the real type is
|
|||
|
=data IO a = IO {unIO :: State# RealWorld -> (# State# RealWorld, a #)}=.
|
|||
|
All the =#= has to do with optimisation and I swapped the fields
|
|||
|
in my example. But this is the basic idea.
|
|||
|
|
|||
|
[fn:7] Well, you'll certainly need to practice a bit to get used to them
|
|||
|
and to understand when you can use them and create your own. But
|
|||
|
you already made a big step in this direction.
|