[Pitch] [Phase 2] New `permuting` keyword for protocols


(Adrian Zubarev) #1

Hi Swift community,

I’d like you to think about the idea of being able to create protocols with a permutation path.

Recently I build a really tiny wrapper API around NSLayoutAnchor, where I wanted the user to have a nice and shiny API. The user can decide with which method he starts and optionally chain the other methods after that. The main restriction was that each method could only be used once, which is what a permutation offers. For that project I had to model and overload over 30 protocols, which overlaps only 5 different methods. If I had to add more methods, the permutation protocol model would silly explode.

You can look up a visual graph here: https://github.com/DevAndArtist/swift-functionallayout

My pitch is purely additive and could be considered during phase 2!

Similar to indirect the new keyword would be applied to protocol members or to the protocol itself.

protocol Foo {
      permuting mutating func foo() -> Foo
      permuting mutating func boo() -> Foo
      permuting mutating func zoo() -> Foo
       
      func bar() -> EscapingType
       
      var x: Foo { get } // returns a new permutation path
}

permuting protocol Boo {
      func a() -> Boo
      func b() -> Boo
       
      func c() -> EscapingType
       
      var x: Boo { get } // participates in the current permutation chain
}
Example with Foo:

class A : Foo {
    permuting mutating func foo() -> Foo { return self }
    permuting mutating func boo() -> Foo { return self }
       
    func bar() -> EscapingType { return … }
       
    var x: Foo { return self }
}

let a = A().foo()

a.
// From here only the following part of `Foo` would be visible
//
// func boo() -> Foo
// func zoo() -> Foo
// func bar() -> EscapingType
// var x: Foo { get }
//
// `foo` is not visible because it's already used in the current permutation chain
//

let aa = a.zoo()

//
// Visible API from `aa`
//
// func boo() -> Foo
// func bar() -> EscapingType
// var x: Foo { get }
//

aa.x.

//
// Since `x` is not marked with `permuting` keyword, it returns a new permutation chain where the whole `Foo` interface is again visible
//

aa.x.bar() // this will escape the permutation chain completely, because we're not returning `Foo` interface here
Only a chain of members that returning the same protocol (or even Self?) and are marked with the permuting keyword are considered as part of the same permutation.

Members that are not permuting will either escape the permutation chain or create a new one, like bar or x in the example above.

Personally I think that would be a really handy tool for neat API design. It also helps to restrict some functional design while ease the build complexity.

My small wrapper API could be reduced to 3 protocols.

···

--
Adrian Zubarev
Sent with Airmail


(Xiaodi Wu) #2

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.

···

On Mon, Dec 26, 2016 at 04:32 Adrian Zubarev via swift-evolution < swift-evolution@swift.org> wrote:

Hi Swift community,

I’d like you to think about the idea of being able to create protocols
with a permutation path.

Recently I build a really tiny wrapper API around NSLayoutAnchor, where I
wanted the user to have a nice and shiny API. The user can decide with
which method he starts and optionally chain the other methods after that.
The main restriction was that each method could only be used once, which is
what a permutation offers. For that project I had to model and overload
over 30 protocols, which overlaps only 5 different methods. If I had to add
more methods, the permutation protocol model would silly explode.

You can look up a visual graph here:
https://github.com/DevAndArtist/swift-functionallayout

My pitch is purely additive and could be considered during phase 2!

Similar to indirect the new keyword would be applied to protocol members
or to the protocol itself.

protocol Foo {
      permuting mutating func foo() -> Foo
      permuting mutating func boo() -> Foo
      permuting mutating func zoo() -> Foo

      func bar() -> EscapingType

      var x: Foo { get } // returns a new permutation path
}

permuting protocol Boo {
      func a() -> Boo
      func b() -> Boo

      func c() -> EscapingType

      var x: Boo { get } // participates in the current permutation chain
}

Example with Foo:

class A : Foo {
    permuting mutating func foo() -> Foo { return self }
    permuting mutating func boo() -> Foo { return self }

    func bar() -> EscapingType { return … }

    var x: Foo { return self }
}

let a = A().foo()

a.
// From here only the following part of `Foo` would be visible
//
// func boo() -> Foo
// func zoo() -> Foo
// func bar() -> EscapingType
// var x: Foo { get }
//
// `foo` is not visible because it's already used in the current permutation chain
//

let aa = a.zoo()

//
// Visible API from `aa`
//
// func boo() -> Foo
// func bar() -> EscapingType
// var x: Foo { get }
//

aa.x.

//
// Since `x` is not marked with `permuting` keyword, it returns a new permutation chain where the whole `Foo` interface is again visible
//

aa.x.bar() // this will escape the permutation chain completely, because we're not returning `Foo` interface here

Only a chain of members that returning the same protocol (or even Self?)
and are marked with the permuting keyword are considered as part of the
same permutation.

