Intro to Haskell
for Erlangers

Bob Ippolito (@etrepum)
Erlang Factory SF
March 7, 2014

bob.ippoli.to/haskell-for-erlangers-2014

Who am I?

  • Not classically trained in CS
  • Erlang user since 2006 (Mochi Media, mochiweb, etc.)
  • Haskell user since 2012 (ported exercism.io curriculum)
  • Currently teaching web technologies to teenagers with Mission Bit
  • Doing a bit of advising/investing in startups

Why learn Haskell?

  • I learn a lot of from studying new languages
  • Types are supposed to help you write better software
  • I like QuickCheck and Dialyzer
  • Good support for parallelism and concurrency
  • Will help me understand more CS papers

RAM footprint per unit of concurrency (approx)

1.3KB
Haskell ThreadId + MVar (GHC 7.6.3, 64-bit)
2.6 KB
Erlang process (64-bit)
4.0 KB
Go goroutine
9.0 KB
C pthread (minimum, 64-bit Mac OS X)
64.0 KB
Java thread stack (minimum)

513 KB
C pthread (default, 64-bit Mac OS X)
1024 KB
Java thread stack (default)

Starting out

  • Intimidated by Haskell for years
  • Took a class while at Facebook
  • Read several books
  • Deliberate practice

Haskell's Appeal

  • Abstractions can often be used without penalty
  • Efficient parallel and concurrent programming
  • Type system makes maintenance easier
  • Nice syntax (not too heavy or lightweight)
  • Fantastic community & ecosystem

Haskell

Haskell B. Curry

Early History

1987
More than a dozen non-strict FP languages in use
FCPA '87 meeting (Peyton Jones, Hudak, et. al.)
Formed FPLang committee
Wanted to base language on Miranda, but Turner declined
1988
Chose the name Haskell
Hudak and Wadler chosen to be editors of the report
1990 (April 1st!)
Haskell 1.0 report published (125 pages)

IFIP 1992 Working Group

John Hughes (QuickCheck), Philip Wadler (Subtyping for Erlang)

Evolution

1992
Glasgow Haskell Compiler (GHC)
1996
Haskell 1.3 - Monadic I/O, seq, strictness annotations
1999
Haskell 98 - Commitment to stability
2002
Revised Haskell 98 (260 pages)
2010
Haskell 2010 (300 pages)

Domain

General Purpose

  • Very effective for parsing and compilation
  • Great for DSEL (Domain Specific Embedded Languages)
  • Has been popular in academia for some time
  • Becoming more popular in industry

Commercial Use

Internet
Facebook - Haxl rule engine "fighting spam with pure functions"
Biotech
Amgen - informatics, simulation
Finance
Credit Suisse - quantitative modeling
Barclays - DSEL for exotic equity derivatives
Deutsche Bank - trading group infrastructure
Tsuru Capital - trading platform
McGraw-Hill Financial - report generation (with ermine)
Semiconductor Design
Bluespec - high-level language for chip design

Consumer Apps

Silk
"A platform for sharing collections about anything"
Chordify
"Chord transcription for the masses"
Bump (Google, Sep 2013)
"Send files, videos, everything!" Mobile + web.
MailRank (Facebook, Nov 2011)
Email inbox prioritization. Shuttered post-acquisition.
Bazqux
"RSS reader that shows comments to posts"

Commercial Services

janrain
User management platform.
Spaceport (Facebook, Aug 2013)
Mobile game framework using ActionScript 3
scrive
E-signing service (nordic market)
OpenBrain
Computing platform for scientific and business analytics
skedge.me
Enterprise appointment scheduling

Compilers

Haskell
GHC, Ajhc, Haste, GHCJS
Dependently typed
Agda - also an interactive proof assistant!
Idris - general purpose
Compile to JavaScript
Elm - functional reactive in the browser
Fay - Haskell subset
Imperative
Pugs - first Perl 6 implementation

Standalone Apps

Pandoc
Markup swiss-army knife (used to make these slides!)
Darcs
Distributed revision control system (like Git or Mercurial)
xmonad
"the tiling window manager that rocks"
Gitit
Wiki backed by Git, Darcs, or Mercurial
git-annex
Manage large files with git (similar to Dropbox)
ImplicitCAD
Programmatic Solid 3D CAD modeler

Haskell Platform

Haskell: Batteries Included

Editor Support

Emacs
ghc-mod + HLint
Vim
hdevtools + Syntastic + vim-hdevtools
Sublime Text
hdevtools + SublimeHaskell
Eclipse
EclipseFP + HLint
Web
FP Haskell Center

