anaynayak / cis194

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Haskell cis 194

  • Functional
    • Functions as first class
    • evaluating expressions instead of executing instructions
  • Statically typed
    • Compile time safety
  • Pure
    • Virtue
      • Immutable
      • Return same value for given args
      • Side effect free
    • Why:
      • Parallelise
      • Reason easily, debug etc
      • Equational reasoning and refactoring
  • Lazy
    • Expressions not evaluated until required
    • infinite data structures
    • Enables compositional programming [?]
    • Difficult to reason w.r.t. space time usage

Course theme:

  • Typed
    • Documentation
    • Compile time safety
    • Thinking
  • Wholemeal programming
    • Think big. Develop solution space.
    • Solve general problem extract specific bits by transforming general program into more specialized ones.
  • Abstraction
    • DRY

Enumeration types

data Thing = Shoe | Ship (deriving Show)

Algebraic data types

data FailableDouble = Failure | OK Double (deriving Show)

Failure and OK are data constructors. OK takes a param FailableDouble is a type constructor.

Pattern matching

checkFav :: Person -> String
checkFav (Person n _ SealingWax) = n ++ ", you're my kind of person!"
checkFav (Person n _ _)          = n ++ ", your favorite thing is lame."

case statement.

case "hello" of
  [] -> 3
  ('H':s) -> 10
  _ -> 1

Recursive data type

data IntList = Empty | Cons Int IntList

Recursion patterns

  • Map
  • Filter
  • Fold

Polymorphic type:

data List t = E | C t (List t)

t is a type variable

Polymorphic functions

mapList :: (a -> b) -> List a -> List b

With polymorphic functions caller gets to pick types.

Total v/s partial functions

e.g. head is a partial function. crashes with an empty list.

Use the safe package.

Higher order programming

Anonymous function or lambda abstraction

(\x -> x > 100)

Operator section: if ? is an operator, then (?y) is equivalent to the function \x -> x ? y

. functional composition

function arrows associate to the right, that is, W -> X -> Y -> Z is equivalent to W -> (X -> (Y -> Z))

Function application, in turn, is left-associative. That is, f 3 2 is really shorthand for (f 3) 2

comp :: (b -> c) -> (a -> b) -> a -> c
comp f g x = f (g x)

comp f g = f . g

The arguments should be ordered from from “least to greatest variation”,

Currying - Represent multi argument functions as one-argument functions. Alternatively, can pass a single paarameter to a function as a tuple. uncurry can be used to unwrap a tuple into args.

Eta conversion

eta reduction \x -> abs x to abs eta abstraction is the reverse

Beta reduction - application of a function to an expression

Alpha conversion - same function different names.

Fold

Fold

foldr f z [a,b,c] == a `f` (b `f` (c `f` z))
foldl f z [a,b,c] == ((z `f` a) `f` b) `f` c

e.g.

Hangs: take 20 $ foldl (\acc s -> acc ++ [s]) [] [1..] Runs to compleltion: take 20 $ foldr (:) [] [1..] Stack overflow: foldr max 0 [1..]

Reducible expression redex . (+) is not redex. max [?]

GHC has a lazy reduction strategy. Use foldl' . See seq which reduces first param before going for the rest.

Polymorphisms and type classes

Caller gets to choose type. What are other examples ? [?]

We say that a function like f :: a -> a -> a is parametric in the type a . Parametricity corresponds to guarantees not restrictions

Examples of parameteric types:

a -> a (id) a -> b (map) a -> b -> a (const) [a] -> [a] (list functions) (b -> c) -> (a -> b) -> (a -> c) (.) (a -> a) -> a -> a ($)

(+) :: Num a => a -> a -> a

Type classes

Num, Eq, Ord, and Show are type classes, and we say that (==), (<), and (+) are “type-class polymorphic”. Intuitively, type classes correspond to sets of types which have certain operations defined for them, and type class polymorphic functions work only for types which are instances of the type class(es) in question.

Type classes are different from java interfaces. Type class instances are declared separately from the type classes. In another module as well. [?]

Multiple-dispatch : not possible in java. Depends on types of both a, b below

class Blerg a b where
  blerg :: a -> b -> Bool

type class constraints can be on the instance as well as functions.

instance (Listable a, Listable b) => Listable (a,b) where
  toList (x,y) = toList x ++ toList y

[?] Language extensions FlexibleInstances etc

When using newtype, you're restricted to just one constructor with one field.

Lazy evaluation

