Skip to content

Instantly share code, notes, and snippets.

@raichoo
Last active February 24, 2016 19:15
Show Gist options
  • Save raichoo/b5d2534c18eadbf9da8b to your computer and use it in GitHub Desktop.
Save raichoo/b5d2534c18eadbf9da8b to your computer and use it in GitHub Desktop.
Staring into the abyss of ===. TypeScript edition.
// Whenever I complain about == in JS people always tell me
// that I should use ===, as if that would make more sense.
// the `sel` function takes three values of type A
// we know nothing about, therefore we should not
// be able to compute anything with these values,
// the only thing we should be able to do is
// to constantly return one of them, their
// value should not be able to influence the
// result in any way.
function sel<A>(a: A, b: A, c: A): A {
// let's not look at the implementation of
// `sel` right now and bask in the guarantees
// given by its type signature. SPOILER ALERT:
// this will bite us.
return sel_impl(a, b, c);
}
// lets run some tests and find out how our function
// behaves.
// this returns the first argument
console.log(sel(666, 777, 888));
// this returns the first argument
console.log(sel("foobar", "quux", "bla"));
// this returns the first argument
console.log(sel(false, true, true));
// OK, looks like our function always returns the
// first argument. Why should it do anyting else?
// The type of `sel` certainly does not give any clue
// about possible operations that we can do on values
// of A.
// what the heck… this returns the third argument…
console.log(sel(false, false, true));
// OK, let's take a look at the implementation of `sel`.
function sel_impl<A>(a: A, b: A, c: A): A {
// making the assumption that
// we can compare `a` and `b`
// in general makes little sense
// there are values which we cannot
// compare in JS in a meaningful way
// e.g. functions.
// Anyway, being able to do this breaks
// parametricity.
if (a === b)
return c;
else
return a;
}
// Now that we've said it, let's compare values
// where JS has no clue to compare them.
// Ah, the trusty `id` function.
function id<A>(a: A): A {
return a;
}
function dummy<A>(a: A) {
throw new Error("I'm just here as a dummy");
}
// let's compare `id` with itself.
// Impressive, it seems to be able to compare functions,
// since this return `dummy`.
console.log(sel(id, id, dummy));
// So this should do the same then.
// No… it doesn't… it returns the first argument.
console.log(sel((x) => x, (x) => x, dummy));
// So, what === does is that it compares if `a` and
// `b` are stored at the same address… think about how
// often you want THAT kind of behavior instead of
// comparing the actual values?
// this behavoir becomes especially funny with arrays.
// nope these empty arrays are stored at different addresses,
// and are therefore not equal, therefore it returns the
// first argument.
console.log(sel([], [], [1,2,3]));
// so this should behave the same way with strings.
// No, it doesn't. this returns "bar". So with
// string this is a special case… gah.
console.log(sel("foo", "foo", "bar"));
// and no this does not happen because those two
// constants are stored at the same address. Let's
// just compute "foo" so we don't have a constant
// here. And it's "bar" again.
console.log(sel("f"+"oo", "foo", "bar"));
// programming languages really managed to pervert
// our understanding of what comparing values means.
// Happy bugfixing, y'all.
@Drezil
Copy link

Drezil commented Feb 24, 2016

Why is it then valid code to compare "non-primitive"-values with ===? Why not refute that Code with a Parse-Error (or Type-Error or similar)?

Every code passing the compiler will sooner or later end up in production..

The Problem with sel is that it does not have to state the Eq-Constraint in the Type. So you cannot rely on the Types to infer the behaviour of the function - quite the opposite. The function does things on A it is not supposed to do (here: using equality).

@coreh
Copy link

coreh commented Feb 24, 2016

=== is an identity operator, not an equality operator. In JavaScript, strings and numbers are passed by value, while other types are passed by reference.

These two pieces of information together explain every single oddity observed in the post.

@raichoo
Copy link
Author

raichoo commented Feb 24, 2016

This post is primarily mourning the loss of parametricity not the behavior of === nor the difference between comparing for equality or identity.

@macedigital
Copy link

@lewisje thanks for the very detailed comment. And for the record, I just had a flashback to the good ol' times, when this Java snippet would return false 😄

package com.company;
public class Main {
    public static void main(String[] args) {
        System.out.println("foo" == "foo");
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment