Make dot shorthand work with generics and existentials

(John McCall) #21

Well, you certainly could make them available on concrete values/types, but I think in general users wouldn't want them to be, and it's easy enough to define them both ways if necessary.

(Matthew Johnson) #22

Wouldn't that be required to make my sample code work?

extension Sequence {
   func fold<M: Monoid>(_ m: M) -> M.Value where M.Value == Element { ... }
struct Sum: Monoid {... }
static extension Monoid {
    static var sum: Sum { ... }

fold takes M: Monoid and .sum is used to drive inference of the type. This is how the workaround works, but that requires taking Sugar<M> instead of M so it breaks when users want to pass a value of some monoid that is stored in a value.

I'm not following this. I can't see why these would be any more limited than protocol extensions. The only difference is that the members declared in these extensions would not be visible on the concrete conforming types.

Wouldn't instance members here be static members on the protocol, thus this view would imply what @Lantua suggested about not using static inside the extension? I was wondering if metatype extension would make sense. That would also be useful on concrete types and would also facilitate the ML style when used with empty enums.

Yeah, I would be strongly opposed to making them visible on the concrete types. This is exactly the reason I didn't suggest using today's protocol extensions for this. I absolutely do not want static var sum: Sum (declared in an extension of Monoid) visible on Product, etc. That would lead to weird symbols polluting the namespace of types whenever these sugar factories are used.

(John McCall) #23

I misunderstood what you were trying to do. That doesn't really fit with what I was thinking, then, no.

(Matthew Johnson) #24

Ok, huh... Does it fit with @jrose suggested then? Would metatype extension P be a reasonable syntax for that? What are your thoughts in general about this? The goal is to use dot shorthand to drive type inference as well as to construct values of concrete conforming types in a DSL-like fashion.

(Jordan Rose) #25

There's definitely a tricky bit here, in that we don't want to change the behavior of this code:

func getMinFromElementForSomeReason<T: BinaryInteger>(_: T) -> T {
  return M.min

let x: Int = getMinFromElementForSomeReason(.max)
// That should be Int.max, even if BinaryInteger.max were defined.

I'm no longer sure this exactly makes sense for your original use case. The only reason it works is because the concrete types conforming to your Monoid protocol get instantiated, which isn't even strictly necessary for normal Monoid use.

(Matthew Johnson) #26

I assume you meant the body to return M.max. How would my pitch break that? API visible on concrete types would not be visible at all, only API defined on the protocol itself or in a metatype extension. If members declared on the protocol itself conflict with members declared in a metatype extension I would expect to get a duplicate symbol declaration error or something like that.

If you want to reference a member of a concrete type then dot shorthand would not be available for the reasons discussed in the beginning of this thread, so you would have to use Int.max to reference it. I don't think that is a problem.

I suppose it could be confusing that static factories declared on the protocol itself that return Self would not participate in the dot shorthand, but if you think about it that wouldn't make sense: dot shorthand requires a concrete type to be known and a static protocol member returning Self explicitly does not specify a concrete type. This shorthand feature is basically useless if it can't be used to drive inference.

It isn't necessary for many monoids but some monoids do benefit from dynamic parameterization (via an initializer). And of course monoid is just the concrete example for the sake of discussion. Many abstractions will benefit from both this sugar and from dynamic parameterization of instances. (Also note that I also pitched support for ML signature style protocols where there is no instance and the sugar is only used to drive metatype passing).

(Jordan Rose) #27

No, I specifically wanted the body to return T.min (but wrote M, oops). That is, the call-site choice for T is entirely specified by the type context, and even if I had

__metatype extension BinaryInteger {
  static var max: UInt64 { return ~0 }

the result still has to be Int.

(Matthew Johnson) #28

Ok, so I think this is what you meant:

func getMinFromElementForSomeReason<T: BinaryInteger>(_: T) -> T {
  return T.min

let x: Int = getMinFromElementForSomeReason(.max)
// That should be Int.min, even if BinaryInteger.max were defined.

Is that correct? I agree that the behavior of this code should not be changed. We should either say that explicit type annotations take precedence over shorthand-driven inference. We could also extend that to cases where there are other circumstances that fix the type T as below:

func getMinFromElementForSomeReason<T: BinaryInteger>(_: T, _: T) -> T {
  return T.min

let int: Int = 42
let x = getMinFromElementForSomeReason(.max, int)
// That should be Int.min, even if BinaryInteger.max were defined.

The other option in cases like these would be to produce an ambiguity error so the behavior would not change but some code might become ambiguous in the presence of metatype extensions and require an explicit Int.max to resolve the ambiguity.

Do you still feel this way? It seems to me like maybe metatype extensions with dot shorthand driven by protocol metatype extensions might be a very nice solution to the use case.

(Benjamin Mayo) #29

I would like to see some kind of feature like this. In my own codebase, I have wanted protocol metatype properties with less complex cases, in the following approximate pattern:

protocol CustomDelegate { 
    func complexRequirementsHere()
class DefaultCustomDelegate : CustomDelegate {

func performAction(withDelegate delegate: CustomDelegate) {

performAction(withDelegate: .default) // versus currently performAction(withDelegate: DefaultCustomDelegate())

The way I imagined this feature being expressed is something like

extension CustomDelegate.Type {
    var `default`: DefaultCustomDelegate { return DefaultCustomDelegate() }

It feels like it falls out quite naturally, with the goal of better call-site readability. Admittedly, I haven't thought of all the possible applications of this, like more complex generic expressions, but I thought I'd contribute another motivating use case (and syntax suggestion) to the thread.

Last year, I identified where this could potentially be used to improve the RandomNumberGenerator API.