[swift-evolution] Passing an optional first argument to sequence(first:next:)

Max Moiseev moiseev at apple.com
Fri Aug 19 12:37:36 CDT 2016


+ 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 at 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 at apple.com <mailto:moiseev at 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 at swift.org <mailto:swift-evolution at 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 at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> 
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160819/10bea150/attachment.html>


More information about the swift-evolution mailing list