Moving the kernel to modern C
Moving the kernel to modern C
Posted Mar 3, 2022 22:20 UTC (Thu) by nybble41 (subscriber, #55106)In reply to: Moving the kernel to modern C by marcH
Parent article: Moving the kernel to modern C
> But once again the problem is the same: where is the compiler / linter flag that limits operator overloading to only "sensible" use cases? Where do you even draw that line?
Sensible languages (such as Haskell) only allow overloading though the implementation of an interface (typeclass), which narrows the problem down: if you want to override `+` in Haskell then you need to provide an instance of `Num` for that data type, which implies that you need to implement the other numerical operations like `*`, `negate`, `fromInteger`, etc. which make up the minimal complete definition. If you try to define `+` at global scope outside of a `Num` instance without explicitly suppressing `Prelude.+` you'll get an error due to the conflicting names.
Typeclasses, in turn, generally come with "laws" which specify how their members should relate to each other, in addition to declaring types for each member which instances must share. (For historical reasons `Num` does not have any official typeclass laws itself, but there are certain basic expectations regarding associativity, commutivity, distributivity, and other properties[0] which amount to informal laws.) The laws, unlike the types, are not automatically enforced, but unlawful instances are strongly discouraged and it is usually simple to write property-based tests to verify that they are met. The key is that the instances of an interface are related by more than just a common name. Implementing a typeclass serves as a declaration of intent that the instance will follow the typeclass laws and generally behave as expected for an instance of that typeclass.
It helps that Haskell allows almost any sequence of symbols as an operator name, so there is no pressure to abuse the left-shift operator for stream output. For example. You can also use any function name as an infix operator by putting it in backquotes—you can even define precedence and fixity for named functions used with infix syntax, just as you can for symbolic operators.
Rust uses essentially the same model, with traits in place of typeclasses, though the set of numerical traits in Rust is a bit more nuanced than Haskell's catch-all `Num` typeclass and the syntax unfortunately doesn't allow for custom operators.
[0] https://hackage.haskell.org/package/base-4.16.0.0/docs/GH...
