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

How about a more radical change that eliminates this entire concern and keeps the whole generics declarations in one place:

Move the entire generic declaration with its brackets somewhere other than between function name and its parameters.

I know this breaks the “norm”, but what do you think?

···

On May 11, 2016, at 10:23 AM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On May 11, 2016, at 6:54 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

Am 11.05.2016 um 03:56 schrieb Joe Groff via swift-evolution <swift-evolution@swift.org>:

On May 10, 2016, at 4:19 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:

On May 10, 2016, at 3:46 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

I think actual keyword “where” provides enough of a delimiter that it won’t be hard to put something before it, and it seems unlikely to me that we would want to add anything after it without some other delimiter. So I’m not too concerned.

Yeah, that’s my feeling as well.

One conceivable use of `where` that this would shut the door on: infix `where` for generalized existentials, e.g. `Protocol where AssociatedType == Int` could be the Protocol existential with Self.AssociatedType constrained to Int.

Why do you think that?

This proposal moves `where` after the return type, which would be ambiguous with any infix use of `where` in the type grammar.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Another idea: what about a comma separating the return type from the where
clause?

func myFunc<A, B>(arg1: A, arg2: B) -> Bool, where A :
CustomStringConvertible { ... }

Best,
Austin

···

On Wed, May 11, 2016 at 1:56 PM, Hooman Mehr via swift-evolution < swift-evolution@swift.org> wrote:

How about a more radical change that eliminates this entire concern and
keeps the whole generics declarations in one place:

Move the entire generic declaration with its brackets somewhere other than
between function name and its parameters.

I know this breaks the “norm”, but what do you think?

On May 11, 2016, at 10:23 AM, Joe Groff via swift-evolution < > swift-evolution@swift.org> wrote:

On May 11, 2016, at 6:54 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

Am 11.05.2016 um 03:56 schrieb Joe Groff via swift-evolution < > swift-evolution@swift.org>:

On May 10, 2016, at 4:19 PM, Douglas Gregor via swift-evolution < > swift-evolution@swift.org> wrote:

On May 10, 2016, at 3:46 PM, Jordan Rose via swift-evolution < > swift-evolution@swift.org> wrote:

I think actual keyword “where” provides enough of a delimiter that it
won’t be hard to put something before it, and it seems unlikely to me that
we would want to add anything after it without some other delimiter. So I’m
not too concerned.

Yeah, that’s my feeling as well.

One conceivable use of `where` that this would shut the door on: infix
`where` for generalized existentials, e.g. `Protocol where AssociatedType
== Int` could be the Protocol existential with Self.AssociatedType
constrained to Int.

Why do you think that?

This proposal moves `where` after the return type, which would be
ambiguous with any infix use of `where` in the type grammar.

-Joe
_______________________________________________
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

I’d find this very confusing because it would make declarations look the the declarations of the generic types comes after their uses.

···

On 11 May 2016, at 22:56, Hooman Mehr via swift-evolution <swift-evolution@swift.org> wrote:

How about a more radical change that eliminates this entire concern and keeps the whole generics declarations in one place:

Move the entire generic declaration with its brackets somewhere other than between function name and its parameters.

I know this breaks the “norm”, but what do you think?

On May 11, 2016, at 10:23 AM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On May 11, 2016, at 6:54 AM, Thorsten Seitz <tseitz42@icloud.com <mailto:tseitz42@icloud.com>> wrote:

Am 11.05.2016 um 03:56 schrieb Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

On May 10, 2016, at 4:19 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On May 10, 2016, at 3:46 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I think actual keyword “where” provides enough of a delimiter that it won’t be hard to put something before it, and it seems unlikely to me that we would want to add anything after it without some other delimiter. So I’m not too concerned.

Yeah, that’s my feeling as well.

One conceivable use of `where` that this would shut the door on: infix `where` for generalized existentials, e.g. `Protocol where AssociatedType == Int` could be the Protocol existential with Self.AssociatedType constrained to Int.

Why do you think that?

This proposal moves `where` after the return type, which would be ambiguous with any infix use of `where` in the type grammar.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto: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

Ah, I see. But wouldn't this already be a problem with the current syntax as well, because the `where` might come after a constraining type which might be an existential?

As an alternative to `where` we could use `with` for existentials.

-Thorsten

···

Am 11.05.2016 um 19:23 schrieb Joe Groff <jgroff@apple.com>:

On May 11, 2016, at 6:54 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

Am 11.05.2016 um 03:56 schrieb Joe Groff via swift-evolution <swift-evolution@swift.org>:

On May 10, 2016, at 4:19 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:

On May 10, 2016, at 3:46 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

I think actual keyword “where” provides enough of a delimiter that it won’t be hard to put something before it, and it seems unlikely to me that we would want to add anything after it without some other delimiter. So I’m not too concerned.

Yeah, that’s my feeling as well.

One conceivable use of `where` that this would shut the door on: infix `where` for generalized existentials, e.g. `Protocol where AssociatedType == Int` could be the Protocol existential with Self.AssociatedType constrained to Int.

Why do you think that?

This proposal moves `where` after the return type, which would be ambiguous with any infix use of `where` in the type grammar.

To be clear, I'm not saying we *should* add infix `where` to the type grammar, only pointing out that this would prevent its use in the future.

-Joe

···

On May 11, 2016, at 10:23 AM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On May 11, 2016, at 6:54 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

Am 11.05.2016 um 03:56 schrieb Joe Groff via swift-evolution <swift-evolution@swift.org>:

On May 10, 2016, at 4:19 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:

On May 10, 2016, at 3:46 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

I think actual keyword “where” provides enough of a delimiter that it won’t be hard to put something before it, and it seems unlikely to me that we would want to add anything after it without some other delimiter. So I’m not too concerned.

Yeah, that’s my feeling as well.

One conceivable use of `where` that this would shut the door on: infix `where` for generalized existentials, e.g. `Protocol where AssociatedType == Int` could be the Protocol existential with Self.AssociatedType constrained to Int.

Why do you think that?

This proposal moves `where` after the return type, which would be ambiguous with any infix use of `where` in the type grammar.

Hi Karl,

As author of this proposal, the one about constraints on associated types, and the one on type-aliases in protocols (all from the Generics Manifesto - original authorship to Douglas Gregor) I’d like to provide additional reasoning behind my wish to push this proposal through, as a whole.

First of all, there is a personal preference. I’ve used C# for many many years, which has its where clause at the end of the declaration (like this proposal), and I’ve used Swift since its unveiling. The experience with using both styles for several years makes me favour this proposal’s syntax because I find it much easier to read and format for readability.

Constraints on associated type will provide more expressivity but I doubt it will greatly reduce protocol constraint clauses in a majority of cases. And yes, type-aliases in protocols will shorten clauses, but I still think they will more readable with the where clause at the end.

For example, here is a method I took (as-is) from the Standard Library which has a few constraints, and which I further simplified if we imagine that Sequence has an Element typealias for Iterator.Element:

internal func _arrayOutOfPlaceReplace<
  B : _ArrayBufferProtocol, C : Collection
  where
  C.Element == B.Element,
  B.Index == Int
