Reorganized HWP

This commit is contained in:
Yann Esposito (Yogsototh) 2018-06-20 21:58:08 +02:00
parent 26216fccb4
commit 16f8b65975
Signed by untrusted user who does not match committer: yogsototh
GPG key ID: 7B19A4C650D59646
15 changed files with 1478 additions and 1325 deletions

View file

@ -1 +0,0 @@
slideLevel: 3

Binary file not shown.

1284
HWP.org

File diff suppressed because it is too large Load diff

132
HWP/1_Introduction.org Normal file
View file

@ -0,0 +1,132 @@
#+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)
* TO-CLEAN Introduction
This is somehow a follow-up from Learn Haskell Fast and Hard.
Which was more about being able to /play/ with Haskell than to /work/ with it.
This is also an experiment.
I'm not sure if it will be as positive as I hope.
This book try to be a good resource to learn Haskell but to speed up the
learning in the first part I'll skip the explanation about why Haskell does
things the way it does.
As a consequence if you don't keep in mind that there is *very good* reasons to
make some things way more difficult in Haskell than in other languages you
might miss the real reason.
Also don't forget in the beginning you might only see what is more difficult or
harder to achieve in Haskell.
But for each thing harder, keep in mind that there are very difficult things in
other languages that are solved extremely easily in Haskell.
And I personnally believe the things Haskell make easier are essential to reach
the best balance between speed, elegance, safety and pragmatism with regards to
any programming language I ever used before.
So this book might be a bit raw.
And in fact not really "fun" unfortunately.
But it should be efficient.
This book is aimed to be one of the fastest way to learn how to be productive
with Haskell.
Know that there still will be a very long road ahead once this book will be
finished to master Haskell. That should be ok. Even with those basic knowledge,
you should already be more productive in Haskell than in most other programming.
Modern computing has unfortunately less to do with algorithmic than to create a
mashup of libs and external APIs.
So while learning all the details of Haskell can seems like an impossible challenge.
Learning the necessary skills to be productive shouldn't be that hard.
What does this book will talk about.
1. Having a clean and stable dev environment
2. Basic Introduction to the language
3. Professional Project developement workflow
4. Make command line program
5. Use external libraries
6. Handle the filesystem
7. Handle a few DBs
8. Make a basic REST API
What Haskell can do few other programming language can.
Ability to put strong constraingt on part of the code. For exemple you can have
confidence in 3rd party functions. You can be certain that there will be NO side
effect. Or you can also ensure that part of you code can only write logs and not
access the DB. We'll technique that will ensure that subpart of the code will
only access the User table in your DB etc...
Writting parallel and concurrent code because /very/ easy to write.
While this is generally a nightmare in most programming language.
** TO-CLEAN What does "working programmer" stand for?
Being able to:
- create a new working program from scratch,
- work with the filesystem (read/write files/directories),
- work with BDD (SQLite, PostgresSQL, MongoDB, etc...),
- work with network (send/receive HTTP request),
- make a REST API,
- use most libraries (OpenGL, ncurses, etc...)
- write test for your application,
- to deploy your application
This is more about being an user, consumer from the Haskell community than being
an active contributor. Hopefully the gap won't be hard to pass from user to
contributor. So I'll write a minimal chapter about how to write your own library
and publish it for other developpers.
** TO-CLEAN Prerequiste
The target audience I'm writting this book for is software developpers.
You should:
- be familiar with some programming language,
- be familiar with command line in a shell,
- know how to editing text files (I try to focus on generic editors like emacs, vim, etc...),
- know the basic usage of =git=
If you don't know that, your journey with this book might be a bit difficult but
I'll do my best to not make it impossible.
** TO-CLEAN Opinionated
Keep in mind that Haskell has a very active and open ecosystem.
And the language itself let you make very different choices to the fundamentals.
This book is very opinionated, because I wanted to be efficient in learning
fast for some specific kind of personalities.
It might not be for you.
One of my goal is to shortcicuit some classic learning detour.
For a lot of decisions I generally make only one choice. I'll try to talk about
the other choices and it will be your duty to explore other choices after you
completed this book to decide which is the one that has your preference.
Also note that this book was written in the past. And as I said Haskell
ecosystem evolve very fast. And some choices which are an evidence today might
be deprecated in a few months from now.
Typically there are many different and concurrent web frameworks, db libs, etc..
** TO-CLEAN A Word about Haskell philosophy
One Haskell main characteristic is that it tends to make the right/most secure
choice by default.
A very simple example is that it is generally harder to write unsafe code than
to write safe and pure code.
Also one of the reason I think Haskell is percieved as hard to learn by many
people is that you generally need to ingest a lot of concepts before being able
to be productive.

