Function equality

I think there is one place where function equality is commonly useful: Occasionally, one wants to create (or use) semi-generic data structures that require more information about the values of a given type than equality. For example, a Dict needs an order on the keys and a HashSet would need a hash function on the potential values. Now, Elm generates some instances of comparable, but custom types are left out. There is no hashable at all.

If one wants to use custom types in these data structures, there are the following options, none of which I find particularly good:

  • Replace a custom type that happens to be used as a key in a Dict by ints, strings and tuples of those two everywhere. This loses type safety in all other parts of the app.
  • Create a toKey function from your type to a type that can serve as a key, and insert this function whenever the type is used as a key. This is quite verbose and not in a way that increases code clarity, in my opinion.
  • Create a new module containing a copy of (parts of) Dict’s API, only with all the toKey calls already included. This is also busywork and it requires the reader of the code to keep an additional type in their head. Also, it increases the size of the compiled product.

A solution to this problem are AnyDict implementations that store the toKey function with the data structure (so you only need to supply it when creating a new instance, not on every operation). They currently work reasonably well: If you always supply the same global toKey function on instantiation, you can even compare them for equality, as references to the exact same function can be compared for equality and are considered equal. If you mess up and supply different functions, you run into a runtime error. (But in particular, always using a top-level toKey is a good heuristic to ensure that only one copy of the function exists.)

If other goal is to remove the runtime error, there should be a solution to the generic data structure problem of some form. Replacing the runtime error by False would do that, though I’m not convinced that replacing errors by default values is not more dangerous than the error (you might corrupt data because you work under false assumptions). Introducing an equatable constraint to exclude function from == altogether does not. Making more types comparable in exchange would help with the case of Dict but not with other situations (e.g., a potential HashSet).