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.
@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