Proposal: Add SequenceType.first

Related to this I've been toying around with a tweak to GeneratorType - it
could clear up some of the issues with .first consuming part of the
sequence:

public protocol NewGeneratorType {

    typealias Element

    func next() -> (value: Element, state: Self)?

}

extension NewGeneratorType {

    mutating func next() -> Element? {

        let nextPair: (value: Element, state: Self)? = self.next()

        if let state = nextPair?.state {

            self = state

        }

        return nextPair?.value

    }

}

I haven't had time for a proposal yet, but thought I'd mention it as it
seemed relevant.

···

On Thu, Dec 31, 2015 at 7:40 PM, Dave Abrahams via swift-evolution < swift-evolution@swift.org> wrote:

-Dave

On Dec 30, 2015, at 5:00 PM, Kevin Ballard via swift-evolution < > swift-evolution@swift.org> wrote:

On Wed, Dec 30, 2015, at 04:39 PM, Daniel Duan wrote:

Here it is
https://github.com/apple/swift/blob/master/stdlib/public/core/CollectionAlgorithms.swift.gyb#L26

On Dec 30, 2015, at 4:27 PM, Kevin Ballard <kevin@sb.org> wrote:

We already don't have a .last on CollectionType and nobody's been
complaining about that. Besides, sequences don't necessarily even terminate.

-Kevin Ballard

On Wed, Dec 30, 2015, at 04:01 PM, Daniel Duan wrote:

Users who don’t get the single-pass nature of SequenceType may expect a
.last as well.

Ah you're right, I was just looking at the unconstrained protocol. In any
case, we could theoretically provide a .last, but I don't think that's
useful enough on sequences to warrant inclusion. I know I've wanted .first
many times and I've never wanted .last.

Another motivation for adding this that I forgot to mention is that today
the code `someCol.lazy.filter(pred).first` actually isn't lazy at all, it
filters the entire collection and builds a new array (because SequenceType
doesn't have .first so it resolves the .filter() to the eager version
instead of the lazy version).

Oh, that’s nasty. I wonder if there’s something we can do with ambiguity
to make the eager overload inaccessible in that context? Would you mind
opening a bug for this?

Adding .first to SequenceType makes that expression actually do what the
user intended (although my other proposal for SequenceType.find() provides
a much better way to accomplish the same task).

On Wed, Dec 30, 2015, at 04:40 PM, gs. wrote:

I like this addition and I think that we should take care to document
whether or not this mutates the sequence. I actually expect it to but maybe
I am mistaken.

(moving this back to the list)

I considered documenting that, but none of the existing "destructive"
methods on SequenceType document that. I think the assumption is that
anything that has to inspect the contents of the sequence is obviously
consuming the sequence to do that. In fact, the one method that doesn't
consume anything (not counting generate() since any use of the generator is
destructive), underestimateCount(), is explicitly documented as being
non-destructive.

Also, I couldn't think of a non-awkward way to say "this property
partially consumes the sequence if it's a sequence that is destructively
"consumed" by iteration". Especially because "partially consumed" isn't
actually a property of sequences; it's explicitly documented that calling
generate() a second time after any iteration is allowed to return a
completely arbitrary sequence of elements from the second generator (for
example, a generator that returns lines read from some stream might buffer
more data internally and therefore constructing a second generator would
possibly skip data that was never returned from the first generator).

-Kevin Ballard
_______________________________________________
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

The best interface is no interface, isn’t it? Why should we absolutely add methods with unclear meanings or behavior, when there are already perfectly clear, if verbose, alternatives? seq.generate().next() may not be nice, but no one can get fooled by it.

Maybe we should discuss use cases, instead of "completing" a standard lib that did not ask anything. It never stops: shouldn’t we add isEmpty to SequenceType, if first was added? With the same ambiguity in both meaning and behavior...

Indeed I agree that the buffered sequence is much more interesting, and that it overlaps with the implicit enhancement request behind SequenceType.first. I have one experience where I wanted to know whether a sequence was empty before consuming it, and a standard buffered sequence would have been a much welcomed tool.

Gwendal

···

Le 2 janv. 2016 à 13:42, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> a écrit :

May I suggest a simple solution?

  extension SequenceType {
    /// Returns one element from the beginning of the sequence, or `nil` if the sequence is empty.
    /// If `self` is a single-pass sequence, this may consume the element.
    func one() -> Generator.Element? {
      var generator = generate()
      return generator.next()
    }
  }

This should probably be a method in the protocol, and CollectionType should have an override which calls `first`.

The BufferedSequence stuff suggested elsewhere is probably useful too, and should be considered in addition to this, but I think this would cover the most common case.

--
Brent Royal-Gordon
Architechies

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

But since it's a CollectionType, you need to preserve the ability to
access older values.

Not once you replace it with a slice of itself.

Ah I see, I missed that subtlety. Or more specifically, when you first
said "slice" I was thinking of the Slice struct, but if you implement
slicing by hand then you can do this just fine.

So the only way to actually have this be a CollectionType is to
buffer the entire sequence up to the highest-accessed index.

Of course, your use-case of processing a stream with some amount of
lookahead and throwing away the old data actually sounds like
something a "BufferedSequence" might provide. Or actually, a
"BufferedGenerator", because the only way to process a sequence is
with a generator and so all a "BufferedSequence" would really do is
just give you a "BufferedGenerator" from its generate() method.

I’m quite certain this is buildable. I’ve got my hands full at the
moment or I’d create a prototype…

Now that I realize exactly what you meant, it should indeed be
buildable. Though short of requiring the user to provide it, I'm not
sure how to pick an appropriate chunk size.

-Kevin Ballard

···

On Sat, Jan 2, 2016, at 07:17 PM, Dave Abrahams wrote:

Related to this I've been toying around with a tweak to GeneratorType - it could clear up some of the issues with .first consuming part of the sequence:

public protocol NewGeneratorType {
    typealias Element
    func next() -> (value: Element, state: Self)?
}

extension NewGeneratorType {
    mutating func next() -> Element? {
        let nextPair: (value: Element, state: Self)? = self.next()
        if let state = nextPair?.state {
            self = state
        }
        return nextPair?.value
    }
}

I haven't had time for a proposal yet, but thought I'd mention it as it seemed relevant.

The only way this can solve the problem is that if your new next() method is required to return the same result for multiple consecutive calls. There’s a reason GeneratorType wasn’t designed that way: it effectively requires generators of volatile streams to have a 1-element buffer, which is an avoidable overhead for the many algorithms make a single pass and don’t try to get the same element multiple times (consecutively). Any algorithm that wants to do that can keep its own cache instead of imposing the cache overhead on every generator.

···

On Dec 31, 2015, at 3:36 PM, Andrew Bennett <cacoyi@gmail.com> wrote:

On Thu, Dec 31, 2015 at 7:40 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

-Dave

On Dec 30, 2015, at 5:00 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Wed, Dec 30, 2015, at 04:39 PM, Daniel Duan wrote:

Here it is https://github.com/apple/swift/blob/master/stdlib/public/core/CollectionAlgorithms.swift.gyb#L26

On Dec 30, 2015, at 4:27 PM, Kevin Ballard <kevin@sb.org <mailto:kevin@sb.org>> wrote:

We already don't have a .last on CollectionType and nobody's been complaining about that. Besides, sequences don't necessarily even terminate.

-Kevin Ballard

On Wed, Dec 30, 2015, at 04:01 PM, Daniel Duan wrote:

Users who don’t get the single-pass nature of SequenceType may expect a .last as well.

Ah you're right, I was just looking at the unconstrained protocol. In any case, we could theoretically provide a .last, but I don't think that's useful enough on sequences to warrant inclusion. I know I've wanted .first many times and I've never wanted .last.

Another motivation for adding this that I forgot to mention is that today the code `someCol.lazy.filter(pred).first` actually isn't lazy at all, it filters the entire collection and builds a new array (because SequenceType doesn't have .first so it resolves the .filter() to the eager version instead of the lazy version).

Oh, that’s nasty. I wonder if there’s something we can do with ambiguity to make the eager overload inaccessible in that context? Would you mind opening a bug for this?

Adding .first to SequenceType makes that expression actually do what the user intended (although my other proposal for SequenceType.find() provides a much better way to accomplish the same task).

On Wed, Dec 30, 2015, at 04:40 PM, gs. wrote:

I like this addition and I think that we should take care to document whether or not this mutates the sequence. I actually expect it to but maybe I am mistaken.

(moving this back to the list)

I considered documenting that, but none of the existing "destructive" methods on SequenceType document that. I think the assumption is that anything that has to inspect the contents of the sequence is obviously consuming the sequence to do that. In fact, the one method that doesn't consume anything (not counting generate() since any use of the generator is destructive), underestimateCount(), is explicitly documented as being non-destructive.

Also, I couldn't think of a non-awkward way to say "this property partially consumes the sequence if it's a sequence that is destructively "consumed" by iteration". Especially because "partially consumed" isn't actually a property of sequences; it's explicitly documented that calling generate() a second time after any iteration is allowed to return a completely arbitrary sequence of elements from the second generator (for example, a generator that returns lines read from some stream might buffer more data internally and therefore constructing a second generator would possibly skip data that was never returned from the first generator).

-Kevin Ballard

_______________________________________________
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

-Dave

Why should we absolutely add methods with unclear meanings or behavior, when there are already perfectly clear, if verbose, alternatives? seq.generate().next() may not be nice, but no one can get fooled by it.

Well, for one thing, because it doesn't work. You can't call a mutating method directly on a return value without assigning it to a variable first. And put simply, a temporary variable seems a bridge too far to me.

···

--
Brent Royal-Gordon
Architechies

No one appears to have considered the C# example yet. In C#, IEnumerable<T> (Swift's SequenceType) has a `First()` (and `FirstOrDefault(T)`) method.

C# also has a coroutine syntax (`yield return xyz`) that makes it easy to create sequences that are evaluated with side effects. However, Googling "c# ienumerable first", nobody seems to be wondering if the sequence will be evaluated multiple times. This true even though C# has a coroutine syntax that makes it very easy to create sequences with side effects.

I'm not passionate about whether a `first` property on sequences needs to feel "part of the language" (since that's what the stdlib is about), or how it should behave exactly. However, off the list into the world of making programs, it doesn't appear to matter much whether using `first` successively evaluates the sequence multiple times or not. If it needs to be evaluated just once, there are trivial ways to make that happen.

(I don't think that the observation can be extended to a lot of other sequence methods. This is about the specific case of `first`.)

Félix

···

Le 2 janv. 2016 à 08:53:39, Gwendal Roué via swift-evolution <swift-evolution@swift.org> a écrit :

The best interface is no interface, isn’t it? Why should we absolutely add methods with unclear meanings or behavior, when there are already perfectly clear, if verbose, alternatives? seq.generate().next() may not be nice, but no one can get fooled by it.

Maybe we should discuss use cases, instead of "completing" a standard lib that did not ask anything. It never stops: shouldn’t we add isEmpty to SequenceType, if first was added? With the same ambiguity in both meaning and behavior...

Indeed I agree that the buffered sequence is much more interesting, and that it overlaps with the implicit enhancement request behind SequenceType.first. I have one experience where I wanted to know whether a sequence was empty before consuming it, and a standard buffered sequence would have been a much welcomed tool.

Gwendal

Le 2 janv. 2016 à 13:42, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> a écrit :

May I suggest a simple solution?

  extension SequenceType {
    /// Returns one element from the beginning of the sequence, or `nil` if the sequence is empty.
    /// If `self` is a single-pass sequence, this may consume the element.
    func one() -> Generator.Element? {
      var generator = generate()
      return generator.next()
    }
  }

This should probably be a method in the protocol, and CollectionType should have an override which calls `first`.

The BufferedSequence stuff suggested elsewhere is probably useful too, and should be considered in addition to this, but I think this would cover the most common case.

--
Brent Royal-Gordon
Architechies

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

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

Exactly. If `seq.generate().next()` worked, I'd be perfectly happy with that. I'd be tempted to submit a proposal saying this should be legal, but I believe that disallowing mutating methods on temporaries is an intentional decision that was intended to prevent the user from writing code that looks like it's mutating something that it isn't.

For example, given the following interface:

struct Foo {
    var ary: [Int] { get }
}

Allowing mutating of temporaries would let me write code like

func foo(x: Foo) -> Int? {
    return x.ary.popLast()
}

and yet this won't actually mutate the array at all (and it will incur an unwanted copy of the array storage that is immediately thrown away).

We could try and come up with some workaround, like maybe an attribute on `mutating func next()` that says "this may be called on a temporary", but it seems kind of hacky. And I can't think of anything other than `GeneratorType.next` that actually wants this behavior anyway (the closest alternative I can think of is passing a scalar as an UnsafePointer or UnsafeMutablePointer to a C function where you don't actually care about the output, where in C you can actually say something like `&(int){42}` if you want, but I haven't actually hit that case in Swift yet and it's pretty rare anyway).

Incidentally, the same rule is also what prohibits arrays from being passed to an inout parameter where a downcast is required. Arrays implicitly downcast as needed to simulate covariance on its element type, e.g. if U <: T then [U] can be used where a [T] is expected because an implicit downcasted copy is made. But you cannot pass a [U] to a function that expects an inout [T] because temporaries are immutable (and implicit array conversion produces a temporary).

-Kevin Ballard

···

On Sat, Jan 2, 2016, at 12:13 PM, Brent Royal-Gordon wrote:

> Why should we absolutely add methods with unclear meanings or behavior, when there are already perfectly clear, if verbose, alternatives? seq.generate().next() may not be nice, but no one can get fooled by it.

Well, for one thing, because it doesn't work. You can't call a mutating method directly on a return value without assigning it to a variable first. And put simply, a temporary variable seems a bridge too far to me.

For a possibly-mutating operation, I think it’s perfectly appropriate that you have to create an intermediate variable. If you want to create an extension with a “func possiblyConsumeFirst() -> Generator.Element”, you are welcome to do so; that’s what extensions are for. IMO the standard library should not hide volatile sequence consumption under a property access.

If I don’t sound sympathetic, it’s because nobody has shown a use-case for this functionality, and until I see one I am going to have a hard time believing there’s a problem worth solving. If you want to make the case that we need something like this, please show me why.

-Dave

···

On Jan 2, 2016, at 12:13 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

Why should we absolutely add methods with unclear meanings or behavior, when there are already perfectly clear, if verbose, alternatives? seq.generate().next() may not be nice, but no one can get fooled by it.

Well, for one thing, because it doesn't work. You can't call a mutating method directly on a return value without assigning it to a variable first. And put simply, a temporary variable seems a bridge too far to me.

If I don’t sound sympathetic, it’s because nobody has shown a use-case for this functionality, and until I see one I am going to have a hard time believing there’s a problem worth solving. If you want to make the case that we need something like this, please show me why.

Didn't this thread start off with a use case?

  seq.lazy.filter(predicate).first // is not actually lazy, and Swift provides no good way to do this

One way to fix this is to add `first` to `SequenceType`, but it feels strange for a property to potentially consume part of the sequence. `buffered` ultimately has the same problem. By representing this as a function, it at least looks like something that might have side effects.

···

--
Brent Royal-Gordon
Architechies

`buffered` is no more problematic than `lazy` is. In fact, calling `buffered` actually doesn't have any side-effects at all (it can avoid fetching the first element until you call `first` on the result of `buffered`).

-Kevin

···

On Sat, Jan 2, 2016, at 09:50 PM, Brent Royal-Gordon via swift-evolution wrote:

> If I don’t sound sympathetic, it’s because nobody has shown a use-case for this functionality, and until I see one I am going to have a hard time believing there’s a problem worth solving. If you want to make the case that we need something like this, please show me why.

Didn't this thread start off with a use case?

  seq.lazy.filter(predicate).first // is not actually lazy, and Swift provides no good way to do this

One way to fix this is to add `first` to `SequenceType`, but it feels strange for a property to potentially consume part of the sequence. `buffered` ultimately has the same problem. By representing this as a function, it at least looks like something that might have side effects.

seq.generate().next() may not be nice, but no one can get fooled by it.

Well, for one thing, because it doesn't work. […]

Exactly. If `seq.generate().next()` worked, I'd be perfectly happy with that. […]

Right.

···

Le 3 janv. 2016 à 01:58, Kevin Ballard <kevin@sb.org> a écrit :

Le 3 janv. 2016 à 06:50, Brent Royal-Gordon <brent@architechies.com> a écrit :

If I don’t sound sympathetic, it’s because nobody has shown a use-case for this functionality, and until I see one I am going to have a hard time believing there’s a problem worth solving. If you want to make the case that we need something like this, please show me why.

Didn't this thread start off with a use case?

  seq.lazy.filter(predicate).first // is not actually lazy, and Swift provides no good way to do this

One way to fix this is to add `first` to `SequenceType`, but it feels strange for a property to potentially consume part of the sequence. `buffered` ultimately has the same problem. By representing this as a function, it at least looks like something that might have side effects.

Out of honesty, here is another use case: `database.fetch(…).first`.

Since this is not possible today, database APIs have to expose another `fetchFirst()` (fetchOne, pluck, whatever) method, that uses a temporary generator on the sequence returned by the fetch() method.

