haskell-for-newbies



haskell-for-newbies

0 1


haskell-for-newbies

A not-so-gentle intro to Haskell

On Github mkawalec / haskell-for-newbies

Why Haskell can make most of your problems disappear

Michal Kawalec @monad_cat

whoami

  • I work at X-Team, doing contracting at LCU
  • I run a biggest Haskell meetup in Poland
  • Cocreated a pretty neat flux variant, fluxApp
  • Did some physics at Archer and Hector
  • Wrote a raw events collating library at CERN
  • Follow me on Twitter :D @monad_cat

Overview

  • we're going to explore Haskell a little bit
  • then I'll show you some great learning resources
  • and show some cool libraries

Geostationary orbit overview

  • Strong static typing
  • Most of the cool stuff introduced through typeclasses (think function overloading)
  • Composition over inheritance
  • Has mutable values
  • Haskell has a first-class interpreter, ghci
  • If you see a command line prompt in the following, it will most likely come from ghci

Strong static typing

  • removes failures occuring from wrong data flow
  • a type checker that works for you, not against you
data AContainer = AContainer Integer
data BiggerContainer = BiggerContainer Text Bool
data Maybe a = Nothing | Just a

data RecordContainer = RecordContainer {
    name :: Text,
    rank :: Int,
    isActive :: Bool
}

let's create some types

data Maybe a = Nothing | Just a

data RecordContainer = RecordContainer {
    name :: Text,
    rank :: Int,
    isActive :: Bool
}

> let wrappedVal = Just "hello"
> :t wrappedVal
wrappedVal :: Maybe Text

> let container = RecordContainer "Michał" 0 True
> let modifiedContainer = container { rank = 5 }

Easy functions

addOne x = x + 1
> :t addOne
Num a => a -> a
  • in most cases we don't have to specify the types ourselves, they will be inferred
  • though it's a good practice, as it helps in understanding what the code does

typeclasses

addOne :: Num a => a -> a
addOne x = x + 1

class  Num a  where
    (+), (-), (*)       :: a -> a -> a
    negate              :: a -> a
    abs                 :: a -> a
    signum              :: a -> a
    fromInteger         :: Integer -> a

    x - y               = x + negate y
    negate x            = 0 - x

Signalling adversity

How do we signal that a value may be missing? Use Maybe

data Maybe a = Nothing | Just a

or better Either for an error message

data Either a b = Left a | Right b

multIfLucky :: Int -> Either Text Int
multIfLucky 42 = Right (42 * 2)
multIfLucky _  = Left "you're unlucky"

lists

Lists are not any special data type, we can define our own lists like:

data List a = Nil | Cons a (List a)
> :t Cons 1 (Cons 4 (Cons 20 Nil))
Cons 1 (Cons 4 (Cons 20 Nil)) :: Num a => List a

In real programs Nil is defined with a [] operator and Cons is :

> :t 1 : 2 : 3 : []
1 : 2 : 3 : [] :: Num a => [a]

defining lists

Let's define a list

let aList = [4,2,5,1]

> aList !! 2
5

> sort aList
[1,2,4,5]

> [1, "foobar"]
No instance for (Num [Char]) arising from the literal ‘1’
In the expression: 1
In the expression: [1, "foobar"]
In an equation for ‘it’: it = [1, "foobar"]

higher order functions

We can have functions accept other functions

> :t map
map :: (a -> b) -> [a] -> [b]

Multiple ways of using this pattern:

> let awesomeList = [1,2,3,0,5]

> let mult x = x * 2
> map mult awesomeList
[2, 4, 6, 0, 10]

> map (\x -> x * 2) awesomeList

> map (\_ -> "foo") awesomeList
["foo","foo","foo","foo","foo"]

currying

Let's take a look on the type of map

> :t map
map :: (a -> b) -> [a] -> [b]

We can partially apply it and get a function that accepts a list

> let parap = map (\x -> x * 2)
> :t parap
parap :: Num b => [b] -> [b]

And apply it completely to get a result

> parap [1,2]
[2,4]

recursion

Factorial is a nice example

factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)

> factorial 2
2
> factorial 4
24

> map factorial [1..10]
[1,2,6,24,120,720,5040,40320,362880,3628800]

based on category theory

  • concepts are not just glued on as needed
  • there is a lot of helpful material from category theory
  • cross-pollination of ideas from the academia
  • hard names come for free!

