Named Trait Instances


#1

Newtypes are a lot of boilerplate and often feel like the wrong abstraction to use. Idris has a concept called named instances which has been proposed for inclusion in Haskell that avoid arbitrary wrapping and unwrapping. Functions which are generic over some constrained type can be passed a named instance instead of defaulting to the canonical one.

ML functors, while much more verbose than typeclasses/traits for the average usecase, have never had this problem of only one way of viewing a type/module as satisfying some constraint/signature because every usage is named in the first place, leading to very intuitive, highly polymoprhic abstractions. You also cannot mix up your “different views” because both generative and applicative functors are type incompatible for different arguments. Thus, something like BTreeSet<i32> is incompatible with BTreeSet<i32 as IntReverseOrd>. (With ML functors, you might even be able to write something like BTreeSet<i32 as Reverse(Int)>, where Reverse is a functor which takes an Ord impl and produces a new, anonymous, reversed Ord impl).

I am not going to float any syntax ideas besides Identifier<Type as NamedInstance> and fun_identifier::<Type as NamedInstance>, and that obviously doesn’t answer the question of multiple trait bounds or input parameters for traits. In any case, I think with trait impl specialization coming, it would be good to investigate other, useful ways of increasing expressiveness and being friendly to users.

Has this had an RFC in the past? I couldn’t find anything.


Making more out of traits
#2

@kristof asked me to post this WIP RFC I had lying around for something similar. To be clear, this was written independently a while ago, and was more concerned with allowing for crates to implement external traits for external types and get around the coherence rules (think patching impl holes in external types).


#3

As a summary of the differences between what I just proposed and what DanielKeep posted:

  1. My proposal works at the generic function level, while his works at a namespacing level.
  2. My proposal is meant to be used explicitly, while his is implicit.
  3. As a consequence, my proposal allows for the exceedingly rare case where you would ever want multiple implementations of the same trait in scope, while his does not, for the good reason that it is exceedingly rare.

#4

To clarify, is this sentence meant to read “Where Reverse is a functor that takes an Ord impl and produces a new anonymous reversed Ord impl?” (i.e. it modifies impls of the one Ord trait, not creating new traits, whatever that means).


#5

That was in fact what I meant. Correcting now.


#6

I don’t think I’ve seen any recent proposals, but long ago we definitely considered (and decided against) named instances, mostly because going with a global namespace for impls (as we have now) seemed simpler overall, and the use-cases for named instances were relatively rare. At that time, things weren’t as well documented, but I think that the options we were considering wound up being pretty close to what you are talking about: basically making the impl become part of the type. At the time we were going from a world where all impls were named, though, which meant you had to do a lot of importing of impls in order to call methods and so forth, which was quite a usability burden. Obviously that is rather different from what you describe here.


#7

I recommend reading http://degoes.net/articles/principled-typeclasses/ and http://degoes.net/articles/newtypes-suck/. They are about Haskell type classes, but Rust has similar issues.