[swift-evolution] Passing an optional first argument to sequence(first:next:)
Max Moiseev
moiseev at apple.com
Tue Aug 30 12:13:02 CDT 2016
FWIW, the ‘classical’ way of doing what I think you’re trying to do is:
extension Sequence {
var pairs: AnySequence<(Iterator.Element, Iterator.Element)> {
return AnySequence(zip(self, self.dropFirst()))
}
}
Does it have the right behavior?
Max
> On Aug 29, 2016, at 9:45 AM, Tim Vermeulen via swift-evolution <swift-evolution at swift.org> wrote:
>
> The intent of my function wasn’t very clear, but it was supposed to return a sequence that contains *all* consecutive pairs, i.e. (0 ..< 4).pairs would result in [(0, 1), (1, 2), (2, 3)].
>
>> on Fri Aug 26 2016, Tim Vermeulen<swift-evolution at swift.org>wrote:
>>
>>> 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.
>> This is why we have sequence(state:next:)
>>
>>
>> extension Sequence {
>> /// Returns a sequence of pairs of consecutive elements
>> /// of the sequence, in order.
>> var pairs: AnySequence<(Iterator.Element, Iterator.Element)>{
>> return AnySequence(
>> sequence(
>> state: makeIterator(),
>> next: { (iter: inout Iterator) in
>> iter.next().flatMap { first in
>> iter.next().map { (first, $0) }
>> }
>> })
>> )
>> }
>> }
>>
>> for j in 0..<10 {
>> print(Array((0..<j).pairs))
>> }
>>
>> HTH,
>> Dave
>>>
>>>> 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 at me.com(mailto:tvermeulen at 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 at apple.com(mailto:moiseev at 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 at swift.org(mailto:swift-evolution at swift.org)>wrote:
>>>>>>>>
>>>>>>>> On 19 Aug 2016, at 19:48, Kevin Ballard<kevin at sb.org(mailto:kevin at 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 at me.com(mailto: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-m3FHrko0VLw at public.gmane.orgg)
>>>>>>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>>>
>>>>>>> _______________________________________________
>>>>>>> swift-evolution mailing list
>>>>>>> swift-evolution at swift.org(mailto:swift-evolution at swift.org)
>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>> --
>> -Dave
>>
>>
>>
>>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
More information about the swift-evolution
mailing list