223 lines
6.6 KiB
Org Mode
223 lines
6.6 KiB
Org Mode
|
#+TODO: TODO TO-CLEAN TO-REVIEW | DONE
|
||
|
#+TITLE: Haskell for the working programmer
|
||
|
#+AUTHOR: Yann Esposito
|
||
|
#+EMAIL: yann.esposito@gmail.com
|
||
|
#+LANGUAGE: en
|
||
|
#+KEYWORDS: haskell
|
||
|
#+PROPERTY: header-args :output-dir HWP :mkdirp yes :tangle-mode (identity #o755)
|
||
|
|
||
|
* Beginner Level
|
||
|
** TODO The syntax
|
||
|
|
||
|
Let's put that behind us ASAP.
|
||
|
Syntax is really the thing most people focus about when learning a new
|
||
|
programming language.
|
||
|
|
||
|
With more experience, I find that its most of the time totally irrelevant.
|
||
|
And the real interrest of a new programming language isn't about the syntax.
|
||
|
Otherwise all programming languages would look either like LISP or Ruby.
|
||
|
|
||
|
*** TODO Copy from my article Learn Haskell Fast & Hard
|
||
|
|
||
|
- Basic: spaces are meaningful like in Python.
|
||
|
- Variables are like math variables. They are immutables.
|
||
|
- Function definition, lack of parenthesis is one of the thing that make it the
|
||
|
most specific and hard to adapt.
|
||
|
=f x y = x=
|
||
|
This is why I'll try to use more parenthesis than in "real world code".
|
||
|
- Functions are first class (can be parameters like any other variables).
|
||
|
- Curring can also be surprising but you should understand that as the ability
|
||
|
to reach a higher level of abstraction.
|
||
|
|
||
|
*** TO-CLEAN *VERY IMPORTANT PART!* Typing Notation
|
||
|
|
||
|
So that will be VERY VERY IMPORTANT to be able to work with Haskell efficiently.
|
||
|
|
||
|
One of the central Haskell property is to try to help you, the developer, to
|
||
|
write checks and constraints on your code while you write it.
|
||
|
That way of writing code take some time to really be used to.
|
||
|
|
||
|
So here we go:
|
||
|
|
||
|
**** TO-CLEAN Basic Types
|
||
|
|
||
|
A type is a way of "labelling" an expression by providing some constraint on it.
|
||
|
The most basic types are the types you might certainly be used to.
|
||
|
|
||
|
- =Bool=: this type has only two possible values; =True= and =False=.
|
||
|
- =Char=: a 8 bits char
|
||
|
- Numbers (There are many of them)
|
||
|
- =Int=: classical integer with min and max depending on your machine properties
|
||
|
- =Word=: unsigned integral type with the same size as =Int=
|
||
|
- =Integer=: unbounded integer representation
|
||
|
- =Float=: single precision floating point
|
||
|
- =Double=: double precision floating point
|
||
|
|
||
|
There is also another interresting type: Unit that is denoted =()=.
|
||
|
=Bool= is inhabited by =True= and =False=, =()= is inhabited only by the /value/ =()=.
|
||
|
|
||
|
It is a bit difficult but =()= denote at the same time a type when it is written
|
||
|
in a context where we deal with types and as a value when the context make it
|
||
|
clear we wait a value.
|
||
|
|
||
|
When you read Haskell code some part are about types and others are about values.
|
||
|
|
||
|
#+BEGIN_SRC haskell
|
||
|
foo :: Int -- after the :: these are types
|
||
|
foo = 42 -- this is about values
|
||
|
#+END_SRC
|
||
|
|
||
|
**** TO-CLEAN Type Composition
|
||
|
|
||
|
One interresting thing to think about is that for each value we associate a type.
|
||
|
But types themselves are categorized. And we use /kind/ for that.
|
||
|
|
||
|
#+BEGIN_QUOTE
|
||
|
A /kind/ is to a type what a type is to a value.
|
||
|
#+END_QUOTE
|
||
|
|
||
|
So all basic types are of kind =*=.
|
||
|
|
||
|
#+BEGIN_SRC
|
||
|
> stack ghci
|
||
|
...
|
||
|
Prelude> :t 'a'
|
||
|
'a' :: Char
|
||
|
Prelude> :k Char
|
||
|
Char :: *
|
||
|
#+END_SRC
|
||
|
|
||
|
Now you should imagine where this is going.
|
||
|
Like functions, types can take another types as variables.
|
||
|
So types can compose.
|
||
|
|
||
|
Basic types that help composes:
|
||
|
|
||
|
- list: =[] :: * -> *=
|
||
|
- =[Char] :: *=
|
||
|
- =[Int] :: *=
|
||
|
- tuples: =(,) :: * -> * -> *=
|
||
|
- =(,) Char :: * -> *=
|
||
|
- =(Char,Int) :: *=
|
||
|
|
||
|
One very important thing to note is that that functions can only be from type of kind =*= to type of kind =*=.
|
||
|
|
||
|
- function: =(->) :: TYPE q -> TYPE r -> *=
|
||
|
|
||
|
**** TO-CLEAN Custom Data Type / Records
|
||
|
|
||
|
So now:
|
||
|
|
||
|
#+BEGIN_SRC haskell
|
||
|
type Foo = Bool -- type synonym
|
||
|
data Bar = BarConstr Int Char
|
||
|
-- Bar is the type
|
||
|
-- BarConstr is the type construction, it's a function of type: Int -> Char -> Bar
|
||
|
-- :kind Bar :: *
|
||
|
-- :kind BarConstr <-- ERROR, this is not a type
|
||
|
data Baz a = BazConstr Char a
|
||
|
-- :kind Baz :: * -> *
|
||
|
-- :kind BazConstr <-- ERROR, a constructor is not a type
|
||
|
#+END_SRC
|
||
|
|
||
|
*** TO-CLEAN Standard library / Prelude / API
|
||
|
|
||
|
One of Haskell strength is that it is about composability.
|
||
|
So in general you can achieve your goal by playing lego.
|
||
|
|
||
|
It is a lot like a UNIX shell in its spirit.
|
||
|
Instead of having a big stand alone application that does a lot of things.
|
||
|
You'll have a lot of small atomic functions you can use to construct a bigger one.
|
||
|
|
||
|
While the absolute minimum amount of function needed to build every other one
|
||
|
can be small. In reallity a lot of intermediate functions are already at your
|
||
|
disposal.
|
||
|
|
||
|
**** TODO Bool
|
||
|
**** TODO Numbers
|
||
|
**** TODO Strings
|
||
|
**** TODO Containers
|
||
|
***** TODO List
|
||
|
***** TODO Generic, Foldable
|
||
|
**** TODO Useful Abstraction
|
||
|
***** Monoid
|
||
|
We can merge values
|
||
|
***** Functor
|
||
|
***** Applicative
|
||
|
***** Monad
|
||
|
***** Foldable
|
||
|
We can "fold" a list of values =fold :: Monoid m => t m -> m=
|
||
|
***** Traversable
|
||
|
=sequenceA :: f (t a) -> t (f a)= Example: =[Maybe a] => Maybe [a]=
|
||
|
=traverse :: (a -> f b) -> t a -> f (t b)=
|
||
|
Example:
|
||
|
** TO-CLEAN IO
|
||
|
|
||
|
If you know another popular programming language you probably aren't aware that
|
||
|
you code "in" =IO=. What I mean by that is that you can write a print statement
|
||
|
anywhere in your code and it will be executed when the program evaluate that
|
||
|
line. This is generally the first method used in debugging or during development
|
||
|
to understand what's going on.
|
||
|
|
||
|
So Haskell is slightly different in this regard.
|
||
|
In Haskell there are places where you'll be able to add the same kind of print statements.
|
||
|
But in some other places, it will be forbidden.
|
||
|
|
||
|
Example:
|
||
|
|
||
|
#+BEGIN_SRC haskell
|
||
|
pureadd x y = x + y
|
||
|
ioAdd x y = do
|
||
|
print x
|
||
|
print y
|
||
|
print (x+y)
|
||
|
return (x+y)
|
||
|
#+END_SRC
|
||
|
|
||
|
|
||
|
So this is not much different than in Python for example:
|
||
|
|
||
|
#+BEGIN_SRC python
|
||
|
>>> def add (x,y):
|
||
|
... print x
|
||
|
... print y
|
||
|
... print (x+y)
|
||
|
... return x+y
|
||
|
>>> add(3,4)
|
||
|
3
|
||
|
4
|
||
|
7
|
||
|
7
|
||
|
#+END_SRC
|
||
|
|
||
|
But one /huge/ difference is the type inferred will be different:
|
||
|
|
||
|
#+BEGIN_SRC haskell
|
||
|
pureadd :: Num a => a -> a -> a
|
||
|
ioAdd :: Num a => a -> a -> {-hi-}IO{-/hi-} a
|
||
|
#+END_SRC
|
||
|
|
||
|
The consequence is that you will only be allowed to use =ioAdd= in function
|
||
|
whose type is also =IO *= for some value of =*=.
|
||
|
|
||
|
#+BEGIN_SRC haskell
|
||
|
circonference :: Int -> Int -> Int
|
||
|
circonference height width = pureadd (2 * height) (2 * width) -- OK
|
||
|
|
||
|
circonferenceIO :: Int -> Int -> Int
|
||
|
circonferenceIO height width = ioAdd (2 * height) (2 * width) -- WON'T COMPILE
|
||
|
#+END_SRC
|
||
|
|
||
|
To fix it you could simply change the type of the calling function:
|
||
|
|
||
|
#+BEGIN_SRC haskell
|
||
|
circonferenceIO :: Int -> Int -> IO Int
|
||
|
circonferenceIO height width = ioAdd (2 * height) (2 * width) -- OK
|
||
|
#+END_SRC
|
||
|
|
||
|
Now, I think, that's it. With that understandment, you should now be able to do
|
||
|
usefull thing with Haskell.
|
||
|
|
||
|
The why is it this way? Why adding that layer of complexity?
|
||
|
Just follow me, the answers will come in time.
|