Members that are not permuting will either escape the permutation chain
or create a new one, like bar or x in the example above.
------------------------------

Personally I think that would be a really handy tool for neat API design.
It also helps to restrict some functional design while ease the build
complexity.

My small wrapper API could be reduced to 3 protocols.

--
Adrian Zubarev
Sent with Airmail

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Adrian Zubarev) #3

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. :slight_smile:

···

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 11:50:50, Xiaodi Wu (xiaodi.wu@gmail.com) 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.


(Adrian Zubarev) #4

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 (adrian.zubarev@devandartist.com) 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. :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 11:50:50, Xiaodi Wu (xiaodi.wu@gmail.com) 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.


(Xiaodi Wu) #5

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 < adrian.zubarev@devandartist.com> 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 (
adrian.zubarev@devandartist.com) 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. :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 11:50:50, Xiaodi Wu (xiaodi.wu@gmail.com) 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.


(Tony Allevato) #6

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 < swift-evolution@swift.org> 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 < > adrian.zubarev@devandartist.com> 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 (
adrian.zubarev@devandartist.com) 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. :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 11:50:50, Xiaodi Wu (xiaodi.wu@gmail.com) 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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Adrian Zubarev) #7

Okay now I see your point there. :slight_smile: 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? :slight_smile:

···

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 17:17:29, Tony Allevato (allevato@google.com) 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 <swift-evolution@swift.org> 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 <adrian.zubarev@devandartist.com> 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 (adrian.zubarev@devandartist.com) 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. :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 11:50:50, Xiaodi Wu (xiaodi.wu@gmail.com) 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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #8

TBH, I think you're trying to solve this problem in a very complicated way.
What you're describing screams out for its own value subtype with let
variables and an initializer that will provide defaults. I'm skeptical such
a complicated design as you propose is necessary to achieve what you want.

···

On Tue, Dec 27, 2016 at 05:25 Adrian Zubarev via swift-evolution < swift-evolution@swift.org> wrote:

Okay now I see your point there. :slight_smile: 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? :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 17:17:29, Tony Allevato (allevato@google.com)
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 < > swift-evolution@swift.org> 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 < > adrian.zubarev@devandartist.com> 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 (
adrian.zubarev@devandartist.com) 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. :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 11:50:50, Xiaodi Wu (xiaodi.wu@gmail.com) 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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Adrian Zubarev) #9

Do you have any suggestions on how this area could be solved differently and less complex as it seems to be here? :slight_smile: I’m open minded and I’d really appreciate if we’d have some language support for this problem.

···

--
Adrian Zubarev
Sent with Airmail

Am 27. Dezember 2016 um 18:49:22, Xiaodi Wu (xiaodi.wu@gmail.com) schrieb:

TBH, I think you're trying to solve this problem in a very complicated way. What you're describing screams out for its own value subtype with let variables and an initializer that will provide defaults. I'm skeptical such a complicated design as you propose is necessary to achieve what you want.

On Tue, Dec 27, 2016 at 05:25 Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:
Okay now I see your point there. :slight_smile: 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? :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 17:17:29, Tony Allevato (allevato@google.com) 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 <swift-evolution@swift.org> 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 <adrian.zubarev@devandartist.com> 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 (adrian.zubarev@devandartist.com) 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. :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 11:50:50, Xiaodi Wu (xiaodi.wu@gmail.com) 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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #10

What do you think of using a value type as a subtype, and having an
initializer that supplies defaults? It can only be used once, and after
that you have to create a new value. Does that not satisfy your
compile-time needs?

···

On Tue, Dec 27, 2016 at 13:20 Adrian Zubarev < adrian.zubarev@devandartist.com> wrote:

Do you have any suggestions on how this area could be solved differently
and less complex as it seems to be here? :slight_smile: I’m open minded and I’d really
appreciate if we’d have some language support for this problem.

--
Adrian Zubarev
Sent with Airmail

Am 27. Dezember 2016 um 18:49:22, Xiaodi Wu (xiaodi.wu@gmail.com) schrieb:

TBH, I think you're trying to solve this problem in a very complicated
way. What you're describing screams out for its own value subtype with let
variables and an initializer that will provide defaults. I'm skeptical such
a complicated design as you propose is necessary to achieve what you want.

On Tue, Dec 27, 2016 at 05:25 Adrian Zubarev via swift-evolution < > swift-evolution@swift.org> wrote:

Okay now I see your point there. :slight_smile: 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? :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 17:17:29, Tony Allevato (allevato@google.com)
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 < > swift-evolution@swift.org> 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 < > adrian.zubarev@devandartist.com> 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 (
adrian.zubarev@devandartist.com) 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. :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 11:50:50, Xiaodi Wu (xiaodi.wu@gmail.com) 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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Adrian Zubarev) #11

I’m not sure I’m following your point here. Could you provide a simple and short code sample please?

···

--
Adrian Zubarev
Sent with Airmail

Am 27. Dezember 2016 um 19:24:15, Xiaodi Wu (xiaodi.wu@gmail.com) schrieb:

What do you think of using a value type as a subtype, and having an initializer that supplies defaults? It can only be used once, and after that you have to create a new value. Does that not satisfy your compile-time needs?
On Tue, Dec 27, 2016 at 13:20 Adrian Zubarev <adrian.zubarev@devandartist.com> wrote:
Do you have any suggestions on how this area could be solved differently and less complex as it seems to be here? :slight_smile: I’m open minded and I’d really appreciate if we’d have some language support for this problem.

--
Adrian Zubarev
Sent with Airmail

Am 27. Dezember 2016 um 18:49:22, Xiaodi Wu (xiaodi.wu@gmail.com) schrieb:

TBH, I think you're trying to solve this problem in a very complicated way. What you're describing screams out for its own value subtype with let variables and an initializer that will provide defaults. I'm skeptical such a complicated design as you propose is necessary to achieve what you want.

On Tue, Dec 27, 2016 at 05:25 Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:
Okay now I see your point there. :slight_smile: 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? :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 17:17:29, Tony Allevato (allevato@google.com) 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 <swift-evolution@swift.org> 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 <adrian.zubarev@devandartist.com> 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 (adrian.zubarev@devandartist.com) 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. :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 11:50:50, Xiaodi Wu (xiaodi.wu@gmail.com) 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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #12

So I guess I'm having trouble understanding the motivating use case for
your idea. As I understand it, you wish to make sure that each constraint
(a, b, c, etc.) is set at most once, at compile time. The way to do this in
Swift is to have a type T like this [I'm writing freehand, so pardon any
typos]:

struct T {
  let a, b, c: Int?
  init(a: Int? = nil, b: Int? = nil, c: Int? = nil) {
    self.a = a; self.b = b; self.c = c
  }
}

Now, any value `x` will have set constraints a, b, and c at most once. If
the issue is that you don't know a, b, and c all at the same time, you
initialize a temporary `x`, then copy the already-set constraints to
another `y` when another constraint is known; the constraints in `y` will
have been set at most once.

If I understand it correctly, you want to guarantee at compile time that a
user never makes a new `y` from `x` by invoking some arbitrary method
`foo()` twice in the same scope, and you're therefore proposing a stateful
static type system? I don't quite understand why. As long as I know the
constraints set on `x`, I can always make a new value that copies over
constraints from `x`, resulting in a value identical in every way to the
`y` that I could get from `foo()`. In code:

// Given a, b, c, d
let x = T(a)
let y = x.addingBConstraint(withValue: b)
let z = y.addingCConstraint(withValue: c)

// You want the following line to be forbidden at compile time
let w = z.addingCConstraint(withValue: d)

// But I can always write instead
let w = T(a, b, d)

// Or
let w = T(z.a, z.b, d)

// And `w` would be in every way identical to the previous forbidden line
// Why do you need the type system to keep track of how `w` was created?
···

On Tue, Dec 27, 2016 at 1:27 PM, Adrian Zubarev < adrian.zubarev@devandartist.com> wrote:

I’m not sure I’m following your point here. Could you provide a simple and
short code sample please?

--

Adrian Zubarev
Sent with Airmail

Am 27. Dezember 2016 um 19:24:15, Xiaodi Wu (xiaodi.wu@gmail.com) schrieb:

What do you think of using a value type as a subtype, and having an
initializer that supplies defaults? It can only be used once, and after
that you have to create a new value. Does that not satisfy your
compile-time needs?
On Tue, Dec 27, 2016 at 13:20 Adrian Zubarev <adrian.zubarev@devandartist. > > wrote:

Do you have any suggestions on how this area could be solved differently
and less complex as it seems to be here? :slight_smile: I’m open minded and I’d really
appreciate if we’d have some language support for this problem.

--
Adrian Zubarev
Sent with Airmail

Am 27. Dezember 2016 um 18:49:22, Xiaodi Wu (xiaodi.wu@gmail.com)
schrieb:

TBH, I think you're trying to solve this problem in a very complicated
way. What you're describing screams out for its own value subtype with let
variables and an initializer that will provide defaults. I'm skeptical such
a complicated design as you propose is necessary to achieve what you want.

On Tue, Dec 27, 2016 at 05:25 Adrian Zubarev via swift-evolution < >> swift-evolution@swift.org> wrote:

Okay now I see your point there. :slight_smile: 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? :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 17:17:29, Tony Allevato (allevato@google.com)
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 < >> swift-evolution@swift.org> 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 < >> adrian.zubarev@devandartist.com> 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 (
adrian.zubarev@devandartist.com) 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. :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 26. Dezember 2016 um 11:50:50, Xiaodi Wu (xiaodi.wu@gmail.com)
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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution