hwp-book/6__1_Beginner.org

223 lines
6.6 KiB
Org Mode
Raw Permalink Normal View History

2018-06-20 20:11:04 +00:00
#+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.