Comp 110 map and the Transform Interface

map and the Transform Interface

This page is going to talk about how we can make use of the generic map function implemented below. Check out the pages on the filter-map-reduce pipeline and generic functions for a review of those concepts!

export let map = <T, U> (list: Node<T>, f: Transform<T, U>): Node<U> => {    
    if (list === null) {        
        return null;    
    } else {        
        return cons(f(first(list)), map(rest(list), f));    
    }
};

Let's take this step by step!

map takes in two parameters:

    1. A Node of some generic type.

    2. A function of type Transform.

The map function is a higher-order function, meaning it accepts another function as a parameter. This function parameter belongs to the Transform interface. 

The Transform interface is defined as follows: 

interface Transform<T, U> {    
    (item: T): U;
}

In English, this means the function passed to map returns some generic type "U" when given an item of the generic type "T." Note here that T and U don't necessarily have to be different types - they can be the same! Defining our Transform interface in this way gives us more flexibility in our mapping algorithms, allowing us to manipulate our data in any number of ways, whether or not we are changing types.

The new mapped list is generated by recursively building a list, with each item we add onto the list being processed by the Transform function, f. Now that we have abstracted the logic of the map algorithm, we can reuse the map function over and over again. Just make sure you import it at the top of your program:

import { map } from "./list-utils";
/* This import statement will only work if you have the list-utils.ts file in the same folder you are working in! */

Let's jump into some examples!

Examples

1. Mapping strings to numbers

let stringLength = (word: string): number => {    
    return word.length;
};
let main = async () => {    
    let original: Node<string> = listify("cat", "broom", "waffle");    
    let mapped: Node<string> = map(original, stringLength);    
    print(mapped);
};

The resulting output will be 3 --> 5 --> 6 --> null. For each element in the original list, the Transform function stringLength is called, which returns the length property of each word. A new "mapped" list is constructed consisting of these manipulated data values.

2. Mapping strings to strings

let pigLatin = (word: string): string => {    
    let firstLetter: string = word.substring(0, 1);     
    let pigWord = word.substring(1) + firstLetter + "ay";   
    return pigWord;
};
let main = async () => {    
    let english: Node<string> = listify("lets", "go", "heels");    
    let pig: Node<string> = map(english, pigLatin);    
    print(pig);
};

The resulting output will be etslay --> ogay --> eelshay --> null. Each English word is mapped to its Pig Latin translation.

3. Mapping with objects

class Car {    
    fuelTankFull: boolean = true;    
    mpg: number = 30;    
    color: string = "blue";
}
let emptyTanks = (car: Car): Car => {    
    car.fuelTankFull = false;    
    return car;
};
let toMPG = (car: Car): number => {    
    return car.mpg;
};
let main = async () => {    
    let car1: Car = new Car();    
    car1.mpg = 32;    
    let car2: Car = new Car();    
    car2.mpg = 15;    
    let car3: Car = new Car();    
    let original: Node<Car> = listify(car1, car2, car3);    
    let emptied: Node<Car> = map(original, emptyTanks);    
    let milesPerGallon: Node<number> = map(original, toMPG);    
    print(emptied);    
    print(milesPerGallon);
};

The resulting "emptied" list is a list of Car objects whose fuelTankFull property has been changed to false.

fuelTankFullmpgcolor
false32blue
false15blue
false30blue
null

The resulting "milesPerGallon" List is a List of numbers - the mpg property of each Car object.

32 --> 15 --> 30 --> null.