C/CPS 506
Comparative Programming Languages Prof. Alex Ufkes
Topic 8: Actions in Haskell
Notice!
Obligatory copyright notice in the age of digital delivery and online classrooms:
The copyright to this original work is held by Alex Ufkes. Students registered in course CCPS 506 can use this material for the purposes of this course but no other use is permitted, and there can be no sale or transfer or use of the work for any other purpose without explicit permission of Alex Ufkes.
© Alex Ufkes, 2020, 2021 2
Course Administration
© Alex Ufkes, 2020, 2021
3
• •
Getting closer! Three more weeks. Don’t forget about the assignments!
© Alex Ufkes, 2020, 2021 4
© Alex Ufkes, 2020, 2021
5
Let’s Get Started!
Last Week
Define what it means for two Pt2 variables to be considered equal
Declare Pt to be an instance of Eq
© Alex Ufkes, 2020, 2021
6
Last Week
© Alex Ufkes, 2020, 2021
7
Last Week
© Alex Ufkes, 2020, 2021
8
Last Week
• Use string concatenation to create a pleasing visual output for Pt2.
• In doing so, we make use of show as defined for Floats
© Alex Ufkes, 2020, 2021 9
No longer need to derive Show, we’ve made our own
© Alex Ufkes, 2020, 2021
10
Pure Code, Monads, Actions
Every function is pure
Pure Functions: Functions that have no side effects.
A function can be said to have a side effect if it has an observable interaction with the outside world aside from returning a value.
• Modify global variable
• Raise an exception
• Write data to display or file
© Alex Ufkes, 2020, 2021
11
Write to Display
This was the very first thing we saw!
© Alex Ufkes, 2020, 2021
12
Haskell and I/O
• Haskell separates pure functions from computations where side effects must be considered
• Encodes side effect-producing functions with a specific type.
• We’ve already seen an example of this:
© Alex Ufkes, 2020, 2021 13
Haskell and I/O
• The actual act of printing to the screen does not occur as a result of a function call.
• Printing to the screen is an action.
• Actions are values, they have a type!
• putStrLn accepts a String argument.
• What it returns is an action of type IO()
© Alex Ufkes, 2020, 2021
14
Haskell and I/O
Speaking precisely:
• putStrLn is a function (no side effects!) o Takes a String as an input argument
o Returns an action, whose type is IO()
• When the IO() action is executed, it returns ().
• This can be read as an empty tuple.
• The action, when executed, produces a
side effect.
• The putStrLn function, strictly speaking,
does not.
© Alex Ufkes, 2020, 2021
15
Haskell and I/O
• Actions are values, just like strings and numbers.
• They are completely inert – they do not affect the
real world until executed.
© Alex Ufkes, 2020, 2021
16
Execute the action
Haskell and I/O
• We can also look at getLine
• getLine returns an IO action also
• It returns a String (IO String vs IO ())
• Ordinary Haskell evaluation doesn’t
cause actions to be executed.
• GHCi will execute actions for us, as
seen previously.
© Alex Ufkes, 2020, 2021
17
© Alex Ufkes, 2020, 2021
18
Just remember: actions are not functions. Functions are pure. Actions (specifically IO actions),
when executed are not.
Functions are evaluated, actions are executed or run
Actions are values. Actions can be returned by functions or passed as arguments.
Actions have a type. We’ve seen one so far, IO
Actions can only be executed from within other actions. A compiled Haskell program begins by executing a
single action – main::IO() https://wiki.haskell.org/Introduction_to_Haskell_IO/Actions
© Alex Ufkes, 2020, 2021 19
main::IO()
Recall: Every compiled Haskell program must have a main function:
• The main function is a single action
• This action is executed when the program is run.
• A Haskell program, by itself, is a single action that
is executed when we run the program.
© Alex Ufkes, 2020, 2021 20
Staying Grounded
• A Haskell program begins with the execution of a single action (main::IO()) o Functions that return actions are often incorrectly referred to as actions.
• From within this action, any number of additional actions can be executed
• Pure functions can also be called/evaluated from within actions!
• However – actions cannot be executed from within pure functions.
• If we try, Haskell will infer the type of the no-longer-pure function as an action.
© Alex Ufkes, 2020, 2021 21
Staying Grounded
© Alex Ufkes, 2020, 2021
22
• An action can be thought of as a recipe
• This recipe (in the case of IO) is a list of instructions that
would produce side effects.
• The act of creating this recipe does not have side effects.
• The recipe can be the output of a pure function.
• Same inputs to the function, same recipe.
IO Actions
We can use the <- operator to execute:
• The <- operator is used to pull out the result from executing an IO action.
• We can then bind a name to it.
• The return value of getLine is an action.
• Executing that action returns a String.
© Alex Ufkes, 2020, 2021
23
IO Actions
© Alex Ufkes, 2020, 2021
24
Combining Actions
We can do this using the do keyword:
When using the do keyword, we can execute one action per line.
© Alex Ufkes, 2020, 2021
25
Combining Actions do is syntactic sugar for >>
• >> says execute this, then this.
• If the first action produces a result,
it is discarded.
• What if we want to use the result?
• Use the >>= operator to pipe the result into the next action.
© Alex Ufkes, 2020, 2021
26
© Alex Ufkes, 2020, 2021
Combining Actions do is syntactic sugar for >>
• >> says execute this, then this.
• If the first action produces a result,
it is discarded.
• What if we want to use the result?
• Use the >>= operator to pipe the result into the next action.
• Here, we grab a string using getLine, and display it using putStrLn
• getLine returns an action that produces a string
• putStrLn takes string as an argument.
27
© Alex Ufkes, 2020, 2021 28
More Complicated
• Lambda function accepting 1 arg, name
• Received directly from the getLine above
© Alex Ufkes, 2020, 2021
29
Up until now, we’ve only really seen how to evaluate expressions (and execute actions, though we didn’t know that’s what we were doing) in GHCi.
Now we’re seeing how to write, compile, and execute a complete Haskell program containing actions.
© Alex Ufkes, 2020, 2021 30
© Alex Ufkes, 2020, 2021 31
Actions & Functions
• Use <- when binding the result of executing an action
• Use let and = when binding the result of an expression
© Alex Ufkes, 2020, 2021
32
Problem?
• We are executing actions in main
• Its return type must be an action.
• The value of a “do” block is the value
of the last expression evaluated
© Alex Ufkes, 2020, 2021
33
return ()
• Return is NOT a keyword; it is a function.
• It wraps data in an IO monad.
• In this case, we’re wrapping an empty tuple ()
© Alex Ufkes, 2020, 2021
34
© Alex Ufkes, 2020, 2021 35
Monads
• Here we get a clue about monads
• Monad is actually a type class
• This syntax resembles other type
classes we’ve seen.
© Alex Ufkes, 2020, 2021
36
Monads
Monad is a typeclass:
We’ve seen these:
• >>= passes the result on the left into the function on the right.
• >> Ignores the result on the left • return wraps data in a monad
© Alex Ufkes, 2020, 2021
37
© Alex Ufkes, 2020, 2021
38
“Monadic”
“type xxx is a Monad”
Monad Jargon
Pertaining to monads. A monadic type is an instance of type class Monad (IO, for example)
xxx is an instance of type class Monad. xxx implements >>, >>=, and return
“action”
By the way:
• •
It turns out that Monads are good for things other than side effect-producing IO. We’ll see an example coming up.
Another name for a monadic value
>>= VS >>
Chains actions together. Result of left side is given as input to the right side.
Chains actions together. Ignore result of left side.
a >> b VS a >>= \_ -> b
© Alex Ufkes, 2020, 2021 39
Where the magic happens
>>= >>
>> can be defined in terms of >>=
Non-main Example
• Function that reads in a number
• Returns true if < zero, false otherwise
• Problem: We’re executing IO actions
• The return type cannot be Boolean
• It must be IO something
© Alex Ufkes, 2020, 2021
40
Non-main Example
What if we still want to get a Boolean back?
• The return type of positive is an IO action.
• When executed, that action produces a Bool
© Alex Ufkes, 2020, 2021
41
Extract the value from the action using <-
Calling Pure Code
We can still call pure functions from actions:
© Alex Ufkes, 2020, 2021
42
Best Practice
Separate pure code into its own functions:
Pure!
!Pure
© Alex Ufkes, 2020, 2021
43
When looking at main, Haskell looks rather imperative...
Even at this point, however, Haskell sets itself apart from imperative languages.
It creates a separate type of programming construct for operations that produce side effects
We can always be sure of which parts of the code will alter the state of the world, and which parts won’t.
Imperative languages do no such thing, and make no guarantees whatsoever regarding function purity
© Alex Ufkes, 2020, 2021
44
https://wiki.haskell.org/Introduction_to_Haskell_IO/Actions
© Alex Ufkes, 2020, 2021 45
https://wiki.haskell.org/Monad
“The essence of monad is thus separation of composition timeline from the composed computation's execution timeline, as well as the ability of computation to implicitly carry extra data”
“This lends monads to supplementing pure calculations with features like I/O, common environment, updatable state, etc.”
Not just for I/O! Not just for side effects!
© Alex Ufkes, 2020, 2021 46
Maybe Monad
Monads were originally introduced for IO operations
It turns out, as a construct, they are useful for modelling other things as well!
For example: exception handling, non-determinism, etc.
© Alex Ufkes, 2020, 2021 47
Maybe Monad
© Alex Ufkes, 2020, 2021
48
Represents a computation that might not produce a result Computations that might “go wrong”
For example – calling tail with a list that might be empty
We can use Maybe to create a safety wrapper for functions that might fail, depending on input.
Maybe Monad
Maybe:
• Custom data type
• Instance of Monad
• Maybe a can be
Nothing, or Just a
© Alex Ufkes, 2020, 2021
49
We’ve seen this before...
Pt can take the value Pt3 Float Float Float, or Pt2 Float Float
Maybe can take the value Nothing or Just a
© Alex Ufkes, 2020, 2021
50
Maybe Monad
• Define safe functions for head and tail. o Using guards - |
• Instead of failing on empty lists, evaluate to Nothing.
• If a tail or head can be found, evaluate to Just head x, or Just tail x
• Just head? Just tail?
© Alex Ufkes, 2020, 2021
51
Maybe Monad
• When we call safeHead on a non- empty list, we don’t get the head.
• We get Just head
• This is the head of the list
wrapped in a Maybe monad.
• Remember that Maybe is a type,
just like our custom Pt type
© Alex Ufkes, 2020, 2021
52
Maybe Monad
© Alex Ufkes, 2020, 2021
53
Unwrap Just a?
© Alex Ufkes, 2020, 2021
54
Just like pulling values out of our Pt data type!
© Alex Ufkes, 2020, 2021 55
Unwrap Nothing?
If you need to decide on some numeric literal for Nothing, you can do so
© Alex Ufkes, 2020, 2021
56
Why Not This?
safeHead x
| (length x > 0) = head x | otherwise = 0
Zero as error code
• What if head of list is actually 0?
• Static typing means list passed to
safeHead can only be instance of Num!
• Just can contain anything
• Nothing is useful as an “error” value
© Alex Ufkes, 2020, 2021
57
Using Maybe
Maybe can make code safer by gracefully dealing with failure. Should we use Maybe for everything?
No. Not everything has a chance to fail. Wrapping the return type of (x > y) in Maybe only serves to obfuscate your code.
© Alex Ufkes, 2020, 2021 58
Consider a Lookup Table We have a list of tuple pairs:
book = [ (“Alex”, 555),
(“John”, 444),
(“Tim”, 333),
(“Mark”, 222),
(“Bill”, 111) ]
• We want to search the table for a name
• If found, return its number
• If not found, return…. ?
© Alex Ufkes, 2020, 2021
59
Use lookup
• It’s not obvious what to return if an item is not found.
• We might return -1, or 0, but what if these are legitimate
values that could be returned if a key was found?
• In Haskell we can use Maybe for this.
• Preferable to an arbitrary default value, or an exception.
© Alex Ufkes, 2020, 2021
60
Just 555 VS 555
• We would like to extract the numeric value 555
• Can’t do arithmetic on
Just 555, for example.
© Alex Ufkes, 2020, 2021
61
Just 555 VS 555
If we have a Just value, we can see its contents and extract through pattern matching
© Alex Ufkes, 2020, 2021
62
Use lookup
• Value from book1 is the key to book2
• Value of book2 is the key to book3
• We want the value from book3
• Not every value in book1 corresponds to a key in book2.
• Not every value in book2 corresponds to a key in book3
• There are several ways a lookup could fail
© Alex Ufkes, 2020, 2021
63
• What happens if lookup fails to find a match?
• We saw that it returns Nothing
• What happens if we try to
lookup Nothing?
© Alex Ufkes, 2020, 2021 64
Cascading Failure
© Alex Ufkes, 2020, 2021
65
Is the same as:
Cascading Failure
• When the first argument to (>>=) is Nothing, it just returns Nothing while ignoring the given function
• This causes failure to cascade
• If the first lookup fails, Nothing is
passed into the second >>=.
• The failure then cascades into the
third >>=, and is returned.
• After the first Nothing, subsequent
>>= pass that Nothing to each other
© Alex Ufkes, 2020, 2021
66
When the first argument to (>>=) is Nothing, it just returns Nothing while ignoring the given function
© Alex Ufkes, 2020, 2021
67
Haskell Tutorials/References:
https://en.wikibooks.org/wiki/Yet_Another_Haskell_Tutorial http://cheatsheet.codeslower.com/CheatSheet.pdf
© Alex Ufkes, 2020, 2021 68
Moving on…
…to imperative.
Rust is an imperative language. However, we’ll see many cool features that remind us of the functional languages we’ve seen.
© Alex Ufkes, 2020, 2021 69
© Alex Ufkes, 2020, 2021 70