192
HWP/2_Install.org Normal file
View file

@ -0,0 +1,192 @@
#+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)
* TO-CLEAN Install a dev environment (about 30 minutes)
Installing a dev environment should hopefully be the most boring part of this
book. But this is a necessary price to pay to really get why Haskell is
considered so great by people using it.
*** TO-CLEAN Working environment
A thing to note is the distinction between learning a language for personal
interrest for some personal project and learning with the goal to achieve a
"product" with some hard deadline.
So for example, it can be nice to understand the language by playing inside a
REPL. We won't use that much in this book as the goal is not to really gain a
deeper knowledge but perhaps to be able to /use/ the language.
The problem I try to solve in this book is to make you a professional /user/ of
Haskell more than a /contributor/ to Haskell. While I encourage everybody to
gain deeper understanding on the internals of Haskell this is not the primary
goal of this book.
What I mean by professional /user/ is that you should have the following
features at your disposal:
- DCVS
- Generated documentation
- Tests (Unit tests, Generative tests, Integration tests, etc...)
- Benchmark
- Continuous Integration
- Continuous Deployment
Choices:
- Raw: get GHC and cabal exectuable and work with that. Too long and manual
- Nix: this is really great because it's like a super make that can deal with
external dependencies. Certainly the best tool in the long term.
- Stack: fast to install focused on being user friendly. Has a lot of easy to
use features like:
- integration with docker that will make it easy to cross-compile, deploy.
- integration with nix
- easy to deal with many private repositories
- good professional starting templates
*** TO-CLEAN Stack
I recommend [[https://haskellstack.org][stack]]. But there are many different method to install Haskell.
Stack should be simple and straight to the point.
If thing haven't changed since the book was written it could be installed with:
#+BEGIN_SRC shell
curl -sSL https://get.haskellstack.org/ | sh
#+END_SRC
*** TO-CLEAN git
You should have [[https://git-scm.com][=git=]] installed.
*** TO-CLEAN Stack template
Before starting to write your first line of code.
Let's create a project with a sane and modern file organisation.
I made a stack templates largely inspired by tasty-travis template. It will
provide a bootstrap for organizing your application with tests, benchmarks and
continuous integration.
This template provide a file organisation for your projects.
Mainly do jump into programmin you could theoretically just download the binary
of the main Haskell compiler GHC to your compiler and compile each file with
=ghc myfile.hs=. But let's face it. It's not suitable for real project which
need more informations about it.
So let's start with a sane professional organisation for your files.
#+BEGIN_SRC shell
stack new myproject https://git.io/vbpej
#+END_SRC
After that, this should generate a new `myproject` directory with the following
files:
#+BEGIN_SRC
> tree
.
├── CHANGELOG.md
├── LICENSE
├── README.md
├── Setup.hs
├── myproject.cabal
├── package.yaml
├── src
│ └── Lib.hs
├── src-benchmark
│ └── Main.hs
├── src-doctest
│ └── Main.hs
├── src-exe
│ └── Main.hs
├── src-test
│ └── Main.hs
├── stack.yaml
└── tutorial.md
5 directories, 13 files
#+END_SRC
Most of your source code should be in the =src= directory. Generally =src-exe=
should be a minimal code that could handle the =main= function to start your
application. We'll talk about other part later in the book but most other file
should be quite straightforward.
*** TO-CLEAN Editor
You should check any of the supported editor here:
https://github.com/rainbyte/haskell-ide-chart#the-chart-with-a-link-to-each-plug-in
I personnaly use [[http://spacemacs.org][spacemacs]] with the haskell layer because it comes with battery
included. If you're not used to vim keybindings I believe it is easy to switch
to more classical editor keybindings easily.
Even if I don't have a strong opinion on the editor you should choose. It should
at least be easy to support the Haskell tooling, like intero or ghc-mod. Because
it's one of the best part of Haskell.
For example without any configuration I have the following features:
- I see errors, warn and even code hints while I'm typing my code.
- very good code completion
- auto styling of my source code and be able to change the style of my entire
buffer
- be able to get the type of the expression under my cursor
- be able to add the type of a top level declaration
- be able to launch a repl easily loading the current code of the file I'm
currently editing
And many other nice features.
Note that in the past I had some problem with ghc-mod during upgrades while
intero was mostly a no problem story.
It is also useful to have hoogle and hayoo, which are search engine focused on
Haskell.
**** TO-CLEAN Spacemacs
So if you want to choose spacemacs:
1. Install a recent emacs
2. =git clone https://github.com/syl20bnr/spacemacs ~/.emacs.d=
3. Launch emacs
4. Edit your =~/.spacemacs= file to add to the layer list:
#+BEGIN_SRC elisp
haskell
(auto-completion :variables
auto-completion-enable-help-tooltip t
auto-completion-enable-short-usage t)
#+END_SRC
If you're not used to vim keybinding and it is too much to handle for you.
I think you can change the value of =dotspacemacs-editing-style= from ='vim=
to ='hybrid= or ='emacs= in the =.spacemacs= file.
It should be good now.
*** TO-CLEAN Conclusion
First you can congratulate yourself to have installed all the prerequiste to
have a great working development environment.
I know it was already a lot of boring tasks to perform before being able to
write any line of code. But I promise it will be worth it.
By starting with this template, you won't use the classic prelude. It's quite a
strong opinionated move. Because many classic function will be overwritten by
safer/more generic one.
So be prepared that the actual learning route is jumping other classical
learning steps you can find in other learning resources. Don't worry I'll do my
best to make the jump as natural as possible.

770
HWP/3_Intermediate.org Normal file
View file

@ -0,0 +1,770 @@
#+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)
#+BEGIN_COMMENT
/THIS IS A WORK IN PROGRESS/
*CONTRIBUTORS*
This part is the real beginning of the book.
The user should have basic Haskell knowledge but shouldn't be familiar with it.
So, I would prefer not to use much operators and prefer named functions.
In the same spirit I would tend to prefer over parentheses usage instead of
using ~(.)~ and ~($)~ and currying.
For an Haskell foreigner the first is easier to read than the second:
#+BEGIN_SRC haskell
myFunc aMiddleware aHandler aRequest =
aMiddleware (aHandler aRequest)
myFunc m h x = m $ h x
#+END_SRC
The part that will be really not shared as a consensus is:
As the target aren't beginner programmers but more Haskell beginners/unfamiliar.
I use another prelude for that part to prevent the first basic mistakes. I might
even think to use the =Strict= pragma for the user to be in a not so foreign
environment. Note =Strict= doesn't make the Haskell strict, it just make it
strict where is should be strict for most usage. But I would imagine we would
enable a lot of common pragmas such as =OverloadedStrings=.
So let's say first, use Protolude, with many pragmas enabled by default.
There are two intermediate parts:
1. The first part is about writing basic programs meant to be contained in a
single file and that should use few dependencies.
For that, I would tend to use stack scripts.
2. The second part we create a few minor projects.
So the workflow is a bit more complex.
To minize frict with the tooling I would recommend using hpack.
First its yaml, and everybody know yaml, second it minimize the number of manipulation when adding
a new Haskell Module.
In that part, there should be a part explaining how to find the informations needed to program.
How to find and use a package. Where to find the documentation, how to read it, etc...
Also, give some tricks, like pointing to hayoo and hoogle, etc...
#+END_COMMENT
* TODO Intermediate
Working like in any other language / Learning with examples
** TO-CLEAN Stack Scripts
In that part of the book, we'll use simple examples. Thus instead of going
directly to a full project structure we'll focus on the language. That file can
be treated as a single executable strict.
For example:
#+BEGIN_SRC haskell
#!/usr/bin/env stack
{- stack script
--resolver lts-11.6
--install-ghc
--package protolude
-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
import Protolude
main = putText "Hello World!"
#+END_SRC
The firsts line are simply here to set the correct execution environment.
The real program starts after them.
Once =stack= will be installed (see the /Install a dev environment/ section)
if you put that content in a file named =hello.hs= then you can launch it with:
#+BEGIN_SRC
> chmod +x hello.hs
> ./hello.hs
#+END_SRC
The first time it is launched can take a little bit of time because it will
download all dependencies. The advantage of this form of distribution is that it
is a quasi self-contained exectuable. That's a good one for minimal examples.
But after a short introduction we'll use full projects.
We'll start by example first and all notion will be introduced as they appear.
If you find confident you could feel free to skip some descriptions and
explanations.
** TODO Basic Examples
*** TO-CLEAN Basics -- Project 1: Guessing Game
**** TO-CLEAN Print and read things
Now let's modify the code of =main= to print things.
First comment the import line for =Lib=.
Haskell comment are =--= till the end of the line or ={- .... -}=
for multiline comments.
Without this comment you'll get a warning that this import is unused.
And by default we compile using =-Werror= flag to GHC which tell that the
compilation should fail also on warnings as well as on errors.
The default template tend to be a professional environment and has more
restrictions in order to maximize confidence in quality.
#+BEGIN_SRC haskell :tangle HWP/code/hello_world.hs
#!/usr/bin/env stack
{- stack script
--resolver lts-11.6
--install-ghc
--package protolude
-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
import Protolude
main = putText "Hello, world!"
#+END_SRC
Simple and natural.
Now let's ask your name.
#+BEGIN_SRC haskell :tangle HWP/code/hello_name.hs
#!/usr/bin/env stack
{- stack script
--resolver lts-11.6
--install-ghc
--package protolude
-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
import Protolude
main = do
putText "What is your name?"
name <- getLine
putText ("Hello " <> name <> "!")
#+END_SRC
We can try that in the REPL (GHCI). You shold be able to start it from your
editor. For example in spacemacs I can load the current buffer (open file) in
the REPL with =SPC m s b=.
You could also start the repl in a terminal with =stack ghci=
And then load the module with =:l src-exe/Main=.
The =:l= is a shortcut for =:load=.
#+BEGIN_SRC
> ./hello_name.sh
What is your name?
Yann
Hello Yann!
#+END_SRC
OK simple enough.
But let's take a moment to understand a bit more what's going on.
We started with the =do= keyword.
It's a syntactical sugar that helps in combining multiple lines easily.
Let's take a look at the type of each part.
#+BEGIN_SRC haskell
putText :: Text -> IO ()
#+END_SRC
It means that =putText= is a function that take a =Text= as parameter and return
an =IO ()=.
Mainly =IO ()= simply means, it will return =()= (nothing) while doing some IO
or border effect.
The border effect here being, writing the text to the standard output.
#+BEGIN_SRC haskell
putText "What is your name?" :: IO ()
#+END_SRC
So yes this line make an IO but returns nothing significant.
#+BEGIN_SRC haskell
name <- getLine
#+END_SRC
The function =getLine= will read from standard input and provide the line read
and send the value as a =Text=. If you look at the type of =getLine= you have:
#+BEGIN_SRC haskell
getLine :: IO Text
#+END_SRC
And that means that to be able to retrieve and manipulate the Text returned by
in an "IO context" you can use the =<-= notation.
So in the code the type of =name= is =Text=
More generally if =foo :: IO a= then when you write
#+BEGIN_SRC haskell
do
x <- foo :: IO a
#+END_SRC
Then the type of =x= is =a=.
Finally the last line:
#+BEGIN_SRC haskell
putText ("Hello " <> name <> "!")
#+END_SRC
=putText= take a =Text= as argument so: =("Hello " <> name <> "!") :: Text=.
So =(<>)= is the infix operator equivalent to the function =mappend=.
Here are equivalent way to write the same thing:
#+BEGIN_SRC haskell
"Hello" <> name <> "!"
"Hello" `mappend` name `mappend` "!"
mappend "Hello" (mappend name "!")
(<>) "Hello" ((<>) name "!")
#+END_SRC
So in Haskell if your function contains chars it will be a prefix function.
If your function contains special chars then it is considered to be an infix
operator.
You can use your function as infix if you put "`" around it name.
And you can make your operator prefix if you put it inside parentheses.
So you should have remarqued a pattern here.
Which is really important. Each line of a =do= bloc has a type of =IO a=.
#+BEGIN_SRC haskell
main = do
putText "What is your name?" :: IO ()
name <- getLine :: IO Text
putText ("Hello " <> name <> "!") :: IO ()
#+END_SRC
So whenever you have an error message try to think about the type of your
expression.
Another very important aspect to notice.
The type of ="Hello " <> name <> "!"= is =Text= not =IO Text=.
This is because this expression can be evaluated purely.
Without any side effect.
Here we see a clear distinction between a pure part of our code and the impure
part.
#+BEGIN_QUOTE
*☞ Pure vs Impure* (function vs procedure)
That is one of the major difference between Haskell and other languages.
Haskell provide a list of function that are considered to have border effects.
Those functions are given a type of the form =IO a=.
And the type system will restrict the way you can manipulate function with type
=IO a=.
So, first thing that might be counter intuitive.
If an expression has a type of =IO a= it means that we potentially perform a
side effect and we "return" something of type =a=.
And we don't want to ever perform a side effect while doing any pure evaluation.
This is why you can't write something like:
#+BEGIN_SRC haskell
-- DOESN'T COMPILE
main = do
putText ("Hello " <> getLine <> "!")
#+END_SRC
Because you need to "traverse" the =IO= barrier to get back the value after the
evaluation.
This is why you NEED to use the =<-= notation.
Now knowing if a code is potentially making any side effect is /explicit/.
#+END_QUOTE
***** TO-CLEAN Strings in Haskell digression
Generally working with string is something you do at the beginning of learning a
programming language.
It is straightforward.
In Haskell you have many different choices when dealing with Strings depending
on the context.
But let just say that 95% of the time, you'll want to use Strict =Text=.
Here are all the possible choices:
- =String=: Just a list of =Char= very inefficient representation,
- =Text=: UTF-16 strings can be Lazy or Strict,
- =Bytestring=: Raw stream of =Char= and also =Lazy.Bytestring=.
That is already 5 different choices. But there is another package that provide other string choices.
In =Foundation= the strings are =UTF-8=.
Mmmm so much choices.
So to make it clear, in general, don't use =String= for anything serious.
Use =Text= most of the time.
Use =Bytestring= if you need efficient bytes arrays.
By using Protolude, we prevent the use of =String= naturally.
**** TO-CLEAN Write a guess my age program
So far so good.
But the logic part of the code should be in a library in =src/= directory.
Because this part is easier to test.
The =src-exe/Main.hs= should be very minimalist, so now let's change its content
by:
#+BEGIN_SRC haskell
import Protolude
import Guess (guess)
main :: IO ()
main = do
guess
putText "Thanks for playing!"
#+END_SRC
Now we need to create the file =src/Guess.hs= which should declare the function
=guess=. Let's start with this content:
#+BEGIN_SRC haskell
module Guess
( guess
) where
import Protolude
guess :: IO ()
guess = undefined
#+END_SRC
We declare a =Guess= module which use Protolude.
We know that the type of guess must be =IO ()=.
We don't know yet what the code will be so I just used =undefined=.
This way the program will be able to typecheck.
So here is the program that will try to guess your age:
#+BEGIN_SRC haskell
guess :: IO ()
guess = guessBetween 0 120
guessBetween :: Integer -> Integer -> IO ()
guessBetween minAge maxAge = do
let age = (maxAge + minAge) `div` 2
if minAge == maxAge
then putText ("You are " <> show minAge)
else do
putText ("Are you younger than " <> show age <> "?")
answer <- getLine
case answer of
"y" -> guessBetween minAge (age - 1)
_ -> guessBetween (if age == minAge then age + 1 else age) maxAge
#+END_SRC
So going from there we declared the =guess= function to call the =guessBetween=
function with the two paramters 0 and 120 to guess an age between 0 and 120.
And the main function is a classic recursive function.
We ask for each age if the user is younger than some age.
the =let= keyword permit to introduce pure values in between =IO= ones.
so =age = (maxAge + minAge) `div` 2= is mostly straightforward.
Note that we manipulate =Integer= and so that mean =`div`= is the integer division.
so =3 `div` 2 == 1=.
We see that working in IO you can put print statements in the middle of your
code. First remark we used a recursive function. In most imperative programming
languages explicit loops are preferred to recursive functions for efficiency reasons.
That shouldn't be the case in Haskell.
In Haskell recursive functions are the natural way to program things.
Important Remarks to note:
- to test equality we use the =(==)= operator.
- Haskell is lazy, so the =age= value is only computed if needed. So if you are
in the case where =minAge == maxAge=, =age= value is not evaluated.
- In Haskell =if .. then .. else ..= form always have an else body. There is no
Implicit "no result" value in Haskell. Each expression need to return
something explicitely. Even if it is the empty tuple =()=.
So now here we go:
#+BEGIN_SRC
> stack build
> stack exec -- guess-exe
Are you younger than 60?
y
Are you younger than 29?
n
Are you younger than 44?
y
Are you younger than 36?
n
Are you younger than 39?
n
Are you younger than 41?
y
Are you younger than 39?
n
You are 40
Bye!
#+END_SRC
We see we can still make the program better.
For example, the same question is asked twice in that example.
Still, it works.
*** TO-CLEAN Use External Library -- Project 1 : use random numbers
Let's write another slightly more complex example.
Instead of guessing the age of somebody.
This will be the role of the user to guess a random number choosen by the
program.
First we'll need to generate random numbers.
To that end we'll use a the =random= package as a new dependency.
In the file =package.yml= add =random= under the dependencies.
#+BEGIN_SRC yaml
dependencies:
- base >=4.8 && <5
- protolude
- random
#+END_SRC
That will tell stack to download the =random= package.
You can get more information either on hackage or on stackage:
- https://hackage.haskell.org/package/random
- https://www.stackage.org/lts-10.2/package/random-1.1
Hackage is the official place where to put Haskell public libraries.
Stackage works in conjunction with =stack= and mainly it takes care of having a
list of packages version working together.
So that means that all packages in an LTS (Long Term Support) release can work
together withoung any conflict.
Now let's use that package.
We'll add a new function in the =Guess.hs= file now it should looks like:
#+BEGIN_SRC haskell
module Guess
( guess
, guessNumber
) where
import Protolude
import System.Random (randomRIO)
...
-- | Choose a random number and ask the user to find it.
guessNumber :: IO ()
guessNumber = do
n <- randomRIO (0::Int,100)
putText "I've choosen a number bettween 0 and 100"
putText "Can you guess which number it was?"
guessNum 0 n
-- | Given a number of try the user already made and the number to find
-- ask the user to find it.
guessNum :: Int -> Int -> IO ()
guessNum nbTry nbToFound = undefined
#+END_SRC
So for now we just focus on how to get a random number:
#+BEGIN_SRC haskell
do
n <- randomRIO (0::Int,100)
-- do stuff with n
#+END_SRC
You NEED to use the =<-= notation inside a =do= bloc.
If you try to use =let n = randomRIO (0::Int,100)= it will fail because the
types won't match.
And that's it!
Now to write the =guessNum= function, we'll write a classical recursive function:
#+BEGIN_SRC haskell
-- | Given a number of try the user already made and the number to find
-- ask the user to find it.
guessNum :: Int -> Int -> IO ()
guessNum nbTry nbToFound = do
putText "What is your guess?"
answer <- getLine
let guessedNumber = readMaybe (toS answer)
case guessedNumber of
Nothing -> putText "Please enter a number"
Just n ->
if n == nbToFound
then putText ("You found it in " <> show (nbTry + 1) <> " tries.")
else do
if n < nbToFound
then putText "Your answer is too low, try a higher number"
else putText "Your answer is too high, try a lower number"
guessNum (nbTry + 1) nbToFound
#+END_SRC
If you want to test it, change the =src-exe/Main.hs= file to use =guessNumber=
instead of =guess=.
**** TO-CLEAN What did we learn so far?
So up until now, if you followed. You should be able to "reproduce" and make
minimal changes.
But I am certain than it still be difficult to make some changes.
It is time to learn some general principles.
I know it might be a bit repetitive but its important to be certain to ingest
those informations.
A generic function of type ~IO ()~ typically =main= should look like:
#+BEGIN_SRC haskell
f :: IO a
f = do
α <- f1
β <- f2
γ <- f3
δ <- f4
f5
#+END_SRC
where each expression =fi= is of type =IO a= for some =a=.
You can use any value =α=, =β=, etc‥ as a parameter.
In order to be valid.
The last expression must have the same type as =f=.
so here =f5 :: IO a=.
Now if I give you the following functions:
- ~getLine :: IO Text~ that read a line from stdin.
- ~putText :: Text -> IO ()~ that read a line from stdin.
With that you have the ability to read stdin and print things.
- ~if τ then f1 else f2~ where =τ :: Bool= and the type of f1 and f2 must be the
same. Generally this is denoted by: =:type f1 ~ :type f2= and that type
will be the same as the entire ~if ‥ then ‥ else ‥~ expression.
- you can compare things that can be compared with ~<~, ~<=~, ~>~, ~>=~, ~==~, ~/=~ (different).
- you can concatenate things that could be concatenated (like Text) with ~<>~
- you can transform things as Text with ~show~ in particular numbers.
So that is a few number of component but they are all composable.
And so far we only needed that to write our first programs.
Haskell libs will provide you with a lot more base functions but also a lot more
composition functions.
*** TODO Command Line Application
Another thing you might want to achieve at first is to retrieve arguments for a
command line application.
**** TO-CLEAN Basic
The simplest way to retrieve parameters to a command line is to use the ~getArgs~ function.
#+BEGIN_SRC haskell
getArgs :: IO [String]
#+END_SRC
Here is a minimal example.
#+BEGIN_SRC haskell :tangle HWP/code/cmdline_1.hs
#!/usr/bin/env stack
-- stack --resolver lts-11.6 script
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}
import Protolude
import System.Environment (getArgs)
main :: IO ()
main = do
arguments <- getArgs
case head arguments of
Just filename -> die ("The first argument is: " <> toS filename)
Nothing -> die "Please enter a filename"
#+END_SRC
#+BEGIN_SRC
> ./cmdline-basic.sh foo
The first argument is: foo
> ./cmdline-basic.sh
Please enter a filename
#+END_SRC
If you have a very basic command line it could be good enough.
But if you plan to have more things to configure you can consider
to use a library to parse options.
**** TODO Option Parsing
For that we will use the =optparse-generic= package.
*** TODO File Access
*** TODO Daemons & Logging
** TODO Intermediate
*** TO-CLEAN Init the project
☞ As a first projet a lot of new concept will be introduced. Don't be
discouraged by that.
#+BEGIN_SRC
> stack new guess https://git.io/vbpej
> cd guess
#+END_SRC
Edit the file =src-exe/Main.hs=
The file contains:
#+BEGIN_SRC haskell
import Protolude
import Lib (inc)
main :: IO ()
main = print (inc 41)
#+END_SRC
To compile it do a
#+BEGIN_SRC
> stack build
> stack exec -- guess-exe
42
#+END_SRC
So that program print 42 and stop.
Let's go line by line about what is occuring here.
- =import Protolude=: Haskell is a language, but you need to start with some
useful definition for you. And Protolude is a /prelude/. That mean that it
provides a list of useful definitions for you. By default you don't need to
import manually the default prelude. But the more you work with Haskell the
more you feel the need to write your own prelude. =Protolude= is a sane
minimal and professional starting point.
- =import Lib (inc)=: That line means that we import the function =inc= from
another module named =Lib= The module correspond to the file =src/Lib.hs=. No
need to look into it now.
- =main :: IO ()=:
This is the declaration of the type of the =main= function.
The main function will be the function called when you launch your
application. Excatly like in =C=. The type is =IO ()=. It means that
=main= will make some interaction with the system and will return
nothing.
- =main = print . inc $ (41 :: Int)=
For that line there are in fact a lot of things going on.
We define the function =main=.
Lets compare this notation with other programming languages:
C:
#+BEGIN_SRC c
void main () {
...
}
#+END_SRC
javascript:
#+BEGIN_SRC javascript
function main() {
...
}
#+END_SRC
python:
#+BEGIN_SRC python
def main:
...
#+END_SRC
LISP:
#+BEGIN_SRC elisp
(define foo () ...)
#+END_SRC
Clojure:
#+BEGIN_SRC clojure
(defn main [] ...)
#+END_SRC
Now take a look at the content:
#+BEGIN_SRC haskell
main = print (inc 41)
#+END_SRC
☞ A very important syntax detail;
function application is done with a simple space.
So =foo bar= means you apply the function =foo= to the parameter =bar=.
And by default the priority is on the left.
So: =foo bar baz= is equivalent to =(foo bar) baz=.
While that notation is quite simpler it can take some time to be used to it and
to parse it naturally.
So here we first call =inc= on =41= wich is =42=.
Then we print it to the standard output.
And that's it.
*** TODO DB Access
**** NoSQL (Redis looks easy)
**** Stream DB (Kafka or NATS, etc...)
**** SQL (SQLite & Postgres)
Not sure about that part. Perhaps this should move in the Production section
*** TODO REST API
**** TODO Servant
**** TODO JSON manipulation
**** TODO Swagger-UI
** TODO Intermediate Conclusion
#+BEGIN_COMMENT
This should conclude a part where the reader should already gained a lot of
knowledge. He should now be mainly autonomous.
Still, the next section will provide many advices.
#+END_COMMENT
Congratulation for going this far. Now you should be able to work in Haskell at
least as well as in any other programming language.
Now there are different directions:
- learning more libraries
- learn to optimise code to make it as fast as C
- learn to understand details of the compilation and Haskell
- learn tips and tricks
- learn more about abstractions and type classes
- learn parallel and concurrent programming
- learn to deploy like a pro using nix
The order in which to learn all thoses things can be very different for everty need.

49
HWP/4_Production.org Normal file
View file

@ -0,0 +1,49 @@
#+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)
#+BEGIN_COMMENT
/THIS IS A WORK IN PROGRESS/
*CONTRIBUTORS*
This part is about giving the tools for an advanced programmer to work with
Haskell and to gain its autonomy.
And another part is about explaining classical things he should not necessarily
master but should know exist and what they can be used for.
Typically, it's nice to explain lenses with a few examples so he'll know this is
an efficient tool to manipulate complex data structures.
#+END_COMMENT
* TODO Make Production Quality Products
** TODO Dhall /Maybe/
** TODO Error Handling
#+BEGIN_COMMENT
This is a tricky one. My guts would suggest to dig enough with the type system
to make impossible state irrepresentable and triggering compile time errors.
But at the margin, this is not always possible. So this has to do with code organization.
#+END_COMMENT
** TODO Unit testing / doctests
** TODO Generative Testing
** TODO Enhance reproductibility with docker
** TODO Enhance reproductibility with nix
** TODO How to deploy?
There are plenty of ways de deploy
*** TODO Trashy and easy
Compile in docker and copy the binary.
*** TODO With =nix= and =nixops=

67
HWP/5_Advanced.org Normal file
View file

@ -0,0 +1,67 @@
#+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)
#+BEGIN_COMMENT
/THIS IS A WORK IN PROGRESS/
*CONTRIBUTORS*
This part is about the real Haskell dope.
The Maths and how this is really useful for coding and enjoying coding.
#+END_COMMENT
* TODO Enhance Code Quality
** TODO Type tricks
#+BEGIN_COMMENT
This part is about providing a few tricks that could make your program safer.
There should be a VERY big warning about NOT creating its own typeclasses.
During the process of Application development this is almost certainly a wrong decision.
#+END_COMMENT
*** TODO New Types
*** TODO Phantom Types
** TODO Useful Abstractions
*** TODO Monoid
*** TODO Functors
*** TODO Applicative
*** TODO Monads
*** TODO Arrows
*** TODO Foldable
*** TODO Traversable
** TODO Useful Abstractions for Applications
*** TODO No organisation, everything in IO
*** TODO Handler Pattern
See the talk from Jasper Van Der Jeught
In my opinion as efficient as MTL, Free, Effects, but with more verbosity and
repetitions.
*** TODO Custom Monad
*** TODO MTL
*** TODO Free
*** TODO Effects
** TODO Lenses
This will only be an introduction for being an user of the library.
** TODO Generics and lens-generic
** TODO Code organisation
*** TODO No organisation, everything in IO
*** TODO Custom Monad
*** TODO Handler Pattern
*** TODO MTL
*** TODO Free
*** TODO Effects
** TODO Recognize some classical abstractions
*** TODO Algebra
*** TODO Catamorphisms
*** TODO Free & Interpreters

23
HWP/6_Appendice.org Normal file
View file

@ -0,0 +1,23 @@
#+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)
#+BEGIN_COMMENT
/THIS IS A WORK IN PROGRESS/
*CONTRIBUTORS*
This part is about things that should be very useful, like beginner
informations, books/articles to read, proofs, digressions, etc...
#+END_COMMENT
* TODO Appendices
#+Include: HWP/6__1_Beginner.org
** TODO Papers You Should have read

222
HWP/6__1_Beginner.org Normal file
View file

@ -0,0 +1,222 @@
#+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.

View file

@ -1,27 +0,0 @@
#!/usr/bin/env stack
-- stack --resolver lts-10.3 --install-ghc runghc
import Text.Read (readMaybe)
displayAge :: Maybe Integer -> IO ()
displayAge maybeAge =
case maybeAge of
Nothing -> putStrLn $ "You provided an invalid year"
Just age -> putStrLn $ "You will be: " ++ show age
yearToAge :: Integer -> Integer -> Integer
yearToAge birthYear year = year - birthYear
askBirth :: IO (Maybe Integer)
askBirth = do
putStrLn "Please enter your birth year"
readMaybe <$> getLine
askYear :: IO (Maybe Integer)
askYear = do
putStrLn "Please enter a year in the future"
readMaybe <$> getLine
main = do
mage <- (\ x y -> yearToAge <$> x <*> y) <$> askBirth <*> askYear
displayAge mage

View file

@ -1,19 +0,0 @@
#!/usr/bin/env stack
-- stack --resolver lts-10.3 --install-ghc runghc
import Text.Read (readMaybe)
displayAge maybeAge =
case maybeAge of
Nothing -> putStrLn $ "You provided an invalid year"
Just age -> putStrLn $ "In 2020, you will be: " ++ show age
yearToAge year = 2020 - year
main = do
putStrLn "Please enter your birth year"
yearString <- getLine
let maybeAge = case readMaybe yearString of
Nothing -> Nothing
Just year -> Just (yearToAge year)
displayAge maybeAge

View file

@ -1,11 +0,0 @@
#!/usr/bin/env stack
-- stack --resolver lts-10.3 --install-ghc runghc
import Text.Read (readMaybe)
main = do
putStrLn "Please enter your birth year"
yearString <- getLine
case readMaybe yearString of
Nothing -> putStrLn $ "You provided an invalid year"
Just year -> putStrLn $ "In 2020, you will be: " ++ show (2020 - year)

View file

@ -1,6 +0,0 @@
#!/usr/bin/env stack
-- stack --resolver lts-10.3 --install-ghc runghc
main = do
putStrLn "Please enter your birth year"
year <- getLine
putStrLn $ "In 2020, you will be: " ++ show (2020 - read year)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB