Learning Objectives
With this assignment you will demonstrate:
- The ability to write and test functions and user-defined types in TypeScript and Haskell. (I will supply the unit tests for you, but you will need to understand them and wire them into to your application.)
- The ability to apply concepts such as type inference, type variables, algebraic data types, functional programming, loop-free programming, point-free programming, typeclasses, and monadic file processing.
Readings and Videos
Please:
- Read Chapters 4 and 11 of Programming Language Explorations, 2nd edition.
- Locate and have handy reference material for TypeScript and Haskell. The online docs for both languages are very good. The Learn You A Haskell For Great Good book is great for browsing. The Axel Rauschmeyer books mentioned in the previous assignment are also excellent resources.
- Review the course notes for Haskell, Types (but skip the section on pointers), Functions, and Functional Programming. Follow as many of the links on those pages as is reasonable.
Instructions
Work in teams of 1 to 4 students.
Do all work in the GitHub repository you created in the previous assignment.
Remember: There must be only one official repository per group.
Repository
Add files according to the folder structure below. You will be implementing the assigned functions and classes from scratch. The test files have been written for you; follow the links to obtain them.
.
├── code/
│ ├── typescript/
│ │ ├── exercises.ts
│ │ └── package.json
│ │ └── tsconfig.json
│ │ └── exercises.test.ts
│ ├── haskell/
│ │ ├── exercises.hs
│ │ └── ExercisesTest.hs
Code
Implement—for TypeScript and Haskell—modules with the following functions and types. You must provide full and explicit type annotations for all functions (and methods), even though each language is capable of inferring every type.
- A function that when given a list (for Haskell) or array (for TypeScript) $a$, a predicate $p$, and a function $f$, return the application of $f$ to the first element of $a$ that satisfies $p$. You must return an optional since there may be no element of $a$ satisfying $p$. (Note that each language specifies optionals in slightly different ways: as always, follow best practices.) In each language, your language must demonstrate parametric polymorphism.
- An implementation of the infinite sequence of powers $b^0, b^1, \ldots$ of base $b$. See the unit tests for details.
- TypeScript requirement: The base must be of type
BigInt (not number).
- Haskell requirements: (1) Restrict the type of the base to any
Integral type. (2) Use a section in your solution.
- A function that returns the number of text lines from a text file that are neither (1) empty, nor (2) made up entirely of whitespace, nor (3) whose first nonwhitespace character is
#. Make sure the file is closed properly, as specified in the language-specific requirements below.
- TypeScript requirement: Your function should be asynchronous. Use the line-reading idiom that autocloses the file.
- Haskell observation: Your function will naturally return an
IO Int. Just propagate exceptions. Use readFile which will autoclose.
- User defined data types in TypeScript and Haskell for three-dimensional shapes that can be either rectangular boxes or spheres, with operations to compute both the surface area and the volume, along with whatever to-string operation is appropriate for each language. The shapes must be fully immutable. Support value-based equality for all shapes.
- TypeScript requirement: Instead of using inheritance, make
Shape be a union type over Box and Sphere.
- Haskell requirement: Use algebraic data types to define the shapes.
- A generic, persistent, binary search tree implementation in TypeScript and Haskell, supporting (1) insertion, (2) lookup, (3) count (number of elements), (4) an inorder traversal, described below, and (5) for TypeScript and Haskell only, the string description of the tree as specified in the unit tests. The trees are to be completely immutable and persistent, and implemented as algebraic data types. The tree type(s) must be generic, and, because the data structure is a search tree, be constrained if possible to a comparable type.
- TypeScript requirement: For
inorder, implement a generator that yields each element in order. As this operation is naturally recursive, you get to use yield*.
- Haskell requirement: Implement the inorder traversal as a function producing a list of elements in order.
- Security requirement: Your implementation must not allow construction of a tree that is not a binary search tree. Find out how to do information hiding in each language. Haskell, it is as easy as exporting the
Empty constructor but not the Node constructor. Perhaps you will find that TypeScript does something similar. Comparative Programming Language Study is so fun. This is something that the unit tests cannot capture so the graders will be examining your code for this requirement visually.
Strive to use the most idiomatic constructs of each language in your solutions.
Generative AI is permitted for this assignment; however, there is no guarantee that it will produce code that meets all of the requirements. Do not try to prompt your way to working solutions. Make sure to thoroughly review and test any code produced by AI tools.
To use the supplied unit tests, make sure you’ve created the test-data folder and its associated files as described in the instructions for Homework 2. Run the tests in their corresponding code folders, with the following commands:
npm test (after doing npm install)
ghc ExercisesTest.hs && ./ExercisesTest
Exercises
In the file exercises/hw3.md, provide solutions to the following problems. You will need to do research, as not all of these topics were covered in class. For exercises that ask you for prose answers, please be comprehensive and demonstrate a broad understanding. But avoid long-winded, silly gen-ai answers. Expect deductions for verbatim copies of AI slop. Be precise and concise.
- Show how to constructively define the type of trees of elements of type $t$.
- Give a definition by cases for the exponentiation of natural numbers.
- (a) Which are the inhabitants of $\textsf{Bool} + \textsf{Unit}$? (b) Which are the inhabitants of $\textsf{Bool} \mid \textsf{Unit}$?
- (a) Which are the inhabitants of $\textsf{Bool} \times \textsf{Unit}$? (b) Which are the inhabitants of $\textsf{Bool} \rightarrow \textsf{Unit}$?
- What are the major arguments put forward in the article The String Type is Broken?
- Can you give a type to $(\lambda x.(x\:x))(\lambda x.(x\:x))$? If so, what is it? If not, why not?
- Represent $x \not\in A$ in function notation.
- What is a pure function? Why do we care about these things?
- How does Haskell isolate pure and impure code?
- In TypeScript, which of
| or & is closer to the idea of “subclassing” or “inheritance” from Python? Why? (An example will help!)
Please use professional and well-structured Markdown formatting for your answers. Take pride in your work. Show you care.
Submission
To submit your work, choose one and only one team member to submit to BrightSpace (1) the names of all team members, (2) the URL of your private repository, and (3) an affidavit for each team member stating that they have done the assigned book readings and reviewed the aforementioned course notes pages.
Teamwork
Remember that, when working in teams, all team members should participate in generating solutions and are responsible for understanding all submitted answers. Ideally, each team member should produce individual answers for all exercises, and the team should combine them to produce the ultimate submissions.
Grading
You will be graded both on the correctness of your solutions, adherence to each language’s conventions, the following of the assignment instructions, and the cleanliness of the repository. Feedback will be in the form of deductions for such things as:
- Not following the spacing, indentation, and capitalization conventions of each language
- Poor names (note that even if the exercise descriptions above use short names, your solutions should use readable names where appropriate)
- Naming an entity in a way that has nothing to do with the variable’s purpose
- Defining variables that are never used
- Unnecessary comments
- Missing necessary comments
- Data that is mutable when immutability is called for
- Lack of information hiding (to the extent allowed by the language)
- Failing to meet any explicitly specified implementation requirement (not every requirement is captured by the unit tests)
- Code that is needlessly inefficient
- Code that does not properly manage resources, such as that which fails to release allocated memory or close open file handles
- Code that is too complex and fails to take advantage of the language features covered in class that are designed to facilitate the assigned task (deductions will be small here: you should, when unsure of the “best” solution, to at least give a solution, since a working substandard solution is better than no solution at all)
- A repository structure that varies from the specified layout
- Extra files in the GitHub repository
- Not following submission instructions; for example, not supplying the names of every group member or not providing the URL of your repository or not including reading affidavits for each group member
This list is not exhaustive, but should help in getting you used to paying attention to your submissions and taking pride in your work. Note that most code editors have plugins that will auto-format your code and even look for suspicious coding practices. You are strongly encouraged to use them.
Your homework submission will be the state of your repository on your main branch, at 18:00 in the America/Los Angeles time zone on the due date.