Haskell Syntax

Types
Defines types and typeclasses

Constructors and record accessors become values

Values
Named bindings
Instances of constructors
Functions

Control flow

Relative to Erlang

  • Syntax is minimal & familiar
  • Haskell's pattern matching is not as clever as Erlang's
  • Types are kinda like having Dialyzer for every compile
    (although Dialyzer is really quite different!)
  • Typeclasses are nice, Erlang doesn't have them
  • Erlang is probably (much) better for long-running systems

lists:map/2

map(F, [H|T]) ->
    [F(H)|map(F, T)];
map(F, []) when is_function(F, 1) -> [].

map

map _ []     = []
map f (x:xs) = f x : map f xs

lists:map/2 (typed)

-spec map(Fun, List1) -> List2 when
      Fun :: fun((A) -> B),
      List1 :: [A],
      List2 :: [B],
      A :: term(),
      B :: term().

map(F, [H|T]) ->
    [F(H)|map(F, T)];
map(F, []) when is_function(F, 1) -> [].

map (typed)

map :: (a -> b) -> [a] -> [b]
map _ []     = []
map f (x:xs) = f x : map f xs

lists:foldr/3

-spec foldr(Fun, Acc0, List) -> Acc1 when
      Fun :: fun((Elem :: T, AccIn) -> AccOut),
      Acc0 :: term(),
      Acc1 :: term(),
      AccIn :: term(),
      AccOut :: term(),
      List :: [T],
      T :: term().

foldr(F, Accu, [Hd|Tail]) ->
    F(Hd, foldr(F, Accu, Tail));
foldr(F, Accu, []) when is_function(F, 2) -> Accu.

foldr

foldr :: (a -> b -> b) -> b -> [a] -> b
foldr k z = go
   where
     go []     = z
     go (y:ys) = y `k` go ys

Sum Type

%% sum type, 3 possible values
-type choice() :: definitely
                | possibly
                | no_way.

Sum Type

-- sum type, 3 possible values
data Choice = Definitely
            | Possibly
            | NoWay

Product Type

%% product type, 9 possible values (3 * 3)
-type choices() :: {choice(), choice()}.

Product Type

-- product type, 9 possible values (3 * 3)
data Choices = Choices Choice Choice

-- as a tuple with a type alias
-- NOT THE SAME AS ABOVE! :)
type Choices = (Choice, Choice)

Product Type (Record)

%% record syntax
-record(choices,
        fst_choice :: choice(),
        snd_choice :: choice()).