If sequence.first would exist, the database API would not have to define this extra method.

Gwendal

`buffered` is no more problematic than `lazy` is. In fact, calling `buffered` actually doesn't have any side-effects at all (it can avoid fetching the first element until you call `first` on the result of `buffered`).

If `seq` is a single-pass sequence, then `seq.buffered.first` will consume an element from `seq`, even though you only accessed two properties. That's why I call it problematic.

(If calling `buffered` somehow rendered the original sequence unusable—for instance, if we had some way to express that the `BufferedSequence` takes unique ownership of its base—this wouldn't bother me as much.)

···

--
Brent Royal-Gordon
Architechies

If `sequence` is a single-pass sequence, wrapping it in any other sequence type and then doing anything with that other sequence type makes the original sequence unusable (or rather, you can still use it but the elements yielded from any further access to the original sequence can be completely arbitrary).

And for the record we already have precedent for the specific case of `seq.prop1.prop2` destructively consuming the original sequence: `seq.lazy.array`.

-Kevin Ballard

···

On Sat, Jan 2, 2016, at 11:17 PM, Brent Royal-Gordon wrote:

> `buffered` is no more problematic than `lazy` is. In fact, calling `buffered` actually doesn't have any side-effects at all (it can avoid fetching the first element until you call `first` on the result of `buffered`).

If `seq` is a single-pass sequence, then `seq.buffered.first` will consume an element from `seq`, even though you only accessed two properties. That's why I call it problematic.

(If calling `buffered` somehow rendered the original sequence unusable—for instance, if we had some way to express that the `BufferedSequence` takes unique ownership of its base—this wouldn't bother me as much.)

Yes, and there are arguments for dropping “.array” as a property. The convention is that “conversions” (ill-defined, I know) use constructor syntax, and we are currently heading towards the elimination of "convenience” interfaces that duplicate functionality, so we might end up with Array(seq).

All that said, single-pass Sequences are just weird in that they get mutated without calling any mutating methods on them; you mutate them by calling a mutating method on a separate generator instance. In other words, they fundamentally have reference semantics. There may be some better way to address this whole area, but we’ll have to go much deeper than merely poking at the question of a `.first` property.

-Dave

···

On Jan 2, 2016, at 11:26 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

On Sat, Jan 2, 2016, at 11:17 PM, Brent Royal-Gordon wrote:

`buffered` is no more problematic than `lazy` is. In fact, calling `buffered` actually doesn't have any side-effects at all (it can avoid fetching the first element until you call `first` on the result of `buffered`).

If `seq` is a single-pass sequence, then `seq.buffered.first` will consume an element from `seq`, even though you only accessed two properties. That's why I call it problematic.

(If calling `buffered` somehow rendered the original sequence unusable—for instance, if we had some way to express that the `BufferedSequence` takes unique ownership of its base—this wouldn't bother me as much.)

If `sequence` is a single-pass sequence, wrapping it in any other sequence type and then doing anything with that other sequence type makes the original sequence unusable (or rather, you can still use it but the elements yielded from any further access to the original sequence can be completely arbitrary).

And for the record we already have precedent for the specific case of `seq.prop1.prop2` destructively consuming the original sequence: `seq.lazy.array`.

FWIW move-only structs (and safe immutable references) are a great solution to this. Rust is a good example. Rust's equivalent to SequenceType is IntoIterator, which has a method into_iter() that consumes the receiver and returns an Iterator. If a sequence is multi-pass, then instead of just implementing it on the type, the IntoIterator trait is also implemented on references to that type (e.g. both `Vec<T>` and `&'a Vec<T>` implement it) and sometimes on mutable references too (`&'a mut Vec<T>`). The multi-pass iterators actually yield references to the elements involved (e.g. the Iterator for `Vec<T>` yields `T`, but the Iterator for `&'a Vec<T>` yields `&'a T`). Similarly Iterator itself can be move-only if it's a single-pass iterator, but for multi-pass iterators the Iterator is either copyable or Clone-able. This scheme means that the type system documents whether a sequence/iterator is multi-pass and statically prevents you from violating that guarantee.

Of course, Swift doesn't have move-only structs and it doesn't have immutable references, but thinking long-term this does seem like a great model to be aware of.

-Kevin Ballard

···

On Sat, Jan 2, 2016, at 11:53 PM, Dave Abrahams wrote:

> On Jan 2, 2016, at 11:26 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:
>
> On Sat, Jan 2, 2016, at 11:17 PM, Brent Royal-Gordon wrote:
>>> `buffered` is no more problematic than `lazy` is. In fact, calling `buffered` actually doesn't have any side-effects at all (it can avoid fetching the first element until you call `first` on the result of `buffered`).
>>
>> If `seq` is a single-pass sequence, then `seq.buffered.first` will consume an element from `seq`, even though you only accessed two properties. That's why I call it problematic.
>>
>> (If calling `buffered` somehow rendered the original sequence unusable—for instance, if we had some way to express that the `BufferedSequence` takes unique ownership of its base—this wouldn't bother me as much.)
>
> If `sequence` is a single-pass sequence, wrapping it in any other sequence type and then doing anything with that other sequence type makes the original sequence unusable (or rather, you can still use it but the elements yielded from any further access to the original sequence can be completely arbitrary).
>
> And for the record we already have precedent for the specific case of `seq.prop1.prop2` destructively consuming the original sequence: `seq.lazy.array`.

Yes, and there are arguments for dropping “.array” as a property. The convention is that “conversions” (ill-defined, I know) use constructor syntax, and we are currently heading towards the elimination of "convenience” interfaces that duplicate functionality, so we might end up with Array(seq).

All that said, single-pass Sequences are just weird in that they get mutated without calling any mutating methods on them; you mutate them by calling a mutating method on a separate generator instance. In other words, they fundamentally have reference semantics. There may be some better way to address this whole area, but we’ll have to go much deeper than merely poking at the question of a `.first` property.

Should "generate()" be a mutating method on SequenceType, then? And a non-mutating one on CollectionType, obviously.

Jordan

···

On Jan 2, 2016, at 23:53, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 2, 2016, at 11:26 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

On Sat, Jan 2, 2016, at 11:17 PM, Brent Royal-Gordon wrote:

`buffered` is no more problematic than `lazy` is. In fact, calling `buffered` actually doesn't have any side-effects at all (it can avoid fetching the first element until you call `first` on the result of `buffered`).

If `seq` is a single-pass sequence, then `seq.buffered.first` will consume an element from `seq`, even though you only accessed two properties. That's why I call it problematic.

(If calling `buffered` somehow rendered the original sequence unusable—for instance, if we had some way to express that the `BufferedSequence` takes unique ownership of its base—this wouldn't bother me as much.)

If `sequence` is a single-pass sequence, wrapping it in any other sequence type and then doing anything with that other sequence type makes the original sequence unusable (or rather, you can still use it but the elements yielded from any further access to the original sequence can be completely arbitrary).

And for the record we already have precedent for the specific case of `seq.prop1.prop2` destructively consuming the original sequence: `seq.lazy.array`.

Yes, and there are arguments for dropping “.array” as a property. The convention is that “conversions” (ill-defined, I know) use constructor syntax, and we are currently heading towards the elimination of "convenience” interfaces that duplicate functionality, so we might end up with Array(seq).

All that said, single-pass Sequences are just weird in that they get mutated without calling any mutating methods on them; you mutate them by calling a mutating method on a separate generator instance. In other words, they fundamentally have reference semantics. There may be some better way to address this whole area, but we’ll have to go much deeper than merely poking at the question of a `.first` property.

No, that would make SequenceType too hard to use. Specific sequences
could still have non-mutating generate() methods, but any kind of
generic Sequence wrapper would be forced to use a mutating generate(),
and that would make the ergonomics of using them awful. For example,
instead of

for x in seq.lazy.map(f) { ... }

you'd have to say

var seq2 = seq.lazy.map(f) for x in seq { ... }

And in the end, it wouldn't really solve anything anyway, because you
can still implement single-pass sequences using a non-mutating
generate() anyway (either the sequence is a class, or it uses a class or
UnsafeMutablePointer internally, or it manipulates global state, e.g. a
sequence that reads lines from stdin with readLine()).

-Kevin Ballard

···

On Tue, Jan 5, 2016, at 03:43 PM, Jordan Rose wrote:

On Jan 2, 2016, at 23:53, Dave Abrahams via swift-evolution <swift- >> evolution@swift.org> wrote:

On Jan 2, 2016, at 11:26 PM, Kevin Ballard via swift-evolution <swift- >>> evolution@swift.org> wrote:

On Sat, Jan 2, 2016, at 11:17 PM, Brent Royal-Gordon wrote:

`buffered` is no more problematic than `lazy` is. In fact, calling
`buffered` actually doesn't have any side-effects at all (it can
avoid fetching the first element until you call `first` on the
result of `buffered`).

If `seq` is a single-pass sequence, then `seq.buffered.first` will
consume an element from `seq`, even though you only accessed two
properties. That's why I call it problematic.

(If calling `buffered` somehow rendered the original sequence
unusable—for instance, if we had some way to express that the
`BufferedSequence` takes unique ownership of its base—this wouldn't
bother me as much.)

If `sequence` is a single-pass sequence, wrapping it in any other
sequence type and then doing anything with that other sequence type
makes the original sequence unusable (or rather, you can still use
it but the elements yielded from any further access to the original
sequence can be completely arbitrary).

And for the record we already have precedent for the specific case
of `seq.prop1.prop2` destructively consuming the original sequence:
`seq.lazy.array`.

Yes, and there are arguments for dropping “.array” as a property.
The convention is that “conversions” (ill-defined, I know) use
constructor syntax, and we are currently heading towards the
elimination of "convenience” interfaces that duplicate functionality,
so we might end up with Array(seq).

All that said, single-pass Sequences are just weird in that they get
mutated without calling any mutating methods on them; you mutate them
by calling a mutating method on a separate generator instance. In
other words, they fundamentally have reference semantics. There may
be some better way to address this whole area, but we’ll have to go
much deeper than merely poking at the question of a `.first`
property.

Should "generate()" be a mutating method on SequenceType, then? And a
non-mutating one on CollectionType, obviously.

That's a good point. I still feel like there's something missing (see also my Sequence/Generator/Collection thoughts on the PermutationGenerator thread), but we certainly wouldn't want to break simple, common uses of LazySequence or AnySequence.

Thanks for steering me in the right direction.
Jordan

···

On Jan 5, 2016, at 16:43 , Kevin Ballard <kevin@sb.org> wrote:

On Tue, Jan 5, 2016, at 03:43 PM, Jordan Rose wrote:

On Jan 2, 2016, at 23:53, Dave Abrahams via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 2, 2016, at 11:26 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Sat, Jan 2, 2016, at 11:17 PM, Brent Royal-Gordon wrote:

`buffered` is no more problematic than `lazy` is. In fact, calling `buffered` actually doesn't have any side-effects at all (it can avoid fetching the first element until you call `first` on the result of `buffered`).

If `seq` is a single-pass sequence, then `seq.buffered.first` will consume an element from `seq`, even though you only accessed two properties. That's why I call it problematic.

(If calling `buffered` somehow rendered the original sequence unusable—for instance, if we had some way to express that the `BufferedSequence` takes unique ownership of its base—this wouldn't bother me as much.)

If `sequence` is a single-pass sequence, wrapping it in any other sequence type and then doing anything with that other sequence type makes the original sequence unusable (or rather, you can still use it but the elements yielded from any further access to the original sequence can be completely arbitrary).

And for the record we already have precedent for the specific case of `seq.prop1.prop2` destructively consuming the original sequence: `seq.lazy.array`.

Yes, and there are arguments for dropping “.array” as a property. The convention is that “conversions” (ill-defined, I know) use constructor syntax, and we are currently heading towards the elimination of "convenience” interfaces that duplicate functionality, so we might end up with Array(seq).

All that said, single-pass Sequences are just weird in that they get mutated without calling any mutating methods on them; you mutate them by calling a mutating method on a separate generator instance. In other words, they fundamentally have reference semantics. There may be some better way to address this whole area, but we’ll have to go much deeper than merely poking at the question of a `.first` property.

Should "generate()" be a mutating method on SequenceType, then? And a non-mutating one on CollectionType, obviously.

No, that would make SequenceType too hard to use. Specific sequences could still have non-mutating generate() methods, but any kind of generic Sequence wrapper would be forced to use a mutating generate(), and that would make the ergonomics of using them awful. For example, instead of

    for x in seq.lazy.map(f) { ... }

you'd have to say

    var seq2 = seq.lazy.map(f)
    for x in seq { ... }

And in the end, it wouldn't really solve anything anyway, because you can still implement single-pass sequences using a non-mutating generate() anyway (either the sequence is a class, or it uses a class or UnsafeMutablePointer internally, or it manipulates global state, e.g. a sequence that reads lines from stdin with readLine()).