For Java, params are evaluated regardless of whether the method actually consumes the passed variables. Side-effect is a primary reason why strict param evaluation is needed.

For lazy, evaluation is delayed as long as possible. Unevaluated expressions are called as thunk

A trigger for evaluation could for e.g. be a pattern match. A Maybe need not be evaluated if the method doesnt care of the shape of it. However if we pattern match to Nothing or Just then we need to know. The thunks are evaluated just enough.

GHC uses graph reduction . Expression represented as graph so that same expressions can be pointers and evaluated only once.

if' :: Bool -> a -> a -> a
if' True  x _ = x
if' False _ y = y

Lazy / short-circuit

(&&) :: Bool -> Bool -> Bool
True  && x = x
False && _ = False
  • Infinite data structures are effectively just thunks.
  • With wholemeal programming, only those thunks that are needed are consumed.
  • Can define our own control structures
  • Dynamic programming [?]

Folds with monoids

exprTFold :: (Integer -> b) -> (b -> b -> b) -> (b -> b -> b) -> ExprT -> b
exprTFold f _ _ (Lit i)     = f i
exprTFold f g h (Add e1 e2) = g (exprTFold f g h e1) (exprTFold f g h e2)
exprTFold f g h (Mul e1 e2) = h (exprTFold f g h e1) (exprTFold f g h e2)

fold-map fusion

fold-map fusion lets you replace such a definition by one that only involves foldr:

foldr op u . map f = foldr (op . f) u

Folds take an argument for each data constructor. Encodes it to a value of returned type. implementation deals with the recursive aspects.

Monoids and Semigroups

Semigroups define a single <> (mappend) operation which lets you combine two values of the type.

Monoids have an additional method mempty which defines the identity

mconcat is available as a fold implementation. foldr <> mempty

There might be multiple Monoid implementations which might be possible. In that case its better to create a newtype which instead provides the implementation of the monoid. E.g. Data.Semigroup.Sum

Monoid Laws

mempty `mappend` x = x
x `mappend` mempty = x
(x `mappend` y) `mappend` z = x `mappend` (y `mappend` z)

Monoids in Haskell Monoids tour Monoids and finger trees Finger tree example

Functors

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

Defines a single fmap method which lets you transform the value inside a computation context. Defined for a type constructor of kind * -> * map is fmap on lists.

Functor for a function is function composition.

instance Functor ((->) r) where
    fmap = (.)

fmap :: (a -> b) -> (f a -> f b) If we pass a single function to fmap we get a function which accepts a functor and returns a different functor. Called lifting a function.

Functor laws

  • Mapping id gives the same functor back fmap id = id
  • Mapping a function composition over a functor is the same as mapping 1st function over a functor and then the second . fmap (f . g) = fmap f . fmap g

Fmap doesn't let us map a function inside a functor with another functor.

Applicative

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

Applicative takes a functor over a function and another functor and gives a functor with function applied over the value.

pure f <*> x <*> y <*> ... a lets us wrap functions in a functor and apply it on values which are in functor contexts.

pure f <*> x is the same as fmap f x . which is equivalent to f <$> x which is also provided by the Applicative module.

Examples:

instance Applicative [] where
    pure x = [x]
    fs <*> xs = [f x | f <- fs, x <- xs]

instance Applicative IO where
    pure = return
    a <*> b = do
        f <- a
        x <- b
        return (f x)

liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c

Monads

(>>=) :: (Monad m) => m a -> (a -> m b) -> m b

class Monad m where
    return :: a -> m a

    (>>=) :: m a -> (a -> m b) -> m b

    (>>) :: m a -> m b -> m b
    x >> y = x >>= \_ -> y

    fail :: String -> m a
    fail msg = error msg

Examples:

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

instance Monad [] where
    return x = [x]
    xs >>= f = concat (map f xs)
    fail _ = []

return 1 :: Maybe Int Just 1 return 1 >>= \x -> Just (x + 1) Just 2 return 1 >>= \x -> Just (x + 1) >> Nothing Nothing

Using a do block:

blah = do
    x <- Just 1
    Just (x + 1)

Monad laws

return x >>= f  -- same as f x
m >>= return -- same as m
(m >>= f) >>= g -- same as m >>= (\x -> f x >>= g)

IO

(>>) :: IO a -> IO b -> IO b also known as and then

Bind operator (>>=) :: IO a -> (a -> IO b) -> IO b

About

License:BSD 3-Clause "New" or "Revised" License


Languages

Language:Haskell 100.0%