>(
  _ source: inout B, _ bounds: Range<Int>, _ newValues: C, _ insertCount: Int
) {

See how the Standard Library authors formatted it for readability and how as a consequence arguments which use the generic types are further apart from the declaration of those generic types. But with this proposal, the declaration might be formatted to:

internal func _arrayOutOfPlaceReplace<B, C>(_ source: inout B, _ bounds: Range<Int>, _ newValues: C, _ insertCount: Int) where
  B : _ArrayBufferProtocol,
  C : Collection,
  C.Iterator.Element == B.Element,
  B.Index == Int
{

Reading this example reinforces my initial sense that while WHERE works well when the information is inlined, I would have preferred WITH in the case where the information is at the end. go figure...

···

On May 15, 2016, at 9:17 AM, David Hart <david@hartbit.com> wrote:

Do you need believe this is now more readable?

David.

On 15 May 2016, at 02:26, Karl Wagner via swift-evolution <swift-evolution@swift.org> wrote:

There is a lot not to like about the idea; even if it was optional. Personally, I feel the problem is solved in a much, much more elegant manner by other proposals.

Firstly, the stuff after the ‘where’ clause is getting shorter once typealiases come to protocols. C.Iterator.Element become C.Element. In this one example, that’s 18 characters down to 9 - a 50% reduction in length. We tend to use quite expressive names for associated types, so I expect we’ll see similar gains elsewhere from this very simple proposal.

Not only that, but there’s a very good proposal to add ‘where’ clauses to associated types in the protocols themselves, which will likely further reduce the verbosity of the constraints you need to specify at each declaration site. https://github.com/hartbit/swift-evolution/blob/9acd75abfbe626bbb3f9458cc3f6edb1d1f88c95/proposals/XXXX-associated-types-constraints.md

And then we have generic typealiases and generalised existentials, which would allow us to wrap those ‘where’ clauses in to something much more intelligible to a human being at first glance. ‘StringCollection’ or ‘CollectionOfStrings’ is much clearer than <C:Collection where C.Element==String>, no matter how you chop it up.

If I look at the other proposals, and where we are headed with much more expressive typealiases and associated types, I just feel that that’s the future: that’s the “swift’ way. It’s like type inference - all of the strict constraints are still there under-the-hood, but you’re able to work at a much clearer and more obvious abstraction level. This proposal pulls us further away from things like ‘obviousness’, and like I said, simply feels like an inelegant solution.

At the very least, I think we should shelve the discussion until the larger expansion of typealiases, etc is complete. We should re-evaluate at that time, with a bigger set of more general-purpose tools to produce readable code.

David Hart via swift-evolution <swift-evolution@...> writes:

Hi Karl,

As author of this proposal, the one about constraints on associated types,

and the one on type-aliases in protocols (all from the Generics Manifesto -
original authorship to Douglas Gregor) I’d like to provide additional
reasoning behind my wish to push this proposal through, as a whole.

First of all, there is a personal preference. I’ve used C# for many many

years, which has its where clause at the end of the declaration (like this
proposal), and I’ve used Swift since its unveiling. The experience with
using both styles for several years makes me favour this proposal’s syntax
because I find it much easier to read and format for readability.

Constraints on associated type will provide more expressivity but I doubt

it will greatly reduce protocol constraint clauses in a majority of cases.
And yes, type-aliases in protocols will shorten clauses, but I still think
they will more readable with the where clause at the end.

For example, here is a method I took (as-is) from the Standard Library

which has a few constraints, and which I further simplified if we imagine
that Sequence has an Element typealias for Iterator.Element:

internal func _arrayOutOfPlaceReplace<
B : _ArrayBufferProtocol, C : Collection
where C.Element == B.Element,
B.Index == Int
>( _ source: inout B, _ bounds: Range<Int>, _ newValues:
C, _ insertCount: Int) {

See how the Standard Library authors formatted it for readability and how

as a consequence arguments which use the generic types are further apart
from the declaration of those generic types. But with this proposal, the
declaration might be formatted to:

internal func _arrayOutOfPlaceReplace<B,C>(_ source: inout B,
_ bounds: Range<Int>, _ newValues: C, _ insertCount: Int)
where
B : _ArrayBufferProtocol,
C : Collection,
C.Iterator.Element == B.Element,
B.Index == Int
{

Do you need believe this is now more readable?

David.

Thanks for the real world example!

I think that the second arguably *looks* better. But is it more *readable*?
Not for me.

Let me try to do a brain dump while I mentally parse the declaration.

internal func _arrayOutOfPlaceReplace<B,C>(

Mmm, here we have a function that has something to do with arrays, generic
on two types. Maybe it will take an Array<B> and output an Array<C>?

_ source: inout B,

Ah, the first argument is a B that will be changed. Maybe the function deals
with Array<B> and will replace occurrences of 'source' with something else?

_ bounds: Range<Int>,

Ok, this surely must be the range of the Array to operate on.

_ newValues: C,

Oh, this parameter is called 'newValues', so C can't be a single value. It
must be a Collection, surely.

_ insertCount: Int)

... I can't figure out what this could be.

where
B : _ArrayBufferProtocol,

Oh, B was some kind of buffer. OK so the function probably takes this
buffer, and replaces 'range' with 'newValues'

C : Collection,
C.Iterator.Element == B.Element,

Good, this confirms what I thought.

B.Index == Int

Looks like an implementation detail, I guess that's why 'range' is a
Range<Int> and 'insertCount' is an Int. Presumably the function needs to do
some operations that the generic Index doesn't guarantee.

So that's the end of it. I have a vague idea of what the function might do,
and had to revise my expectations a few times while reading its declaration.

Let's try again with the original declaration.

internal func _arrayOutOfPlaceReplace<
B : _ArrayBufferProtocol, C : Collection

OK, here we have a function that deals with Arrays, generic on some buffer
thing and on a Collection. B is presumably what it needs to operate on, and
C will be the thing to replace with.

where C.Element == B.Element,

Indeed thic confirms that B and C must be compatible.

B.Index == Int

Looks like an implementation detail, presumably we need to indicate the
position in the array where to do the replace.

>( _ source: inout B,

OK so this is a mutating function, and B is a buffer that will be modified.

_ bounds: Range<Int>,

This is probably the range of the buffer that needs to be replaced, indeed
we had B.Index == Int.

_ newValues: C,

And this collection contains what we need to insert in the buffer.

_ insertCount: Int) {

Not sure what this might be, maybe the function will copy only the first
'insertCount' elements of C.

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.

Now if we had generic typealiases, we could do something like

typealias CollectionOf<T> = protocol<Collection> where Collection.Element == T

and if my intuition about B.Index is right, the declaration could be further
simplified to something like this

internal func _arrayOutOfPlaceReplace<
  B : _ArrayBufferProtocol, C : CollectionOf<B.Element>
  where B.Index == Int

( _ source: inout B, _ bounds: Range<B.Index>, _ newValues: C, _

insertCount: B.Index) {

Now *that* is more readable.

Nicola

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:

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.

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

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:

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.

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."

I think you hit the nail on the head. Being able to let the developer decide how to place relevant type information with the freedom of being able to keep some/all/none type information local and some/none/all extra information moved to the end of the declaration. Don't we allow the same kind of freedom with protocols and extensions in a way?

···

Sent from my iPhone
On 15 May 2016, at 15:05, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

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

Funny, for me reading a function is quite the other way around:
I prefer to first look at the function name and parameter list to give me an idea of what the function will do (the parameter names help a lot).
Having instead first to memorize a list of types with all their constraints just builds up the cognitive load without helping much, because their usage is yet unknown.

So, for me reading the signature would look like (with the proposal in place):

internal func _arrayOutOfPlaceReplace<B,C>(_ source: inout B, _ bounds: Range<Int>, _ newValues: C, _ insertCount: Int)

Ah, this function does some replacement within a source which is mutated. The replacement seems to affect a range within the source and there are some new values given together with their count.
Now I have already a feeling what the method does and can look at the types to get more details.

If I would have to read the types first I would not have been able to extract as much information from them to guess what the function does, so trying to interpret them would just have been a waste of time and I would have to look them up again after reading the parameter list anyway.

-Thorsten

···

Am 15.05.2016 um 12:38 schrieb Nicola Salmoria via swift-evolution <swift-evolution@swift.org>:

David Hart via swift-evolution <swift-evolution@...> writes:

Hi Karl,

As author of this proposal, the one about constraints on associated types,

and the one on type-aliases in protocols (all from the Generics Manifesto -
original authorship to Douglas Gregor) I’d like to provide additional
reasoning behind my wish to push this proposal through, as a whole.

First of all, there is a personal preference. I’ve used C# for many many

years, which has its where clause at the end of the declaration (like this
proposal), and I’ve used Swift since its unveiling. The experience with
using both styles for several years makes me favour this proposal’s syntax
because I find it much easier to read and format for readability.

Constraints on associated type will provide more expressivity but I doubt

it will greatly reduce protocol constraint clauses in a majority of cases.
And yes, type-aliases in protocols will shorten clauses, but I still think
they will more readable with the where clause at the end.

For example, here is a method I took (as-is) from the Standard Library

which has a few constraints, and which I further simplified if we imagine
that Sequence has an Element typealias for Iterator.Element:

internal func _arrayOutOfPlaceReplace<
  B : _ArrayBufferProtocol, C : Collection
  where C.Element == B.Element,
  B.Index == Int

( _ source: inout B, _ bounds: Range<Int>, _ newValues:

C, _ insertCount: Int) {

See how the Standard Library authors formatted it for readability and how

as a consequence arguments which use the generic types are further apart
from the declaration of those generic types. But with this proposal, the
declaration might be formatted to:

internal func _arrayOutOfPlaceReplace<B,C>(_ source: inout B,
_ bounds: Range<Int>, _ newValues: C, _ insertCount: Int)
  where
B : _ArrayBufferProtocol,
  C : Collection,
  C.Iterator.Element == B.Element,
  B.Index == Int
{

Do you need believe this is now more readable?

David.

Thanks for the real world example!

I think that the second arguably *looks* better. But is it more *readable*?
Not for me.

Let me try to do a brain dump while I mentally parse the declaration.

internal func _arrayOutOfPlaceReplace<B,C>(

Mmm, here we have a function that has something to do with arrays, generic
on two types. Maybe it will take an Array<B> and output an Array<C>?

_ source: inout B,

Ah, the first argument is a B that will be changed. Maybe the function deals
with Array<B> and will replace occurrences of 'source' with something else?

_ bounds: Range<Int>,

Ok, this surely must be the range of the Array to operate on.

_ newValues: C,

Oh, this parameter is called 'newValues', so C can't be a single value. It
must be a Collection, surely.

_ insertCount: Int)

... I can't figure out what this could be.

where
B : _ArrayBufferProtocol,

Oh, B was some kind of buffer. OK so the function probably takes this
buffer, and replaces 'range' with 'newValues'

C : Collection,
C.Iterator.Element == B.Element,

Good, this confirms what I thought.

B.Index == Int

Looks like an implementation detail, I guess that's why 'range' is a
Range<Int> and 'insertCount' is an Int. Presumably the function needs to do
some operations that the generic Index doesn't guarantee.

So that's the end of it. I have a vague idea of what the function might do,
and had to revise my expectations a few times while reading its declaration.

Let's try again with the original declaration.

internal func _arrayOutOfPlaceReplace<
B : _ArrayBufferProtocol, C : Collection

OK, here we have a function that deals with Arrays, generic on some buffer
thing and on a Collection. B is presumably what it needs to operate on, and
C will be the thing to replace with.

where C.Element == B.Element,

Indeed thic confirms that B and C must be compatible.

B.Index == Int

Looks like an implementation detail, presumably we need to indicate the
position in the array where to do the replace.

( _ source: inout B,

OK so this is a mutating function, and B is a buffer that will be modified.

_ bounds: Range<Int>,

This is probably the range of the buffer that needs to be replaced, indeed
we had B.Index == Int.

_ newValues: C,

And this collection contains what we need to insert in the buffer.

_ insertCount: Int) {

Not sure what this might be, maybe the function will copy only the first
'insertCount' elements of C.

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.

Now if we had generic typealiases, we could do something like

typealias CollectionOf<T> = protocol<Collection> where Collection.Element == T

and if my intuition about B.Index is right, the declaration could be further
simplified to something like this

internal func _arrayOutOfPlaceReplace<
B : _ArrayBufferProtocol, C : CollectionOf<B.Element>
where B.Index == Int

( _ source: inout B, _ bounds: Range<B.Index>, _ newValues: C, _

insertCount: B.Index) {

Now *that* is more readable.

Nicola

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

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

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.

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.

···

On 15 May 2016, at 16:05, Brent Royal-Gordon via swift-evolution <swift-evolution@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Being able to let the developer decide how to place relevant type information with the freedom of being able to keep some/all/none type information local and some/none/all extra information moved to the end of the declaration.

To be clear, I do think it makes sense to move *all* `where` clauses to the end of the parameter list, not allow them both within the angle brackets and at the end of the definition. And I'm not convinced it makes sense to support moving all conformance information to the `where` clause, either. A generic parameter list like `<T, U>` is vacuous; when you're dealing with something like this:

  public func transcode
      <Input: IteratorProtocol, InputEncoding: UnicodeCodec, OutputEncoding: UnicodeCodec>
      (_ input: Input, from inputEncoding: InputEncoding.Type, to outputEncoding: OutputEncoding.Type, stoppingOnError stopOnError: Bool, sendingOutputTo processCodeUnit: @noescape (OutputEncoding.CodeUnit) -> Void) -> Bool
      where InputEncoding.CodeUnit == Input.Element

The detail in the `where` clause really is of secondary importance, especially compared to how much syntax it takes to specify, while this:

  public func transcode
      <Input, InputEncoding, OutputEncoding>(_ input: Input, from inputEncoding: InputEncoding.Type, to outputEncoding: OutputEncoding.Type, stoppingOnError stopOnError: Bool, sendingOutputTo processCodeUnit: @noescape (OutputEncoding.CodeUnit) -> Void) -> Bool
      where Input: IteratorProtocol, InputEncoding: UnicodeCodec, OutputEncoding: UnicodeCodec, InputEncoding.CodeUnit == Input.Element

Leaves far too much unspecified until the end.

I'm actually tempted to suggest that a conformance should be *mandatory* and you should have to specify `Any` if you don't have anything more specific to say about the generic parameter:

  func map<T: Any>(@noescape transform: (Element) throws -> T) rethrows -> [T]

That would penalize the "leave everything until the end" style without actually introducing any arbitrary rules forbidding it.

···

--
Brent Royal-Gordon
Architechies

Funny, for me reading a function is quite the other way around:
I prefer to first look at the function name and parameter list to give me an idea of what the function will do (the parameter names help a lot).
Having instead first to memorize a list of types with all their constraints just builds up the cognitive load without helping much, because their usage is yet unknown.

This is also my biggest motivation for the proposal: keeping the function name and arguments close to each other.

So, for me reading the signature would look like (with the proposal in place):

internal func _arrayOutOfPlaceReplace<B,C>(_ source: inout B, _ bounds: Range<Int>, _ newValues: C, _ insertCount: Int)

^ Agreed! That's essentially what I'm trying to find too when skimming through code.

An alternative approach with the same desired outcome would be moving the parameter list before the function name, either `func<...> someFunction(...)`, or even before the `func` keyword. But since we already use `where`, it seems more natural to me for Swift to place the constraints list to the end.

— Pyry

···

On 16 May 2016, Thorsten Seitz wrote:

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 { ... }

-Thorsten

···

Am 16.05.2016 um 17:04 schrieb Karl via swift-evolution <swift-evolution@swift.org>:

On 15 May 2016, at 16:05, Brent Royal-Gordon via swift-evolution <swift-evolution@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@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

Makes sense, I would not mind the mandatory conformance actually.

···

Sent from my iPhone

On 15 May 2016, at 23:45, Brent Royal-Gordon <brent@architechies.com> wrote:

Being able to let the developer decide how to place relevant type information with the freedom of being able to keep some/all/none type information local and some/none/all extra information moved to the end of the declaration.

To be clear, I do think it makes sense to move *all* `where` clauses to the end of the parameter list, not allow them both within the angle brackets and at the end of the definition. And I'm not convinced it makes sense to support moving all conformance information to the `where` clause, either. A generic parameter list like `<T, U>` is vacuous; when you're dealing with something like this:

   public func transcode
       <Input: IteratorProtocol, InputEncoding: UnicodeCodec, OutputEncoding: UnicodeCodec>
       (_ input: Input, from inputEncoding: InputEncoding.Type, to outputEncoding: OutputEncoding.Type, stoppingOnError stopOnError: Bool, sendingOutputTo processCodeUnit: @noescape (OutputEncoding.CodeUnit) -> Void) -> Bool
       where InputEncoding.CodeUnit == Input.Element

The detail in the `where` clause really is of secondary importance, especially compared to how much syntax it takes to specify, while this:

   public func transcode
       <Input, InputEncoding, OutputEncoding>(_ input: Input, from inputEncoding: InputEncoding.Type, to outputEncoding: OutputEncoding.Type, stoppingOnError stopOnError: Bool, sendingOutputTo processCodeUnit: @noescape (OutputEncoding.CodeUnit) -> Void) -> Bool
       where Input: IteratorProtocol, InputEncoding: UnicodeCodec, OutputEncoding: UnicodeCodec, InputEncoding.CodeUnit == Input.Element

Leaves far too much unspecified until the end.

I'm actually tempted to suggest that a conformance should be *mandatory* and you should have to specify `Any` if you don't have anything more specific to say about the generic parameter:

   func map<T: Any>(@noescape transform: (Element) throws -> T) rethrows -> [T]

That would penalize the "leave everything until the end" style without actually introducing any arbitrary rules forbidding it.

--
Brent Royal-Gordon
Architechies

That's a good idea indeed if we ever intend to have other kinds of generic arguments (in particular, constant values like `length: Int`).

Even so, I wouldn't want to prohibit adding more protocol constraints in the where clause even if you can introduce all constaints of `T` at once as `T: protocol<A, B>`.

— Pyry

···

On 16 May 2016, at 01:45, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

I'm actually tempted to suggest that a conformance should be *mandatory* and you should have to specify `Any` if you don't have anything more specific to say about the generic parameter:

  func map<T: Any>(@noescape transform: (Element) throws -> T) rethrows -> [T]

Being able to let the developer decide how to place relevant type information with the freedom of being able to keep some/all/none type information local and some/none/all extra information moved to the end of the declaration.

To be clear, I do think it makes sense to move *all* `where` clauses to the end of the parameter list, not allow them both within the angle brackets and at the end of the definition. And I'm not convinced it makes sense to support moving all conformance information to the `where` clause, either. A generic parameter list like `<T, U>` is vacuous; when you're dealing with something like this:

   public func transcode
       <Input: IteratorProtocol, InputEncoding: UnicodeCodec, OutputEncoding: UnicodeCodec>
       (_ input: Input, from inputEncoding: InputEncoding.Type, to outputEncoding: OutputEncoding.Type, stoppingOnError stopOnError: Bool, sendingOutputTo processCodeUnit: @noescape (OutputEncoding.CodeUnit) -> Void) -> Bool
       where InputEncoding.CodeUnit == Input.Element

The detail in the `where` clause really is of secondary importance, especially compared to how much syntax it takes to specify, while this:

   public func transcode
       <Input, InputEncoding, OutputEncoding>(_ input: Input, from inputEncoding: InputEncoding.Type, to outputEncoding: OutputEncoding.Type, stoppingOnError stopOnError: Bool, sendingOutputTo processCodeUnit: @noescape (OutputEncoding.CodeUnit) -> Void) -> Bool
       where Input: IteratorProtocol, InputEncoding: UnicodeCodec, OutputEncoding: UnicodeCodec, InputEncoding.CodeUnit == Input.Element

Leaves far too much unspecified until the end.

Purely a matter of opinion... I'm fine with it.

···

On May 16, 2016, at 12:45 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

I'm actually tempted to suggest that a conformance should be *mandatory* and you should have to specify `Any` if you don't have anything more specific to say about the generic parameter:

   func map<T: Any>(@noescape transform: (Element) throws -> T) rethrows -> [T]

That would penalize the "leave everything until the end" style without actually introducing any arbitrary rules forbidding it.

--
Brent Royal-Gordon
Architechies

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

Regards
(From mobile)

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 May 16, 2016, at 8:16 PM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

Am 16.05.2016 um 17:04 schrieb Karl via swift-evolution <swift-evolution@swift.org>:

On 15 May 2016, at 16:05, Brent Royal-Gordon via swift-evolution <swift-evolution@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@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

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

Having to declare conformance to `Any` is just noise and should *not* be mandatory IMHO.

-Thorsten

···

Am 16.05.2016 um 07:59 schrieb Pyry Jahkola via swift-evolution <swift-evolution@swift.org>:

On 16 May 2016, at 01:45, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I'm actually tempted to suggest that a conformance should be *mandatory* and you should have to specify `Any` if you don't have anything more specific to say about the generic parameter:

  func map<T: Any>(@noescape transform: (Element) throws -> T) rethrows -> [T]

That's a good idea indeed if we ever intend to have other kinds of generic arguments (in particular, constant values like `length: Int`).

Even so, I wouldn't want to prohibit adding more protocol constraints in the where clause even if you can introduce all constaints of `T` at once as `T: protocol<A, B>`.

— Pyry

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

+1. Non-type generic parameters can be handled if/when they are proposed after Swift 3. There have to be better ways to encourage proper organization of the elements of a method header than forcing a redundant and non-obvious conformance to 'Any'; if this is mandatory the proposal is fatally flawed and should be taken back to the drawing board.

Austin

···

On May 16, 2016, at 2:36 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

Having to declare conformance to `Any` is just noise and should *not* be mandatory IMHO.

-Thorsten

Am 16.05.2016 um 07:59 schrieb Pyry Jahkola via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

On 16 May 2016, at 01:45, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I'm actually tempted to suggest that a conformance should be *mandatory* and you should have to specify `Any` if you don't have anything more specific to say about the generic parameter:

  func map<T: Any>(@noescape transform: (Element) throws -> T) rethrows -> [T]

That's a good idea indeed if we ever intend to have other kinds of generic arguments (in particular, constant values like `length: Int`).

Even so, I wouldn't want to prohibit adding more protocol constraints in the where clause even if you can introduce all constaints of `T` at once as `T: protocol<A, B>`.

— Pyry

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto: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