#+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.