Re: Self and associated type requirements (yes again!)
Re: Self and associated type requirements (yes again!)
- Subject: Re: Self and associated type requirements (yes again!)
- From: Roland King <email@hidden>
- Date: Tue, 01 Dec 2015 09:01:54 +0800
> On 1 Dec 2015, at 06:17, Quincey Morris <email@hidden> wrote:
>
> On Nov 30, 2015, at 04:12 , Roland King <email@hidden <mailto:email@hidden>> wrote:
>>
>> I want to say that timeSeries is any TimeSeries with an underlying type of Float, but you can’t.
>
> The problem is that this says that you want a generic protocol, and there’s no such thing. Unfortunately, TimeSeries is not (in a sense) non-generic either, because of its associated type requirement.
>
> I agree that the two types of protocols in the current Swift (conformable protocols and constraint protocols) is Swift’s biggest defect right now. I don’t know if it’s technically feasible (or even makes sense), but I’d love to see those unspecified typealias’s in protocols turn into generic type parameters, so the whole thing just turns into a comprehensible generic system. (Yeah, right.)
>
>
TL;DR version - don’t bother reading it, this is just my ramblings partly so I can find them later on google and re-read what I wrote
That’s how I feel too - the combination of parameters for Generics and associated types for protocols get into conflict in situations like this and while generically it’s a hard problem, there are cases in which you could express intent and the compiler could write the code, and I hope that comes in the next iteration. It’s really easy to get in this mess too. My thinking went, “I need a time series, what would one look like, let me make it a protocol with methods to add a value, get values, subsequence etc. What do I want it to be a timeseries of .. I don’t really care, Floats, Doubles, Bananas, let’s just make that an Element". At this point I have now nailed one foot to the floor, I have a protocol with an associated type AND Self requirements (the subsequence gives you that).
I do, when I get to this point, understand why what I want to express isn’t sufficient, I want to have a time series of floats, I want to type a variable as one, perhaps with a syntax like this (which I just made up)
var series : T:TimeSeries where T.Element == Float
or perhaps putting the protocol constraints in as if they were generic parameters, I made this up too, along the lines of Quincey’s ‘typealiases turning into generic type parameters'
var series : TimeSeries<Element == Float>
You think that defines the behaviour of ‘series’ enough for the compiler to work with it, and it does, it says what you can *do* with series, that should allow you to use ‘series’ in the code, call its methods, mutate its properties, but it doesn’t tell you how big it is, is it a struct with 123 bytes of data or a class, ie a pointer. Without knowing what actually implements this protocol, the compiler can’t actually build the object which contains it. I understand this, although I always understand it after the fact.
Swift lets you hoist that information up into a description of a generic parameter to a type
public struct UserOfTimeSeriesOfFloat<T:TimeSeries where T.Element == Float>
{
var series : T
}
The compiler similarly knows what a ’T’ can do and what can be called on it and what it can return and because the actual type is passed when you make one, the compiler knows how big ‘series’ is and can make the concrete type which contains it.
There are cases where the compiler I think could do better. If TimeSeries is a class, or if you could specify in the constraint that TimeSeries in this instance must be implemented by a class, the compiler knows how big series is, it’s a pointer, and any implementation of TimeSeries where the Element is a Float which is implemented by a class, works and the type’s size is known. UserOfTimeSeriesOfFloat is (I think) fully compilable at that point. That’s a restriction I would probably happily accept.
C++, ugly though it is when you have multiple nested <> and you’ve typedef’ed yourself into oblivion, manages to let you express this kind of intent because when you do you always end up using a pointer or a reference, so the type defines the behaviour you can use, and the pointer/reference defines the storage size.
For anyone still reading, it actually got worse^H^H^H^H^H more interesting. TimeSeries is a SequenceType, made sense right, I want to iterate them and I love embracing standards. A SequenceType returns a GeneratorType which itself generates Elements. In this particular case what you get out of iterating a TimeSeries is a TimeSeriesPoint which is a struct { time : Int, value : Underlying }, both the full and compressed series implementations return Generators which generate those. So I wrote this
for tick in series { total += tick.value }
and of course it didn’t compile. The error was something like "cannot call ‘value’ on T.Generator.Element” which stumped me for a while. But indeed, just because my two implementations of TimeSeries produced GeneratorTypes who’s Elements were TimeSeriesPoints didn’t mean that *any* implementation of TimeSeries would make GeneratorTypes which generated those, they could have generated series of Underlying, or CGPoints or Giraffes. So I ended up specifying that too
public struct UserOfTimeSeriesOfFloat<T:TimeSeries where T.Element == Float, T.Generator.Element == TimeSeriesPoint<Float>>{ .. }
which, to its credit, Swift allows me to write. That let me use the results of generate() because the compiler knows what their behaviour is and constrains what concrete types I can use for a UserOfTimeSeriesOfFloat<>.
I wish I’d been able to express that constraint at the protocol definition level instead. I would have liked to have been able to write
protocol TimeSeries : SequenceType where TimeSeries.Generator.Element == TimeSeriesPoint<TimeSeries.Underlying>
because that’s really what I actually wanted a TimeSeries to be. I would have been happy to impose those requirements at that lower point on every TimeSeries implementation because I never really needed one to generate Giraffes.
I don’t know how much of this is ‘fixable’ because it’s not really broken, it’s just different and I don’t know either whether adding more syntactic sugar or compiler smarts would make it more or less comprehensible, I do look forward to the next WWDC and Swift 3.0 to see if this pretty common situation is addressed either by clever language additions or more ‘best practice’ suggestions.
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden