| My problem with ad-polymorphism is primarily that it encourages more generic-ness in authoring code, which I am aware is often lauded as a positive characteristic of a given program. Taken to the extreme, there are whole communities of developers that pride themselves of writing the most generic code possible. I hold the opinion that all code should be clearly specified and particularized to processing the actual data required by the specification of the problem domain. I am certainly on the extreme end of the spectrum of generic to highly specified, but I genuinely feel that better code and the resulting programs come from a low level and data specific design ethos. Additionally, and more relevant to Programming Language Theory, the entanglement between ad-hoc polymorphism implementations and the detention of a programming language are a huge mistake from my perspective. This is where Haskell becomes relevant, because although there are some differences (implicit self, orphan issues, etc) between Rust traits and Haskell’s typeclasses, they are an extremely similar mechanism for achieving ad-hoc polymorphism. At this point in its development, and due to the pervasiveness of usage throughout the stdlib, I see it as almost certain that Rust will attempt to kludge traits into a formal definition of its type theory and language definition making any attempts at coexisting with other language in a single application more difficult. Comparably, in Haskell, typeclasses are merely syntactic sugar allowing for a better developer experience using ad-hoc polymorphism. Specifically, Haskell, the language as defined, achieves ad-hoc polymorphism by passing a dictionary parameter to functions using overloaded function names. This is done using the standard terms in Haskell and has a clear denotaional semantics for such terms. Rust usage of traits is talked about and reasoned about as a more primitive part of the language, not just a pretty piece of sugar with a more basic existence in the formally defined core of a programming language. If ad-hoc polymorphism is something a language designer wants to incorporate into the usage of a language, my position is that defining a typeclass style sugar over a clearly defined set of terms in the core, formally defined language would be at least a method of prohibiting issues with the resolution, definition, and various methods of implementation of ad-hoc polymorphism polluting the syntax and semantics of the language itself. But the above requires a firm definitional boundary between what is the language and what is syntactic sugar to be built into the definition of the language and built into the compiler infrastructure. A more out of band way to achieve ad-hoc polymorphism would be to have a pre-processor that is a part of the language distribution and maintained by the language designer/org that does the resolution/solving of the ad-hoc polymorphism and then presents the compiler with source text with no overloading. There are also type theoretic solutions to ad-hoc polymorphism l, but that’s a little outside the scope (even if it my personal solution to having and limiting ad-hoc polymorphism in a language). |
| |
| ▲ | aw1621107 6 days ago | parent [-] | | Woah! Thank you for taking the time to explain your perspective and thoughts! It's a lot of food for thought. I wish I had the background and knowledge to discuss things on equal footing :( Just a few additional questions/comments: > Specifically, Haskell, the language as defined, achieves ad-hoc polymorphism by passing a dictionary parameter to functions using overloaded function names. This is done using the standard terms in Haskell and has a clear denotaional semantics for such terms. Rust usage of traits is talked about and reasoned about as a more primitive part of the language, not just a pretty piece of sugar with a more basic existence in the formally defined core of a programming language. Would it be accurate to say that Swift's non-monomorphized generics are more along the lines of the Haskell approach you prefer (i.e., single function implementation with a separate parameter for additional type info)? And a bit more hand-wavey, but IIRC Rust's generics were defined in such a way that monomorphization is technically an implementation detail; in other words, I want to say a Haskell-like approach isn't strictly ruled out. I'd take that with a large grain of salt, though. > If ad-hoc polymorphism is something a language designer wants to incorporate into the usage of a language, my position is that defining a typeclass style sugar over a clearly defined set of terms in the core, formally defined language would be at least a method of prohibiting issues with the resolution, definition, and various methods of implementation of ad-hoc polymorphism polluting the syntax and semantics of the language itself. But the above requires a firm definitional boundary between what is the language and what is syntactic sugar to be built into the definition of the language and built into the compiler infrastructure. Might MiniRust [0] be something along the lines of what you would desire? It seems to fit the general idea of a smaller formally-specified "core" language. There's also this bit about traits specificlaly from the readme: > That translation [from Rust to MiniRust] does a lot of work; for example, traits and pattern matching are basically gone on the level of MiniRust. [0]: https://github.com/minirust/minirust | | |
| ▲ | throwaway17_17 6 days ago | parent [-] | | Swift is a good reference point in this area because Swift essentially took the dictionary-passing approach of Haskell and added the ‘low level’ type information like bit-width, offsets, etc as a Type Metadata parameter. The big upside is that Swift gets a good deal of performance boost compared to Haskell and other languages that have uniform datatypes (boxed values). So to extend the concept I was describing from Haskell to Swift would be creating concrete syntax for the language, which has no semantic identity outside those already existing in the semantics of Witness Tables. Sometimes the term “derived form” is used to talk about syntax like this. It is the term for concrete syntactic forms that are only defined in terms of syntax forms in the base/core language. It is, for the most part, correct to view derived forms as macros or as a more general metaprogramming construct, but but in Haskell’s case the macros are implemented in the compiler and expanded in a particular phase of compilation. Swift is also good touch point for considering what the compiler does with the actual implementation of ad-hoc polymorphism. Swift is optimizing to monomorphized instances of a generic function give some heuristics about expect performance gains by specializing the function for a given type (Haskell also does this in GHC, but still pays a performance price for using boxed values). So to answer the question: the part of Haskell’s implementation of typeclasses that I think is the correct method is that it is merely a derived syntactic form that is expanded by the compiler into Haskell’s actual language (its abstract syntax as encoded as ghc-core in GHC in particular). From this perspective Swift doesn’t provide the derived form, it just provides the implementation directly for developers to use omitting the sugar that Haskell provides. I tend towards explicitness as a strong default in language design. Rust doesn’t have a formal semantics currently, so they could certainly adopt a derived form approach to traits, but I don’t know enough Rust to be able to determine what issues existing thoughts, assumptions, and type theory ‘commitments’ would act as obstacles to such a semantic. As to MiniRust, Ralf Jung (at ETH Zurich) has done some excellent work, along with some of his students (Max Vistrup’s recent paper on Logics a la Carte is very, very good). MiniRust does attempt to be comparable to Haskell’s GHC-core. So in the sense of being or approaching what I would view as the (excluding type theory based options) correct way to implement ad/hoc polymorphism, yes, to sum: MiniRust as the semantic definition of a derived syntactic form and a compilation phase for ‘expansion’ of the trait macro. Those explanations aside, my issues with ad-hoc polymorphism do not go away under this implementation, I’m generally opposed to generic functions (especially function name overloading). But I think that if a language is pursuing ad-hoc polymorphism as a feature they should pursue it in a well founded and formal manner. | | |
| ▲ | JoshTriplett 5 days ago | parent | next [-] | | > I’m generally opposed to generic functions I'd be interested to know how, in your preferred model, you'd handle things like `Vec<T>` or `HashMap<K, V>`, without duplicating code. | | |
| ▲ | throwaway17_17 5 days ago | parent [-] | | Both of the example things you picked are generic types, and container-esque types at that. I think that my opposition to generics in general is a scale of dislike for different uses of generics. So, an off the cuff scale from (well founded and acceptable in certain cases) to (a strict negative in nearly all cases) would be: Polymorphic Types
Parametricly Polynorphic functions
‘Well Motivated’ Ad-hoc Polymorphism
Basic Function overloading
Basic ‘Operator Overloading’
Function overloading for symbols that are treated as ‘special’ by the compiler I think the hashmap case is illustrative for my general perspective. I do see the value in being able to have polymorphism for function arguments which are generic in a type parameter. However, consider that the ideal/most performant hashing function for keys differs not just based on general types (int vs string) but can differ based on something like string length or bit width or signededness. My position is that a language should prioritize the ability to encode those exact requirements in a data structure and difficulties for achieving generic-ness be damned. Each function taking a hashmap as argument should be tied to the optimizations and low level considerations intended by the developer. I am not opposed to some duplication of code where it produces the appropriate code for the problem being solved. My generalized dislike of ‘generics’ is there, but in my comment above I was mostly discussing ad-hoc polymorphism as a means of enforcing some general reasoning ability onto function name overloading. And as I implied in my scale above I find it particularly distasteful (basic function name overloading), if not actively harmful. For generics there are two areas often conflated in conversation that I find to be wildly different in formalizations of type theories and languages: first, there is static phase type application associated with System F and higher order polymorphic lambda calculus more broadly. I obviously would like to see a more specific and limited implementation of generics at all levels of abstraction, but the higher the abstraction goes the more ‘sense’ generic-ness makes. Second, there is generics as a name for function name overloading, which is distinct from parametricly polymorphic function as well as distinct from generic types. I really dislike this usage of generics and do not think it is a good practice for developing quality software or for readability, maintainability, or efficient optimization. Obviously this is a scale as well. I would put Swift in the lead with Witness Table semantics for generics, then typeclasses and traits, then any less structured implementations at the bottom. | | |
| ▲ | JoshTriplett 4 days ago | parent [-] | | That's a helpful explanation, thank you. I do generally agree that overuse of generics can produce bad interfaces. I also agree that generics tempt people to assume that the type fully determines the desired behavior, rather than giving any other means of selecting it. (It's worth noting, for instance, that HashMap lets you replace the hasher, if you have some strings with a property that make them more hashable. But if we didn't have that, then the generic would force a given type to have only one hasher, and force a newtype wrapper to change the hasher, which would make for suboptimal designs.) |
|
| |
| ▲ | IshKebab 6 days ago | parent | prev | next [-] | | > I’m generally opposed to generic functions I agree there are significant costs to generics (especially readability), but there are large classes of problems that are a right pain without them. Even Go added them eventually. | |
| ▲ | aw1621107 5 days ago | parent | prev [-] | | Thanks again for taking the time to answer! I think I have a bit more comfortable understanding of what's going on. I appreciate it! |
|
|
|