TypeScript Coding Interview Question 1 - Tuples

Last updated: August 21st 2022

O. The solution #

If you're looking for my solution to the challenge, it's not here. But I know there is at least one valid approach.

Don't trust me? Fine. The SHA-1 of my solution is c6dcf30cd50276669689cfb9670c669a5371961d.

I'll upload a TypeScript source matching that SHA-1 later, when I get a chance to write a post about it.

1. Introduction #

Your mission is to define some types, and a few functions, to basically reinvent tuples/pairs in TypeScript.

"That's it? Implement some interface to work with tuples?" you're thinking, unimpressed.

Good, confidence. You have nothing to worry about, then.

"Ah, I know you, Noriega. You're gonna ask that I do it purely functional, ie. without mutation. Easy-peasy!"

Well, yes. Pure FP is a given with me. But I suspect immutability is not gonna be the challenging part of this little challenge. Anyway, enough fake dialogue, let's get on with it.

2. Coordinates #

The context is a hypothetical geographical surveillance/monitoring software. So I'll start with a pair of numbers that is likely familiar to you: Latitude and Longitude.

As you know, a Latitude-Longitude pair represents some place on Earth.

(Flashback to nightmares while working with those godawful map libraries in JS)

type Pair<A,B> = /* YOUR CODE GOES HERE */;
type Coord = Pair<number, number>;

That's "Coord" as in "Coordinate." But we're programmers, so we truncate the names of things for no real gain.

And you can see what your first task is: Define the type Pair<A,B>. Your success or failure depends on how you define this type.

3. Devices #

The system monitors people's devices. You're an agent of the NSA, or one of those creepy surveillance agencies. (Supposedly) dangerous citizens' phones are being constantly tracked.

The devices themselves are modeled as pairs of values too:

type Device = Pair<string, boolean>;

Where the string component is the name and the boolean component tells us whether the device is moving.

Wait, I just heard myself having to explain what string and boolean and number mean. This is a sign that we could use some additional type aliases for code clarity:

type Lat = number;
type Lng = number;
type Coord = Pair<Lat, Lng>;

type Name = string;
type IsMoving = boolean;
type Device = Pair<Name, IsMoving>;

So we have Coord and Device, as two simple pair types. Good. Now let's create one last pair type.

4. Devices being tracked #

This last pair type pairs up (literally) Device and Coord, to model the notion of a device that's being tracked:

type TrackedDevice = Pair<Device, Coord>;

It's a "tuple of tuples." I'm sure you've worked with "array of arrays," or "objects within objects," or other forms of "nested" data before. Same thing. Right now, for this code, you should be picturing an example value such as:

(("J-BAUER-24",true),(28.378312, -81.570783))

5. Pair<A,B> construction #

As you probably already deduced using your big Sherlock-Holmesian brain, you need to put your code where I've written "/* YOUR CODE GOES HERE */" as a placeholder. Of course, feel free to add "auxiliary" type and function definitions on other lines.

Anyway, for pairs to exist, we need to construct them. So we gotta have some pair constructors:

const pair = <A, B>(a: A, b: B): Pair<A, B> => /* YOUR CODE GOES HERE */;

const createCoord = pair;
const createDevice = pair;
const createTracked = pair;

pair is a function that takes two values of types T and U and gives back a Pair<T,U>. Ie. a simple, dumb constructor. Like we use in functional programming. (As opposed to the OOP thing where "constructing" a value new Foo() might entail everything from connecting to databases to launching terrorist attacks.)

Using your simple dumb constructors, you will construct these values (which we will use in some assert tests later):

// eg. building a pair representing some device
const d: Device = createDevice('J-BAUER-24', true);

// eg. building a pair representing some coordinate
const latLng: Coord = createCoord(28.378312, -81.570783);

6. Pair<A,B> getters #

What good is a tuple if we can't access its components, though?

After you decide what a Pair<A,B> is, implement the fundamental functions for reading data from a pair. Ie. to extract the A from a Pair<A,B>. Then, use those functions to write more complex getters that drill down a TrackedDevice to read data from its inner pairs.

// eg. work on a "pair of pairs"
const td: TrackedDevice = createTracked(d, latLng);

const deviceName = (x: TrackedDevice): Name => /* YOUR CODE GOES HERE */;

const deviceIsMoving = (x: TrackedDevice): IsMoving => /* YOUR CODE GOES HERE */;

const deviceLat = (x: TrackedDevice): Lat => /* YOUR CODE GOES HERE */;

7. Pair<A,B> setters #

Just to prove that we really can work with your Pair<A,B> type, please write a function that takes a TrackedDevice, drills down into the Person's IsMoving component, which is a boolean, and toggles it.

(Ie. when it's true, make it false, and viceversa. The word "not" comes to mind. And the symbol !.)

(Yes, I'm treating you like a baby. Making you real comfortable. Just trying to ensure you keep that confidence of yours real high, you know.)

// setter ("toggler"?) for the second component of the first component.
const toggleMovement = (x: TrackedDevice): TrackedDevice => /* YOUR CODE GOES HERE */;

Needless to say, I expect that you do it purely functionally, pretty please, with sugar on top. Ie. no mutation.

Yes, the "copy-whole-thing-but-put-new-value-in-the-right-place" protocol you are familiar with. Classic FP stuff.

8. Lastly, some assertions #

Please ensure that the following assertions pass:

const assert = require('assert');
assert(deviceName(td) === 'J-BAUER-24');
assert(deviceLat(td) === 28.378312);
assert(deviceIsMoving(td) === true);
assert(deviceIsMoving(toggleMovement(td)) === false);
assert(deviceIsMoving(toggleMovement(toggleMovement(td))) === true);

(I'm assuming typescript on node. I'm using v16.15.0 as of this writing, for no particular reason. It's just the one I found on my computer.)

Alright, that's all.

You're thinking you're going to ace this challenge in like five minutes, aren't you? Well, if your mind happens to be in the right place, you will.

However, your solution must also satisfy certain additional requirements. Namely, things that you are NOT allowed to do. So let's recap the mission objectives, and give you these additional requirements.

9. Your Mission #

9.1. Objectives #

Your mission, should you choose to accept it, is:

  1. To define the Pair<A,B> type.
  2. Define the constructors, getters, and setters,
  3. Such that all the asserts pass, without any hacks.

And now for the requirements.

9.2. Requirements #

  1. You CANNOT use objects or arrays or typescript's tuple syntax.
    • (Or any type of collection-like data structure, whether "literal" or class.)
    • I DON'T want to see the characters [, ], {, or } (in an expression context) anywhere.
    • I DON'T want to see the keyword new anywhere.
    • Anything resembling [ x, y ], or { a, b, ...} = Rejected.
    • Anything resembling new Array (or Set, or Map, or Object.create(), etc.) = Rejected.
    • Anything resembling x.foo, or x[0] = Rejected.
    • Anything resembling const pair = (a,b) => [ a, b ] = Rejected.
    • Anything resembling const pair = (a,b) => ({ first: a, second: b }) = Rejected.
    • Anything resembling class Pair { ... }; const pair = (a,b) => new Pair(a,b) = Rejected.
  2. ALL code MUST be 100% generic.
    • Eg. this is not generic const square = (x: number): number => x * x
    • Eg. this is objectively generic const id = <A>(x: A): A => x
  3. No mutation, no global variables, no let, no var, no object property assignment.
  4. No hacks or tricks. No any, no eval, no JSON.stringify, no obscure compiler options.

You have 1 hour.

Good luck.

Related