[SOLVED] CS COMP 481, Week 7, Chapter 11

$25

File Name: CS_COMP_481,_Week_7,_Chapter_11.zip
File Size: 292.02 KB

5/5 - (1 vote)

COMP 481, Week 7, Chapter 11

Copyright By Assignmentchef assignmentchef

University of the Fraser Valley

COMP 481: Functional and Logic Programming

functor design
IO actions as functors
functions as functors
functor laws
breaking the functor laws
using applicative functors
`Maybe` the applicative functor
the applicative style
lists (as applicative functors)
IO (as an applicative functor)
functions (as applicative values)
`ZipList` Applicative Functor
Applicative Laws
Useful Functions for Applicatives

Interface-style

The Haskell programming language is:
bigger on interface-style design
than on classes- and subclass-hierarchy design

as in other object-oriented programming languages.

some value in our programs can act as many different
kinds of things, described by different type classes

A thing can be categorized into many type classes,
not just one hierarchy.

Type Class

Recall type classes such as:
`Eq` for describing types with values we can check for equality, and
`Ord` for describing types with values that are orderable.

These examples of type classes demonstrate
the usefulness of abstract descriptions

Recall the `Functor` type class describes:
types with nested values
that can have a function applied
and maintain the parent structure.

Functionality

`Functor` type class: types that can be mapped over.

Applying functions on elements of:
an input set (a domain)
to an output set (a range)
(there could be repetition both in the input set and in the output set)

this may seem overkill when the input set is a singleton
(like with the `Maybe` type)

but it allows you to focus your work on the nested values

Realize that `Functors` allow you to begin to think of things
such as lists, `Maybe`, binary trees, etc., as having similar
possible behaviour.

Functor Design

The `Functor` type class has only one method that must be
implemented on any instances called `fmap`, which we have
already seen.

again, its description is `fmap :: (a -> b) -> f a -> f b`

see how the description fits within the context of `f` to the nested
values `a` and `b`

the function passed in to `fmap` is NOT `f`, but the parameter
function `(a -> b)`

`(a -> b)` is the function applied to the nested values, where as `f`
maintains itself as parent context

Parameters

To describe an instance of `Functor` as a type constructor, it
must be of kind `* -> *`:

give one type parameter as input, and the type
constructor will evaluate to one concrete type

e.g.: `Maybe` takes one type parameter, such
as `Maybe Int`, to describe a concrete type

Then with a type constructor such as `Either` that takes
two type parameters:

we must additionally supply exactly one type parameter, `Either a`
cannot write `instance Functor Either where`
must write `instance Functor (Either a) where`

`Either a`
as a Functor

To implement `fmap` with the `Either a` type constructor
would then be described as:
fmap :: (b -> c) -> Either a b -> Either a c

in the above `Either a` remains as a fixed type constructor
the context is always a type constructor taking exactly one parameter

I/O Actions as Functors

Notice that the `IO a` type has the one parameter `a`,
where `IO` has been implemented as a Functor.

A description for how it is implemented already:

instance Functor IO where

fmap g action = do

let result <- actionreturn (g result)The input parameter `g` is NOT the parent context `f` (in this case `IO`)!The textbook often uses the same letter `f` for both functor and function: `g` is some function passed in as a parameter of `fmap` the context is an I/O action, suppose `IO String` (which is NOT `g`)Note that `return` wraps the IO parent context: this requires an I/O action in the process, so it must be bound with `<-` assignment (unless it is the last line of the `do` block) this must be done within a `do` block as part of multiple I/O actionsThis has many layers of concepts, so a few examples, first without, and then with:line <- getLinelet line’ = reverse lineputStrLn $ “You said ” ++ line’ ++ ” backwards!”putStrLn $ “Yes, you said ” ++ line’ ++ ” backwards!”Then `IO` as a functor where the type parameter is `String`:line <- fmap reverse getLineputStrLn $ “You said ” ++ line ++ ” backwards!”putStrLn $ “Yes, you said ” ++ line ++ ” backwards!”See how the function `reverse` passed in to `fmap` must work with types `String`: input of `reverse` is String(the type for nested `getLine` output) the output of `reverse` is also a String(determining the type for `line`) but, we passed `reverse` in to `fmap`, which returns an `IO` context, so `fmap reverse getLine` result is of type `IO String` the `<-` operation removes the `IO` contextand stores the nested `String` value in `line`Point-free if you are wanting to perform I/O action and then a function on the instead consider using `fmap` and pass the function in together with the I/O action then the function passed in can be a composition using point-free notation, or a lambda function, etc.line <- fmap (intersperse ‘-‘ . reverse . map toUpper) getLineputStrLn lineThe equivalent function passed to `fmap` written without using point-free is:(xs -> intersperse – (reverse (map toUpper xs)))

Functions as Functors

The syntax we have seen for descriptions
of functions is `a -> b`:

notice it is written similar to a binary operator

consider it written as `(->) a b`

if we omit the last parameter, we get `(->) a`

this describes the syntax for a constructor
of a function that takes one parameter

this is used to implement an instance of `Functor`

instance Functor ((->) r) where

fmap f g = (x -> f (g x))

*Equivalent
Composition

The textbook just demonstrates how the composition
operator `.` is equivalent to `fmap` when implementing a
function as a `functor`.

function composition suffers from the notation of mathematics
where they are applied in backward order from their evaluation

piping would be much easier to read as code, similar to a `do` block

The above is just function composition, which could be
written more concisely as:

instance Functor ((->) r) where

fmap = (.)

The implementation exists in `Control.Monad.Instances`
module. Consider some re-writing of types:

fmap :: (a -> b) -> f a -> f b

fmap :: (a -> b) -> (->) r a -> (->) r b

fmap :: (a -> b) -> (r -> a) -> (r -> b)

then see in this instance, `fmap` takes two functions as parameters
the composition would be, mathematically `r -> a` then `a -> b`,

so that altogether the result is `r -> b`

Demonstrations
of Functions as

ghci> :t fmap (*3) (+100)
fmap (*3) (+100) :: (Num a) => a -> a

ghci> fmap (*3) (+100) 1

ghci> (*3) `fmap` (+100) $ 1

ghci> (*3) . (+100) $ 1

ghci> fmap (show . (*3)) (+100) 1

Note that the order of operations will first compose the
functions and then apply the one resulting function.

ghci> fmap (replicate 3) [1,2,3,4]

[[1,1,1],[2,2,2],[3,3,3],[4,4,4]]

ghci> fmap (replicate 3) (Just 4)

Just [4,4,4]

ghci> fmap (replicate 3) (Right blah)

Right [blah,blah,blah]

ghci> fmap (replicate 3) Nothing

ghci> fmap (replicate 3) (Left foo)

Left foo

Functor Laws

There are properties and behaviours of functors
we call laws:

they are not checked by Haskell automatically

however, all the library functors implement them

we must check these laws when implementing our own functors

1. the function `id` mapped over a functor
must return the same functor value

2. `fmap` distributes across composition

Details of

1. the function `id` mapped over a functor must return
the same functor value
i.e.: `fmap id = id`

e.g.: `fmap id (Just 3)` vs `id (Just 3)`

2. `fmap` distributes across composition
i.e.: `fmap (f . g) = (fmap f . fmap g)`
i.e.: `fmap (f . g) x = fmap f (fmap g x)`
ultimately, nothing about applying the functor as a type changes the

behaviour of other functions applied over it

for example, there is nothing about lists that changes how a function
will operate on its elements

Breaking the Functor Laws

We will consider an example that breaks the laws, just to
see what happens.

data CMaybe a = CNothing | CJust Int a deriving (Show)

the `C` stands for counter

the first field in the `CJust` constructor will always have type `Int`

this is similar to `Maybe a`,
but will just be used as a counter

the second field is of type `a` and will depend
on the concrete type we choose later for `CMaybe a`

ghci> CNothing

ghci> CJust 0 haha

CJust 0 haha

ghci> :t CNothing

CNothing :: CMaybe a

ghci> :t CJust 0 haha

CJust 0 haha :: CMaybe String

ghci> CJust 100 [1,2,3]

CJust 100 [1,2,3]

Instance of

Now we will implement `CMaybe a` as a functor.
so `fmap`

applies the function passed in to the second field of `CJust`
and increments the first field,

and otherwise, a `CNothing` is left alone:

instance Functor CMaybe where

fmap g CNothing = CNothing

fmap g (CJust counter x) = CJust (counter + 1) (g x)

(in ghci, no need for `let` with instance and can be multiline)

First Functor
Law Broken

See how we can apply fmap now:

ghci> fmap (++ ha) (CJust 0 ho)

CJust 1 hoha

ghci> fmap (++ he) (fmap (++ ha) (CJust 0 ho))

CJust 2 hohahe

ghci> fmap (++ blah) CNothing

But the first law does not hold:

ghci> fmap id (CJust 0 haha)

CJust 1 haha

ghci> id (CJust 0 haha)

CJust 0 haha

Functor Law

And neither does the second law hold:

ghci> fmap (++ he) . fmap (++ ha) $ (CJust 0 ho)

CJust 2 hohahe

ghci> fmap ((++ he) . (++ ha)) $ (CJust 0 ho)

CJust 1 hohahe

Independent
of Context

The functor laws are necessary to ensure they do not
obfuscate the use of our other functions we may write.

i.e.: we should not get confused about how a function will be applied
to nested elements depending on context

this makes our code easier to read

in turn, many of the other -ities become supported, such as
extensibility, maintainability, etc.

Using

in Context

Functors can be taken to a more general context by
partially applying the function passed in to `fmap`:

fmap (*) Just 3

The above results in `Just ((*) 3)` or `Just (3 *)`.

the nested value becomes a partially applied function

ghci> :t fmap (++) (Just hey)

fmap (++) (Just hey) :: Maybe ([Char] -> [Char])

ghci> :t fmap compare (Just a)

fmap compare (Just a) :: Maybe (Char -> Ordering)

ghci> :t fmap compare A LIST OF CHARS

fmap compare A LIST OF CHARS :: [Char -> Ordering]

ghci> :t fmap (x y z -> x + y / z) [3,4,5,6]

fmap (x y z -> x + y / z) [3,4,5,6] :: Fractional a => [a -> a -> a]

In the expressions involving `compare` function

the type for compare is `compare :: (Ord a) => a -> a -> Ordering`

`fmap compare A LIST OF CHARS`

the first `a` in the type description for `compare` is inferred to be `Char`

then the second `a` must be type `Char`

the combined partially-applied compare function and the functor
together generate a list of functions of type `Char -> Ordering`

Multiparameter

you may wonder how to work with the last expression
assign the expression result to a variable: `functions` (see below)

each function is missing two parameters: `y` and `z`

these correspond to the `[ a -> a ->` part of the type description

apply the element functions `fmap (f -> f 1 2) $ functions`
this adds `0.5 = y / z` to each of the already

supplied values of `x` in the original list [3,4,5,6]

functions = (fmap (x y z -> x + y / z) [3,4,5,6])

ghci> fmap (f -> f 1 2) functions

`Maybe` the Applicative Functor

We have seen how to use functions on the nested
elements of functors.

functor value just means some context with nested elements

Applicative functors go one step more abstract and allow
us to define operations between functor values.

Consider the following situation:

we have a functor full of nested partially applied functions

we have another functor full of nested elements

we want the corresponding nested functions and
nested elements to be calculated together

Consider such an operation:

ghci> Just (+3) <*> Just 9

We need the `Applicative` type class:
we must then implement the `pure` function, and
we must implement the `<*>` function

The `Applicative` type class
(remember, `f` is likely NOT a function!):

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

Maybe as an
Applicative

A function named with all special characters is
automatically a binary operator.

Implementation for the `Maybe` type:

instance Applicative Maybe where

pure = Just

Nothing <*> _ = Nothing

(Just g) <*> something = fmap g something

`pure = Just` is equivalent to `pure x = Just x`

Implementation

(Just g) <*> something = fmap g something

the last line may be difficult to imagine what is happening, but recall
the example we are working toward

we want to get the function `g` out of the first functor `(Just g)`
apply the function `g` to the second functor
(`something` contains elements that can have `g` applied to them)
by implementation, we are forced to have the two functors in exactly

this order with `<*>`

we cannot transpose the order for nested function and something

These implementations are already part of Haskell, so give
them a try:

Just (+3) <*> Just 9

pure (+3) <*> Just 9

Just (++ haha) <*> Nothing

Nothing <*> Just woot

there are many kinds of applicative functors
so, there are many kinds of results for `pure`
`pure (+3)` takes advantage of Haskells inference

what functor type will match with `Just 9`
in order to match on the left an expression `Just (+3)`

The Applicative Style

The order of operations using `<*>` is from left-to-right
when writing larger expressions of more than two functor values
this is called left-associative
then partially applied functions leftmost need more parameters

For example:
pure (+) <*> Just 3 <*> Just 5

notice that the above expression is similar in syntax as `(+) 3 5`
the given expression is equivalent to

`(pure (+) <*> Just 3) <*> Just 5`
and result of `(pure (+) <*> Just 3)` is `Just (3+)`

Applicative

The advantage of applicative types:
we can use functions on nested values within functors without

having to worry about what those functors are

`pure g <*> x <*> y <*> `

the `g` can have as many parameters as desired

each successive evaluation of `<*>` applies one more parameter

`pure g <*> x` is equivalent to `fmap g x`

(this is one of the applicative laws we will discuss later)

Instead of writing `pure g <*> x <*> ` we could just write
`fmap g x <*> `

however, there is an infix version of `fmap` to make expressions even
more concise with `<$>`

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

g <$> x = fmap g x

so, we could instead write `g <$> x <*> y <*> `
note that `g` is a function (a variable one)

Type Descriptions with <$> and <*>

Another example:

(++) <$> Just Doctor Strange <*> Just and the Multiverse of Madness

recall the type for concatenation `(++) :: [a] -> [a] -> [a]`

the `<$>` operation results in a partially applied function of type
Just (Doctor Strange ++) :: Maybe ([Char] -> [Char])

can you work out the type of the last functor in the example?

(Simranjit Singh)

Presentation 3

Simranjit Singh

playing with random, IO, and <$>

import System.Random

getList :: String -> [Int]

getList xs = foldr (
acc -> (read n :: Int) : acc) [] list

where list = words xs

(Simranjit Singh)

genNewList :: [Int] -> StdGen -> IO ()

genNewList xs gen =

(randNumber, newGen) =
randomR (1,3) gen :: (Int, StdGen)

secretCalc x

| x == 1= print $(+75) <$> xs

| x == 2= print $ (*5) <$> xs

| x == 3= print $ (`div` 3) <$> xs

| True=

putStrLn Something went terribly wrong

secretCalc randNumber

(Simranjit Singh)

gen <- getStdGenputStrLn “Enter a list of numbers (no commas or brackets):”input <- getLinelet list = getList input putStr “The list you entered was: “print(list) — == putStrLn $ show listputStrLn “I have now done a secret operation on your list”putStr “Your new list is: “genNewList list gen Lists (as applicative functors) We have the implementation of lists as applicative instance Applicative [] wherepure x = [x]fs <*> xs = [g x | g <- fs, x <- xs] notice that `pure` creates a singleton list always also notice that the `g` and `x` in the above create ALL possible combinations of functions from `fs` and values from `xs` the type of `<*>` restricted to lists:
`(<*>) :: [a -> b] -> [a] -> [b]`

since there are potentially many functions,
the implementation needs list comprehensions
(to facilitate all possible combinations)

with Lists

Lists with `<*>` will remind you when you apply it that you
will get every combination of result possible.

[(*0),(+100),(^2)] <*> [1,2,3]

The next example shows step-by-step evaluation of
multiple operations:

ghci> [(+),(*)] <*> [1,2] <*> [3,4]

[(+1),(+2),(*1),(*2)] <*> [3,4]

[4,5,5,6,3,4,6,8]

One more example:

ghci> (++) <$> [ha, he, hm] <*> [?, !, .]

[ha?,ha!,ha.,he?,he!,he.,hm?,hm!,hm.]

Nondeterministic
Computation

We can think of lists as nondeterministic computations.

a value such as `what` or `100` is deterministic
a value such as `[1,2,3]` may decide among its three elements
the `<*>` presents us with all possible outcomes on lists

Notice how we can use `<*>` to replace list
comprehensions:

ghci> [ x*y | x <- [1,2,3], y <- [4,5,6] ][4,5,6,8,10,12,12,15,18]ghci> (*) <$> [1,2,3] <*> [4,5,6]

[4,5,6,8,10,12,12,15,18]

Combining with `filter` is especially useful:

ghci> filter (> 10) $ (*) <$> [1,2,3] <*> [4,5,6]

[12,12,15,18]

IO (as an applicative functor)

Applicative

We look at the implementation of `IO` as an applicative

instance Applicative IO where

pure = return

s <*> t = do

return (g x)

`pure = return` works as an IO action ignoring the value passed in

`<*>` for `IO` has description `<*> :: IO (a -> b) -> IO a -> IO b`

implementation of `<*>` must then remove the `IO` context for both s
and t parameter values

`do` is needed to glue together multiple I/O actions into one

`return` will place the result `(g x)` back into an `IO` context

x <- (++) <$> getLine <*> getLine

putStrLn $ two lines concatenated: ++ x

the nested result of a `getLine` I/O action is a `String`

the order of the performed I/O action of each `getLine`
determines the order of the concatenated values

the result of `(++) <$> getLine <*> getLine`
is of type `IO b`
where `b` in this case is `String`

this is altogether one I/O action and we can
assign the yield to `x` as a `String` value

Functions (as applicative values)

The implementation for functions as applicatives:

instance Applicative ((->) r) where

CS: assignmentchef QQ: 1823890830 Email: [email protected]

Reviews

There are no reviews yet.

Only logged in customers who have purchased this product may leave a review.

Shopping Cart
[SOLVED] CS COMP 481, Week 7, Chapter 11
$25