Okay now I see your point there. :) Thank you Xiaodi and Tony.

It’s an interesting approach you have there, but I see another problem with 
Self and - ProtocolName. Self does not refer to the current type returned from 
the protocol member. SE–0068 might help there, but as soon we’re working with 
non-final classes it will be problematic again.

That means something like this will be possible constraint.x(1).y(1).x(2), 
which by solving the main problem of this topic we’d like to avoid.

We’d need a way to subtract a protocol from the returned type + the ability of 
keeping T only members.

As for T : P1 & P2 & P3, T - P1 should return T + P2 & P3, because it’s what 
the user would logically assume there. The next chain needs to remember - P1 on 
that path, so the result for the followed reduction of - P3 would be equivalent 
to T - P1 & P3 = T + P2.

As you already mentioned, one would assume that we might be able to cast back 
to T from T - P1 & P3. I think this leads us to the right direction where we 
should realize that we should escape from T in general. That means that the 
return type should somehow create a new subtraction type, which can be reduced 
further by member chaining or escaped similar to what I wrote in the original 
post.

We’d need a new type or keyword that refers to the current (reduced) type. 
Let’s call it Current instead of Self. Furthermore we’d need a concrete result 
type, to be able to pass the result value around. Let’s call the latter type 
Subtraction<T>.

protocol WidthConstrainable {
  func width(_ v: CGFloat) -> Subtraction<Current – WidthConstrainable>
}
protocol HeightConstrainable {
  func height(_ v: CGFloat) -> Subtraction<Current – HeightConstrainable>
}
protocol XConstrainable {
  func x(_ v: CGFloat) -> Subtraction<Current – XConstrainable>
}
protocol YConstrainable {
  func y(_ v: CGFloat) -> Subtraction<Current – YConstrainable>
}
struct Constraint: WidthConstrainable, HeightConstrainable, XConstrainable, 
YConstrainable {
  ...
}
Subtraction<T> could be a similar type, like we’re proposing to change the 
metatypes from T.Type/Protocol to Type<T> and AnyType<T>. That means that it 
would know all the members of Current without the subtrahend. Each further 
member chain would create ether a nested Subtraction<Subtraction<T - P1> - P2> 
or a flattened type like Subtraction<T - P1 & P2>.

let constraint = Constraint()
let a: Subtraction<Constraint - XConstrainable> = constraint.x(1)
let b: Subtraction<Subtraction<Constraint - XConstrainable> - YConstrainable> = 
a.y(2)
let c: Subtraction<Subtraction<Subtraction<Constraint - XConstrainable> - 
YConstrainable> - WidthConstrainable> = b.width(100)

_ = constraint.x(1).y(2).width(100)
That way we could even remove the new - operator and use generics parameter 
list instead. Maybe instead of Subtraction we could call the new type 
Difference<T>

type Difference<Minuend, Subtrahend> : Minuend - Subtrahend { … } // should 
know everything `Minuend` has, but exclude everything from `Subtrahend`

struct Constraint: WidthConstrainable, HeightConstrainable, XConstrainable, 
YConstrainable {
   func width(_ v: CGFloat) -> Difference<Current, WidthConstrainable> {
     
      var copy = self
      copy.width = v
      return Difference(copy)
   }

   func height(_ v: CGFloat) -> Difference<Current,  HeightConstrainable> { … } 
 
   func x(_ v: CGFloat) -> Difference<Current, XConstrainable> { … }
   func y(_ v: CGFloat) -> Difference<Current, YConstrainable> { … }
}
What do you guys think about this approach? :)



-- 
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 17:17:29, Tony Allevato ([email protected]) schrieb:

Xiaodi's point is really important—being able to express the notions 
simultaneously that "T has method a()" and "T does not have method a()" would 
break the type system.

Instead of focusing on the proposed syntax, let's consider the problem you're 
trying to solve. It sounds like what you're asking for could be expressed more 
cleanly with a richer protocol algebra that supported subtraction. It wouldn't 
be quite as automatic as what you propose, but it would feel like a more 
natural extension of the type system if you could do something like below, and 
would avoid combinatorial explosion of protocol types (you go from O(n!) to 
O(n) things you actually have to define concretely):

```
protocol WidthConstrainable {
  func width(_ v: CGFloat) -> Self – WidthConstrainable
}
protocol HeightConstrainable {
  func height(_ v: CGFloat) -> Self – HeightConstrainable
}
protocol XConstrainable {
  func x(_ v: CGFloat) -> Self – XConstrainable
}
protocol YConstrainable {
  func y(_ v: CGFloat) -> Self – YConstrainable
}
struct Constraint: WidthConstrainable, HeightConstrainable, XConstrainable, 
YConstrainable {
  ...
}
```

