Welcome to CPSC 312!

Lecture #1
Play with the lecture code on replit!
Complete the associated in-class exercises.

Table of Contents
module Lecture1 where
import System.Random
import Data.List

1 Today’s Notes, 2021/09/10

  1. Quick course status update (waitlist, rapidly changing world, etc.)
  2. In-class Exercises 1 is on PrairieLearn and due Thu Sep 16. If you get something wrong, just try it again. Use these exercises to learn in class and prepare for the quiz!
  3. Quiz 1 is coming up on Friday September 17 (during class time).
  4. To prepare for that, you must fill out this survey about whether you have a device for the quiz by end of day Monday. (Waitlisted students: working on a solution for you now!)
  5. Assignment 1 is due Sep 23 on PrairieLearn. Submit by Sep 16 for a chance at some extra credit.
  6. To reduce Covid transmission risk, please sit in the same area each lecture when possible.
  7. Before next class:
    • Finish the Lecture 1 in-class exercises, if you haven’t already!
    • work through the rest of CIS 194 Lecture 1

2 Quick Note on Health and Masks

Masks are mandated in class. I’m immune suppressed. For me and others at-risk or close to those who are at-risk, please wear your mask, including over your nose.

However, there are medical circumstances that merit exemptions. To seek such accommodation, please contact the Centre for Accessibility at info.accessibility@ubc.ca.

While we’re on the subject: please get vaccinated if you can (it’s another line of defence for those around you who cannot!) and please do not come to class if you’re sick (there are so many good online options for us!).

3 Showing Off in Haskell

Haskell is a pure, lazy, statically-typed functional programming language with powerful type inference. You’ll learn about a lot of that in the reading. Today, we’re just going to show off with a bit of what that means.

For now, we need to know that: a Haskell program is just an expression to be evaluated, not a series of statements to execute. Programs build up complex expressions to be evaluated by giving names to and using smaller expressions (functions and variables).

3.1 Quicksort in English

Quicksort is a beautiful sorting algorithm. Here’s a simple version. To quick sort a list:

  1. If the list is empty, simply return it.
  2. Otherwise, let p be the first element of the list. Construct two sublists: everything else that is < p and everything else that is >= p. Quick sort each sublist recursively. Then return the quick-sorted < p list, followed by a list containing just p, followed by the quick-sorted >= p list.

Now, we could read all that. Or, we could sing it instead.

3.2 Quicksort in Haskell

First, let’s write Quicksort in Haskell. The first line is the signature. The second is the function definition… which is undefined so far. We’ll finish it!

-- qsort xs returns xs but in sorted order.
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (p:ps) = qsort [small | small <- ps, small < p] ++ 
               [p] ++
               qsort [large | large <- ps, large >= p]

What did we see? Haskell:

3.3 Test Data for Quicksort

Let’s test qsort a bit: on [], on [2, 4, 6, 0, 1], and on "mindblowing". We’ll use ghci for its REPL (read-eval-print-loop) to try these out. (Exercise!)

Note: we stopped here on day 1.

Next, let’s try qsort on something bigger. We’ll need some input to call it on:

-- | @genList cap seed@ returns an unending list of (pseudo-)random
-- @Int@s in the range @[0, cap)@, using @seed@ for the random
-- generator. (Don't worry about the implementation for now!)
genList :: Int -> Int -> [Int]
genList cap seed = map (`mod` cap) (randoms (mkStdGen seed))

We could try out qsort with:

infResults :: [Int]
infResults = qsort (genList 10000 0)

But wait. How many elements are we sorting?! Try infResults at the ghci REPL. (Wait… why didn’t that run forever before we typed infResults?)

(Exercise!)

3.4 Finite Test Data

Here’s code for a random list of n numbers in the range [0, n), plus a sample bigList to work with.

randList :: Int -> [Int]
randList n = take n (genList n 0)

bigList :: [Int]
bigList = randList 1000000

The take function uses parametric polymorphism. take n lst produces the first n elements of the list lst, without caring what type of value is in lst. Its type is Int -> [a] -> [a]: given an Int and a list of any type of value a, return a list of (that same type of) a values.

You can run bigList at the REPL, but it prints for a long time. Instead, get just the last element of bigList:

:set +s       -- turn on performance information
last bigList

How long did that take? Try last bigList again. How long did it take the second time? (Exercise!)

Haskell uses lazy evaluation. Loosely: it evaluates expressions only when forced to, e.g., by you the user. (Once it evaluates a variable’s expression, it caches it to avoid evaluating it again.)2

3.5 Laziness Goes Deep

Let sort bigList but just look at the last result. Run last (qsort bigList).

How long did that take?

How about take 100 (qsort bigList)? (That’s the first 100 elements of the sorted list.) How long did that take? Why?!?

(Exercise!)

4 Showing off in Prolog

As strange as Haskell is, Prolog is stranger. That makes it a little easier to show off.

Above, we relied on Haskell’s ++ operator to append two lists. It’s defined as:

(++) :: [a] -> [a] -> [a]
[]     ++ ys = ys
(x:xs) ++ ys = x : xs ++ ys

Let’s redefine this in Prolog. We’ll call it append.

4.1 Functions That Are (Not) Functions

Haskell’s “functions” aren’t like Java “functions” because Java “functions” are not functions. You can call a Java function twice with the same arguments and (because it sets variables, reads input from the user, accesses a datasource via REST, etc.), it can return two different things or move a robot or something whacky like that.

That is not how mathematical functions work. So, Haskell does many clever things to use real mathematical functions while still being able to read input from the user and use REST APIs and such.

Prolog really doesn’t do normal “functions”.

append doesn’t return the result of appending two lists. Instead, it is a (logical) predicate of three values that describes the relationship in which the third value is the result of appending the first two.

The Haskell code above kind of tells us our cases. append(Xs, Ys, Zs) is true when:

  1. Xs is just the empty list and Ys = Zs.

  2. Xs and Zs start with the same element (one element down!), and append is true for the rest of Xs, Ys, and the rest of Zs.

4.2 Append in Prolog

Let’s write that together in Prolog.

Notes: append(X, Y, Z) :- stuff(over, here) means “if stuff(over, here) is true, then append(X, Y, Z) is true”. Also, anything that starts with an uppercase letter is a variable; anything else is just an atom (like a symbol or a bit like a string).

% Fill in the blanks on append/3 so that append(Xs, Ys, Zs)
% is true if appending together Xs and Ys results in Zs.
append([], Ys, Ys).
append([X|Xs], Ys, [X|Zs]) :- append(Xs, Ys, Zs).

(Exercise!)

4.3 What a Predicate Can Do, Forwards and Backwards

Let’s try that in the swipl (SWI-Prolog) REPL.

It’s downright strange what we can do with append now that we’ve defined it. Remember that append(Xs, Ys, Zs) just describes the circumstances under which it is true that Zs is the result of appending Xs and Ys.

So, start by trying append([1, 2], [3, 4, 5], Zs). Then, start asking for stranger things.

(Exercise!)

We’ll try to show off two interesting features of Prolog:

5 Today’s Notes, 2021/09/08

  1. Let’s spend a few minutes with the Syllabus. (Especially important: do not come to class if you are sick!!)
  2. In-class Exercises 1 is on PrairieLearn and due Thu Sep 16. If you get something wrong, just try it again. Use these exercises to learn in class and prepare for the quiz!
  3. Quiz 1 is coming up on Friday September 17 (during class time).
  4. Assignment 1 is due Sep 23 on PrairieLearn. Submit by Sep 16 for a chance at some extra credit.
  5. To reduce Covid transmission risk, please sit in the same area each lecture when possible.
  6. Before next class:
    • access PrairieLearn and Piazza from the Resources page
    • get Haskell set up so you can try things out; the Resources page has help
    • work through CIS 194 Lecture 1 up to the “GHCi” section

6 Appendix

Monday Morning Haskell recently released a In-Place QuickSort in Haskell video. If you haven’t already studied Haskell, the latter 2/3 of the video is (very) challenging. If you want to give it a try, you can think of a “monad” for this context as a special structure for assembling computations-that-produce-values-when-run that ensures you don’t accidentally mistake those computations with the values they produce.

Here are some longer/different versions of the code above.

-- | @qsort xs@ should produce a sorted version of xs.
--
-- Haskell's doctest can turn these @>>>@ examples into
-- runnable tests. It actually also has a powerful testing
-- system where you assert logical expressions as properties.
--
-- >>> qsort []
-- []
--
-- >>> qsort [2, 4, 6, 0, 1]
-- [0,1,2,4,6]
--
-- >>> qsort "mindblowing"
-- "bdgiilmnnow"
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (p:ps) =
   qsort [s | s <- ps, s < p]
   ++ [p]
   ++ qsort [b | b <- ps, b >= p]

-- | @genList cap seed@ takes a seed value and returns an unending
-- list of (pseudo-)random Int values in the range [0, cap).
--
-- Here's a more idiomatic version that "chains" together existing
-- functions using the function composition operator @.@.
genList :: Int -> Int -> [Int]
genList cap = map (`mod` cap) . randoms . mkStdGen

  1. List comprehensions actually demonstrate some of Haskell’s fascinating type classes that support common and flexible ways to build large computations out of smaller ones.↩︎

  2. ghc did not cache last bigList because we didn’t attach that to a name. It did cache the bigList because in demanding the value of last bigList, we also demanded that it evaluate bigList all the way to its last element.)↩︎