Passing an optional first argument to sequence(first:next:)


(Tim Vermeulen) #1

sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...


#2

Hi Tim,

If you pass "nil" to "first" isn't this an empty sequence? So it would be redundant.

Best regards
Maximilian

···

Am 15.08.2016 um 01:27 schrieb Tim Vermeulen via swift-evolution <swift-evolution@swift.org>:

sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Max Moiseev) #3

Hi Tim,

Thanks for bringing this up.
Here are my thoughts on the change you’re proposing.

func sequence<T>(first: T, next: (T) -> T?) -> UnfoldFirstSequence<T>

To me the type of the function as it is tells a clear story of what’s going to happen: take the `first`, make it a head of the resulting sequence, and then try to produce the tail by a series of applications of `next`. The only thing that controls when the sequence generation terminates is the result of `next`.

If we change the type of `first` to an Optional<T>, it would make the termination condition non-trivial. After all, the only thing it would do is try to unwrap the `first`, before doing what it needs to, but we already have a `map` for that. One should be able to simply do the `first.map { sequence(first: $0, next: next) } ?? []` but that won’t work with the types very well, unfortunately.

As an alternative, `let first: Int? = ...; sequence(first: first, next: next).flatMap({$0})` (or even `.lazy.flatMap({$0})`) will do the right thing without making an API more complex.

I see the point of `sequence(first:next:)` to be precisely the "generate the non-empty sequence using a seed and a simple producer", for anything more than that, there is `sequence(state:next:)`.

What do you think?

Max

···

On Aug 14, 2016, at 4:27 PM, Tim Vermeulen via swift-evolution <swift-evolution@swift.org> wrote:

sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Dave Abrahams) #4

sequence(first:next:) takes a non-optional first argument. Is there a
reason for that?

Yes; it's understandable and fits many common use cases.

sequence(state:next:) allows empty sequences, and I don’t see why
sequence(first:next:) shouldn’t.

The question is, what's the use case? What user code would be
simplified by complicating the API and implementation?

···

on Sun Aug 14 2016, Tim Vermeulen <swift-evolution@swift.org> wrote:

The fix would be to simply add the `?` in the function signature; no
other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...

--
-Dave


(Tim Vermeulen) #5

You can’t; the `first` parameter has type `T`, not `T?`.

···

On 15 Aug 2016, at 22:10, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

Hi Tim,

If you pass "nil" to "first" isn't this an empty sequence? So it would be redundant.

Best regards
Maximilian

Am 15.08.2016 um 01:27 schrieb Tim Vermeulen via swift-evolution <swift-evolution@swift.org>:

sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Tim Vermeulen) #6

Hi Max, thanks for having a look.

A big part of why I’m not really happy with the current implementation is that the function always produces a nonempty sequence, though the compiler doesn’t know it. `sequence(first: first, next: next).last` returns an optional, even though it can’t possibly be nil. The same goes for something like `sequence(first: 5, next: { $0 * 3 }).first(where: { $0 > 1000 })`, because the sequence is infinite, which means `first(while:)` will either keep running forever, or return a non-optional.

Ideally, we’d have three types of sequences, with three corresponding `sequence(first:next:)` functions:

func sequence<T>(first: T?, next: (T) -> T?) — returns any sequence
func sequence<T>(first: T, next: (T) -> T?) — returns a nonempty sequence
func sequence<T>(first: T, next: (T) -> T) — returns an infinite sequence

Default implementations for methods on sequences would either return optionals or non-optionals depending on their emptiness/finiteness. We just have the first kind of sequence right now, so in that regard it would make sense to also give `sequence(first:next)` the corresponding signature. Later, when the language / standard library supports the other two kinds of sequences (if that ever happens), the other versions could be added.

Another reason that makes me think that the version that accepts an optional `first` argument is more natural, is the fact that the function body doesn’t need to be changed at all. It supports optional seeds by design; only the signature prevents it.

I know these arguments might not be very convincing, but I feel like Swift misses an opportunity if it unnecessarily constrains the `first` parameter to be non-optional. The `.lazy.flatMap({ $0 })` alternative that you pointed out does work, but it makes everything very unreadable: not just the `.lazy.flatMap({ $0 })` part, but also the body of the `next` parameter because you’re now dealing with optionals (i.e. you have to `flatMap` over the closure argument). The best solution I’ve come up with is to copy the `sequence(first:next)` implementation from the source code and change the signature. :-/

`sequence(state:next:)` isn’t very appropriate for this task either, because naive usage with an optional seed has the downside of being unnecessarily eager just like a naive `sequence(first:next)` implementation (as described in a comment in the source code).

···

On 19 Aug 2016, at 00:18, Max Moiseev <moiseev@apple.com> wrote:

Hi Tim,

Thanks for bringing this up.
Here are my thoughts on the change you’re proposing.

func sequence<T>(first: T, next: (T) -> T?) -> UnfoldFirstSequence<T>

To me the type of the function as it is tells a clear story of what’s going to happen: take the `first`, make it a head of the resulting sequence, and then try to produce the tail by a series of applications of `next`. The only thing that controls when the sequence generation terminates is the result of `next`.

If we change the type of `first` to an Optional<T>, it would make the termination condition non-trivial. After all, the only thing it would do is try to unwrap the `first`, before doing what it needs to, but we already have a `map` for that. One should be able to simply do the `first.map { sequence(first: $0, next: next) } ?? []` but that won’t work with the types very well, unfortunately.

As an alternative, `let first: Int? = ...; sequence(first: first, next: next).flatMap({$0})` (or even `.lazy.flatMap({$0})`) will do the right thing without making an API more complex.

I see the point of `sequence(first:next:)` to be precisely the "generate the non-empty sequence using a seed and a simple producer", for anything more than that, there is `sequence(state:next:)`.

What do you think?

Max

On Aug 14, 2016, at 4:27 PM, Tim Vermeulen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


#7

Probably I didn't understand your proposal. What do you want to change exactly?

I thought:
public func sequence<T>(first: T, next: @escaping (T) -> T?) -> UnfoldFirstSequence<T> { ... }

To:
public func sequence<T>(first: T?, next: @escaping (T) -> T?) -> UnfoldFirstSequence<T> { ... }

···

Am 15.08.2016 um 22:17 schrieb Tim Vermeulen <tvermeulen@me.com>:

You can’t; the `first` parameter has type `T`, not `T?`.

On 15 Aug 2016, at 22:10, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

Hi Tim,

If you pass "nil" to "first" isn't this an empty sequence? So it would be redundant.

Best regards
Maximilian

Am 15.08.2016 um 01:27 schrieb Tim Vermeulen via swift-evolution <swift-evolution@swift.org>:

sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Max Moiseev) #8

+ Erica, Kevin, as the authors of the original proposal.

Do you remember the problem of non-emptiness being discussed before? And if not, what’s your opinion on the proposed change?

Thanks,
Max

···

On Aug 19, 2016, at 7:53 AM, Tim Vermeulen <tvermeulen@me.com> wrote:

Hi Max, thanks for having a look.

A big part of why I’m not really happy with the current implementation is that the function always produces a nonempty sequence, though the compiler doesn’t know it. `sequence(first: first, next: next).last` returns an optional, even though it can’t possibly be nil. The same goes for something like `sequence(first: 5, next: { $0 * 3 }).first(where: { $0 > 1000 })`, because the sequence is infinite, which means `first(while:)` will either keep running forever, or return a non-optional.

Ideally, we’d have three types of sequences, with three corresponding `sequence(first:next:)` functions:

func sequence<T>(first: T?, next: (T) -> T?) — returns any sequence
func sequence<T>(first: T, next: (T) -> T?) — returns a nonempty sequence
func sequence<T>(first: T, next: (T) -> T) — returns an infinite sequence

Default implementations for methods on sequences would either return optionals or non-optionals depending on their emptiness/finiteness. We just have the first kind of sequence right now, so in that regard it would make sense to also give `sequence(first:next)` the corresponding signature. Later, when the language / standard library supports the other two kinds of sequences (if that ever happens), the other versions could be added.

Another reason that makes me think that the version that accepts an optional `first` argument is more natural, is the fact that the function body doesn’t need to be changed at all. It supports optional seeds by design; only the signature prevents it.

I know these arguments might not be very convincing, but I feel like Swift misses an opportunity if it unnecessarily constrains the `first` parameter to be non-optional. The `.lazy.flatMap({ $0 })` alternative that you pointed out does work, but it makes everything very unreadable: not just the `.lazy.flatMap({ $0 })` part, but also the body of the `next` parameter because you’re now dealing with optionals (i.e. you have to `flatMap` over the closure argument). The best solution I’ve come up with is to copy the `sequence(first:next)` implementation from the source code and change the signature. :-/

`sequence(state:next:)` isn’t very appropriate for this task either, because naive usage with an optional seed has the downside of being unnecessarily eager just like a naive `sequence(first:next)` implementation (as described in a comment in the source code).

On 19 Aug 2016, at 00:18, Max Moiseev <moiseev@apple.com <mailto:moiseev@apple.com>> wrote:

Hi Tim,

Thanks for bringing this up.
Here are my thoughts on the change you’re proposing.

func sequence<T>(first: T, next: (T) -> T?) -> UnfoldFirstSequence<T>

To me the type of the function as it is tells a clear story of what’s going to happen: take the `first`, make it a head of the resulting sequence, and then try to produce the tail by a series of applications of `next`. The only thing that controls when the sequence generation terminates is the result of `next`.

If we change the type of `first` to an Optional<T>, it would make the termination condition non-trivial. After all, the only thing it would do is try to unwrap the `first`, before doing what it needs to, but we already have a `map` for that. One should be able to simply do the `first.map { sequence(first: $0, next: next) } ?? []` but that won’t work with the types very well, unfortunately.

As an alternative, `let first: Int? = ...; sequence(first: first, next: next).flatMap({$0})` (or even `.lazy.flatMap({$0})`) will do the right thing without making an API more complex.

I see the point of `sequence(first:next:)` to be precisely the "generate the non-empty sequence using a seed and a simple producer", for anything more than that, there is `sequence(state:next:)`.

What do you think?

Max

On Aug 14, 2016, at 4:27 PM, Tim Vermeulen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Lily Ballard) #9

AFAIK this issue has never been discussed with sequence(first:next:)
before. It certainly wasn't brought up during review.

As for my opinion, I'm really not sure. I was going to point out that
right now sequence(first:next:) guarantees that the first element of the
resulting sequence is the value provided as "first", but it occurs to me
that if you treat the nil result from next() as an element, then this
still holds true. So I guess my biggest worry is this change will make
it harder to use sequence(first:next:) to produce sequences of optional
values. So I guess I'm ambivalent, and would prefer to defer to the
wisdom of the Swift core team on this matter.

That said, didn't the deadline for source-breaking changes already
come and go?

-Kevin Ballard

···

On Fri, Aug 19, 2016, at 10:37 AM, Max Moiseev wrote:

+ Erica, Kevin, as the authors of the original proposal.

Do you remember the problem of non-emptiness being discussed before?
And if not, what’s your opinion on the proposed change?

Thanks,
Max

On Aug 19, 2016, at 7:53 AM, Tim Vermeulen <tvermeulen@me.com> wrote:

Hi Max, thanks for having a look.

A big part of why I’m not really happy with the current
implementation is that the function always produces a nonempty
sequence, though the compiler doesn’t know it. `sequence(first:
first, next: next).last` returns an optional, even though it can’t
possibly be nil. The same goes for something like `sequence(first: 5,
next: { $0 * 3 }).first(where: { $0 > 1000 })`, because the sequence
is infinite, which means `first(while:)` will either keep running
forever, or return a non-optional.

Ideally, we’d have three types of sequences, with three corresponding
`sequence(first:next:)` functions:

func sequence<T>(first: T?, next: (T) -> T?) — returns any sequence
func sequence<T>(first: T, next: (T) -> T?) — returns a nonempty
sequence
func sequence<T>(first: T, next: (T) -> T) — returns an infinite
sequence

Default implementations for methods on sequences would either return
optionals or non-optionals depending on their emptiness/finiteness.
We just have the first kind of sequence right now, so in that regard
it would make sense to also give `sequence(first:next)` the
corresponding signature. Later, when the language / standard library
supports the other two kinds of sequences (if that ever happens), the
other versions could be added.

Another reason that makes me think that the version that accepts an
optional `first` argument is more natural, is the fact that the
function body doesn’t need to be changed at all. It supports optional
seeds by design; only the signature prevents it.

I know these arguments might not be very convincing, but I feel like
Swift misses an opportunity if it unnecessarily constrains the
`first` parameter to be non-optional. The `.lazy.flatMap({ $0 })`
alternative that you pointed out does work, but it makes everything
very unreadable: not just the `.lazy.flatMap({ $0 })` part, but also
the body of the `next` parameter because you’re now dealing with
optionals (i.e. you have to `flatMap` over the closure argument). The
best solution I’ve come up with is to copy the `sequence(first:next)`
implementation from the source code and change the signature. :-/

`sequence(state:next:)` isn’t very appropriate for this task either,
because naive usage with an optional seed has the downside of being
unnecessarily eager just like a naive `sequence(first:next)`
implementation (as described in a comment in the source code).

On 19 Aug 2016, at 00:18, Max Moiseev <moiseev@apple.com> wrote:

Hi Tim,

Thanks for bringing this up.
Here are my thoughts on the change you’re proposing.

func sequence<T>(first: T, next: (T) -> T?) ->
UnfoldFirstSequence<T>

To me the type of the function as it is tells a clear story of
what’s going to happen: take the `first`, make it a head of the
resulting sequence, and then try to produce the tail by a series of
applications of `next`. The only thing that controls when the
sequence generation terminates is the result of `next`.

If we change the type of `first` to an Optional<T>, it would make
the termination condition non-trivial. After all, the only thing it
would do is try to unwrap the `first`, before doing what it needs
to, but we already have a `map` for that. One should be able to
simply do the `first.map { sequence(first: $0, next: next) } ?? []`
but that won’t work with the types very well, unfortunately.

As an alternative, `let first: Int? = ...; sequence(first: first,
next: next).flatMap({$0})` (or even `.lazy.flatMap({$0})`) will do
the right thing without making an API more complex.

I see the point of `sequence(first:next:)` to be precisely the
"generate the non-empty sequence using a seed and a simple
producer", for anything more than that, there is
`sequence(state:next:)`.

What do you think?

Max

On Aug 14, 2016, at 4:27 PM, Tim Vermeulen via swift-evolution <swift- >>>> evolution@swift.org> wrote:

sequence(first:next:) takes a non-optional first argument. Is there
a reason for that? sequence(state:next:) allows empty sequences,
and I don’t see why sequence(first:next:) shouldn’t. The fix would
be to simply add the `?` in the function signature; no other
changes are required to make it work.

I considered just filing a bug report, but since this is a change
of the public API...
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Tim Vermeulen) #10

AFAIK this issue has never been discussed with sequence(first:next:) before. It certainly wasn't brought up during review.

As for my opinion, I'm really not sure. I was going to point out that right now sequence(first:next:) guarantees that the first element of the resulting sequence is the value provided as "first", but it occurs to me that if you treat the nil result from next() as an element, then this still holds true. So I guess my biggest worry is this change will make it harder to use sequence(first:next:) to produce sequences of optional values.

I don’t think producing sequences of optional values would really be a problem, because type inference will figure this out based on whether you treat the argument to the `next` closure as an optional or not. And if you only do things in `next` that work both with optionals and non-optionals (very unlikely), you can always manually specify the type of the sequence.

···

On 19 Aug 2016, at 19:48, Kevin Ballard <kevin@sb.org> wrote:

So I guess I'm ambivalent, and would prefer to defer to the wisdom of the Swift core team on this matter.

That said, didn't the deadline for source-breaking changes already come and go?

-Kevin Ballard

On Fri, Aug 19, 2016, at 10:37 AM, Max Moiseev wrote:

+ Erica, Kevin, as the authors of the original proposal.

Do you remember the problem of non-emptiness being discussed before? And if not, what’s your opinion on the proposed change?

Thanks,
Max

On Aug 19, 2016, at 7:53 AM, Tim Vermeulen <tvermeulen@me.com <mailto:tvermeulen@me.com>> wrote:

Hi Max, thanks for having a look.

A big part of why I’m not really happy with the current implementation is that the function always produces a nonempty sequence, though the compiler doesn’t know it. `sequence(first: first, next: next).last` returns an optional, even though it can’t possibly be nil. The same goes for something like `sequence(first: 5, next: { $0 * 3 }).first(where: { $0 > 1000 })`, because the sequence is infinite, which means `first(while:)` will either keep running forever, or return a non-optional.

Ideally, we’d have three types of sequences, with three corresponding `sequence(first:next:)` functions:

func sequence<T>(first: T?, next: (T) -> T?) — returns any sequence
func sequence<T>(first: T, next: (T) -> T?) — returns a nonempty sequence
func sequence<T>(first: T, next: (T) -> T) — returns an infinite sequence

Default implementations for methods on sequences would either return optionals or non-optionals depending on their emptiness/finiteness. We just have the first kind of sequence right now, so in that regard it would make sense to also give `sequence(first:next)` the corresponding signature. Later, when the language / standard library supports the other two kinds of sequences (if that ever happens), the other versions could be added.

Another reason that makes me think that the version that accepts an optional `first` argument is more natural, is the fact that the function body doesn’t need to be changed at all. It supports optional seeds by design; only the signature prevents it.

I know these arguments might not be very convincing, but I feel like Swift misses an opportunity if it unnecessarily constrains the `first` parameter to be non-optional. The `.lazy.flatMap({ $0 })` alternative that you pointed out does work, but it makes everything very unreadable: not just the `.lazy.flatMap({ $0 })` part, but also the body of the `next` parameter because you’re now dealing with optionals (i.e. you have to `flatMap` over the closure argument). The best solution I’ve come up with is to copy the `sequence(first:next)` implementation from the source code and change the signature. :-/

`sequence(state:next:)` isn’t very appropriate for this task either, because naive usage with an optional seed has the downside of being unnecessarily eager just like a naive `sequence(first:next)` implementation (as described in a comment in the source code).

On 19 Aug 2016, at 00:18, Max Moiseev <moiseev@apple.com <mailto:moiseev@apple.com>> wrote:

Hi Tim,

Thanks for bringing this up.
Here are my thoughts on the change you’re proposing.

func sequence<T>(first: T, next: (T) -> T?) -> UnfoldFirstSequence<T>

To me the type of the function as it is tells a clear story of what’s going to happen: take the `first`, make it a head of the resulting sequence, and then try to produce the tail by a series of applications of `next`. The only thing that controls when the sequence generation terminates is the result of `next`.

If we change the type of `first` to an Optional<T>, it would make the termination condition non-trivial. After all, the only thing it would do is try to unwrap the `first`, before doing what it needs to, but we already have a `map` for that. One should be able to simply do the `first.map { sequence(first: $0, next: next) } ?? []` but that won’t work with the types very well, unfortunately.

As an alternative, `let first: Int? = ...; sequence(first: first, next: next).flatMap({$0})` (or even `.lazy.flatMap({$0})`) will do the right thing without making an API more complex.

I see the point of `sequence(first:next:)` to be precisely the "generate the non-empty sequence using a seed and a simple producer", for anything more than that, there is `sequence(state:next:)`.

What do you think?

Max

On Aug 14, 2016, at 4:27 PM, Tim Vermeulen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


#11

Ok, I see. However this could be a potential source of bugs/performance issues where you don't consider the nil case and you do some unnecessary work. By prohibiting to pass nil you have to manually unwrap and you immediately see the "optionality".

···

Am 15.08.2016 um 22:36 schrieb Tim Vermeulen <tvermeulen@me.com>:

Oh, that’s true, I misunderstood your previous message. It’s not about passing nil, but it’s about passing optionals. The point is to be able to do something like this:

let number = functionThatReturnsAnOptional()
sequence(first: number, next: { $0 % 2 == 0 ? $0 / 2 : nil })

On 15 Aug 2016, at 22:26, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

Probably I didn't understand your proposal. What do you want to change exactly?

I thought:
public func sequence<T>(first: T, next: @escaping (T) -> T?) -> UnfoldFirstSequence<T> { ... }

To:
public func sequence<T>(first: T?, next: @escaping (T) -> T?) -> UnfoldFirstSequence<T> { ... }

Am 15.08.2016 um 22:17 schrieb Tim Vermeulen <tvermeulen@me.com>:

You can’t; the `first` parameter has type `T`, not `T?`.

On 15 Aug 2016, at 22:10, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

Hi Tim,

If you pass "nil" to "first" isn't this an empty sequence? So it would be redundant.

Best regards
Maximilian

Am 15.08.2016 um 01:27 schrieb Tim Vermeulen via swift-evolution <swift-evolution@swift.org>:

sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Max Moiseev) #12

Hi Tim,

I still believe that having 2 termination conditions is wrong. But I guess we need a tie breaker here, someone with a strong opinion about the problem.
As Kevin mentioned we are very late in the release process, so waiting for another opinion for a day or two won’t change anything, really.

Meanwhile, I played a little bit with an idea of making `first.map { sequence(first $0, next: next} ?? []` work.
Turns out, if we add an `ExpressibleByArrayLiteral` protocol conformance to the `UnfoldSequence`, this snippet will compile just fine. One downside is that the `ExpressibleByArrayLiteral` protocol allows creating non-empty sequences as well, which does not make sense for the `UnfoldSequence`.

Max

···

On Aug 19, 2016, at 3:48 PM, Tim Vermeulen via swift-evolution <swift-evolution@swift.org> wrote:

On 19 Aug 2016, at 19:48, Kevin Ballard <kevin@sb.org <mailto:kevin@sb.org>> wrote:

AFAIK this issue has never been discussed with sequence(first:next:) before. It certainly wasn't brought up during review.

As for my opinion, I'm really not sure. I was going to point out that right now sequence(first:next:) guarantees that the first element of the resulting sequence is the value provided as "first", but it occurs to me that if you treat the nil result from next() as an element, then this still holds true. So I guess my biggest worry is this change will make it harder to use sequence(first:next:) to produce sequences of optional values.

I don’t think producing sequences of optional values would really be a problem, because type inference will figure this out based on whether you treat the argument to the `next` closure as an optional or not. And if you only do things in `next` that work both with optionals and non-optionals (very unlikely), you can always manually specify the type of the sequence.

So I guess I'm ambivalent, and would prefer to defer to the wisdom of the Swift core team on this matter.

That said, didn't the deadline for source-breaking changes already come and go?

-Kevin Ballard

On Fri, Aug 19, 2016, at 10:37 AM, Max Moiseev wrote:

+ Erica, Kevin, as the authors of the original proposal.

Do you remember the problem of non-emptiness being discussed before? And if not, what’s your opinion on the proposed change?

Thanks,
Max

On Aug 19, 2016, at 7:53 AM, Tim Vermeulen <tvermeulen@me.com <mailto:tvermeulen@me.com>> wrote:

Hi Max, thanks for having a look.

A big part of why I’m not really happy with the current implementation is that the function always produces a nonempty sequence, though the compiler doesn’t know it. `sequence(first: first, next: next).last` returns an optional, even though it can’t possibly be nil. The same goes for something like `sequence(first: 5, next: { $0 * 3 }).first(where: { $0 > 1000 })`, because the sequence is infinite, which means `first(while:)` will either keep running forever, or return a non-optional.

Ideally, we’d have three types of sequences, with three corresponding `sequence(first:next:)` functions:

func sequence<T>(first: T?, next: (T) -> T?) — returns any sequence
func sequence<T>(first: T, next: (T) -> T?) — returns a nonempty sequence
func sequence<T>(first: T, next: (T) -> T) — returns an infinite sequence

Default implementations for methods on sequences would either return optionals or non-optionals depending on their emptiness/finiteness. We just have the first kind of sequence right now, so in that regard it would make sense to also give `sequence(first:next)` the corresponding signature. Later, when the language / standard library supports the other two kinds of sequences (if that ever happens), the other versions could be added.

Another reason that makes me think that the version that accepts an optional `first` argument is more natural, is the fact that the function body doesn’t need to be changed at all. It supports optional seeds by design; only the signature prevents it.

I know these arguments might not be very convincing, but I feel like Swift misses an opportunity if it unnecessarily constrains the `first` parameter to be non-optional. The `.lazy.flatMap({ $0 })` alternative that you pointed out does work, but it makes everything very unreadable: not just the `.lazy.flatMap({ $0 })` part, but also the body of the `next` parameter because you’re now dealing with optionals (i.e. you have to `flatMap` over the closure argument). The best solution I’ve come up with is to copy the `sequence(first:next)` implementation from the source code and change the signature. :-/

`sequence(state:next:)` isn’t very appropriate for this task either, because naive usage with an optional seed has the downside of being unnecessarily eager just like a naive `sequence(first:next)` implementation (as described in a comment in the source code).

On 19 Aug 2016, at 00:18, Max Moiseev <moiseev@apple.com <mailto:moiseev@apple.com>> wrote:

Hi Tim,

Thanks for bringing this up.
Here are my thoughts on the change you’re proposing.

func sequence<T>(first: T, next: (T) -> T?) -> UnfoldFirstSequence<T>

To me the type of the function as it is tells a clear story of what’s going to happen: take the `first`, make it a head of the resulting sequence, and then try to produce the tail by a series of applications of `next`. The only thing that controls when the sequence generation terminates is the result of `next`.

If we change the type of `first` to an Optional<T>, it would make the termination condition non-trivial. After all, the only thing it would do is try to unwrap the `first`, before doing what it needs to, but we already have a `map` for that. One should be able to simply do the `first.map { sequence(first: $0, next: next) } ?? []` but that won’t work with the types very well, unfortunately.

As an alternative, `let first: Int? = ...; sequence(first: first, next: next).flatMap({$0})` (or even `.lazy.flatMap({$0})`) will do the right thing without making an API more complex.

I see the point of `sequence(first:next:)` to be precisely the "generate the non-empty sequence using a seed and a simple producer", for anything more than that, there is `sequence(state:next:)`.

What do you think?

Max

On Aug 14, 2016, at 4:27 PM, Tim Vermeulen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...
_______________________________________________
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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Tim Vermeulen) #13

What you’re saying makes sense, and I might not have brought this up in the first place if `first.map { sequence(first: $0, next: next } ?? []` worked. The main annoyance is that the best solution (currently) seems to be to copy the source code and make a change.

(cc-ing Jordan Rose because of a related swift-users thread) This might be a bit of a stretch, but can’t Swift upcast sequences to AnySequence implicitly, like is done with AnyHashable? That would make `first.map { sequence(first: $0, next: next } ?? []` instantly valid, I think. There’s also something to be said for consistency between type erasers. (I’m not necessarily talking about Swift 3)

···

On 20 Aug 2016, at 02:22, Max Moiseev <moiseev@apple.com> wrote:

Hi Tim,

I still believe that having 2 termination conditions is wrong. But I guess we need a tie breaker here, someone with a strong opinion about the problem.
As Kevin mentioned we are very late in the release process, so waiting for another opinion for a day or two won’t change anything, really.

Meanwhile, I played a little bit with an idea of making `first.map { sequence(first $0, next: next} ?? []` work.
Turns out, if we add an `ExpressibleByArrayLiteral` protocol conformance to the `UnfoldSequence`, this snippet will compile just fine. One downside is that the `ExpressibleByArrayLiteral` protocol allows creating non-empty sequences as well, which does not make sense for the `UnfoldSequence`.

Max

On Aug 19, 2016, at 3:48 PM, Tim Vermeulen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 19 Aug 2016, at 19:48, Kevin Ballard <kevin@sb.org <mailto:kevin@sb.org>> wrote:

AFAIK this issue has never been discussed with sequence(first:next:) before. It certainly wasn't brought up during review.

As for my opinion, I'm really not sure. I was going to point out that right now sequence(first:next:) guarantees that the first element of the resulting sequence is the value provided as "first", but it occurs to me that if you treat the nil result from next() as an element, then this still holds true. So I guess my biggest worry is this change will make it harder to use sequence(first:next:) to produce sequences of optional values.

I don’t think producing sequences of optional values would really be a problem, because type inference will figure this out based on whether you treat the argument to the `next` closure as an optional or not. And if you only do things in `next` that work both with optionals and non-optionals (very unlikely), you can always manually specify the type of the sequence.

So I guess I'm ambivalent, and would prefer to defer to the wisdom of the Swift core team on this matter.

That said, didn't the deadline for source-breaking changes already come and go?

-Kevin Ballard

On Fri, Aug 19, 2016, at 10:37 AM, Max Moiseev wrote:

+ Erica, Kevin, as the authors of the original proposal.

Do you remember the problem of non-emptiness being discussed before? And if not, what’s your opinion on the proposed change?

Thanks,
Max

