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

Dave Abrahams dabrahams at apple.com
Sun Aug 28 15:24:18 CDT 2016


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



More information about the swift-evolution mailing list