Hacker News new | ask | show | jobs
by veets 2094 days ago
You can do the same in Java and C++. This may violate a strict definition of parametricity (I've read the definition from a few different sources and am still mulling it over), but I'm not sure how this relates to parametric polymorphism.

The _behavior_ of this function is the same for all types, the _output_ is different. That is, for all types, the function body is the same. Maybe there is a more abstract definition of parametric polymorphism you are using, but as I said above, this seems pedantic.

2 comments

The internal behavior can trivially be made different just by operating on the value:

    fn foo<T>() -> usize {
        let x = std::mem::size_of::<T>();
        if x % 2 == 0 {
            panic!();
        }
    }

    foo::<u8>(); // 1
    foo::<u16>(); // panic
That the body is the same isn't necessarily the issue at hand (though of course it's still a useful property in its own right), what matters is that reasoning about what this function will do requires knowing which specific types it is used with.

> this seems pedantic

The first code example is merely the simplest demonstration, in the wild I would expect lots of `size_of` in generic contexts to result in type-dependent behavior somehow.

I'm not saying this is necessarily a very bad thing, nor do I have strong opinions on the usefulness of strict parametricity (which AFAIK Haskell doesn't have either). But in discussions relevant to parametricity, it's useful to know the ways a given language can subvert it (and Rust will further encourage it to be subverted, once the specialization feature is developed).

> I'm not saying this is necessarily a very bad thing, nor do I have strong opinions on the usefulness of strict parametricity (which AFAIK Haskell doesn't have either). But in discussions relevant to parametricity, it's useful to know the ways a given language can subvert it (and Rust will further encourage it to be subverted, once the specialization feature is developed).

In practice Haskell seems to have pretty strong views on enforcing parametric polymorphism, doesn't it?

Haskell gives you ad-hoc polymorphism via typeclasses and there are also existential types and GADTs etc, if you need those. But once you declare something to abide by parametric polymorphism, you are expected to keep your end of the bargain.

(Yes, you could violate the pact via some unsafePerformIO trickery, but that's considered bad form.)

The whole point of parametric polymorphism (as opposed to eg ad-hoc polymorphism) is that just from reading the type of a function you get a guarantee about the limits of its behaviour.

If you functions routinely violate those limits as a matter of course, those guarantees are useless.

I'm all for abusing notation and terminology a bit when it makes sense in practice, but loosening our definitions too much risks making them useless, too.

In practice in Haskell, I often only need a helper function for eg integers, but when the implementation allows, I will give the function the most parametric-polymorphic type that fits, because that makes the readers job easier:

Just like an invocation of `filter` is easier to read than a for-loop, because `filter` is strictly less powerful than the loop.

(In addition, the more general type serves to give the compiler a hint, so it can yell at me in case I accidentally do an operation on the integer that I didn't mean to.)