On Aug 19, 2016, at 7:53 AM, Tim Vermeulen <tvermeulen@me.com <mailto:tvermeulen@me.com>> wrote:

Hi Max, thanks for having a look.

A big part of why I’m not really happy with the current implementation is that the function always produces a nonempty sequence, though the compiler doesn’t know it. `sequence(first: first, next: next).last` returns an optional, even though it can’t possibly be nil. The same goes for something like `sequence(first: 5, next: { $0 * 3 }).first(where: { $0 > 1000 })`, because the sequence is infinite, which means `first(while:)` will either keep running forever, or return a non-optional.

Ideally, we’d have three types of sequences, with three corresponding `sequence(first:next:)` functions:

func sequence<T>(first: T?, next: (T) -> T?) — returns any sequence
func sequence<T>(first: T, next: (T) -> T?) — returns a nonempty sequence
func sequence<T>(first: T, next: (T) -> T) — returns an infinite sequence

Default implementations for methods on sequences would either return optionals or non-optionals depending on their emptiness/finiteness. We just have the first kind of sequence right now, so in that regard it would make sense to also give `sequence(first:next)` the corresponding signature. Later, when the language / standard library supports the other two kinds of sequences (if that ever happens), the other versions could be added.

Another reason that makes me think that the version that accepts an optional `first` argument is more natural, is the fact that the function body doesn’t need to be changed at all. It supports optional seeds by design; only the signature prevents it.

I know these arguments might not be very convincing, but I feel like Swift misses an opportunity if it unnecessarily constrains the `first` parameter to be non-optional. The `.lazy.flatMap({ $0 })` alternative that you pointed out does work, but it makes everything very unreadable: not just the `.lazy.flatMap({ $0 })` part, but also the body of the `next` parameter because you’re now dealing with optionals (i.e. you have to `flatMap` over the closure argument). The best solution I’ve come up with is to copy the `sequence(first:next)` implementation from the source code and change the signature. :-/

`sequence(state:next:)` isn’t very appropriate for this task either, because naive usage with an optional seed has the downside of being unnecessarily eager just like a naive `sequence(first:next)` implementation (as described in a comment in the source code).

On 19 Aug 2016, at 00:18, Max Moiseev <moiseev@apple.com <mailto:moiseev@apple.com>> wrote:

Hi Tim,

Thanks for bringing this up.
Here are my thoughts on the change you’re proposing.

func sequence<T>(first: T, next: (T) -> T?) -> UnfoldFirstSequence<T>

To me the type of the function as it is tells a clear story of what’s going to happen: take the `first`, make it a head of the resulting sequence, and then try to produce the tail by a series of applications of `next`. The only thing that controls when the sequence generation terminates is the result of `next`.

If we change the type of `first` to an Optional<T>, it would make the termination condition non-trivial. After all, the only thing it would do is try to unwrap the `first`, before doing what it needs to, but we already have a `map` for that. One should be able to simply do the `first.map { sequence(first: $0, next: next) } ?? []` but that won’t work with the types very well, unfortunately.

As an alternative, `let first: Int? = ...; sequence(first: first, next: next).flatMap({$0})` (or even `.lazy.flatMap({$0})`) will do the right thing without making an API more complex.

I see the point of `sequence(first:next:)` to be precisely the "generate the non-empty sequence using a seed and a simple producer", for anything more than that, there is `sequence(state:next:)`.

What do you think?

Max

On Aug 14, 2016, at 4:27 PM, Tim Vermeulen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...
_______________________________________________
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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Tim Vermeulen) #14

I doubt that’s it, in no way is an an empty sequence inherently unsafe. The entire standard library is built with empty sequences in mind. I’m more inclined to think it’s an oversight.

···

On 15 Aug 2016, at 23:15, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

Ok, I see. However this could be a potential source of bugs/performance issues where you don't consider the nil case and you do some unnecessary work. By prohibiting to pass nil you have to manually unwrap and you immediately see the "optionality".

Am 15.08.2016 um 22:36 schrieb Tim Vermeulen <tvermeulen@me.com <mailto:tvermeulen@me.com>>:

Oh, that’s true, I misunderstood your previous message. It’s not about passing nil, but it’s about passing optionals. The point is to be able to do something like this:

let number = functionThatReturnsAnOptional()
sequence(first: number, next: { $0 % 2 == 0 ? $0 / 2 : nil })

On 15 Aug 2016, at 22:26, Maximilian Hünenberger <m.huenenberger@me.com <mailto:m.huenenberger@me.com>> wrote:

Probably I didn't understand your proposal. What do you want to change exactly?

I thought:
public func sequence<T>(first: T, next: @escaping (T) -> T?) -> UnfoldFirstSequence<T> { ... }

To:
public func sequence<T>(first: T?, next: @escaping (T) -> T?) -> UnfoldFirstSequence<T> { ... }

Am 15.08.2016 um 22:17 schrieb Tim Vermeulen <tvermeulen@me.com <mailto:tvermeulen@me.com>>:

You can’t; the `first` parameter has type `T`, not `T?`.

On 15 Aug 2016, at 22:10, Maximilian Hünenberger <m.huenenberger@me.com <mailto:m.huenenberger@me.com>> wrote:

Hi Tim,

If you pass "nil" to "first" isn't this an empty sequence? So it would be redundant.

Best regards
Maximilian

Am 15.08.2016 um 01:27 schrieb Tim Vermeulen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Max Moiseev) #15

Hi Tim,

After having a quick conversation with Dave, here is the question I should have asked right away: can you share the typical problem you are solving with your overload of the `sequence(first:next:)` function?

Max

···

On Aug 20, 2016, at 2:26 AM, Tim Vermeulen <tvermeulen@me.com> wrote:

What you’re saying makes sense, and I might not have brought this up in the first place if `first.map { sequence(first: $0, next: next } ?? []` worked. The main annoyance is that the best solution (currently) seems to be to copy the source code and make a change.

(cc-ing Jordan Rose because of a related swift-users thread) This might be a bit of a stretch, but can’t Swift upcast sequences to AnySequence implicitly, like is done with AnyHashable? That would make `first.map { sequence(first: $0, next: next } ?? []` instantly valid, I think. There’s also something to be said for consistency between type erasers. (I’m not necessarily talking about Swift 3)

On 20 Aug 2016, at 02:22, Max Moiseev <moiseev@apple.com <mailto:moiseev@apple.com>> wrote:

Hi Tim,

I still believe that having 2 termination conditions is wrong. But I guess we need a tie breaker here, someone with a strong opinion about the problem.
As Kevin mentioned we are very late in the release process, so waiting for another opinion for a day or two won’t change anything, really.

Meanwhile, I played a little bit with an idea of making `first.map { sequence(first $0, next: next} ?? []` work.
Turns out, if we add an `ExpressibleByArrayLiteral` protocol conformance to the `UnfoldSequence`, this snippet will compile just fine. One downside is that the `ExpressibleByArrayLiteral` protocol allows creating non-empty sequences as well, which does not make sense for the `UnfoldSequence`.

Max

On Aug 19, 2016, at 3:48 PM, Tim Vermeulen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 19 Aug 2016, at 19:48, Kevin Ballard <kevin@sb.org <mailto:kevin@sb.org>> wrote:

AFAIK this issue has never been discussed with sequence(first:next:) before. It certainly wasn't brought up during review.

As for my opinion, I'm really not sure. I was going to point out that right now sequence(first:next:) guarantees that the first element of the resulting sequence is the value provided as "first", but it occurs to me that if you treat the nil result from next() as an element, then this still holds true. So I guess my biggest worry is this change will make it harder to use sequence(first:next:) to produce sequences of optional values.

I don’t think producing sequences of optional values would really be a problem, because type inference will figure this out based on whether you treat the argument to the `next` closure as an optional or not. And if you only do things in `next` that work both with optionals and non-optionals (very unlikely), you can always manually specify the type of the sequence.

So I guess I'm ambivalent, and would prefer to defer to the wisdom of the Swift core team on this matter.

That said, didn't the deadline for source-breaking changes already come and go?

-Kevin Ballard

On Fri, Aug 19, 2016, at 10:37 AM, Max Moiseev wrote:

+ Erica, Kevin, as the authors of the original proposal.

Do you remember the problem of non-emptiness being discussed before? And if not, what’s your opinion on the proposed change?

Thanks,
Max

On Aug 19, 2016, at 7:53 AM, Tim Vermeulen <tvermeulen@me.com <mailto:tvermeulen@me.com>> wrote:

Hi Max, thanks for having a look.

A big part of why I’m not really happy with the current implementation is that the function always produces a nonempty sequence, though the compiler doesn’t know it. `sequence(first: first, next: next).last` returns an optional, even though it can’t possibly be nil. The same goes for something like `sequence(first: 5, next: { $0 * 3 }).first(where: { $0 > 1000 })`, because the sequence is infinite, which means `first(while:)` will either keep running forever, or return a non-optional.

Ideally, we’d have three types of sequences, with three corresponding `sequence(first:next:)` functions:

func sequence<T>(first: T?, next: (T) -> T?) — returns any sequence
func sequence<T>(first: T, next: (T) -> T?) — returns a nonempty sequence
func sequence<T>(first: T, next: (T) -> T) — returns an infinite sequence

Default implementations for methods on sequences would either return optionals or non-optionals depending on their emptiness/finiteness. We just have the first kind of sequence right now, so in that regard it would make sense to also give `sequence(first:next)` the corresponding signature. Later, when the language / standard library supports the other two kinds of sequences (if that ever happens), the other versions could be added.

Another reason that makes me think that the version that accepts an optional `first` argument is more natural, is the fact that the function body doesn’t need to be changed at all. It supports optional seeds by design; only the signature prevents it.

I know these arguments might not be very convincing, but I feel like Swift misses an opportunity if it unnecessarily constrains the `first` parameter to be non-optional. The `.lazy.flatMap({ $0 })` alternative that you pointed out does work, but it makes everything very unreadable: not just the `.lazy.flatMap({ $0 })` part, but also the body of the `next` parameter because you’re now dealing with optionals (i.e. you have to `flatMap` over the closure argument). The best solution I’ve come up with is to copy the `sequence(first:next)` implementation from the source code and change the signature. :-/

`sequence(state:next:)` isn’t very appropriate for this task either, because naive usage with an optional seed has the downside of being unnecessarily eager just like a naive `sequence(first:next)` implementation (as described in a comment in the source code).

On 19 Aug 2016, at 00:18, Max Moiseev <moiseev@apple.com <mailto:moiseev@apple.com>> wrote:

Hi Tim,

Thanks for bringing this up.
Here are my thoughts on the change you’re proposing.

func sequence<T>(first: T, next: (T) -> T?) -> UnfoldFirstSequence<T>

To me the type of the function as it is tells a clear story of what’s going to happen: take the `first`, make it a head of the resulting sequence, and then try to produce the tail by a series of applications of `next`. The only thing that controls when the sequence generation terminates is the result of `next`.

If we change the type of `first` to an Optional<T>, it would make the termination condition non-trivial. After all, the only thing it would do is try to unwrap the `first`, before doing what it needs to, but we already have a `map` for that. One should be able to simply do the `first.map { sequence(first: $0, next: next) } ?? []` but that won’t work with the types very well, unfortunately.

As an alternative, `let first: Int? = ...; sequence(first: first, next: next).flatMap({$0})` (or even `.lazy.flatMap({$0})`) will do the right thing without making an API more complex.

I see the point of `sequence(first:next:)` to be precisely the "generate the non-empty sequence using a seed and a simple producer", for anything more than that, there is `sequence(state:next:)`.

What do you think?

Max

On Aug 14, 2016, at 4:27 PM, Tim Vermeulen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...
_______________________________________________
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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jordan Rose) #16

Sorry to let this sit. I really would not want to implement this by upcasting to AnySequence. That would be tremendously inefficient.

In general, AnyHashable is special because of dictionary literals, where the same thing would have to be specified over and over again. That’s not true here. (And we weren’t even going to do it for AnyHashable originally.)

I’m mildly in favor of the very original form of this proposal,

func sequence(first: T?, next: @escaping (T) -> T?) -> UnfoldFirstSequence<T>

It’s also very nearly an additive change; the only existing code that would change meaning is that IUOs would not cause traps. (And the contrived examples with almost no type information, where the user is currently trying to infer T = Optional<X> and return a sequence of Optional<X>.)

Jordan

···

On Aug 20, 2016, at 02:26, Tim Vermeulen <tvermeulen@me.com> wrote:

What you’re saying makes sense, and I might not have brought this up in the first place if `first.map { sequence(first: $0, next: next } ?? []` worked. The main annoyance is that the best solution (currently) seems to be to copy the source code and make a change.

(cc-ing Jordan Rose because of a related swift-users thread) This might be a bit of a stretch, but can’t Swift upcast sequences to AnySequence implicitly, like is done with AnyHashable? That would make `first.map { sequence(first: $0, next: next } ?? []` instantly valid, I think. There’s also something to be said for consistency between type erasers. (I’m not necessarily talking about Swift 3)


(Xiaodi Wu) #17

Given:

let foo: T? = something()
let bar = sequence(first: foo, next: { $0?.frobnicate() })

If first could be of type `T` or `T?`, is bar of type `UnfoldSequence<T>`
or `UnfoldSequence<T?>`?

···

On Mon, Aug 15, 2016 at 17:15 Tim Vermeulen via swift-evolution < swift-evolution@swift.org> wrote:

I doubt that’s it, in no way is an an empty sequence inherently unsafe.
The entire standard library is built with empty sequences in mind. I’m more
inclined to think it’s an oversight.

On 15 Aug 2016, at 23:15, Maximilian Hünenberger <m.huenenberger@me.com> > wrote:

Ok, I see. However this could be a potential source of bugs/performance
issues where you don't consider the nil case and you do some unnecessary
work. By prohibiting to pass nil you have to manually unwrap and you
immediately see the "optionality".

Am 15.08.2016 um 22:36 schrieb Tim Vermeulen <tvermeulen@me.com>:

Oh, that’s true, I misunderstood your previous message. It’s not about
passing nil, but it’s about passing optionals. The point is to be able to
do something like this:

let number = functionThatReturnsAnOptional()
sequence(first: number, next: { $0 % 2 == 0 ? $0 / 2 : nil })

On 15 Aug 2016, at 22:26, Maximilian Hünenberger <m.huenenberger@me.com> > wrote:

Probably I didn't understand your proposal. What do you want to change
exactly?

I thought:
public func sequence<T>(first: T, next: @escaping (T) -> T?) ->
UnfoldFirstSequence<T> { ... }

