[swift-evolution] [Review] SE-0081: Move where clause to end of declaration

L. Mihalkovic laurent.mihalkovic at gmail.com
Mon May 16 17:54:40 CDT 2016



Regards
(From mobile)

> On May 16, 2016, at 8:16 PM, Thorsten Seitz via swift-evolution <swift-evolution at swift.org> wrote:
> 
> 
>>> Am 16.05.2016 um 17:04 schrieb Karl via swift-evolution <swift-evolution at swift.org>:
>>> 
>>> internal func _arrayOutOfPlaceReplace
>>> <B: _ArrayBufferProtocol, C: Collection>
>>> (_ source: inout B, _ bounds: Range<Int>, _ newValues: C, _ insertCount: Int)
>>> where
>>> C.Iterator.Element == B.Element,
>>> B.Index == Int
>>> {
>>> 
>>> Now only the relatively unimportant details—that the buffer and collection must have elements of the same type, and that the buffer must have integer indices—are at the end, whereas the basic conformances required to make any sense at all of the declaration are still inline.
>> 
>> You see, I’m of the complete opposite opinion - I don’t think those details are “relatively unimportant”; I think they’re absolutely critical.
>> 
>> If you try and call it when your C.Iterator.Element is not equal to B.Element, the function does not exist. You’re talking about some other function in that case, not this one.
>> 
>> Same goes if B.Index is not an Int. If that was an unimportant detail, they would have given ‘bounds’ the type Range<B.Index> and not specified any constraints at all. The function requires the index be an Int - maybe it’s doing some mathematical operations which wouldn’t make sense for a more complex index type, such as DictionaryIndex.
>> 
>> Basically that is it - the ‘where’ clause is a vital part of the function declaration; it defines the specification under which the function exists at all (along with the function name, arguments and return type). If you don’t match every single part of that specification, the type checker won’t match your call to this function - if you don’t meet the constraints, you’re not talking about this function; imagine you have several overloaded function declarations which differ only by ‘where’ condition:
>> 
>> func insert<T>(contentsOf:T) where T:RandomAccessCollection, T.Element == Element
>> func insert<T>(contentsOf:T) where T:Collection, T.Element == Element
>> func insert<T>(contentsOf:T) where T:Sequence, T.Element == Element
>> … etc
>> 
>> the ‘where’ clause isn’t incidental here - it’s the only disambiguating feature between these declarations. I think it’s critical information and shouldn’t be stuffed at the end because you think it’s not important; it is important. If it hinders initial readability of the declaration so much, you can wrap it behind a typealias:
>> 
>> func insert<T: RandomAccessCollectionOf<Element>>(contentsOf: T)
>> func insert<T: CollectionOf<Element>>(contentsOf: T)
>> func insert<T: SequenceOf<Element>>(contentsOf: T)
>> … etc
> 
> Either you have the constraints first and the parameter list last or the other way around. Fact is that *both* decide whether it is the function you need.
> So just their order cannot help with that.
> I would still argue that the proposed version is more readable: just look at your example above where the first part up to the where is identical at one glance, so that the disambiguating part stands out whereas your other example looks much more unstructured because of the different lengths of the generic type list which makes the congruence of the parameter lists difficult to see because they do not align anymore.
> 
>> 
>> I think that’s much easier to follow, and attempts to reduces the length and verbosity of the where clauses (i.e. like the fact that Collection’s associated type referring to its element is called ‘Element’; ‘CollectionOf’ encodes an equivalent constraint in less characters). This proposal just feels kind of lazy - we’ll just tack them on the end so we can ignore them a bit more easily, even though they’re still going to be monstrously long and difficult-to-read.
> 
> The typealias idea can be combined with the where clause at the end:
> 
> func insert<T>(contentsOf:T) where T:RandomAccessCollectionOf<Element>
> func insert<T>(contentsOf:T) where T:CollectionOf<Element>
> func insert<T>(contentsOf:T) where T:SequenceOf<Element>
> 
> Much easier to discern (at least IMO) because everything is aligned.
> 
>> 
>> Are there any other languages that do this? Or anything even similar? It seems to me that the context-switching is something that human beings in general are not going to find very legible; like if you insert too many commas in a sentence.
> 
> Yes, Ceylon places all constraints (it only knows inheritance constraints) at the end:
> 
> shared Value sum<Value>({Value+} values) 
>         given Value satisfies Summable<Value> { ... }
> shared <Key->Item>[] zip<Key,Item>({Key*} keys, {Item*} items)
>         given Key satisfies Object
>         given Item satisfies Object { ... }
> 
> It does the same for type definitions:
> shared class Singleton<out Element>(Element element)
>         extends Object()
>         satisfies [Element+]
>         given Element satisfies Object { ... }
> 

This confirms my impression that WHERE does not fit as well at the end as if the information was inlined. WITH is my preferred replacement, which IMO has a similar connotation as ceylon's GIVEN.


> 
>> 
>>>> On 15 May 2016, at 16:05, Brent Royal-Gordon via swift-evolution <swift-evolution at swift.org> wrote:
>>>> 
>>>> There we are. I read the declaration of the function from beginning to end
>>>> and gradually formed a rough understanding of it without needing to change
>>>> my expectations halfway through. I still have doubts about 'insertCount',
>>>> but I was at least able to formulate an hypothesis about its use.
>>>> 
>>>> YMMV, but as far as I'm concerned, the original declaration was much easier
>>>> to understand.
>>> 
>>> I actually agree with you for this case, but not *all* type information *has* to be moved to the end. Try this on for size, which is actually still the most natural way to write this declaration in SE-0081:
>>> 
>>> 
>>> This proposal *permits* you to hollow out the generic parameter list to the point of vacuousness, but that doesn't mean it's the right thing to do. It might make sense to ban a direct `X: Foo` requirement unless there was already an `X: Bar` in the generic parameter list. Or we might simply need to paraphrase the Perl manual page: "There are several ways to write a generic requirement, so consider picking the most readable one."
>>> 
>>> -- 
>>> Brent Royal-Gordon
>>> Architechies
>>> 
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160517/1db065b7/attachment.html>


More information about the swift-evolution mailing list