– Beginning Haskell – Bob Ippolito (@etrepum) BayHac 2014



– Beginning Haskell – Bob Ippolito (@etrepum) BayHac 2014

1 4


beginning-haskell-bayhac-2014

Intro to Haskell tutorial for BayHac 2014

On Github etrepum / beginning-haskell-bayhac-2014

Beginning Haskell

Bob Ippolito (@etrepum) BayHac 2014

bob.ippoli.to/beginning-haskell-bayhac-2014

Now is a good time to install GHCbit.ly/install-ghc

Who am I?

  • Haskell user since 2012 (ported exercism.io curriculum)
  • Spending most of my time with Mission Bit, teaching after school coding classes (but not in Haskell… yet)
  • Doing a bit of advising/investing in startups

Haskell's Appeal

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

My stumbling blocks

  • So . many <$> operators >>=, many $ without <*> names !!
  • Types took some getting used to
  • Non-strict evaluation didn't match my intuition/experience
  • Loads of new terminology

Use the Hoogle

Haskell Syntax

Types Defines types and typeclasses

Constructors and record accessors become terms

Terms Named bindings Instances of constructors Functions

Control flow

Types

  • Examples: Bool, Int, [a], Maybe a
  • Start with a capital letter
  • Defined with data or newtype
  • Aliases made with type

Sum Types

  • Sum types enumerate all possible inhabitants
  • Bool has 2 possibilities, Ordering has 3, …
data Bool = True | False

data Ordering = LT | EQ | GT

data Choice = Definitely | Possibly | NoWay

data Int = … | -1 | 0 | 1 | 2 | …

data Char = … | 'a' | 'b' | …

Product Types

  • Product types are like structs, with fields that contain other types
  • Choices has 3 * 3 == 9 possible inhabitants
  • Can name these fields using record syntax (defines getters automatically)
data CoinFlip = CoinFlip Bool

data Choices = Choices Choice Choice

data Coord = Coord { x :: Double, y :: Double }

Sum of Products

  • Possibly has (1 * 2) + 1 possibilities
data Possibly = Certainly Bool
              | Uncertain

data DrawCommand = Point Int Int
                 | Line Int Int Int Int
                 | Rect Int Int Int Int

data IntTree = Node Int IntTree IntTree
             | Leaf

Abstract Data Types

  • Type variables are lowercase
  • type creates aliases (can help readability)
data List a = Cons a (List a)
            | Nil

data Maybe a = Just a
             | Nothing

type IntList = List Int
type MaybeBool = Maybe Bool
type String = [Char]

Special Type Syntax

  • Unit, Tuples, Lists, and Functions have special syntax
  • Can also be written in prefix form
type Unit = ()

type ListOfInt = [Int]
type ListOfInt = [] Int

type AddFun = Int -> Int -> Int
type AddFun = Int -> (Int -> Int)
type AddFun = (->) Int ((->) Int Int)

type IntTuple = (Int, Int)
type IntTuple = (,) Int Int

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

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

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

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

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.8.2: 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 term 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"

map

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

foldr

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

Pattern Matching

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

Pattern Matching

Haskell only implements linear patterns

-- DOES NOT WORK!
isEqual :: a -> a -> Bool
isEqual a a = True
isEqual _ _ = False
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
-- 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

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

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

-- [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"

Challenge?

Finish this dice game (see TODO):

lpaste.net/104237

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_intIdentity :: Int -> Bool
prop_intIdentity i = i == i

QuickCheck

$ ghci
λ> import Test.QuickCheck
λ> quickCheck (\i -> (i :: Int) == i)
+++ OK, passed 100 tests.

QuickCheck Isn't Magic

λ> import Test.QuickCheck
λ> quickCheck (\i -> (i :: Double) + 1 > i)
+++ OK, passed 100 tests.

QuickCheck Isn't Magic

λ> import Test.QuickCheck
λ> quickCheck (\i -> (i :: Double) + 1 > i)
+++ OK, passed 100 tests.
λ> let i = 0/0 :: Double in i + 1 > i
False

QuickCheck Isn't Magic

λ> import Test.QuickCheck
λ> quickCheck (\i -> (i :: Double) + 1 > i)
+++ OK, passed 100 tests.
λ> let i = 0/0 :: Double in i + 1 > i
False
λ> let i = 1e16 :: Double in i + 1 > i
False

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])

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 = (=<<)

-- 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 phasesParseRenameTypecheckDesugarCoreOptimizeCode genLLVM

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

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 …

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

Parsing

{-# 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` ",") <*. "]"

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 _
      | acc `seq` 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

Learn More

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

Thanks!

Slides

bob.ippoli.to/beginning-haskell-bayhac-2014

Source

github.com/etrepum/beginning-haskell-bayhac-2014

Email

bob@redivi.com

Twitter

@etrepum

0