Skip to main content

Closures

  • Please download the project here.
  • You may edit any file inside ./src. DO NOT make any edits to any file inside ./include.

Overview

Learning Objectives

  • Practice working with and reasoning about closures.

Student Expectations

Students will be graded on their ability to:

Testing

You must write tests for all your functions, following the principles used so far.

It may helpful to write some tests first to make sure your implementation addresses those cases.

Programming Tasks

composeFunctions

export function composeFunctions<T>(funs: GenericFunction<T>[]): (x: T) => T[] {
// TODO
}

Write a function that takes in an array of functions, each of type GenericFunction<T>, i.e., (x: T) => T. It returns a closure that takes one argument v of type T. When called, the closure returns an array of the values obtained by starting with v and successively applying each function of the array passed into composeFunctions. That is, the array returned by the closure is [v, f0(v), f1(f0(v)), ...] if the array of functions is [f0, f1, ...].

cyclic

Write a function called cyclic

export function cyclic<T>(values: T[]): () => T {
// TODO
}

cyclic takes an array of values. If called with an empty array, it throws an Error. Otherwise, it returns a closure. Each time the closure is called, it returns the next value in the array, cycling back to the beginning after reaching the end. The syntax for throwing an error in TypeScript looks like the following

throw new Error("lorem ipsum")

rateLimiter

Write a function called rateLimiter.

export function rateLimiter<T, R>(func: (x: T, y: T) => R, limit: number): (x: T, y: T) => R | undefined {
// TODO
}

rateLimiter takes a function func: (x: T, y: T) => R and a non-negative integer limit. It returns a new function (closure) that:

  • Has the type signature (x: T, y: T) => R | undefined
  • Calls func with the provided arguments and returns its result
  • Only allows func to be called limit times in total
  • Returns undefined after func has been called limit times

Example

const func = (x: number, y: number) => x === y        // some function
const limitedFunc = rateLimiter(func, 2);
console.log(limitedFunc(1, 1)); // true
console.log(limitedFunc(1, 2)); // false
console.log(limitedFunc(1, 1)); // undefined

byParity

Write a function called byParity

export function byParity(
evenFunc: (n: number) => number,
oddFunc: (n: number) => number
): (n: number) => number {
// TODO
}

byParity takes two functions as arguments, evenFunc and oddFunc. It returns a closure that takes in a number and returns the result of passing that number to evenFunc if the number is even or oddFunc if the number is odd. You may assume that all numerical input will be integers.

vendingMachine

Write a function called vendingMachine

export function vendingMachine(
price: number,
stock: number
): (amount: number) => number | undefined {
// TODO
}

vendingMachine takes in two numbers:

  • price: the cost of one item
  • stock: the number of items available (you may assume stock is an integer)

It returns a closure simulating vending machine purchases.

The returned closure should take an amount of money as input and simulate the purchase of one item:

  • Return the change if the amount >= price and the item is in stock
  • Return undefined if the amount is insufficient or the item is out of stock
  • Each successful call (purchase) should reduce the stock by 1

wageChange

Write a function

export function wageChange(
calcNew: (yr: number, prevWage: number) => number
): (startWage: number, startYr: number, endYr: number) => number {
// TODO
}

used to compute wage changes over the years.

wageChange takes as argument a function that, given a year and the previous year’s wage, returns the new wage. wageChange returns a closure which takes three arguments: a starting wage, the year for which this wage applies, and the end year for which to compute the new wage, which is returned. The end year passed to this closure should be no lower than the start year. Valid years are 1970 to 2026, and valid wage values are numbers greater than 0. The closure returned by wageChange should return NaN when any arguments or computed values at any point are invalid.

Example:

const wc1 = wageChange((_yr: number, prev: number) => prev + 100);
wc1(1000, 2020, 2023) // 1300

Suppose wageChange is initialized with a callback that increases the previous wage by 100 each year. Calling the returned closure with starting wage 1000 for 2020 and end year 2023 yields a wage of 1300 in 2023.

sineSeries

The sine of a number x can be approximated via the Taylor series:

sin(x)=x1x33!+x55!x77!+\sin(x) = \frac{x}{1} - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!} + \dots

The terms of the series can be generalized to:

(1)kx2k+1(2k+1)!\left(-1\right)^k \frac{x^{2k+1}}{(2k+1)!}

Write a function called sineSeries

export function sineSeries(x: number): (moreTerms?: number) => number {
// TODO
}

It accepts a number x. When called with some value moreTerms (defaults to 1 if no value provided), the closure computes the specified number of additional terms, updates the sum computed with these new terms and returns it. You may assume moreTerms is a positive integer.

You must use state to avoid re-calculating factorials and powers from scratch on every call.

Submission

Use the following command to build a zip file:

npm run build:submission

Please upload the zip file created by the command to Gradescope.