On Github etrepum / intro-haskell-bayhac-2014
RAM footprint per unit of concurrency (approx)
1.3KBGeneral Purpose
Constructors and record accessors become values
Values Named bindings Instances of constructors FunctionsControl flow
map(F, [H|T]) ->
[F(H)|map(F, T)];
map(F, []) when is_function(F, 1) -> [].
map _ [] = [] map f (x:xs) = f x : map f xs
-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 :: (a -> b) -> [a] -> [b] map _ [] = [] map f (x:xs) = f x : map f xs
-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 :: (a -> b -> b) -> b -> [a] -> b
foldr k z = go
where
go [] = z
go (y:ys) = y `k` go ys
%% sum type, 3 possible values
-type choice() :: definitely
| possibly
| no_way.
-- sum type, 3 possible values
data Choice = Definitely
| Possibly
| NoWay
%% product type, 9 possible values (3 * 3)
-type choices() :: {choice(), choice()}.
-- 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)
%% 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.
-- 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 for a list
-type cons(A) :: nil
| {cons, A, cons(A)}.
-- abstract data type for a list
data List a = Nil
| Cons a (List a)
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
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
-- 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 -> () _ -> ()
-spec is_just({just, A} | nothing) -> boolean().
is_just({just, _}) ->
true;
is_just(nothing) ->
false.
isJust :: Maybe a -> Bool isJust (Just _) = True isJust Nothing = False
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.
Haskell's... does not.
isEqual :: a -> a -> Bool isEqual a b = undefinedThis isn't even possible! Only constructors can be pattern matched. Types have no built-in equality.
%% Symbolic operators can be used %% as functions from the erlang module erlang:'+'(A, B).Erlang doesn't have user-defined infix operators
-- 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
-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).
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
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
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
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
add :: Integer -> Integer -> Integer add acc = (+) acc sumFun :: [Integer] -> Integer sumFun = foldl add 0 sumLambda :: [Integer] -> Integer sumLambda = foldl (\acc -> (+) acc) 0
add :: Integer -> Integer -> Integer add = (+) sumFun :: [Integer] -> Integer sumFun = foldl add 0 sumLambda :: [Integer] -> Integer sumLambda = foldl (+) 0
-spec is_negative(number()) -> boolean(). is_negative(X) when X < 0 -> true; is_negative(_) -> false.
isNegative :: (Num a) => a -> Bool isNegative x | x < 0 = True | otherwise = False
isNegative :: (Num a) => a -> Bool isNegative x | x < 0 = True | otherwise = False absoluteValue :: (Num a) => a -> Bool absoluteValue x | isNegative x = -x | otherwise = x
-- (), 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
some_list() ->
[1, 2, 3].
some_other_list() ->
[4 | [5 | [6 | []]]].
some_tuple() ->
{10, $4}.
some_string() ->
"foo".
-- [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"
Erlang doesn't have typeclasses.
Elixir has Protocols, which are closer, but they are also not typeclasses.
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
{-
class Eq a where
(==) :: a -> a -> Bool
-}
instance Eq Choice where
Definitely == Definitely = True
Possibly == Possibly = True
NoWay == NoWay = True
_ == _ = False
data Choice = Definitely
| Possibly
| NoWay
deriving (Eq)
data Choice = Definitely
| Possibly
| NoWay
deriving ( Eq, Ord, Enum, Bounded
, Show, Read )
prop_itsthere() ->
?FORALL(I,int(),
[I] == queue:to_list(
queue:cons(I,
queue:new()))).
import Data.Sequence ((|>), empty) import Data.Foldable (toList) prop_itsthere :: Int -> Bool prop_itsthere i = [i] == toList (empty |> i)
$ ghci
λ> import Test.QuickCheck
λ> import Data.Foldable
λ> import Data.Sequence
λ> quickCheck (\i -> [i :: Int] ==
toList (empty |> i))
+++ OK, passed 100 tests.
-spec main([string()]) -> ok.
main(_Args) ->
{ok, Secret} = file:read_file("/etc/passwd"),
file:write_file("/tmp/passwd", Secret),
ok.
main :: IO () main = do secret <- readFile "/etc/passwd" writeFile "/tmp/passwd" secret return ()
do m -- desugars to: m do a <- m return a -- desugars to: m >>= \a -> return a do m return () -- desugars to: m >> return ()
main :: IO () main = do secret <- readFile "/etc/passwd" writeFile "/tmp/passwd" secret return ()
main :: IO () main = readFile "/etc/passwd" >>= \secret -> do writeFile "/tmp/passwd" secret return ()
main :: IO () main = readFile "/etc/passwd" >>= \secret -> writeFile "/tmp/passwd" secret >> return ()
main :: IO () main = readFile "/etc/passwd" >>= \secret -> writeFile "/tmp/passwd" secret
main :: IO () main = readFile "/etc/passwd" >>= writeFile "/tmp/passwd"
-spec flat_map(fun((A) -> [B]), [A]) -> [B] when A :: term(), B :: term(). flat_map(F, Xs) -> [ Y || X <- Xs, Y <- F(X) ].
flatMap :: (a -> [b]) -> [a] -> [b] flatMap f xs = [ y | x <- xs, y <- f x ]
flatMap :: (a -> [b]) -> [a] -> [b] flatMap f xs = do x <- xs y <- f x return y
flatMap :: (a -> [b]) -> [a] -> [b] flatMap f xs = do x <- xs f x
flatMap :: (a -> [b]) -> [a] -> [b] flatMap f xs = xs >>= \x -> f x
flatMap :: (a -> [b]) -> [a] -> [b] flatMap f xs = xs >>= f
flatMap :: (a -> [b]) -> [a] -> [b] flatMap f xs = flip (>>=) f xs
flatMap :: (a -> [b]) -> [a] -> [b] flatMap = flip (>>=)
flatMap :: (a -> [b]) -> [a] -> [b] flatMap = (=<<)
$ 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>
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
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'
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 >>=
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'
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
-- 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
GHC compilation phasesParseRenameTypecheckDesugarCoreOptimizeCode genLLVM
{-# RULES
"ByteString specialise break (x==)" forall x.
break ((==) x) = breakByte x
"ByteString specialise break (==x)" forall x.
break (==x) = breakByte x
#-}
{-# 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')
{-# 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'
-- [1..] is an infinite list, [1, 2, 3, ...] print (head (map (*2) [1..]))
-- [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..]))
-- 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))))
-- 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)))))
-- 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)))))
-- apply map putStrLn (show (head ((1*2) : …))) -- apply head putStrLn (show (1*2))
-- apply head putStrLn (show (1*2)) -- show pattern matches on its argument putStrLn (show 2)
-- 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 = []
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
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
class Monoid a where mempty :: a mappend :: a -> a -> a instance Monoid [a] where mempty = [] mappend = (++) infixr 6 <> (<>) :: (Monoid a) => a -> a -> a (<>) = mappend
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
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
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
{-# 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` ",") <*. "]"
{-# 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)
-- 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)
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
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
Slides
bob.ippoli.to/haskell-for-erlangers-2014
Source
github.com/etrepum/haskell-for-erlangers-2014
bob@redivi.com