Remix.run Logo
throwaway17_17 5 days ago

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 5 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!