To:
public func sequence<T>(first: T?, next: @escaping (T) -> T?) ->
UnfoldFirstSequence<T> { ... }

Am 15.08.2016 um 22:17 schrieb Tim Vermeulen <tvermeulen@me.com>:

You can’t; the `first` parameter has type `T`, not `T?`.

On 15 Aug 2016, at 22:10, Maximilian Hünenberger <m.huenenberger@me.com> > wrote:

Hi Tim,

If you pass "nil" to "first" isn't this an empty sequence? So it would be
redundant.

Best regards
Maximilian

Am 15.08.2016 um 01:27 schrieb Tim Vermeulen via swift-evolution < > swift-evolution@swift.org>:

sequence(first:next:) takes a non-optional first argument. Is there a
reason for that? sequence(state:next:) allows empty sequences, and I don’t
see why sequence(first:next:) shouldn’t. The fix would be to simply add the
`?` in the function signature; no other changes are required to make it
work.

I considered just filing a bug report, but since this is a change of the
public API...
_______________________________________________
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


(Tim Vermeulen) #18

If I'm not mistaken, the type of T can be inferred from the `next` closure here. $0 is an optional, so you end up with a sequence of optionals.

···

On 16 Aug 2016, at 00:51, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Given:

let foo: T? = something()
let bar = sequence(first: foo, next: { $0?.frobnicate() })

If first could be of type `T` or `T?`, is bar of type `UnfoldSequence<T>` or `UnfoldSequence<T?>`?

On Mon, Aug 15, 2016 at 17:15 Tim Vermeulen via swift-evolution <swift-evolution@swift.org> wrote:
I doubt that’s it, in no way is an an empty sequence inherently unsafe. The entire standard library is built with empty sequences in mind. I’m more inclined to think it’s an oversight.

On 15 Aug 2016, at 23:15, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

Ok, I see. However this could be a potential source of bugs/performance issues where you don't consider the nil case and you do some unnecessary work. By prohibiting to pass nil you have to manually unwrap and you immediately see the "optionality".

Am 15.08.2016 um 22:36 schrieb Tim Vermeulen <tvermeulen@me.com>:

Oh, that’s true, I misunderstood your previous message. It’s not about passing nil, but it’s about passing optionals. The point is to be able to do something like this:

let number = functionThatReturnsAnOptional()
sequence(first: number, next: { $0 % 2 == 0 ? $0 / 2 : nil })

On 15 Aug 2016, at 22:26, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

Probably I didn't understand your proposal. What do you want to change exactly?

I thought:
public func sequence<T>(first: T, next: @escaping (T) -> T?) -> UnfoldFirstSequence<T> { ... }

To:
public func sequence<T>(first: T?, next: @escaping (T) -> T?) -> UnfoldFirstSequence<T> { ... }

Am 15.08.2016 um 22:17 schrieb Tim Vermeulen <tvermeulen@me.com>:

You can’t; the `first` parameter has type `T`, not `T?`.

On 15 Aug 2016, at 22:10, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

Hi Tim,

If you pass "nil" to "first" isn't this an empty sequence? So it would be redundant.

Best regards
Maximilian

Am 15.08.2016 um 01:27 schrieb Tim Vermeulen via swift-evolution <swift-evolution@swift.org>:

sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...
_______________________________________________
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


#19

It's true, empty sequences aren't inherently unsafe but an oversight of the nil case can lead to unexpected empty sequences which then lead to bugs. Eg.:

let number = Int(string)
for i in sequence(first: number, next: { $0 * 2 - 1 }).prefix(10) {
    print(i)
}

If the string cannot be converted to an Int the for loop doesn't run at all. However the original intention was to provide a default and the loop should run in either case:

let number = Int(string) ?? 2

This could be a scenario where "first" should be of type "T". And checking for nil isn't that hard if you want to avoid to run the loop in the nil case.

···

Am 15.08.2016 um 23:33 schrieb Tim Vermeulen <tvermeulen@me.com>:

I doubt that’s it, in no way is an an empty sequence inherently unsafe. The entire standard library is built with empty sequences in mind. I’m more inclined to think it’s an oversight.

On 15 Aug 2016, at 23:15, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

Ok, I see. However this could be a potential source of bugs/performance issues where you don't consider the nil case and you do some unnecessary work. By prohibiting to pass nil you have to manually unwrap and you immediately see the "optionality".

Am 15.08.2016 um 22:36 schrieb Tim Vermeulen <tvermeulen@me.com>:

Oh, that’s true, I misunderstood your previous message. It’s not about passing nil, but it’s about passing optionals. The point is to be able to do something like this:

let number = functionThatReturnsAnOptional()
sequence(first: number, next: { $0 % 2 == 0 ? $0 / 2 : nil })

On 15 Aug 2016, at 22:26, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

Probably I didn't understand your proposal. What do you want to change exactly?

I thought:
public func sequence<T>(first: T, next: @escaping (T) -> T?) -> UnfoldFirstSequence<T> { ... }

To:
public func sequence<T>(first: T?, next: @escaping (T) -> T?) -> UnfoldFirstSequence<T> { ... }

Am 15.08.2016 um 22:17 schrieb Tim Vermeulen <tvermeulen@me.com>:

You can’t; the `first` parameter has type `T`, not `T?`.

On 15 Aug 2016, at 22:10, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

Hi Tim,

If you pass "nil" to "first" isn't this an empty sequence? So it would be redundant.

Best regards
Maximilian

Am 15.08.2016 um 01:27 schrieb Tim Vermeulen via swift-evolution <swift-evolution@swift.org>:

sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.

I considered just filing a bug report, but since this is a change of the public API...
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Tim Vermeulen) #20

This is when I first wanted to pass an optional for the `first` parameter:

extension Sequence {
    
    /// Returns a sequence of pairs of consecutive elements
    /// of the sequence, in order.
    var pairs: AnySequence<(Iterator.Element, Iterator.Element)> {
        var iterator = makeIterator()
        
        let firstPair = iterator.next().flatMap { first in
            iterator.next().map { second in (first, second) }
        }

        // I can't do this, because `firstPair` is optional
        let seq = sequence(first: firstPair) { _, second in
            guard let next = iterator.next() else { return nil }
            return (second, next)
        }
        
        return AnySequence(seq)
    }
    
}

In this particular case, finding a workaround is fairly easy (with a guard and `return AnySequence([])` in the `else` block). However, this becomes less elegant if you just want to iterate over it rather than wrap it in AnySequence.

···

Hi Tim,

After having a quick conversation with Dave, here is the question I should have asked right away: can you share the typical problem you are solving with your overload of the `sequence(first:next:)` function?

Max

