UNB/ CS/ David Bremner/ teaching/ cs2613/ labs/ CS2613 Labs

Rubric

Criteria Excellent (6) Good (5) OK (3-4) Needs improvement (0-2)
Content Good, plus at least one of the answers demonstrates careful thought or genuine insight. Good answers to most or all of the questions from the journal page. Answers to most or all of the questions from the journal page. Answers few or none of the questions from the journal page.
Technical Skills / Presentation

Good, plus use of markdown / scribble to enhance the visual effects, or interesting/useful use of hyperlinks.

No spelling or grammar problems. Writing style is appropriate.

Pushed to git, builds, has reasonable git commit messages, markdown or scribble formatting is not obviously broken.

No spelling mistakes or obvious grammar problems.

Builds, in a sane location, pushed to git. Does not build and or / not pushed to git.

Approximate translation of scores

11 - 12
A+
10
A
8 - 9
B
7
C
6
D
1-5
F

Labs

Lab 1

Before the lab

Before every lab in this course, you will be given tasks to complete. These will generally be easy tasks like watching videos, but you need to complete them in order to keep up with the class.

Command Line Familiarity Check

  1. In an FCS linux lab (remote or locally) log in, and open a terminal.

  2. Make a directory *

  3. Create a file in that directory using one of the available text editors

  4. Now clean up, removing the file and the directory.

If any of this was new to you, then please take the time to go through parts 1 to 5 of the Learning the Shell Tutorial.

Read the course syllabus.

The Course Syllabus is available on line. Please read it, and bring any questions you have about it to the first lab.

Background Reading

For every lab there will be some related reading. I'll point these out as we go through the lab, but I'll also collect them at the start of the lab in case you want to get a head start (or refer back to them later).


About the course

Time
10 minutes
Activity
Q&A, discuss course syllabus.
  1. What's similar to other CS courses you've taken?

  2. What's different?

  3. Key point, how are labs evaluated?

Software Setup

Time
10 minutes
Activity
Group work
Summary
Install software needed to work with frog
  1. Open a terminal.

  2. Install frog and the other tools we need via the following command (throughout the course, $ at the beginning of a line will indicate a shell prompt, you don't need to type it.):

      $ raco pkg install --auto unb-cs2613
    
  3. Install the python library pygments, used by frog for syntax highlighting

     $ python -m ensurepip --upgrade
     $ python -m pip install pygments
    

There are an unfortunate number of warnings here, but we can ignore them for now.

Getting Started with Frog

Time
30 minutes
Activity
Individual work
Summary
Getting started with frog

We'll be using frog frog to keep a journal of what we learn in this course.

  1. Open a terminal.

  2. Make a directory called cs2613 that will keep all of your work (note that case and spaces matter in Linux and this is not the same as CS2613 or CS 2613. For the rest of this course we will assume it is directly under your home directory. The shortcut ~/cs2613 will refer to this directory in the lab texts and in the shell.

  3. Make a directory journal inside ~/cs2613. Inside ~/cs2613/journal, run

     $ raco frog --init
    
  4. Try viewing the newly created blog with

     $ raco frog -bp
    
  5. Start a new blog page for today's lab, and delete the fake entry created by the frog setup command. Note that you may have to refresh the browser after running raco frog -bp.


Setting up a git repo

Time
30 minutes
Activity
Individual work
Summary
This is where we create the git repository used for the rest of the term to hand things in.
  1. Change to ~/cs2613. This directory should have one subdirectory called journal, which has the results of your experiments above with frog.

  2. Create the git repository

     $ git init -b main
    

    Git will reply with something like

     Initialized empty Git repository in /home1/ugrads/$username/cs2613/.git/
    

    You’ve now initialized the working directory — you may notice a new directory created, named ".git". You should mentally replace "$username" with whatever the login name is that you use to log into the FCS linux machines.

  3. Read the git-quickref page, and follow the initial configuration steps there.

    • note that the "--wait" option for gedit is important here.
  4. Next, tell Git to take a snapshot of the contents of all files under the journal, with git add:

    $ git add journal
    
Notes
Many revision control systems provide an add command that tells the system to start tracking changes to a new file. Git’s add command does something simpler and more powerful: git add is used both for new and newly modified files, and in both cases it takes a snapshot of the given files and stages that content in the index, ready for inclusion in the next commit.

This snapshot is now stored in a temporary staging area which Git calls the "index". You can permanently store the contents of the index in the repository with git commit:

   $ git commit

This will open and editor and prompt you for a commit message. Enter one and exit the editor. You’ve now stored the first version of your project in Git. See §5.2 of Pro Git for some hints about writing good commit messages. As mentioned in the class git policy, you will be marked on your commit messages, so you may as well get started with good habits.

Pushing to a central repo

Summary
Learn how to upload your work to a server
Time
20 minutes
Activity
Individual work
Notes
You absolutely have to understand this before continuing in the course, since all marks in the course will be based on work pushed to the coursegit repos.

Since we are using the FCS git repositories there is an existing repository for all students who registered early enough. If it turns out there is no repository for you, you may need to do the last step later.


Before next lab

  • Make sure you can push to coursegit from an FCS linux machine. A good way to test this is to push a (work in progress) journal entry for Lab 1.
  • Test that your push was successful by cloning the repo, per the instructions in faq. After cloning, change directory to journal/ and run raco frog -bp
  • Read about how to write a good commit message in §5.2 of Pro Git
  • Read Section 2 of FICS

Lab 2

Before the lab

  • Make sure you can push to coursegit from an FCS linux machine. A good way to test this is to push a (work in progress) journal entry for Lab 1.
  • Test that your push was successful by cloning the repo, per the instructions in faq. After cloning, change directory to journal/ and run raco frog -bp
  • Read about how to write a good commit message in §5.2 of Pro Git
  • Read Section 2 of FICS

Background


Git Tutorial Continued

Making Changes in git

Time
20 minutes
Activity
Individual work
Summary
Get some practice commiting your changes to git.

Having at look at our test post from Lab 1, we can observe the post template adds a bunch of stuff related to social media. Let's suppose that for our cs2613 journal we want a more minimal look.

Start by finding the right files to edit with

$ git grep disqus

git grep is a very useful (and fast!) tool to find occurrences of strings in your git repository. Notice in the output there are some files created by frog; we will clean those up later. Find the template file (under _src) and edit it to remove the undesired social media links. Check the results with

$ raco frog -bp

You might notice one more link to twitter in a different template file. Feel free to remove that one as well.

Use git add to stage your changes:

$ git add file1 file2 file3

(where file1, file2 and file3 are actual files you modified). You are now ready to commit. You can see what is about to be committed using git diff with the --cached option:

$ git diff --cached

(Without --cached, git diff will show you any changes that you’ve made but not yet added to the index.) You can also get a brief summary of the situation with git status:

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   file1
#       modified:   file2
#       modified:   file3
#

It’s a good idea to begin the commit message with a single short (less than 50 character) line summarizing the change, followed by a blank line and then a more thorough description. The text up to the first blank line in a commit message is treated as the commit title, and that title is used throughout Git.

If you need to make any further adjustments, do so now, and then add any newly modified content to the index. Finally, commit your changes with:

$ git commit

This will again prompt you for a message describing the change, and then record a new version of the project.

Alternatively, instead of running git add beforehand, you can use

$ git commit -a

which will automatically notice any modified (but not new) files, add them to the index, and commit, all in one step. Keep in mind that you will be marked on the logical structure of your git commits, so you are better off using git add to explicitely choose what changes to commit.

Cleaning up generated files

Time
15 minutes
Activity
Individual work
Summary
Get some practice commiting your changes to git.

A common phenomenon in software development is the existence of generated files. These files are created by some tool, typically based on some source files. In general it is a bad idea to track generated files in version control because they introduce spurious changes into history. We'll look at this more later, but for now let's try to clean up. We can find out what files are generated .e.g. by consulting the frog docs. Let's first try a shortcut. Run

$ cd ~/cs2613/journal
$ raco frog --clean

To find out what changed, run

$ git diff --stat

All going well, you will see a bunch of deletions. We can tell git to make those deletions permanent in several ways. It turns out that there is a handy option to git add that tells git to stage all of the changes in the working tree. Try to figure out which option it is.

When you are satisfied with the changes, run git commit.

It will turn out that this is not all of the generated files; we can use git rm to clean up further as we find more.

Make sure you run

$ raco frog -bp

To make sure the the blog still works after your deletions.

Viewing project history

Time
15 minutes
Activity
Small group discussion, presenting work to the group, peer feedback
Summary
Reinforce idea of commit message quality
  1. At any point you can view the history of your changes using

     $ git log
    

    Use this command to verify that all the changes you expected to be pushed to the server really were.

    If you also want to see complete diffs at each step, use

     $ git log -p
    

    Often the overview of the changes is useful to get a feel for each step

     $ git log --stat --summary
    
  2. In most projects, you have to share commit messages to (at least) the same people who view your source code. Share your "best commit" message with one or more of your neighbours.

  3. Find something positive to say about the other commit messages you are reading.

  4. Find a constructive improvement with one of the other messages. Don't be mean, people have varying levels of experience with this.


Getting started with racket

Hello Racket World

Time
15 Minutes
Activity
Group walkthrough
#lang htdp/bsl
"hello world"
(* 6 7)

Command Line

DrRacket: A Racket Specific IDE

Racket Expressions.

Time
20 minutes
Activity
Small groups
#lang htdp/bsl
(define y 18)
(define (t1 x)
  (or (= x 0) (< 0 (/ y x))))
(define (t2 x)
  (or (< 0 (/ y x)) (= x 0)))
(define (t3 x)
  (and (= x 0) (< 0 (/ y x))))
(define (t4 x)
  (or (< 0 (/ y x)) (not (= x 0))))

Racket functions

Time
20 minutes
Activity
Individual work
(check-expect (middle-of-three 1 2 3) 2)
(check-expect (middle-of-three 2 1 3) 2)
(check-expect (middle-of-three 1 3 2) 2)

Before Next Lab

Reading

Git Practice: Working with multiple git repos

Time
15-30 minutes
Activity
Individual work, outside scheduled lab time.

Cloning

  1. Open a terminal.

  2. Make a directory lab2-scratch somewhere outside the cs2613 git repository you created earlier.

  3. Now move to the lab2-scratch directory, and make a clone of the central repo.

     $ git clone -b main https://$username@vcs.cs.unb.ca/git/cs2613-$username cs2613-clone
    

    This creates a new directory "cs2613-clone" containing a clone of your repository. Notice that in general this is a good way of checking that your work is properly submitted. The TA and Prof will do exactly this cloning step in order to mark your work. The clone is on an equal footing with the original project, possessing its own copy of the original project’s history.

Sharing changes with a central repo

Optional, but useful if you plan to run git on your own computer

  1. Open a terminal

  2. Navigate to the ~/lab2-scratch/cs2613-clone/journal directory.

  3. create a new blog entry, and commit it.

  4. Push your changes back to the central repository.

     $ git push origin main
    
  5. Change directory to your original directory ~/cs2613. Bring in the changes you made

     $ git pull origin main
    

This merges the changes from the central copy of "main" branch into the current branch. If you made other changes in the meantime, then you may need to manually fix any conflicts.

The "pull" command thus performs two operations: it fetches changes from a remote branch, then merges them into the current branch.

Questions

Here are some questions we will be discussing at the beginning of L03.

  • What is a remote?
  • What is merging?
  • What is a conflict?

Git next steps

Congratulations, you now know enough git to finish this course.

There is lots more to learn, particularly about branching and collaboration. If you want to get a big(ger) picture view, a good place to start is Git Concepts Simplified.

Lab 3

Before the lab

Reading

Git Practice: Working with multiple git repos

Time
15-30 minutes
Activity
Individual work, outside scheduled lab time.

Cloning

  1. Open a terminal.

  2. Make a directory lab2-scratch somewhere outside the cs2613 git repository you created earlier.

  3. Now move to the lab2-scratch directory, and make a clone of the central repo.

     $ git clone -b main https://$username@vcs.cs.unb.ca/git/cs2613-$username cs2613-clone
    

    This creates a new directory "cs2613-clone" containing a clone of your repository. Notice that in general this is a good way of checking that your work is properly submitted. The TA and Prof will do exactly this cloning step in order to mark your work. The clone is on an equal footing with the original project, possessing its own copy of the original project’s history.

Sharing changes with a central repo

Optional, but useful if you plan to run git on your own computer

  1. Open a terminal

  2. Navigate to the ~/lab2-scratch/cs2613-clone/journal directory.

  3. create a new blog entry, and commit it.

  4. Push your changes back to the central repository.

     $ git push origin main
    
  5. Change directory to your original directory ~/cs2613. Bring in the changes you made

     $ git pull origin main
    

This merges the changes from the central copy of "main" branch into the current branch. If you made other changes in the meantime, then you may need to manually fix any conflicts.

The "pull" command thus performs two operations: it fetches changes from a remote branch, then merges them into the current branch.

Questions

Here are some questions we will be discussing at the beginning of L03.

  • What is a remote?
  • What is merging?
  • What is a conflict?

Git next steps

Congratulations, you now know enough git to finish this course.

There is lots more to learn, particularly about branching and collaboration. If you want to get a big(ger) picture view, a good place to start is Git Concepts Simplified.


Git Questions

Time
10 Minutes
Activity
Group discussion

Some questions from before:

Setup

The DrRacket stepper

Time
30 min
Activity
Individual work

Semantics

Time
25 min
Activity
Small Groups
Summary
new evaluation rules for and and or

As you read in FICS unit 3, we can understand evaluation ("running") of Racket programs as a sequence of "reductions" or "substitutions". These rules are similar to the reduction steps in the DrRacket stepper.

The stepper uses the following rules for and and or (notice that these rules enforce short circuit evaluation)

(and true exp2 ...) => (and exp2 ...)
(and false exp2 ...) => false
(or true exp2 ...) => true
(or false exp2 ...) => (or exp2 ...)
(and) => true
(or) => false

Following Exercise 7, write a new set of rules that requires at least two arguments for and and or. The rules are for human consumption; you can write them as comments in DrRacket. You can write "exp1 exp2 ..." to mean at least 2 expressions.

Discuss your answers with a your group, and try a couple evaluation small examples by hand using your rules.

Test Coverage

Time
25 min
Activity
Individual work

Unit testing is an important part of programming, and has inspired something called test driven development.

If you have extra time


Before Next Lab

Reading for next lab

On your own

Time
20 min
Activity
Independent research

See if you can come up with answers to the following questions for next time.

  • The programming languages we will study this term are all dynamically typed. This means that not only the value but also the type of variables can change at runtime. Why does this make testing even more important?

  • What kind of software problems is testing not well suited to find?

  • Why might mutable state (e.g. instance variables in Java) make writing unit tests harder?

Lab 4

Before the lab

Reading for next lab

On your own

Time
20 min
Activity
Independent research

See if you can come up with answers to the following questions for next time.

  • The programming languages we will study this term are all dynamically typed. This means that not only the value but also the type of variables can change at runtime. Why does this make testing even more important?

  • What kind of software problems is testing not well suited to find?

  • Why might mutable state (e.g. instance variables in Java) make writing unit tests harder?


Questions from last time

Time
10 Minutes
Activity
Group discussion

Setup

Simulating Natural Numbers I

Time
30 minutes
Activity
Individual work
Summary
Learn about structures and recursion.

Write the times function from Exercise 11 in FICS. You can (and should) use the following code from the linked discussion

#lang htdp/bsl
(define-struct Z ())
(define-struct S (pred))
(define (pred nat)
  (cond
    [(Z? nat) (error "can't apply pred to Z")]
    [(S? nat) (S-pred nat)]))

(define (plus nat1 nat2)
  (cond
    [(Z? nat1) nat2]
    [(S? nat1) (make-S (plus (S-pred nat1) nat2))]))

Here is the template for structural recursion on (simulated) natural numbers. See the linked text (or the plus function just above) for how to add a second "carried-along" parameter.

(define (my-nat-fn nat)
  (cond
    [(Z? nat) ...]
    [(S? nat) ... (my-nat-fn (S-pred nat)) ...]))

Here are some tests to get you started. As always, try to have as complete test coverage as possible. Depending on how you choose the language in DrRacket, the line (define-struct S (pred)) may show partial coverage; you can ignore this for this lab.

;; 0 * 0 = 0
(check-expect (times (make-Z) (make-Z)) (make-Z))
;; 0 * 1 = 0
(check-expect (times (make-Z) (make-S (make-Z))) (make-Z))
;; 2 * 1 = 2
(check-expect (times (make-S (make-S (make-Z)))
                       (make-S (make-Z)))
              (make-S (make-S (make-Z))))

You may find it helpful to refer back to your solution from L03.

Simulating Natural Numbers II

Time
30 minutes
Activity
Individual work
Summary
Learn about structures and recursion.

Write the compare function from Exercise 11 in FICS. Your function compare should use the struct definitions Z and S, and pass the following tests

;; 0 = 0
(check-expect (compare (make-Z) (make-Z)) 'equal)
;; 0 < 1
(check-expect (compare (make-Z) (make-S (make-Z))) 'less)
;; 1 > 0
(check-expect (compare (make-S (make-Z)) (make-Z)) 'greater)
;; 2 > 1
(check-expect (compare (make-S (make-S (make-Z)))
                       (make-S (make-Z))) 'greater)

Structural recursion, on numbers

Time
20 minutes
Activity
Individual work
Summary
Learn about structural recursion.

Use structural (note that structural here is only indirectly related to Racket structs) recursion on natural numbers (not the simulated ones from above, but regular Racket numbers like 1, 42, and 1337) to define a function (sum-factors n max-factor) that sums all factors of n (including 1) no larger than max-factor

Recall the template for structural recursion on natural numbers:

(define (my-nat-fn n)
  (cond
    [(zero? n) ...]
    [(positive? n) ... (my-nat-fn (sub1 n)) ...]))

Try to use only one recursive call to sum-factors like in the template. Keep in mind that the n in the template might be a different parameter of your function. You can use the builtin function remainder to test for divisibility.

The following tests should pass

;; 1+2+3 = 6
(check-expect (sum-factors 6 5) 6)
;; 1+2+4+7+14 = 28
(check-expect (sum-factors 28 27) 28)

Before Next Lab

Lab 5

Before the lab


Setup

Substitute in a list

Time
25 minutes
Activity
Individual work
Summary
Learn about lists and recursion.

Complete FICS Exercise 13.

Note the template for structural recursion.

(define (my-list-fn lst)
  (cond
    [(empty? lst) ...]
    [(cons? lst) ... (first lst) ...
                 ... (my-list-fn (rest lst)) ...]))

Here is a test case based on the linked test

(check-expect
 (substitute 3 "three" (list "four" 3 4 "three" 3))
 (list "four" "three" 4 "three" "three"))

Uniquifying lists

Time
25 minutes
Activity
Individual work
Summary
Learn about lists and recursion.

Complete FICS Exercise 15. Although probably not intended by the original question, use the built-in BSL function member? to simplify your solution. Otherwise use only the constructs specified in Exercise 14, and structural recursion. Here is one test case derived from the linked text.

(check-expect
 (unique-right
  (list 1 4 2 1 5 4))
 (list 2 1 5 4))

Add an element to all lists

Time
25 minutes
Activity
Individual work
Summary
Learn about lists and recursion.

Recall the template for structural recursion on lists

(define (my-list-fn lst)
  (cond
    [(empty? lst) ...]
    [(cons? lst) ... (first lst) ...
                 ... (my-list-fn (rest lst)) ...])

Use this template to write a function cons-all that adds a given element to the front of each given sublist. This is related to FICS Exercises 23-25 (but you only need to write cons-all, not do those exercises).

The following test case illustrates the use of cons-all. You will need to use the language #lang htdp/bsl+ or "Beginning Student with List Abbreviations" or replace the use of ' in the following.

(check-expect (cons-all 3 '((2 4) () (5)))
              '((3 2 4) (3) (3 5)))

Before next lab

Lab 6

Before the lab


Questions

Time
5 Minutes
Activity
Group discussion / Announcments

Setup

Structural recursion on two parameters

Time
35 minutes
Activity
Small groups
Summary
Learn about lists and recursion.

Intersection of two ordered lists

Time
35 minutes
Activity
Individual work
Summary
Learn about lists and recursion.

The template for structural recursion on two lists is

(define (my-list-fn lst1 lst2)
  (cond
    [(empty? lst1) ... lst2 ...]
    [(empty? lst2) ... lst1 ...]
    [(cons? lst1)
        ... (first lst1) ... (first lst2) ...
        ... (my-list-fn (rest lst1) lst2) ...
        ... (my-list-fn lst1 (rest lst2)) ...
        ... (my-list-fn (rest lst1) (rest lst2)) ...]))

Use this template to complete FICS Exercise 20 You may find it helpful to refer to the function o-union defined in Section 5.4. Note that the fact that the lists are sorted is key an efficient solution here; you do not have to check that property.

Here are some test cases for the function intersection

(check-expect (intersection (list 1 2 3 4 5) (list 2 4 6 7))
              (list 2 4))

(check-expect (intersection (list 2 4 11) (list 1 2 3 4 5))
              (list 2 4))

Generating all subsets of fixed size

Time
35 minutes
Activity
Individual work
Summary
Learn about lists and recursion.

Start with the template from the first exercise and complete FICS Exercise 24. If your template needs updating, compare with cases in the two list template.

Use the built-in function append and the function cons-all you wrote in Lab 5.

Here are some test cases to illustrate the use of the function comb

(check-expect (comb '(a) 1) '((a)))
(check-expect (comb '(1 2) 1) '((1) (2)))
(check-expect (comb '(1 2 3) 2) '((1 2) (1 3) (2 3)))

Before next lab

Lab 7

Before the lab


Questions

Time
5 Minutes
Activity
Group discussion / Announcments

Setup

Introducing foldr

Time
25 minutes
Activity
Small groups
Summary
foldr, functional_abstraction, contracts
(define (Foldr combine base lst)
  (cond
    [(empty? lst) base]
    [else (combine
            (first lst)
            (Foldr combine base (rest lst)))]))

Guess the function

Time
25 minutes
Activity
Individual work
Summary
foldr, functional_abstraction, lists

This part of the lab is based on FICS Exercise 29

Fill in the two instances of ... in the following template with a builtin function so that the tests pass. Try some other values for mylist, mylist1, mylist2 to help you guess.

#lang htdp/isl+
(define mylist '(1 2 buckle my shoe))
(check-expect (foldr cons empty mylist) (... mylist))
(define mylist1 '(3 4 5))
(define mylist2 '(1 2))
(check-expect (foldr cons mylist1 mylist2) (... mylist2 mylist1))

Filter in terms of foldr

Time
40 minutes
Activity
Individual work
Summary
foldr, functional_abstraction, lists

On your own, complete FICS Exercise 28

(check-expect (Filter (lambda (x) #f) '(1 2 3)) '())

(check-expect (Filter odd? '(0 1 2 3 4 5 6 7 8 9))
              (list 1 3 5 7 9))

Before next lab

Lab 8

Before the Lab

Background

Getting started

Questions

Time
5 Minutes
Activity
Group discussion / Announcments

Running JavaScript

Time
15 minutes
Activity
Demo

In a file

    console.log("Hello world");

Running a script

In a Browser

Note
Examples involving "prompt" and "alert" will only work in a browser.

In the REPL

JavaScript equality and type coercion

Time
10 Minutes
Activity
Demo

From Eloquent JavaScript Chapter 1, and the WAT talk by Gary Bernhardt, we know that type coercion is an important part of evaluating JS expressions. This is sometimes useful

> 42 + 'is a big number'

But just as often a source of errors and confusion.

> "" + 1
> x=""
> x++

One operator where type coercion can be particularly surprising is the standard equality test ==. Not only does type coercion apply:

> "" == 0
> false == 0
> "" == 0

but special rules apply to "undefined" and "null"

> false == undefined
> undefined == null
> undefined == undefined

even though they would normally be considered falsy (considered false in boolean contexts).

   > if (undefined) { console.log("truthy") } else { console.log("falsey") }

NaN is another falsy value not == to the other falsy values, not even itself:

   > NaN == undefined
   > NaN == NaN

To avoid this twisty maze of "helpful" type coercion, you can use the "strict equality" checker ===

   > "" === 0
   > false === 0
   > "" === 0
   > false === undefined
   > undefined === null
   > undefined === undefined

Javascript functions

Time
20 minutes
Activity
Individual

Reference for this section is JavaScript functions Like Racket, JavaScript has two different ways of defining functions. The first way is by assigning an anonymous function to a variable.

    (define square (lambda (x) (* x x)))
    let square = x => x*x;
    let square2 = function (x) {return x*x};

The more compact way of defining functions in both Racket and JavaScript combines the binding and creation of a function/lambda

    (define (square x) (* x x))
    function square(x) { return x*x }
const plus = (a,b) => {
    for (let i=0; i < a; i++){
        b++;
    }
    return b;
}

const mult = function (a,b) {
    sum=0;



    return sum;
}

Node test runner

Time
10 Minutes
Activity
Demo

In order to provide similar functionality to check-expect in racket, we will use the nodejs test framework.

In order to use the test runner, we need to add two lines to the beginning of our javascript files.

const test=require("node:test");
const assert=require("assert");

We'll talk more about both const and require later.

A test is considered to pass if it does not throw an exception. assert provides several functions useful for this kind of test. We will concentrate on assert.strictEqual for now, which corresponds to the === test discussed above.

Here is a complete example with one passing test and one failing one

const test=require("node:test");
const assert=require("assert");

function add1(n) {
    return n+1;
}

test('one plus one is two', (t)=>assert.strictEqual(add1(1),2));
test('one plus one is three', (t)=>assert.strictEqual(add1(1),3));

Test coverage

We will use an experimental feature of node v20 in order to make sure we have test coverage (analogous to how we set up DrRacket for visual test coverage).

Getting a coverage report

Time
15 minutes
Activity
Group activity
ℹ start of coverage report
ℹ ---------------------------------------------------------
ℹ file      | line % | branch % | funcs % | uncovered lin…
ℹ ---------------------------------------------------------
ℹ sample.js | 100.00 |   100.00 |  100.00 |
ℹ ---------------------------------------------------------
ℹ all files | 100.00 |   100.00 |  100.00 |
ℹ ---------------------------------------------------------
ℹ end of coverage report

Writing tests

Time
25 minutes
Activity
Individual

Before the next lab

Lab 09

Before the lab

Questions

Time
5 Minutes
Activity
Group discussion / Announcments

Deep Comparison

Time
40 minutes
Activity
Exercise from book

Follow the Deep Comparison Exercise to write a function deepEqual that passes the following tests. assert uses something very similar for the deepStrictEqual test used below. Your function should pass the following tests.

let obj = {here: {is: "an"}, object: 2};
test("self", (t)=> assert.strictEqual(deepEqual(obj,obj),true));
test("null", (t)=> assert.strictEqual(deepEqual(null,null),true));
test("!null", (t)=> assert.strictEqual(deepEqual(null,obj),false));
test("different", (t)=>
    assert.strictEqual(deepEqual(obj, {here: 1, object: 2}),false));
test("equivalent", (t)=>
    assert.strictEqual(deepEqual(obj, {here: {is: "an"}, object: 2}),true));

Remember from L08 that you need the following lines to use the test framework

const test=require("node:test");
const assert=require("assert");

It can be frustrating trying to achieve full branch coverage (with our current tools). One useful trick is to turn one sided ifs into two sided ones, so the missing branch is turned into a missing line.


JavaScript Arrays

JavaScript supports arrays. They are actually implemented as objects, which leads to some slightly surprising behaviour:

> x=[]
> x[10]=1
> x
> x["bob"] = 2
> x

Generating arrays

Time
10 minutes
Activity
Group programming / demo

Follow the (first part of the) Sum of a Range Exercise to write a range function that passes the following tests.

test("empty", (t)=>assert.deepStrictEqual(range(2,1),[]));
test("single", (t)=> assert.deepStrictEqual(range(2,2),[2]));
test("multiple", (t)=>
    assert.deepStrictEqual(range(42,50),[42,43,44,45,46,47,48,49,50]));

Arrays Continued

Time
20 minutes
Activity
Exercise from book

Follow the (second part of the) Sum of a Range Exercise to write a sum function that passes the following tests.

test("empty", (t)=> assert.strictEqual(sum([]),0));
test("single", (t)=> assert.strictEqual(sum([2]),2));
test("multiple", (t)=> assert.strictEqual(sum(range(1,10)),55));

Adding step argument to range.

Time
20 minutes
Activity
Individual

Add an optional step argument to the range function so that the following tests pass. The original tests should keep passsing, that's a big part of the point of having unit tests.

test("step 2", (t)=> assert.deepStrictEqual(range(1,10,2),[1,3,5,7,9]));
test("step -1", (t)=> assert.deepStrictEqual(range(5,2,-1),[5,4,3,2]));

Before next lab

Lab 10

Before the lab

Questions

Time
5 Minutes
Activity
Group discussion / Announcments

Flattening

Time
25 minutes
Activity
Exercise from book.

Solve the Flatten Exercise using reduce and concat methods. Your solution should pass the following test suite.

test("base case", ()=> assert.deepStrictEqual(flatten([]),[]))
test("single element",
     function () {
     assert.deepStrictEqual(flatten([1]),[1]);
     assert.deepStrictEqual(flatten(["foo"]),["foo"]);
     assert.deepStrictEqual(flatten([{foo: 1}]),[{foo: 1}]);
     });
test("longer list", ()=>
    assert.deepStrictEqual(flatten([[1],2,[3],4,[5,6]]),[1,2,3,4,5,6]));

Does the following test pass with your implementation? If not, why not?

test("deeper nesting", (t)=>
    assert.deepStrictEqual(flatten([[[1],2,[3]],4,[5,6]]),[1,2,3,4,5,6]));

Referring back to L07, answer the following in your journal.

Every

Time
25 minutes
Activity
Exercise from book.

Solve the Every Exercise

Call the version using a loop every_loop

test("all true", (t)=>
    assert.strictEqual(every_loop([1, 3, 5], n => n < 10), true));
test("last false", (t)=>
    assert.strictEqual(every_loop([2, 4, 16], n => n < 10), false));
test("last middle false", (t)=>
    assert.strictEqual(every_loop([2, 16, 3], n => n < 10), false));
test("empty list", (t)=>
    assert.strictEqual(every_loop([], n => n < 10),true));

Call the version using the some method every_some. The tricky part here is to construct a function that is the negation of the given function.

test("all true", (t)=>
    assert.strictEqual(every_some([1, 3, 5], n => n < 10), true));
test("last false", (t)=>
    assert.strictEqual(every_some([2, 4, 16], n => n < 10), false));
test("last middle false", (t)=>
    assert.strictEqual(every_some([2, 16, 3], n => n < 10), false));
test("empty list", (t)=>
    assert.strictEqual(every_some([], n => n < 10),true));

Timing functions

Time
40 minutes
Activity
Individual work.

We are presented with two different implementations of the Fibonacci function, and we want to see which one is faster 1.

const test=require("node:test");
const assert=require("assert");

function fib(n) {
    if (n<=2)
        return 1;
    else
        return fib(n-1)+fib(n-2);
}

let cache = {};
function memfib(n) {
    if (cache[n] !== undefined)
        return cache[n];

    if (n<=2)
        cache[n] = 1;
    else
        cache[n] = memfib(n-1)+memfib(n-2);

    return cache[n];
}

test("fib(35)=9227465",(t)=>assert.strictEqual(fib(35),9227465));
test("memfib(35)=9227465",(t)=>assert.strictEqual(memfib(35),9227465));

Using the function noisy as a template, write the higher order function timed that uses the builtins console.time and console.timeEnd to output timing information. The wrapped function produced by timed should produce the same values as the original function, as shown by the following tests.

test("timed(fib)(35)=9227465", (t)=>assert.strictEqual(timed(fib)(35),9227465));
test("timed(memfib)(35)=9227465",(t)=>assert.strictEqual(timed(memfib)(35),9227465));

let out=timed(fib)(42);
let out2=timed(memfib)(42);

test("fib(42)≈memfib(42)",assert.ok(Math.abs(out-out2)<0.000001))

It turns out that memfib is much faster. On my computer, the output from the last two timed calls is

fib(42): 970.887ms
memfib(42): 0.034ms

Notice how I have included the name of the function and the parameter in the output. Try to do the same.

Answer the following in your journal:

Before Next Lab


  1. There are several better versions of Fibonacci that we will see later in the course.
Lab 11 (racket quiz)

Before the lab

A Vector class

Time
25 minutes
Activity
Exercise from book.

Do the Vector Type exercise.

test("addition", (t) =>
    assert.deepStrictEqual(new Vec(1, 2).plus(new Vec(2, 3)),
                           new Vec(3,5)));
test("subtraction", (t)=>
    assert.deepStrictEqual(new Vec(1, 2).minus(new Vec(2, 3)),
                           new Vec(-1,-1)));
test("length", (t) =>
    assert.strictEqual(new Vec(3, 4).length,5));

A Set ("Group") class

Time
25 minutes
Activity
Exercise from book.

Do the Group exercise. Instead of the suggested array, use an object to store the elements of the "Group". The simplest way to implement from is to loop over the iterable argument and repeatedly call add.

const test=require("node:test");
const assert=require("assert");
class Group {
    constructor() {
        this.elements={}
    }
    add(element) {
        this.elements[element]=true;
    }
    has(element) {
    }
    delete(element) {
    }
    static from(iterable) {
    }
}
let group = Group.from([10, 20]);
test("has yes",(t)=>assert.strictEqual(group.has(10),true));
test("has no",(t)=>assert.strictEqual(group.has(30),false));
test("add existing is not error", (t)=>group.add(10));
test("delete existing", (t) => {
    group.delete(10);
    assert.strictEqual(group.has(10),false);
});

Racket Quiz


Before next lab

No lab: Thanksgiving Day
Lab 12

Before the lab

Lab 13

Before the lab

Background

Callback Chaining

Time
25 minutes
Activity
Code puzzle.

The recommended way to get maximimum performance from the node.js filesystem API is to use callbacks, i.e. functions to call when the I/O operation completes.

Replace the ALL-CAPITALS words in the following code sample with defined identifiers to make this callback chaining example work.

const fs=require("node:fs");

function log(string, next) {
    let date = new Date();
    function finish(err) {
        if (err) throw err;
        console.timeEnd("write");
        CALLBACK();
    }
    function write(err, fd) {
        if (err) throw err;
        fs.write(VALUE, date.toISOString() + ": " + string, CALLBACK);
    }
    console.time("write");
    fs.open("log.txt", "a", CALLBACK)
}

console.time("log");
log("first\n",
    () => {
        log("second\n",
            () => console.log("done"));
    });
console.timeEnd("log");

When you get the code working, the file log.txt should contain lines like the following (the exact date and time may differ):

2024-10-25T17:31:56.626Z: first
2024-10-25T17:31:56.629Z: second

Questions for your journal:

Promises

Time
25 minutes
Activity
Code puzzle, reading documentation

In the Promises API file system methods return a Promise object that automates the chaining of callbacks. These Promise objects can be chained together using then. Fill in the appropriate API calls (they should be very similar to the first part, without the explicit callbacks).

const fs=require("node:fs/promises");

function log(string) {
    let date = new Date();
    return new Promise(next=> {
        console.time("write");
        API_CALL
            .then(fd=>API_CALL)
            .then(()=>console.timeEnd("write"))
            .then(CALLBACK)
            .catch((err) => console.log(err));
    });
}

console.time("log");
log("first\n")
    .then(()=>log("second\n"))
    .catch((err) => console.log(err));
console.timeEnd("log");

Questions for your journal:


Async Functions

Time
25 minutes
Activity
Code puzzle, reading book

Async Functions provide some nice features for programming with Promises, essentially eliminating some of the boilerplate from our previous example. They also introduce one very important keyword.

Replace the ALL-CAPITALS parts of the following program to duplicate the functionality of the previous versions.

const fs=require("node:fs/promises");

async function log(string) {
    console.time("write")
    const fd = MAGIC_KEYWORD API_CALL()
    let date = new Date();

    const promise=API_CALL();
    console.timeEnd("write");
    return promise;
}

console.time("log");
log("first\n")
    .then(()=>log("second\n"))
    .catch((err) => console.log(err));
console.timeEnd("log");

Before Next Lab