commit 44e1b7805c24571e6e325a5604efc97bb722fee5 Author: Yann Esposito (Yogsototh) Date: Wed Jun 20 22:11:04 2018 +0200 initial commit diff --git a/0_HWP.org b/0_HWP.org new file mode 100644 index 0000000..010344b --- /dev/null +++ b/0_HWP.org @@ -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 diff --git a/1_Introduction.org b/1_Introduction.org new file mode 100644 index 0000000..2d23b6b --- /dev/null +++ b/1_Introduction.org @@ -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. diff --git a/2_Install.org b/2_Install.org new file mode 100644 index 0000000..f3612e3 --- /dev/null +++ b/2_Install.org @@ -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. + diff --git a/3_Intermediate.org b/3_Intermediate.org new file mode 100644 index 0000000..aafe9a1 --- /dev/null +++ b/3_Intermediate.org @@ -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. + diff --git a/4_Production.org b/4_Production.org new file mode 100644 index 0000000..08a1b9d --- /dev/null +++ b/4_Production.org @@ -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= + diff --git a/5_Advanced.org b/5_Advanced.org new file mode 100644 index 0000000..cb050ea --- /dev/null +++ b/5_Advanced.org @@ -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 diff --git a/6_Appendice.org b/6_Appendice.org new file mode 100644 index 0000000..3c3c24f --- /dev/null +++ b/6_Appendice.org @@ -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 diff --git a/6__1_Beginner.org b/6__1_Beginner.org new file mode 100644 index 0000000..0c11b23 --- /dev/null +++ b/6__1_Beginner.org @@ -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. diff --git a/code/cmdline_1.hs b/code/cmdline_1.hs new file mode 100755 index 0000000..9203027 --- /dev/null +++ b/code/cmdline_1.hs @@ -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" diff --git a/code/hello_name.hs b/code/hello_name.hs new file mode 100755 index 0000000..d137ce6 --- /dev/null +++ b/code/hello_name.hs @@ -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 <> "!") diff --git a/code/hello_world.hs b/code/hello_world.hs new file mode 100755 index 0000000..04abb87 --- /dev/null +++ b/code/hello_world.hs @@ -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!" diff --git a/tools/hwp.hsfiles b/tools/hwp.hsfiles new file mode 100644 index 0000000..e20b36e --- /dev/null +++ b/tools/hwp.hsfiles @@ -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}} +maintainer: {{author-email}}{{^author-email}}TODO:{{/author-email}} +copyright: © {{year}}{{^year}}2017{{/year}} {{author-name}}{{^author-name}}TODO:{{/author-name}} +github: {{github-username}}{{^github-username}}TODO:{{/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}} +License : ISC +Maintainer : {{author-email}}{{^author-email}}TODO:{{/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}} + +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 + . Alternatively, you might prefer using + a name from the shorter list at + . + +* 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!