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 < [email protected]> wrote:
> Do you have any suggestions on how this area could be solved differently > and less complex as it seems to be here? :) 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 ([email protected]) 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 < > [email protected]> wrote: > > 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 > >
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
