Skip to the content.

CSE 331: Topics

1. Intro to JavaScript

We start our journey to writing more complex, full-stack applications by learning a new programming language: JavaScript (JS). We will need JS to write code that runs in the browser (in Topic 2), but we can also use JS to write code that runs in the server.

We do not assume any prior knowledge JavaScript. We will cover all the parts we need in lecture. After learning the basics of the language, we will see how to write our own HTTP server in JS and then spend some time discussing how to debug the problems that we find in our code.

2. Intro to the Browser

In this topic, we switch to writing code that runs in the browser, presenting a User Interface (UI) that the user can interact with. We will discuss how UIs are written in modern web apps versus other settings and see how the code of modern web apps gets split up into manageable pieces.

3. Client-Server Interaction

Having written already clients and servers separately, in this topic, we see how to connect them together into full stack applications. In this setting, the potential for difficult debuging is substantial. Bugs in the server can show up as failures in the client and vice versa, in both cases requiring us to debug across multple programs to find the bug.

4. Specifications

Before we can talk in detail about correctness of our code, we need a definition of what the code should do. This is called a “specification”. It is important that our specifications are precise. In addition, we would like our tools for writing specifications to work for any language, not just TypeScript. For those two reasons, we will use our own mathematical notation that is independent of programming language. (And we will find additional uses for this notation in subsequent topics.)

  • Lecture slides
  • Section slides and worksheet (solution)
  • Homework 4: written and coding worksheets.
    Change log for written:
    • Changed the definition of the function "ns" to write in Task 3(a).
    • Clarified that restrictions on row number inputs are in user-visible specification in Task 4(b).
    • Fixed typo in "rconcat" and "rlen" function definitions to have correct parameters.
    Change log for code:
    • Added export statements for functions in funcs.ts.
    • Added /** */ style comments above exported functions in starter code to comply with linter.
    • Added missing "expected" values in example on test commenting.

5. Reasoning

Now that we have a better understanding of the work required to write and debug more realistic applications, we can see that being a productive programmer requires another tool, reasoning, which allows us to find the bugs missed by type checkers and unit tests. In this topic, we will see how we can reason formally and prove the correctness of code that does not involve any mutation. (Code with mutation is harder to reason about and will be examined in future topics.)

  • Lecture slides
  • Section slides and worksheet (solution)
  • Homework 5: written and coding worksheets.
    Change log for written:
    • Task 4: Fixed some "a"s in defs of keep, skip, and echo that should have been "x"s. Fixed the code example in 4b to match the problem statement.
    • Task 5: Fixed the expression for f(xs) in 5a to match the one in 5b.

6. Floyd Logic

In this topic, we continue our study of reasoning, now moving to slightly more complex (and more common) types of code. Previously we considered code without mutation, in which complex calculations are performed via recursion. Here, we will look at code that mutates local variables. In particular, this allows us to write loops instead of recursion. However, it also makes reasoning more complicated, necessitating new reasoning tools.

7. Tail Recursion

In prior topics, we have seen problems that can be solved either with recursion or with a loop, showing that these two are sometimes alternatives to one another. In this topic, we clear up the relationship between them by showing that loops are equivalent to “tail recursion”, a special case of general recursion, and demonstrate how to translate between the two, allowing us to use whichever is more convenient for the current setting.

8. Abstraction

Now that we have learned our full set of tools for ensuring correctness, we turn our attention to the other properties of high quality code: understability, changeability, and modularity. These properties are often provided by means of abstraction, which allows us to hide details of some parts of the code from other parts. This makes the code easier to understand and change, and allows multiple people to work on parts of the code simultaneously (modularity).

We will begin by studying how to hide the details of code via “procedural abstraction” and then see how to hide the details of data via “data abstraction”.

9. Arrays

In the previous topic, we looked at ADTs and saw that they describe the interfaces of many well-known data structures. However, the most commonly used data structure in imperative languages — arrays — was notably missing. In this topic, we rectify that omission.

As we will see, arrays create new reasoning difficulties that do not occur for alternatives like lists and basic trees. Their use of indexing gives us extra power but comes at a cost of more complex reasoning, requiring the use of “for any” facts to describe their known state. These are inherently more complex, but we can mitigate some of that complexity by turning to pictures as a simpler way of describing them.

  • Lecture slides (more coming shortly)

More topics coming soon...