initial commit
This commit is contained in:
commit
44e1b7805c
12 changed files with 1914 additions and 0 deletions
44
0_HWP.org
Normal file
44
0_HWP.org
Normal file
|
@ -0,0 +1,44 @@
|
|||
#+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*
|
||||
|
||||
- To extract all source code files do ~org-babel-tangle~ (, b t) in spacemacs
|
||||
- To go to an imported file use (, ')
|
||||
|
||||
Audience of this book:
|
||||
- people that are asked to work in Haskell but don't know much about it
|
||||
- people that already tried a bit of Haskell and want to go further
|
||||
- people that need to make a professional level product with Haskell
|
||||
- people that are ok not understanding every detail yet but need the job to be done
|
||||
|
||||
Book is not for:
|
||||
- Total beginner that just want to know what Haskell is about
|
||||
- Haskell contributors that already know a lot of things about Haskell
|
||||
|
||||
|
||||
Goals:
|
||||
|
||||
After finishing this book an reader should be able to:
|
||||
|
||||
- script in Haskell instead of bash
|
||||
- write a REST API with a swagger documentation and deploy it
|
||||
- perhaps write a frontend app (I don't know looks like a LOT of work)
|
||||
|
||||
#+END_COMMENT
|
||||
|
||||
|
||||
#+Include: 1_Introduction.org
|
||||
#+Include: 2_Install.org
|
||||
#+Include: 3_Intermediate.org
|
||||
#+Include: 4_Production.org
|
||||
#+Include: 5_Advanced.org
|
||||
#+Include: 6_Appendices.org
|
132
1_Introduction.org
Normal file
132
1_Introduction.org
Normal 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
2_Install.org
Normal file
192
2_Install.org
Normal 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
3_Intermediate.org
Normal file
770
3_Intermediate.org
Normal 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 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 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 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
4_Production.org
Normal file
49
4_Production.org
Normal 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
5_Advanced.org
Normal file
67
5_Advanced.org
Normal 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
6_Appendice.org
Normal file
23
6_Appendice.org
Normal 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
6__1_Beginner.org
Normal file
222
6__1_Beginner.org
Normal 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.
|
13
code/cmdline_1.hs
Executable file
13
code/cmdline_1.hs
Executable file
|
@ -0,0 +1,13 @@
|
|||
#!/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"
|
14
code/hello_name.hs
Executable file
14
code/hello_name.hs
Executable file
|
@ -0,0 +1,14 @@
|
|||
#!/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 <> "!")
|
11
code/hello_world.hs
Executable file
11
code/hello_world.hs
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env stack
|
||||
{- stack script
|
||||
--resolver lts-11.6
|
||||
--install-ghc
|
||||
--package protolude
|
||||
-}
|
||||
{-# LANGUAGE NoImplicitPrelude #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
import Protolude
|
||||
|
||||
main = putText "Hello, world!"
|
377
tools/hwp.hsfiles
Normal file
377
tools/hwp.hsfiles
Normal file
|
@ -0,0 +1,377 @@
|
|||
{-# START_FILE package.yaml #-}
|
||||
name: {{ name }}
|
||||
version: '0.1.0.0'
|
||||
category: Test
|
||||
author: {{author-name}}{{^author-name}}TODO:<Author name here>{{/author-name}}
|
||||
maintainer: {{author-email}}{{^author-email}}TODO:<example@example.com>{{/author-email}}
|
||||
copyright: © {{year}}{{^year}}2017{{/year}} {{author-name}}{{^author-name}}TODO:<Author name here>{{/author-name}}
|
||||
github: {{github-username}}{{^github-username}}TODO:<githubuser>{{/github-username}}/{{name}}
|
||||
license: ISC
|
||||
extra-source-files:
|
||||
- README.md
|
||||
- stack.yaml
|
||||
default-extensions:
|
||||
- OverloadedStrings
|
||||
- NoImplicitPrelude
|
||||
- ScopedTypeVariables
|
||||
ghc-options:
|
||||
- -Wall
|
||||
- -Wcompat
|
||||
- -Wincomplete-uni-patterns
|
||||
- -Wredundant-constraints
|
||||
- -Wnoncanonical-monad-instances
|
||||
- -Werror
|
||||
- -O2
|
||||
dependencies:
|
||||
- base >=4.8 && <5
|
||||
- protolude
|
||||
library:
|
||||
source-dirs: src
|
||||
executables:
|
||||
{{ name }}-exe:
|
||||
main: Main.hs
|
||||
source-dirs: src-exe
|
||||
ghc-options:
|
||||
- -threaded
|
||||
- -rtsopts
|
||||
- -with-rtsopts=-N
|
||||
dependencies:
|
||||
- {{ name }}
|
||||
tests:
|
||||
{{ name }}-doctest:
|
||||
main: Main.hs
|
||||
source-dirs: src-doctest
|
||||
ghc-options:
|
||||
- -threaded
|
||||
- -rtsopts
|
||||
- -with-rtsopts=-N
|
||||
dependencies:
|
||||
- doctest >=0.10
|
||||
- Glob >=0.7
|
||||
- QuickCheck >=2.5
|
||||
- {{ name }}
|
||||
{{ name }}-test:
|
||||
main: Main.hs
|
||||
source-dirs: src-test
|
||||
ghc-options:
|
||||
- -threaded
|
||||
- -rtsopts
|
||||
- -with-rtsopts=-N
|
||||
dependencies:
|
||||
- tasty >=0.11
|
||||
- tasty-hunit >=0.9
|
||||
- tasty-smallcheck >=0.8
|
||||
- {{ name }}
|
||||
benchmarks:
|
||||
{{ name }}-benchmark:
|
||||
main: Main.hs
|
||||
source-dirs: src-benchmark
|
||||
ghc-options:
|
||||
- -threaded
|
||||
- -rtsopts
|
||||
- -with-rtsopts=-N
|
||||
dependencies:
|
||||
- criterion >=1.1
|
||||
- {{ name }}
|
||||
stability: alpha (experimental)
|
||||
{-# START_FILE src/Lib.hs #-}
|
||||
{- |
|
||||
Module : Lib
|
||||
Description : Example of a library file.
|
||||
Copyright : (c) {{year}}{{^year}}2017{{/year}}, {{author-name}}{{^author-name}}TODO:<Author name here>{{/author-name}}
|
||||
License : ISC
|
||||
Maintainer : {{author-email}}{{^author-email}}TODO:<example@example.com>{{/author-email}}
|
||||
Stability : experimental
|
||||
Portability : POSIX
|
||||
|
||||
Example of library file which is also used for testing the test suites.
|
||||
You can write a longer description of this module here and add @some markup@.
|
||||
|
||||
-}
|
||||
module Lib
|
||||
(
|
||||
-- * Exported functions
|
||||
inc
|
||||
) where
|
||||
|
||||
import Protolude
|
||||
|
||||
-- | Increment one 'Num' value.
|
||||
--
|
||||
-- >>> let answer = 42 :: Int
|
||||
-- >>> let prev = answer - 1
|
||||
-- >>> inc prev
|
||||
-- 42
|
||||
-- >>> succ . Prelude.last . Prelude.take prev . iterate inc $ 1
|
||||
-- 42
|
||||
--
|
||||
-- Properties:
|
||||
--
|
||||
-- prop> succ x == inc x
|
||||
-- prop> inc (negate x) == negate (pred x)
|
||||
--
|
||||
inc :: Int -- ^ value to increment
|
||||
-> Int -- ^ result
|
||||
inc x = x + 1
|
||||
|
||||
{-# START_FILE src-exe/Main.hs #-}
|
||||
import Protolude
|
||||
|
||||
import Lib (inc)
|
||||
|
||||
main :: IO ()
|
||||
main = print (inc 41)
|
||||
|
||||
{-# START_FILE Setup.hs #-}
|
||||
import Distribution.Simple
|
||||
main = defaultMain
|
||||
|
||||
{-# START_FILE src-test/Main.hs #-}
|
||||
import Protolude
|
||||
|
||||
import Test.Tasty
|
||||
import Test.Tasty.HUnit
|
||||
import Test.Tasty.SmallCheck
|
||||
|
||||
import Lib (inc)
|
||||
|
||||
main :: IO ()
|
||||
main = defaultMain $ testGroup "all-tests" tests
|
||||
|
||||
tests :: [TestTree]
|
||||
tests =
|
||||
[ testGroup "SmallCheck" scTests
|
||||
, testGroup "Unit tests" huTests
|
||||
]
|
||||
|
||||
scTests :: [TestTree]
|
||||
scTests =
|
||||
[ testProperty "inc == succ" prop_succ
|
||||
, testProperty "inc . negate == negate . pred" prop_pred
|
||||
]
|
||||
|
||||
huTests :: [TestTree]
|
||||
huTests =
|
||||
[ testCase "Increment below TheAnswer" case_inc_below
|
||||
, testCase "Decrement above TheAnswer" case_dec_above
|
||||
]
|
||||
|
||||
prop_succ :: Int -> Bool
|
||||
prop_succ n = inc n == succ n
|
||||
|
||||
prop_pred :: Int -> Bool
|
||||
prop_pred n = inc (negate n) == negate (pred n)
|
||||
|
||||
case_inc_below :: Assertion
|
||||
case_inc_below = inc 41 @?= (42 :: Int)
|
||||
|
||||
case_dec_above :: Assertion
|
||||
case_dec_above = negate (inc (negate 43)) @?= (42 :: Int)
|
||||
|
||||
{-# START_FILE src-doctest/Main.hs #-}
|
||||
import Protolude
|
||||
|
||||
import System.FilePath.Glob
|
||||
import Test.DocTest
|
||||
|
||||
main :: IO ()
|
||||
main = glob "src/**/*.hs" >>= doctest
|
||||
|
||||
{-# START_FILE src-benchmark/Main.hs #-}
|
||||
import Protolude
|
||||
|
||||
import Criterion
|
||||
import Criterion.Main
|
||||
|
||||
import Lib (inc)
|
||||
|
||||
main :: IO ()
|
||||
main = defaultMain [bench "inc 41" (whnf inc (41 :: Int))]
|
||||
|
||||
{-# START_FILE LICENSE #-}
|
||||
Copyright (c) {{year}}{{^year}}2017{{/year}}, {{author-name}}{{^author-name}}TODO:<Author name here>{{/author-name}}
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
{-# START_FILE .hlint.yaml #-}
|
||||
- ignore: {name: Use String}
|
||||
- error: {lhs: foldl x, rhs: foldl' x}
|
||||
- error: {lhs: modifyTVar x, rhs: modifyTVar' x}
|
||||
- error: {lhs: atomicModifyIORef x, rhs: atomicModifyIORef' x}
|
||||
- group: {name: generalise, enabled: true}
|
||||
|
||||
{-# START_FILE README.md #-}
|
||||
{{ name }}
|
||||
==========
|
||||
|
||||
New Haskell project using stack template `hwp`.
|
||||
|
||||
Please read file `tutorial.md` for first steps in using the template.
|
||||
|
||||
{-# START_FILE .gitignore #-}
|
||||
/tutorial.md
|
||||
/.stack-work/
|
||||
|
||||
{-# START_FILE .travis.yml #-}
|
||||
# Use new container infrastructure to enable caching
|
||||
sudo: false
|
||||
|
||||
# Choose a lightweight base image; we provide our own build tools.
|
||||
language: c
|
||||
|
||||
# GHC depends on GMP. You can add other dependencies here as well.
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libgmp-dev
|
||||
|
||||
# The different configurations we want to test. You could also do things like
|
||||
# change flags or use --stack-yaml to point to a different file.
|
||||
env:
|
||||
- ARGS=""
|
||||
#- ARGS="--resolver lts-10"
|
||||
- ARGS="--resolver lts"
|
||||
- ARGS="--resolver nightly"
|
||||
|
||||
before_install:
|
||||
# Download and unpack the stack executable
|
||||
- mkdir -p ~/.local/bin
|
||||
- export PATH=$HOME/.local/bin:$PATH
|
||||
- travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
|
||||
|
||||
# This line does all of the work: installs GHC if necessary, builds the
|
||||
# library, executables, and test suites, and runs the test suites.
|
||||
# `--no-terminal works` around some quirks in Travis's terminal implementation.
|
||||
script: stack $ARGS --no-terminal --install-ghc test
|
||||
|
||||
# Caching so the next build will be fast too.
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.stack
|
||||
|
||||
{-# START_FILE CHANGELOG.md #-}
|
||||
Change log
|
||||
==========
|
||||
|
||||
{{ name }} uses [Semantic Versioning][1].
|
||||
The change log is available [on GitHub][2].
|
||||
|
||||
[1]: http://semver.org/spec/v2.0.0.html
|
||||
[2]: https://github.com/{{github-username}}{{^github-username}}githubuser{{/github-username}}/{{ name }}/releases
|
||||
|
||||
## v0.1.0.0
|
||||
|
||||
* Initially created.
|
||||
|
||||
{-# START_FILE tutorial.md #-}
|
||||
|
||||
Thanks for using the stack template `hwp`!
|
||||
It is a fork from the `tasty-travis` template but use
|
||||
[`Protolude`](https://github.com/sdiehl/protolude#readme)
|
||||
and [`hpack`](https://github.com/sol/hpack#readme).
|
||||
This file is here to guide you through customizing the template files.
|
||||
|
||||
This template allows you to start a simple Haskell project, either to create a
|
||||
library or an application. It offers you the choice to customize the source
|
||||
directory while providing hints on the proposed hierarchy that the author uses
|
||||
(inspired by other Haskell projects).
|
||||
|
||||
In the following sections, I will explain how to use the template.
|
||||
|
||||
1. Initial configurations
|
||||
=========================
|
||||
|
||||
Before you get started, there are a few things that this template couldn't
|
||||
provide for you. You should:
|
||||
|
||||
* Add a synopsis to `package.yaml`. It should be a short, one sentence
|
||||
explanation of your project.
|
||||
|
||||
* Edit the description field in `{{ name }}.cabal` if you don't like having
|
||||
the description in the `README.md` file.
|
||||
|
||||
* In `{{ name }}.cabal`, the category of the project has been set as 'Test'.
|
||||
You might wish to change it to a more descriptive value. A list of
|
||||
categories that you can use for the project is available on Hackage at
|
||||
<http://hackage.haskell.org/packages>. Alternatively, you might prefer using
|
||||
a name from the shorter list at
|
||||
<https://byorgey.wordpress.com/2010/04/15/cabal-init/>.
|
||||
|
||||
* If you haven't provided the `author-email`, `author-name`, and
|
||||
`github-username` to the `config.yaml` global file, you will have to search
|
||||
for "TODO" markup and complete this information in `{{ name }}.cabal` and/or
|
||||
in `LICENSE`.
|
||||
|
||||
2. Creating the git repository
|
||||
==============================
|
||||
|
||||
If this project is a subdirectory of a larger project with an existing version
|
||||
control or you want to use another version control system or another setup,
|
||||
then you can ignore this section.
|
||||
|
||||
From the root directory of the project (the directory of this file) you will
|
||||
need to run the following three commands:
|
||||
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Initial commit"
|
||||
|
||||
Now you can create a repository on GitHub to publish the code.
|
||||
|
||||
Note that this file is excluded from the repository by being included in the
|
||||
`.gitignore` file. If you want this file to be tracked, you can remove the
|
||||
line `/tutorial.md` from that file.
|
||||
|
||||
3. Testing the initial code
|
||||
===========================
|
||||
|
||||
These are the stack commands you will likely use the most:
|
||||
|
||||
``` sh
|
||||
# Build the project.
|
||||
stack build
|
||||
|
||||
# Run the binary
|
||||
stack exec {{ name }}-exe
|
||||
|
||||
# Run the test suite.
|
||||
stack test
|
||||
|
||||
# Run the benchmarks.
|
||||
stack bench
|
||||
|
||||
# Generate documentation.
|
||||
stack haddock
|
||||
```
|
||||
|
||||
4. Customizing
|
||||
==============
|
||||
|
||||
As you see, the template creates both a library and a binary and tests the
|
||||
library using two test suites (doctests from comments and tests with Tasty).
|
||||
Both test suites can test both properties and expected testcases. Finally,
|
||||
the template also offers a way to benchmark the code.
|
||||
|
||||
Your project might differ significantly from this template. For example, you
|
||||
might want to have a different number of executables. In that case, you should
|
||||
remove/add more executable stanzas in `{{ name }}.cabal`.
|
||||
|
||||
Similarly, if you don't want both test suites, you can remove one of the
|
||||
stanzas. You could do the same for the benchmarks.
|
||||
|
||||
*More importantly* you might want to change the contents of the library.
|
||||
Rename `src/Lib` to whatever you want your top-module to be, usually the name
|
||||
of your project but using `CamelCase`. Don't forget to change this name in all
|
||||
places where it is referenced (executable(s), test(s) and benchmark(s)).
|
||||
|
||||
Thanks again, and happy hacking!
|
Loading…
Reference in a new issue