TypeScript: you can pass class method as lambda, but it will blow up

TypeScript allows passing class methods to functions that expect a lambda, but it won’t work at runtime. Consider this code:

function applyTwice(f: (x: number) => number, x: number): number {
    return f(f(x));
}

Suppose we have class Calculator like this:

class Calculator {
    private factor: number;

    constructor(factor: number) {
        this.factor = factor;
    }

    multiply(x: number): number {
        return x * this.factor;
    }
}

We can then do:

const calculator = new Calculator(3);
const result = applyTwice(calculator.multiply, 2);
console.log(result);

It will compile, but it will blow up at runtime with Cannot read properties of undefined (reading 'factor').
When calculator.multiply is passed in this manner, the magic this parameter becomes undefinedthis to calculator, as follows:

const calculator = new Calculator(3);
const result = applyTwice(calculator.multiply.bind(calculator), 2); // returns 2*3*3 = 18
console.log(result);

This works as expected, but looks kind of redundant.

TypeScript behaves a little bit like C++ in this situation, where class method also needs to be explicitly bound to a variable:

#include <iostream>
#include <functional>

class Calculator {
public:
    explicit Calculator(int factor) : factor_(factor) {}

    int multiply(int x) const {
        return x * factor_;
    }

private:
    int factor_;
};

// applyTwice applies a function f to x twice: f(f(x))
int applyTwice(const std::function<int(int)>& f, int x) {
    return f(f(x));
}

int main() {
    Calculator calculator(3);

    auto boundMultiply = [&calculator](int x) {
        return calculator.multiply(x);
    };

    int result = applyTwice(boundMultiply, 2); // 3 * (3 * 2) = 18
    std::cout << result << std::endl;

    return 0;
}

Unlike TypeScript, if we try to simply pass calculator.multiply to applyTwice, it will generate an error. Exact error message depends on the compiler, but the main point is it won’t compile.

  // GCC: invalid use of non-static member function 'int Calculator::multiply(int) const'
  int result = applyTwice(calculator.multiply, 2); 

Python, Java, and C# automatically bind class methods to the variable, so the code works as expected out of the box.

Python:

class Calculator:
    def __init__(self, factor):
        self.factor = factor

    def multiply(self, x):
        return x * self.factor

def apply_twice(f, x):
    return f(f(x))

calculator = Calculator(3)
result = apply_twice(calculator.multiply, 2)  # 3 * (3 * 2) = 18
print(result)

Java:

import java.util.function.IntUnaryOperator;

public class Main {

    // Method that applies a function twice
    public static int applyTwice(IntUnaryOperator f, int x) {
        return f.applyAsInt(f.applyAsInt(x));
    }

    static class Calculator {
        private final int factor;

        public Calculator(int factor) {
            this.factor = factor;
        }

        public int multiply(int x) {
            return x * factor;
        }
    }

    public static void main(String[] args) {
        Calculator calculator = new Calculator(3);
        int result = applyTwice(calculator::multiply, 2); // 3 * (3 * 2) = 18
        System.out.println(result);
    }
}

C#:

using System;

class Program
{
    static int ApplyTwice(Func<int, int> f, int x)
    {
        return f(f(x));
    }

    class Calculator
    {
        private int factor;

        public Calculator(int factor)
        {
            this.factor = factor;
        }

        public int Multiply(int x)
        {
            return x * factor;
        }
    }

    static void Main()
    {
        var calculator = new Calculator(3);
        int result = ApplyTwice(calculator.Multiply, 2); // 3 * (3 * 2) = 18
        Console.WriteLine(result);
    }
}

TypeScript behaves worse than most popular languages, because it neither binds the method automatically (Java, C#, Python), nor enforces the binding via the compiler (C++).

Thanks to ChatGPT for creating this example and quickly translating it into 5 different languages.

Leave a Reply

Your email address will not be published. Required fields are marked *