laziness

  • harder to digest at first, but very useful in real-life programs
  • [1..] is actually an infinite sequence
  • but we have finite time and memory. So we can work with infinite data as long as we don't evaluate it

let's write a fibbonacci

> let fib = 1 : (scanl (+) 1 fib)
> take 10 fib
[1,1,2,3,5,8,13,21,34,55]

Fibonacci

> :t scanl
scanl :: (b -> a -> b) -> b -> [a] -> [b]

> let fib = 1 : (scanl (+) 1 fib)
> take 10 fib
[1,1,2,3,5,8,13,21,34,55]

Let's disect that per iteration:

fib = 1 : 1
fib = 1 : 1 : (1 + fib[0]) = 1 : 1 : 2
fib = 1 : 1 : 2 : (2 + fib[1]) = 1 : 1 : 2 : 3
fib = 1 : 1 : 2 : 3 : (3 + fib[2]) = 1 : 1 : 2 : 3 : 5

So we can work with bottoms as long as we only see their finite parts!

purity

  • all of what we seen adds up to make most of our programs pure
  • that means side effects can only exist in functions marked as impure
impure :: IO ()
impure = print "hey user"

what if we tried to make a pure version?

<interactive>:25:12:
    Couldn't match expected type ‘()’ with actual type ‘IO ()’
    In the expression: print "a" :: ()
    In an equation for ‘pure’: pure = print "a" :: ()

STM

We can implement way more complex effects simply with the type system. Consider that:

data Env = Env {
    counter :: TVar Int,
    chan :: TChan Text
}

modify :: Env -> STM ()
modify env = do
    modifyTVar (counter env) (\x -> x + 1)
    writeTChan (chan env) "I've added one"

> ourChan <- newTChanIO
> let ourEnv = Env 0 ourChan
> atomically (modify ourEnv)
> readTVar (counter ourEnv)
1

threading model

  • very lightweight threads using only the CPUNUM system threads
  • it's actually cheaper to spawn a thread than play with an event loop (and threads are isolated!)

Hoogle

haskellbook

other learning resources

There's a plenthora of other learning resources:

  • Ollie Charles' papers collection
  • 'What I wish I knew when I was learning Haskell' by Stephen Diehl
  • 'Haskell for all' by Gabriel Gonzalez
  • 'Parallel and Concurrent Programming in Haskell' by Simon Marlow
  • Will Sewell's blog
  • Learn you a Haskell
  • ...

Useful libraries

  • This is an opinionated selection
  • We'll start with DB access libs, go to web frameworks then to parser combinators and to concurrency management

Database access

  • opaleye gives automatic query generation and a typesafe knex
  • *-simple (postgres-simple is most popular) gives raw SQL DB access
  • hasql is a typesafe sql generator

web frameworks

  • yesod is a 'complete', big, RoR-like web framework for people who need their batteries
  • scotty is a simple, Flask-like library that effectively binds functions to endpoints
main = scotty 3000 $ do
    get "/:word" $ do
        beam <- param "word"
        html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]

servant

  • APIs are types and are validated at compile time
  • if it compiles, it works (to an extent)
  • automatic swagger description generation
  • at the same time the framework is extensible
  • it will blow your mind

speed

Parser combinators

  • Build parsers from small parts. attoparsec & parsec
emailAddressParser :: Parser EmailAddress
emailAddressParser = nameAddrParser <|> addrSpecParser

nameAddrParser :: Parser EmailAddress
nameAddrParser = do
  label <- takeWhile (/= '<')
  _ <- char '<'
  address <- takeWhile (/= '>')
  _ <- char '>'
  return $ EmailAddress address (Just $ strip label)

addrSpecParser :: Parser EmailAddress
addrSpecParser = do
  address <- takeWhile (\c -> c /= '\r' && c /= ',')
  return $ EmailAddress address Nothing

property based testing

  • the boring tests are written for us, so what do we test?
  • we can test logic
  • or we can have the computer find a minimal failing case for us!
propIdempotent xs = sort (sort xs) == sort xs
> quickCheck propIdempotent
+++ OK, passed 100 tests.

[1,5,-1,3,6]

summary

  • Haskell makes encapsulating complexity straightforward
  • Data operations are bound to data types
  • Side effects are explicit, so we can see which part of our program sends the missiles
  • Refactoring is (almost) a joy
  • No need to write boring tests
  • It's fast if you want it to be

Any questions?

@monad_cat

Why Haskell can make most of your problems disappear Michal Kawalec @monad_cat