If a type X is just a union or protocols (for example, X: WidthConstrainable & 
HeightConstrainable & XConstrainable & YConstrainable), the subtraction (X – 
something) is easy to define. It's either valid if the subtrahend is present in 
the set, or it's invalid (and detectable at compile time) if it's not.

But there are still some rough edges: what does it mean when a concrete type is 
involved? Let's say you have T: P1 & P2 & P3, and you write (T – P1). That 
could give you a type that contains all the members of T except those in P1, 
which would be the members in P2, P3, and any that are defined directly on T 
that do not come from protocol conformances.

But what is the relationship between types T and (T – P1)? (T – P1) being a 
supertype of T seems fairly straightforward—any instance of T can be expressed 
as (T – P1). But if I have an instance of type (T – P1), should I be able to 
cast that back to T? On the one hand, why not? I can obviously only get (T – 
P1) by starting with T at some point, so any instance of (T – P1) must *also* 
be an instance of T. So that implies that T is a supertype of (T – P1). In 
other words, they're supertypes of each other, without being the same type? 
That would be a first in Swift's type system, I believe. And if we allow the 
cast previously mentioned, that effectively circumvents the goal you're trying 
to achieve. (We could argue that you'd have to use a force-cast (as!) in this 
case.)

This could be worked around by forbidding subtraction from concrete types and 
reducing T to the union of its protocols before performing the subtraction. In 
that case, (T – P1) would equal P2 & P3. But that relationship is still a 
little wonky: in that case, (T – P1) would also not contain any members that 
are only defined on T, even though the expression (T – P1) implies that they 
should. You would have to make that reduction explicit somehow in order for 
that to not surprise users (require writing something like `#protocols(of: T) – 
P1`?), and it leaves a certain subset of possible type expressions (anything 
that wants members defined on T without members of a protocol) unexpressible.

I actually glossed over this earlier by writing "..." in the struct body. If I 
defined `width(_:)` there, what would my return type be? We currently forbid 
`Self` in that context. Would `Self – WidthConstrainable` be allowed? Would I 
have to use the new protocol-reduction operator above? More details that would 
have to be worked out.

Protocol inheritance would pose similar questions. If you have this:

```
protocol P1 {}
protocol P2: P1 {}
```

What is the subtype/supertype relationship between P2 and (P2 – P1)? It's the 
same situation we had with a concrete type. Maybe you just can't subtract a 
super-protocol without also subtracting its lowest sub-protocol from the type?

My PL type theory knowledge isn't the deepest by any means, but if something 
like this was workable, I think it would be more feasible and more expressive 
than the member permutation approach. And that being said, this is probably a 
fairly narrow use case that wouldn't warrant the complexity it would bring to 
the type system to make it work.


On Mon, Dec 26, 2016 at 7:03 AM Xiaodi Wu via swift-evolution 
<[email protected]> wrote:
Should the following compile?

let bar = foo.a()
func f(_ g: T) {
_ = g.a()
}
f(bar)

If so, your proposal cannot guarantee each method is called only once. If not, 
how can bar be of type T?

On Mon, Dec 26, 2016 at 06:30 Adrian Zubarev <[email protected]> 
wrote:
I think I revise what I said about value semantics in my last post.

let chain: T = foo.a()

let new = chain
new. // should not see `a` here
It’s more something like a local scoped chain. I’m not sure how to call it 
correctly here. I’m not a native English speaker. =)



-- 
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 12:11:23, Adrian Zubarev 
([email protected]) schrieb:

By ‘calling once’ I meant, calling once at a single permutation chain. If the 
chain is escaped or followed by a non-permuting member that returns the same 
protocol, you’d have the ability to use all members at the starting point of 
the new chain.

permuting protocol T {
    func a()
    func b()
    func c()
    func d()
}

var foo: T = …

func boo(_ val: T) -> U {
    // Here val escapes the chain and creates a new one
    // That means that you can create a local permutation chain here again
       
    val.a() // we can use `a` here
    return …
}

boo(foo.a()) // a is immediately invoked here
I imagine this keyword to follow value semantics, so that any possible mutation 
is handled locally with a nice extra ability of permutation member chaining.

Did I understood your point correctly here?

Sure the idea needs to be more fleshed out, but I’m curious if that’s something 
that we might see in Swift one day. :)



-- 
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 11:50:50, Xiaodi Wu ([email protected]) schrieb:

Given `foo: T` and methods a(), b(), c(), d(), each of which can only be called 
once, how can the return value of these methods be represented in the type 
system?

That is, if `foo.a()` can be passed as an argument to an arbitrary function of 
type `(T) -> U`, either the function cannot immediately invoke a(), in which 
case foo is not of type T, or it can immediately invoke a(), in which case your 
keyword does not work.
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to