%% getters need to be implemented manually
-spec fst_choice(#choices{}) -> choice().
fst_choice(#choices{fst_choices=X}) -> X.

-spec snd_choice(#choices{}) -> choice().
snd_choice(#choices{snd_choices=X}) -> X.

Product Type (Record)

-- record syntax defines accessors automatically
data Choices =
  Choices { fstChoice :: Choice
          , sndChoice :: Choice
          }

-- these getters are automatically defined
fstChoice :: Choices -> Choice
fstChoice (Choices { fstChoice = x }) = x

sndChoice :: Choices -> Choice
sndChoice (Choices { sndChoice = x }) = x

Abstract Data Type

%% abstract data type for a list
-type cons(A) :: nil
               | {cons, A, cons(A)}.

Abstract Data Type

-- abstract data type for a list
data List a = Nil
            | Cons a (List a)

Types and Constructors

data Choice = Definitely
            | Possibly
            | NoWay

data Choices = Choices Choice Choice

mkChoices :: Choice -> Choice -> Choices
mkChoices a b = Choices a b

fstChoice :: Choices -> Choice
fstChoice (Choices a _) = a

Types and Constructors

data Choice = Definitely
            | Possibly
            | NoWay

data Choices = Choices Choice Choice

mkChoices :: Choice -> Choice -> Choices
mkChoices a b = Choices a b

fstChoice :: Choices -> Choice
fstChoice (Choices a _) = a

Using Types

-- Values can be annotated in-line
2 ^ (1 :: Int)

-- Bindings can be annotated
success :: a -> Maybe a
-- Constructors are values
-- (and product constructors are functions)
success x = Just x

-- Constructors can be pattern matched
-- _ is a wildcard
case success True of
  Just True -> ()
  _         -> ()

Pattern Matching

-spec is_just({just, A} | nothing) -> boolean().
is_just({just, _}) ->
    true;
is_just(nothing) ->
    false.

Pattern Matching

isJust :: Maybe a -> Bool
isJust (Just _) = True
isJust Nothing  = False

Pattern Matching

Erlang's pattern matching allows non-linear patterns.

-spec is_equal(A, A) -> boolean() when
      A :: term().
is_equal(A, A) -> true;
is_equal(_, _) -> false.

Pattern Matching

Haskell's... does not.

isEqual :: a -> a -> Bool
isEqual a b = undefined
This isn't even possible! Only constructors can be pattern matched. Types have no built-in equality.

`Infix` and (Prefix)

%% Symbolic operators can be used
%% as functions from the erlang module
erlang:'+'(A, B).
Erlang doesn't have user-defined infix operators

`Infix` and (Prefix)

-- Symbolic operators can be used
-- prefix when in (parentheses)
(+) a b

-- Named functions can be used
-- infix when in `backticks`
x `elem` xs

-- infixl, infixr define associativity
-- and precedence (0 lowest, 9 highest)
infixr 5 `append`
a `append` b = a ++ b

Functions & Lambdas

-spec add(integer(), integer()) -> integer().
add(X, Acc) ->
    X + Acc.

-spec sum_fun([integer()]) -> integer().
sum_fun(Xs) ->
    lists:foldl(fun add/2, 0, Xs).

-spec sum_lambda([integer()]) -> integer().
sum_lambda(Xs) ->
    lists:foldl(
        fun (X, Acc) -> X + Acc end,
        0,
        Xs).

Functions & Lambdas

add :: Integer -> Integer -> Integer
add acc x = acc + x

sumFun :: [Integer] -> Integer
sumFun xs = foldl add 0 xs

sumLambda :: [Integer] -> Integer
sumLambda xs = foldl (\acc x -> acc + x) 0 xs

Functions & Lambdas

  • Haskell only has functions of one argument
  • a -> b -> c is really a -> (b -> c)
  • f a b is really (f a) b
  • Let's leverage that…

Functions & Lambdas

add :: Integer -> Integer -> Integer
add acc x = acc + x

sumFun :: [Integer] -> Integer
sumFun xs = foldl add 0 xs

sumLambda :: [Integer] -> Integer
sumLambda xs = foldl (\acc x -> acc + x) 0 xs

Functions & Lambdas

add :: Integer -> Integer -> Integer
add acc x = acc + x

sumFun :: [Integer] -> Integer
sumFun = foldl add 0

sumLambda :: [Integer] -> Integer
sumLambda = foldl (\acc x -> acc + x) 0

Functions & Lambdas

add :: Integer -> Integer -> Integer
add acc x = (+) acc x

sumFun :: [Integer] -> Integer
sumFun = foldl add 0

sumLambda :: [Integer] -> Integer
sumLambda = foldl (\acc x -> (+) acc x) 0

Functions & Lambdas

add :: Integer -> Integer -> Integer
add acc = (+) acc

sumFun :: [Integer] -> Integer
sumFun = foldl add 0

sumLambda :: [Integer] -> Integer
sumLambda = foldl (\acc -> (+) acc) 0

Functions & Lambdas

add :: Integer -> Integer -> Integer
add = (+)

sumFun :: [Integer] -> Integer
sumFun = foldl add 0

sumLambda :: [Integer] -> Integer
sumLambda = foldl (+) 0

Guards

-spec is_negative(number()) -> boolean().
is_negative(X) when X < 0 ->
  true;
is_negative(_) ->
  false.

Guards

isNegative :: (Num a) => a -> Bool
isNegative x
  | x < 0     = True
  | otherwise = False

Guards

isNegative :: (Num a) => a -> Bool
isNegative x
  | x < 0     = True
  | otherwise = False

absoluteValue :: (Num a) => a -> Bool
absoluteValue x
  | isNegative x = -x
  | otherwise    = x

Built-in types

-- (), pronounced "unit"
unit :: ()
unit = ()

-- Char
someChar :: Char
someChar = 'x'

-- Instances of Num typeclass
someDouble :: Double
someDouble = 1

-- Instances of Fractional typeclass
someRatio :: Rational
someRatio = 1.2345

Lists & Tuples

some_list() ->
    [1, 2, 3].

some_other_list() ->
    [4 | [5 | [6 | []]]].

some_tuple() ->
    {10, $4}.

some_string() ->
    "foo".

Lists & Tuples

-- [a], type can be written prefix as `[] a`
someList, someOtherList :: [Int]
someList = [1, 2, 3]
someOtherList = 4 : 5 : 6 : []
dontWriteThis = (:) 4 (5 : (:) 6 [])

-- (a, b), can be written prefix as `(,) a b`
someTuple, someOtherTuple :: (Int, Char)
someTuple = (10, '4')
someOtherTuple = (,) 4 '2'

-- [Char], also known as String
-- (also see the OverloadedStrings extension)
someString :: String
someString = "foo"

Typeclass Syntax

  • Erlang doesn't have typeclasses.

  • Elixir has Protocols, which are closer, but they are also not typeclasses.

Typeclass Syntax

class Equals a where
  isEqual :: a -> a -> Bool

instance Equals Choice where
  isEqual Definitely Definitely = True
  isEqual Possibly   Possibly   = True
  isEqual NoWay      NoWay      = True
  isEqual _          _          = False

instance (Equals a) => Equals [a] where
  isEqual (a:as) (b:bs) = isEqual a b &&
                          isEqual as bs
  isEqual as     bs     = null as && null bs

Typeclass Syntax

{-
class Eq a where
  (==) :: a -> a -> Bool
-}

instance Eq Choice where
  Definitely == Definitely = True
  Possibly   == Possibly   = True
  NoWay      == NoWay      = True
  _          == _          = False

Typeclass Syntax

data Choice = Definitely
            | Possibly
            | NoWay
            deriving (Eq)

Typeclass Syntax

data Choice = Definitely
            | Possibly
            | NoWay
            deriving ( Eq, Ord, Enum, Bounded
                     , Show, Read )

QuickCheck

prop_itsthere() ->
    ?FORALL(I,int(),
        [I] == queue:to_list(
            queue:cons(I,
                queue:new()))).

QuickCheck

import Data.Sequence ((|>), empty)
import Data.Foldable (toList)

prop_itsthere :: Int -> Bool
prop_itsthere i = [i] == toList (empty |> i)

QuickCheck

$ ghci
λ> import Test.QuickCheck
λ> import Data.Foldable
λ> import Data.Sequence
λ> quickCheck (\i -> [i :: Int] ==
                       toList (empty |> i))
+++ OK, passed 100 tests.

Do syntax

-spec main([string()]) -> ok.
main(_Args) ->
  {ok, Secret} = file:read_file("/etc/passwd"),
  file:write_file("/tmp/passwd", Secret),
  ok.

Do syntax (IO)

main :: IO ()
main = do
  secret <- readFile "/etc/passwd"
  writeFile "/tmp/passwd" secret
  return ()

Do syntax


do m
-- desugars to:
m

do a <- m
   return a
-- desugars to:
m >>= \a -> return a

do m
   return ()
-- desugars to:
m >> return ()

Do syntax (IO)

main :: IO ()
main = do
  secret <- readFile "/etc/passwd"
  writeFile "/tmp/passwd" secret
  return ()

Do syntax (IO)

main :: IO ()
main =
  readFile "/etc/passwd" >>= \secret -> do
  writeFile "/tmp/passwd" secret
  return ()

Do syntax (IO)

main :: IO ()
main =
  readFile "/etc/passwd" >>= \secret ->
  writeFile "/tmp/passwd" secret >>
  return ()

Do syntax (IO)

main :: IO ()
main =
  readFile "/etc/passwd" >>= \secret ->
  writeFile "/tmp/passwd" secret

Do syntax (IO)

main :: IO ()
main =
  readFile "/etc/passwd" >>=
  writeFile "/tmp/passwd"

Do syntax ([a])

-spec flat_map(fun((A) -> [B]), [A]) -> [B] when
  A :: term(),
  B :: term().
flat_map(F, Xs) -> [ Y || X <- Xs, Y <- F(X) ].

Do syntax ([a])

flatMap :: (a -> [b]) -> [a] -> [b]
flatMap f xs = [ y | x <- xs, y <- f x ]

Do syntax ([a])

flatMap :: (a -> [b]) -> [a] -> [b]
flatMap f xs = do
  x <- xs
  y <- f x
  return y

Do syntax ([a])

flatMap :: (a -> [b]) -> [a] -> [b]
flatMap f xs = do
  x <- xs
  f x

Do syntax ([a])

flatMap :: (a -> [b]) -> [a] -> [b]
flatMap f xs = xs >>= \x -> f x

Do syntax ([a])

flatMap :: (a -> [b]) -> [a] -> [b]
flatMap f xs = xs >>= f

Do syntax ([a])

flatMap :: (a -> [b]) -> [a] -> [b]
flatMap f xs = flip (>>=) f xs

Do syntax ([a])

flatMap :: (a -> [b]) -> [a] -> [b]
flatMap = flip (>>=)

Do syntax ([a])

flatMap :: (a -> [b]) -> [a] -> [b]
flatMap = (=<<)

Key Features

  • Interactive
  • Pure
  • Non-strict (lazy) evaluation
  • Types and typeclasses
  • Abstractions
  • Multi-paradigm

GHCi

Interactive Haskell


$ runhaskell --help
Usage: runghc [runghc flags] [GHC flags] module [program args]

The runghc flags are
    -f /path/to/ghc       Tell runghc where GHC is
    --help                Print this usage information
    --version             Print version number


$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
h> 

:t shows type information


h> :t map
map :: (a -> b) -> [a] -> [b]
h> :t map (+1)
map (+1) :: Num b => [b] -> [b]
h> :t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b

:i shows typeclass info


h> :i Num
class Num a where
  (+) :: a -> a -> a
  (*) :: a -> a -> a
  (-) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a
    -- Defined in `GHC.Num'
instance Num Integer -- Defined in `GHC.Num'
instance Num Int -- Defined in `GHC.Num'
instance Num Float -- Defined in `GHC.Float'
instance Num Double -- Defined in `GHC.Float'

:i shows value info


h> :info map
map :: (a -> b) -> [a] -> [b]   
-- Defined in `GHC.Base'
h> :info (>>=)
class Monad m where
  (>>=) :: m a -> (a -> m b) -> m b
  ...
    -- Defined in `GHC.Base'
infixl 1 >>=

:i shows type info


h> :info Int
data Int = ghc-prim:GHC.Types.I#
  ghc-prim:GHC.Prim.Int#
  -- Defined in `ghc-prim:GHC.Types'
instance Bounded Int -- Defined in `GHC.Enum'
instance Enum Int -- Defined in `GHC.Enum'
instance Eq Int -- Defined in `GHC.Classes'
instance Integral Int -- Defined in `GHC.Real'
instance Num Int -- Defined in `GHC.Num'
instance Ord Int -- Defined in `GHC.Classes'
instance Read Int -- Defined in `GHC.Read'
instance Real Int -- Defined in `GHC.Real'
instance Show Int -- Defined in `GHC.Show'

:l load a module

:r to reload


h> :! echo 'hello = print "hello"' > Hello.hs
h> :l Hello
[1 of 1] Compiling Main ( Hello.hs, interpreted )
Ok, modules loaded: Main.
h> hello
"hello"
h> :! echo 'hello = print "HELLO"' > Hello.hs
h> :r
[1 of 1] Compiling Main ( Hello.hs, interpreted )
Ok, modules loaded: Main.
h> hello
"HELLO"


-- WordCount1.hs

main :: IO ()
main = do
  input <- getContents
  let wordCount = length (words input)
  print wordCount


-- WordCount2.hs

main :: IO ()
main =
  getContents >>= \input ->
    let wordCount = length (words input)
    in print wordCount


-- WordCount3.hs

main :: IO ()
main = getContents >>= print . length . words

what.the >>=?

  • do is just syntax sugar for the >>= (bind) operator.
  • IO is still purely functional, we are just building a graph of actions, not executing them in-place!
  • Starting from main, the Haskell runtime will evaluate these actions
  • It works much like continuation passing style, with a state variable for the current world state (behind the scenes)
  • There are ways to cheat and write code that is not pure, but you will have to go out of your way to do it

Common combinators


-- Function composition
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)

-- Function application (with a lower precedence)
($) :: (a -> b) -> a -> b
f $ x =  f x

Pure

  • Haskell's purity implies referential transparency
  • This means that function invocation can be freely replaced with its return value without changing semantics
  • Fantastic for optimizations
  • Also enables equational reasoning, which makes it easier to prove code correct

GHC compilation phases Parse Rename Typecheck Desugar Core Optimize Code gen LLVM

Optimizations

  • Common sub-expression elimination
  • Inlining (cross-module too!)
  • Specialize
  • Float out
  • Float inwards
  • Demand analysis
  • Worker/Wrapper binds
  • Liberate case
  • Call-pattern specialization (SpecConstr)

GHC RULES!

  • Term rewriting engine
  • RULES pragma allows library defined optimizations
  • Used to great effect for short cut fusion
  • Example: map f (map g xs) = map (f . g) xs
  • Prevent building of intermediate data structures
  • Commonly used for lists, Text, ByteString, etc.
  • Great incentive to write high-level code!
  • ANY LIBRARY CAN USE THIS!


{-# RULES
"ByteString specialise break (x==)" forall x.
    break ((==) x) = breakByte x
"ByteString specialise break (==x)" forall x.
    break (==x) = breakByte x
  #-}

GHC RULES


{-# RULES
"ByteString specialise break (x==)" forall x.
    break ((==) x) = breakByte x
"ByteString specialise break (==x)" forall x.
    break (==x) = breakByte x
  #-}

import Data.ByteString.Char8 (ByteString, break)

splitLine :: ByteString -> (ByteString, ByteString)
splitLine = break (=='\n')

GHC RULES


{-# RULES
"ByteString specialise break (x==)" forall x.
    break ((==) x) = breakByte x
"ByteString specialise break (==x)" forall x.
    break (==x) = breakByte x
  #-}

import Data.ByteString.Char8 (ByteString, break)

splitLine :: ByteString -> (ByteString, ByteString)
splitLine = breakByte '\n'

Lazy

  • Call by need (outside in), not call by value (inside out)
  • Non-strict evaluation separates equation from execution
  • No need for special forms for control flow, no value restriction
  • Enables infinite or cyclic data structures
  • Can skip unused computation (better minimum bounds)

lazy
lazy

Call by need

  • Expressions are translated into a graph (not a tree!)
  • Evaluated with STG (Spineless Tagless G-Machine)
  • Pattern matching forces evaluation

Non-Strict Evaluation

-- [1..] is an infinite list, [1, 2, 3, ...]
print (head (map (*2) [1..]))

Non-Strict Evaluation

-- [1..] is an infinite list, [1, 2, 3, ...]
print (head (map (*2) [1..]))
-- Outside in, print x = putStrLn (show x)
putStrLn (show (head (map (*2) [1..]))

Non-Strict Evaluation

-- Outside in, print x = putStrLn (show x)
putStrLn (show (head (map (*2) [1..]))
-- head (x:_) = x
-- map f (x:xs) = f x : map f xs
-- desugar [1..] syntax
putStrLn (show (head (map (*2) (enumFrom 1))))

Non-Strict Evaluation

-- desugar [1..] syntax
putStrLn (show (head (map (*2) (enumFrom 1))))
-- enumFrom n = n : enumFrom (succ n)
putStrLn (show (head (map (*2)
                          (1 : enumFrom (succ 1)))))

Non-Strict Evaluation

-- enumFrom n = n : enumFrom (succ n)
putStrLn (show (head (map (*2)
                          (1 : enumFrom (succ 1)))))
-- apply map
putStrLn (show (head
                  ((1*2) :
                   map (*2) (enumFrom (succ 1)))))

Non-Strict Evaluation

-- apply map
putStrLn (show (head ((1*2) : …)))
-- apply head
putStrLn (show (1*2))

Non-Strict Evaluation

-- apply head
putStrLn (show (1*2))
-- show pattern matches on its argument
putStrLn (show 2)

Non-Strict Evaluation

-- show pattern matches on its argument
putStrLn (show 2)
-- apply show
putStrLn "2"


if' :: Bool -> a -> a -> a
if' cond a b = case cond of
  True  -> a
  False -> b

(&&) :: Bool -> Bool -> Bool
a && b = case a of
  True  -> b
  False -> False

const :: a -> b -> a
const x = \_ -> x


fib :: [Integer]
fib = 0 : 1 : zipWith (+) fib (tail fib)

cycle :: [a] -> [a]
cycle xs = xs ++ cycle xs

iterate :: (a -> a) -> a -> [a]
iterate f x = x : iterate f (f x)

takeWhile :: (a -> Bool) -> [a] -> [a]
takeWhile _ [] = []
takeWhile p (x:xs)
  | p x       = x : takeWhile p xs
  | otherwise = []

Types

  • Enforce constraints at compile time
  • No NULL
  • Can have parametric polymorphism and/or recursion
  • Built-in types are not special (other than syntax)
  • Typeclasses for ad hoc polymorphism (overloading)


h> let f x = head True

<interactive>:23:16:
    Couldn't match expected type `[a0]' with actual type `Bool'
    In the first argument of `head', namely `True'
    In the expression: head True
    In an equation for `f': f x = head True

h> let f x = heads True

<interactive>:24:11:
    Not in scope: `heads'
    Perhaps you meant one of these:
      `reads' (imported from Prelude), `head' (imported from Prelude)


h> let x = x in x
-- Infinite recursion, not a fun case to deal with!

h> case False of True -> ()
*** Exception: <interactive>:29:1-24: Non-exhaustive patterns in case

h> head []
*** Exception: Prelude.head: empty list

h> error "this throws an exception"
*** Exception: this throws an exception

h> undefined
*** Exception: Prelude.undefined


-- Polymorphic and recursive
data List a = Cons a (List a)
            | Nil
            deriving (Show)

data Tree a = Leaf a
            | Branch (Tree a) (Tree a)
            deriving (Show)

listMap :: (a -> b) -> List a -> List b
listMap _ Nil         = Nil
listMap f (Cons x xs) = Cons (f x) (listMap f xs)

treeToList :: Tree a -> List a
treeToList root = go root Nil
  where
    -- Note that `go` returns a function!
    go (Leaf x)     = Cons x
    go (Branch l r) = go l . go r

Typeclasses

  • Used for many of the Prelude operators and numeric literals
  • Ad hoc polymorphism (overloading)
  • Many built-in typeclasses can be automatically derived (Eq, Ord, Enum, Bounded, Show, and Read)!


module List where

data List a = Cons a (List a)
            | Nil

instance (Eq a) => Eq (List a) where
  (Cons a as) == (Cons b bs) = a == b && as == bs
  Nil         == Nil         = True
  _           == _           = False

instance Functor List where
  fmap _ Nil         = Nil
  fmap f (Cons x xs) = Cons (f x) (fmap f xs)


{-# LANGUAGE DeriveFunctor #-}

module List where

data List a = Cons a (List a)
            | Nil
            deriving (Eq, Functor)


import Data.List (sort)

newtype Down a = Down { unDown :: a }
                 deriving (Eq)

instance (Ord a) => Ord (Down a) where
  compare (Down a) (Down b) = case compare a b of
    LT -> GT
    EQ -> EQ
    GT -> LT

reverseSort :: Ord a => [a] -> [a]
reverseSort = map unDown . sort . map Down

Abstractions

Monoid
Has an identity and an associative operation
Functor
Anything that can be mapped over (preserving structure)
Applicative
Functor, but can apply function from inside
Monad
Applicative, but can return any structure

Monoid

class Monoid a where
  mempty :: a
  mappend :: a -> a -> a

instance Monoid [a] where
  mempty = []
  mappend = (++)

infixr 6 <>
(<>) :: (Monoid a) => a -> a -> a
(<>) = mappend

Functor

class Functor f where
  fmap :: (a -> b) -> f a -> f b

instance Functor [] where
  fmap = map

instance Functor Maybe where
  fmap f (Just x) = Just (f x)
  fmap _ Nothing  = Nothing

infixl 4 <$>
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<$>) = fmap

Applicative

class (Functor f) => Applicative f where
  pure :: a -> f a
  infixl 4 <*>
  (<*>) :: f (a -> b) -> f a -> f b

instance Applicative [] where
  pure x = [x]
  fs <*> xs = concatMap (\f -> map f xs) fs

instance Applicative Maybe where
  pure = Just
  Just f <*> Just x = Just (f x)
  _      <*> _      = Nothing

Monad

class Monad m where
  return :: a -> m a
  (>>=) :: m a -> (a -> m b) -> m b
  (>>)  :: m a -> m b -> m b
  ma >> mb = ma >>= \_ -> mb

instance Monad [] where
  return = pure
  m >>= f = concatMap f m

instance Monad Maybe where
  return = pure
  Just x  >>= f = f x
  Nothing >>= _ = Nothing

Parser Combinators

{-# LANGUAGE OverloadedStrings #-}
module SJSON where
import Prelude hiding (concat)
import Data.Text (Text, concat)
import Data.Attoparsec.Text
import Control.Applicative

data JSON = JArray [JSON]
          | JObject [(Text, JSON)]
          | JText Text
          deriving (Show)

pJSON :: Parser JSON
pJSON = choice [ pText, pObject, pArray ]
  where
    pString = concat <$> "\"" .*> many pStringChunk <*. "\""
    pStringChunk = choice [ "\\\"" .*> pure "\""
                          , takeWhile1 (not . (`elem` "\\\""))
                          , "\\" ]
    pText = JText <$> pString
    pPair = (,) <$> pString <*. ":" <*> pJSON
    pObject = JObject <$> "{" .*> (pPair `sepBy` ",") <*. "}"
    pArray = JArray <$> "[" .*> (pJSON `sepBy` ",") <*. "]"

Foreign Function Interface


{-# LANGUAGE ForeignFunctionInterface #-}

import Foreign.C.Types
import Control.Monad

foreign import ccall unsafe "stdlib.h rand"
     c_rand :: IO CUInt

main :: IO ()
main = replicateM_ 20 (c_rand >>= print)

Parallel Programming

-- FlipImage.hs
import System.Environment
import Data.Word
import Data.Array.Repa hiding ((++))
import Data.Array.Repa.IO.DevIL
import Data.Array.Repa.Repr.ForeignPtr

main :: IO () 
main = do
  [f] <- getArgs
  (RGB v) <- runIL $ readImage f
  rotated <- (computeP $ rot180 v) :: IO (Array F DIM3 Word8)
  runIL $ writeImage ("flip-"++f) (RGB rotated)

rot180 :: (Source r e) => Array r DIM3 e -> Array D DIM3 e
rot180 g = backpermute e flop g
  where
    e@(Z :. x :. y :. _) = extent g
    flop (Z :. i         :. j         :. k) =
         (Z :. x - i - 1 :. y - j - 1 :. k)

Concurrency


import Control.Concurrent
import Network.HTTP

getHTTP :: String -> IO String
getHTTP url = simpleHTTP (getRequest url) >>= getResponseBody

urls :: [String]
urls = map ("http://ifconfig.me/"++) ["ip", "host"]

startRequest :: String -> IO (MVar ())
startRequest url = do
  v <- newEmptyMVar
  forkIO (getHTTP url >>= putStr >> putMVar v ())
  return v

main :: IO ()
main = do
  mvars <- mapM startRequest urls
  mapM_ takeMVar mvars

Why not Haskell?

  • Lots of new terminology
  • Mutable state takes more effort
  • Laziness changes how you need to reason about code
  • Once you get used to it, these aren't problematic

A monad is just a monoid in the category of endofunctors, what's the problem?

Terminology from category theory can be intimidating (at first)!

return probably doesn't mean what you think it means.


sum :: Num a => [a] -> a
sum []     = 0
sum (x:xs) = x + sum xs

sum :: Num [a] => [a] -> a
sum = go 0
  where
    go acc (x:xs) = go (acc + x) (go xs)
    go acc []     = acc

sum :: Num [a] => [a] -> a
sum = go 0
  where
    go acc _
      | seq acc False = undefined
    go acc (x:xs)     = go (acc + x) (go xs)
    go acc []         = acc

{-# LANGUAGE BangPatterns #-}

sum :: Num [a] => [a] -> a
sum = go 0
  where
    go !acc (x:xs) = go (acc + x) (go xs)
    go  acc []     = acc

Notable Libraries

Web Frameworks

Snap
HTTP + Templates. Extensible with "Snaplets"
Yesod
Full stack, uses Template Haskell
Happstack
Full stack, does not rely on Template Haskell (happstack-lite)
scotty
Like Ruby Sinatra, great for simple REST apps

Publishing and docs

Haddock
Standard library documentation tool for Haskell projects
diagrams
DSL for vector graphics
hakyll
Static site generator
Pandoc
Markup format swiss-army knife (Markdown, LaTeX, EPUB, …)

Parser Combinators

Parsec
Industrial strength, monadic parser combinator library for Haskell
attoparsec
Like Parsec, but makes a few trade-offs for performance

Dev Tools

HLint
Suggests improvements for your code
ghc-mod, hdevtools
Editor integration
Hoogle, Hayoo
Search for Haskell functions by name or by type!
Djinn
Automatically generate code given a type!
tidal
DSL for live coding music patterns ("algorave")

Parallel / Distributed

repa
High performance, regular, multi-dimensional arrays (with multi-core!)
accelerate
Like repa, but can utilize CUDA to run on GPUs
Cloud Haskell
Erlang-like concurrency and distribution for Haskell

Testing & Profiling

QuickCheck
Property based testing
HUnit
Standard unit testing framework
hpc
Haskell Program Coverage
ThreadScope
Visualize multi-core utilization
criterion
Gold standard for performance benchmarking
EKG
Embeds a web-server for live monitoring of metrics

Learn More

Books
Learn You a Haskell for Great Good
Parallel and Concurrent Programming in Haskell
Real World Haskell
Lectures
Functional Systems in Haskell - CS240h Autumn 2011, Stanford
Introduction to Haskell - CS1501 Spring 2013, UVA
Introduction to Haskell - CIS 194 Spring 2013, UPenn
Haskell Track - CS 11 Fall 2011, Caltech
Practice
exercism.io, Talentbuddy, HackerRank
H-99, Project Euler

Thanks!

Slides

bob.ippoli.to/haskell-for-erlangers-2014

Source

github.com/etrepum/haskell-for-erlangers-2014

Email

bob@redivi.com

Twitter

@etrepum