> On Aug 20, 2016, at 2:26 AM, Tim Vermeulen<tvermeulen@me.com(mailto:tvermeulen@me.com)>wrote:
> What you’re saying makes sense, and I might not have brought this up in the first place if `first.map { sequence(first: $0, next: next } ?? []` worked. The main annoyance is that the best solution (currently) seems to be to copy the source code and make a change.
>
> (cc-ing Jordan Rose because of a related swift-users thread) This might be a bit of a stretch, but can’t Swift upcast sequences to AnySequence implicitly, like is done with AnyHashable? That would make `first.map { sequence(first: $0, next: next } ?? []` instantly valid, I think. There’s also something to be said for consistency between type erasers. (I’m not necessarily talking about Swift 3)
> > On 20 Aug 2016, at 02:22, Max Moiseev<moiseev@apple.com(mailto:moiseev@apple.com)>wrote:
> > Hi Tim,
> >
> > I still believe that having 2 termination conditions is wrong. But I guess we need a tie breaker here, someone with a strong opinion about the problem.
> > As Kevin mentioned we are very late in the release process, so waiting for another opinion for a day or two won’t change anything, really.
> >
> > Meanwhile, I played a little bit with an idea of making `first.map { sequence(first $0, next: next} ?? []` work.
> > Turns out, if we add an `ExpressibleByArrayLiteral` protocol conformance to the `UnfoldSequence`, this snippet will compile just fine. One downside is that the `ExpressibleByArrayLiteral` protocol allows creating non-empty sequences as well, which does not make sense for the `UnfoldSequence`.
> >
> >
> > Max
> >
> > > On Aug 19, 2016, at 3:48 PM, Tim Vermeulen via swift-evolution<swift-evolution@swift.org(mailto:swift-evolution@swift.org)>wrote:
> > > >
> > > > On 19 Aug 2016, at 19:48, Kevin Ballard<kevin@sb.org(mailto:kevin@sb.org)>wrote:
> > > > AFAIK this issue has never been discussed with sequence(first:next:) before. It certainly wasn't brought up during review.
> > > >
> > > > As for my opinion, I'm really not sure. I was going to point out that right now sequence(first:next:) guarantees that the first element of the resulting sequence is the value provided as "first", but it occurs to me that if you treat the nil result from next() as an element, then this still holds true. So I guess my biggest worry is this change will make it harder to use sequence(first:next:) to produce sequences of optional values.
> > >
> > > I don’t think producing sequences of optional values would really be a problem, because type inference will figure this out based on whether you treat the argument to the `next` closure as an optional or not. And if you only do things in `next` that work both with optionals and non-optionals (very unlikely), you can always manually specify the type of the sequence.
> > > > So I guess I'm ambivalent, and would prefer to defer to the wisdom of the Swift core team on this matter.
> > > >
> > > > That said, didn't the deadline for source-breaking changes already come and go?
> > > >
> > > > -Kevin Ballard
> > > >
> > > > On Fri, Aug 19, 2016, at 10:37 AM, Max Moiseev wrote:
> > > > > + Erica, Kevin, as the authors of the original proposal.
> > > > >
> > > > > Do you remember the problem of non-emptiness being discussed before? And if not, what’s your opinion on the proposed change?
> > > > >
> > > > > Thanks,
> > > > > Max
> > > > >
> > > > > > On Aug 19, 2016, at 7:53 AM, Tim Vermeulen<tvermeulen@me.com(mailto:tvermeulen@me.com)>wrote:
> > > > > >
> > > > > > Hi Max, thanks for having a look.
> > > > > >
> > > > > > A big part of why I’m not really happy with the current implementation is that the function always produces a nonempty sequence, though the compiler doesn’t know it. `sequence(first: first, next: next).last` returns an optional, even though it can’t possibly be nil. The same goes for something like `sequence(first: 5, next: { $0 * 3 }).first(where: { $0>1000 })`, because the sequence is infinite, which means `first(while:)` will either keep running forever, or return a non-optional.
> > > > > >
> > > > > > Ideally, we’d have three types of sequences, with three corresponding `sequence(first:next:)` functions:
> > > > > >
> > > > > > func sequence<T>(first: T?, next: (T) ->T?)—returns any sequence
> > > > > > func sequence<T>(first: T,next: (T) ->T?)—returns a nonempty sequence
> > > > > > func sequence<T>(first: T,next: (T) ->T)—returns an infinite sequence
> > > > > >
> > > > > > Default implementations for methods on sequences would either return optionals or non-optionals depending on their emptiness/finiteness. We just have the first kind of sequence right now, so in that regard it would make sense to also give `sequence(first:next)` the corresponding signature.Later, when the language / standard library supports the other two kinds of sequences (if that ever happens), the other versions could be added.
> > > > > >
> > > > > > Another reason that makes me think that the version that accepts an optional `first` argument is more natural, is the fact that the function body doesn’t need to be changed at all. It supports optional seeds by design; only the signature prevents it.
> > > > > >
> > > > > > I know these arguments might not be very convincing, but I feel like Swift misses an opportunity if it unnecessarily constrains the `first` parameter to be non-optional. The `.lazy.flatMap({ $0 })` alternative that you pointed out does work, but it makes everything very unreadable: not just the `.lazy.flatMap({ $0 })` part, but also the body of the `next` parameter because you’re now dealing with optionals (i.e. you have to `flatMap` over the closure argument). The best solution I’ve come up with is to copy the `sequence(first:next)` implementation from the source code and change the signature. :-/
> > > > > >
> > > > > > `sequence(state:next:)` isn’t very appropriate for this task either, because naive usage with an optional seed has the downside of being unnecessarily eager just like a naive `sequence(first:next)` implementation (as described in a comment in the source code).
> > > > > >
> > > > > > > On 19 Aug 2016, at 00:18, Max Moiseev<moiseev@apple.com(mailto:moiseev@apple.com)>wrote:
> > > > > > >
> > > > > > > Hi Tim,
> > > > > > >
> > > > > > > Thanks for bringing this up.
> > > > > > > Here are my thoughts on the change you’re proposing.
> > > > > > >
> > > > > > > func sequence<T>(first: T, next: (T) ->T?) ->UnfoldFirstSequence<T>
> > > > > > >
> > > > > > > To me the type of the function as it is tells a clear story of what’s going to happen: take the `first`, make it a head of the resulting sequence, and then try to produce the tail by a series of applications of `next`. The only thing that controls when the sequence generation terminates is the result of `next`.
> > > > > > >
> > > > > > > If we change the type of `first` to an Optional<T>, it would make the termination condition non-trivial. After all, the only thing it would do is try to unwrap the `first`, before doing what it needs to, but we already have a `map` for that. One should be able to simply do the `first.map { sequence(first: $0, next: next) } ?? []` but that won’t work with the types very well, unfortunately.
> > > > > > >
> > > > > > > As an alternative, `let first: Int? = ...; sequence(first: first, next: next).flatMap({$0})` (or even `.lazy.flatMap({$0})`) will do the right thing without making an API more complex.
> > > > > > >
> > > > > > > I see the point of `sequence(first:next:)` to be precisely the "generate the non-empty sequence using a seed and a simple producer", for anything more than that, there is `sequence(state:next:)`.
> > > > > > >
> > > > > > > What do you think?
> > > > > > >
> > > > > > > Max
> > > > > > >
> > > > > > > > On Aug 14, 2016, at 4:27 PM, Tim Vermeulen via swift-evolution<swift-evolution@swift.org(mailto:swift-evolution@swift.org)>wrote:
> > > > > > > >
> > > > > > > > sequence(first:next:) takes a non-optional first argument. Is there a reason for that? sequence(state:next:) allows empty sequences, and I don’t see why sequence(first:next:) shouldn’t. The fix would be to simply add the `?` in the function signature; no other changes are required to make it work.
> > > > > > > >
> > > > > > > > I considered just filing a bug report, but since this is a change of the public API...
> > > > > > > > _______________________________________________
> > > > > > > > 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(mailto:swift-evolution@swift.org)
> > > https://lists.swift.org/mailman/listinfo/swift-evolution
>