`once` keyword for use with loops


(Nicholas Maccharoli) #1

​Hello Swift Evolution,

Its not uncommon to have to do a piece of work only once or on the first
iteration of
a loop.
Take for example producing a comma separated string from an Array:

    var result = ""

    let values = [1, 2, 3, 4, 5]

    var gen = values.generate()

    if let first = gen.next() {

        result += "\(first)"

        while let value = gen.next() {

            result += ", "

            result += "\(value)"

        }

    }

Since on the first iteration we want to skip putting a comma in front we
use an `if let` to grab the first element and then embed a `while let`
inside the `if let` to handle the rest.

Another way to do this could be using a bool to keep track of the first
iteration:

    var first = true

    while let value = gen.next() {

        if first {

            result += "\(value)"

            first = false

            continue

        } else {

            result += ", "

            result += "\(value)"

        }

    }

These approaches work, but I think there may be a way to do this with less
noise.

If there was a keyword to execute a block of code only on the first
iteration of a loop I think that would make code like this more concise.

If there was a keyword like `once` then the same thing could be achieved
with something like:

    while let value = gen.next() {

        once {

            result += "\(value)"

            continue

        }

        result += ", "

        result += "\(value)"

    }

How does it sound?

- Nick


(Robert Widmann) #2

Not to doubt the usefulness of this proposal in general, but your example is subsumed by simply declaring

var result = "\(gen.next()!)"

and proceeding with the rest of the example sans `once`. I think you'll also have to address how this is any different from dispatch_once or a DSL over the same.

Cheers,

~Robert Widmann

2016/05/17 0:03、Nicholas Maccharoli via swift-evolution <swift-evolution@swift.org> のメッセージ:

···

Hello Swift Evolution,

Its not uncommon to have to do a piece of work only once or on the first iteration of
a loop.
Take for example producing a comma separated string from an Array:

    var result = ""

    let values = [1, 2, 3, 4, 5]

    var gen = values.generate()

    if let first = gen.next() {

        result += "\(first)"

        while let value = gen.next() {

            result += ", "

            result += "\(value)"

        }

    }

Since on the first iteration we want to skip putting a comma in front we use an `if let` to grab the first element and then embed a `while let` inside the `if let` to handle the rest.

Another way to do this could be using a bool to keep track of the first iteration:

    var first = true

    while let value = gen.next() {

        if first {

            result += "\(value)"

            first = false

            continue

        } else {

            result += ", "

            result += "\(value)"

        }

    }

These approaches work, but I think there may be a way to do this with less noise.

If there was a keyword to execute a block of code only on the first iteration of a loop I think that would make code like this more concise.

If there was a keyword like `once` then the same thing could be achieved with something like:

    while let value = gen.next() {

        once {

            result += "\(value)"

            continue

        }

        result += ", "

        result += "\(value)"

    }

How does it sound?

- Nick

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


(Joe Groff) #3

​Hello Swift Evolution,

Its not uncommon to have to do a piece of work only once or on the first iteration of
a loop.
Take for example producing a comma separated string from an Array:

    var result = ""

    let values = [1, 2, 3, 4, 5]

    var gen = values.generate()

    if let first = gen.next() {

        result += "\(first)"

        while let value = gen.next() {

            result += ", "

            result += "\(value)"

        }

    }

Since on the first iteration we want to skip putting a comma in front we use an `if let` to grab the first element and then embed a `while let` inside the `if let` to handle the rest.

Another way to do this could be using a bool to keep track of the first iteration:

    var first = true

    while let value = gen.next() {

        if first {

            result += "\(value)"

            first = false

            continue

        } else {

            result += ", "

            result += "\(value)"

        }

    }

These approaches work, but I think there may be a way to do this with less noise.

If there was a keyword to execute a block of code only on the first iteration of a loop I think that would make code like this more concise.

If there was a keyword like `once` then the same thing could be achieved with something like:

    while let value = gen.next() {

        once {

            result += "\(value)"

            continue

        }

        result += ", "

        result += "\(value)"

    }

How does it sound?

IMO, a better approach would be to write an 'interleave' combinator, like this:

func interleave<S: Sequence>(_ x: S, between: () -> (), each: S.GeneratorType.Element -> ()) {
  var generator = x.generate()
  if let first = generator.next() {
    each(first)
    while let rest = generator.next() {
      between()
      each(rest)
    }
  }
}

-Joe

···

On May 16, 2016, at 11:03 PM, Nicholas Maccharoli via swift-evolution <swift-evolution@swift.org> wrote:

- Nick

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


(Haravikk) #4

This only works with the example exactly as-is, I think a fairer re-write would be something like the following:

func toCSV(values:[CustomStringConvertible]) -> String {
    var gen = values.generate()

    var result = gen.next()?.description ?? ""
    while let value = gen.next() { result += ", \(value)" }
    return result
}

toCSV([1, 2, 3, 4, 5])

For brevity I opted to use nil-coalescing and a default value to handle the first (potentially nil) element cleanly.

I’m undecided about whether I want the feature or not; as shown the example can actually be handled very neatly already, so it doesn’t really highlight a need for the feature. One thing I like about it though is the ability to possibly eliminate the need for a generator and use a for in loop instead, like so:

func toCSV(values:[CustomStringConvertible]) -> String {
    var result = ""
    for value in values {
        once { result += "\(value)"; continue }
        result += ", \(value)"
    }
    return result
}

But this isn’t really much of a saving. I can’t think of a real-world example offhand that can better demonstrate a use for this; ideally you’d want to use several once {} blocks, or do something in the loop that requires other statements in the loop, i.e- you use the once to avoid duplicating statements in the loop while processing the first element.

···

On 17 May 2016, at 07:13, Robert Widmann via swift-evolution <swift-evolution@swift.org> wrote:

Not to doubt the usefulness of this proposal in general, but your example is subsumed by simply declaring

var result = "\(gen.next()!)"

and proceeding with the rest of the example sans `once`. I think you'll also have to address how this is any different from dispatch_once or a DSL over the same.

Cheers,

~Robert Widmann

2016/05/17 0:03、Nicholas Maccharoli via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> のメッセージ:

​Hello Swift Evolution,

Its not uncommon to have to do a piece of work only once or on the first iteration of
a loop.
Take for example producing a comma separated string from an Array:

    var result = ""

    let values = [1, 2, 3, 4, 5]

    var gen = values.generate()

    if let first = gen.next() {

        result += "\(first)"

        while let value = gen.next() {

            result += ", "

            result += "\(value)"

        }

    }

Since on the first iteration we want to skip putting a comma in front we use an `if let` to grab the first element and then embed a `while let` inside the `if let` to handle the rest.

Another way to do this could be using a bool to keep track of the first iteration:

    var first = true

    while let value = gen.next() {

        if first {

            result += "\(value)"

            first = false

            continue

        } else {

            result += ", "

            result += "\(value)"

        }

    }

These approaches work, but I think there may be a way to do this with less noise.

If there was a keyword to execute a block of code only on the first iteration of a loop I think that would make code like this more concise.

If there was a keyword like `once` then the same thing could be achieved with something like:

    while let value = gen.next() {

        once {

            result += "\(value)"

            continue

        }

        result += ", "

        result += "\(value)"

    }

How does it sound?

- Nick

_______________________________________________
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
https://lists.swift.org/mailman/listinfo/swift-evolution


(Daryle Walker) #5

Aren’t there already methods that stick elements between others (and only between, neither before the first nor after the last)?

//=====
fun toCSV2(values: [CustomStringConvertible]) -> String {
    return values.map { $0.description }.joinWithSeparator(“, “)
}
//=====

···


Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com

On May 17, 2016, at 11:49 AM, Haravikk via swift-evolution <swift-evolution@swift.org> wrote:

This only works with the example exactly as-is, I think a fairer re-write would be something like the following:

func toCSV(values:[CustomStringConvertible]) -> String {
    var gen = values.generate()

    var result = gen.next()?.description ?? ""
    while let value = gen.next() { result += ", \(value)" }
    return result
}

toCSV([1, 2, 3, 4, 5])

For brevity I opted to use nil-coalescing and a default value to handle the first (potentially nil) element cleanly.

I’m undecided about whether I want the feature or not; as shown the example can actually be handled very neatly already, so it doesn’t really highlight a need for the feature. One thing I like about it though is the ability to possibly eliminate the need for a generator and use a for in loop instead, like so:

func toCSV(values:[CustomStringConvertible]) -> String {
    var result = ""
    for value in values {
        once { result += "\(value)"; continue }
        result += ", \(value)"
    }
    return result
}

But this isn’t really much of a saving. I can’t think of a real-world example offhand that can better demonstrate a use for this; ideally you’d want to use several once {} blocks, or do something in the loop that requires other statements in the loop, i.e- you use the once to avoid duplicating statements in the loop while processing the first element.

On 17 May 2016, at 07:13, Robert Widmann via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Not to doubt the usefulness of this proposal in general, but your example is subsumed by simply declaring

var result = "\(gen.next()!)"

and proceeding with the rest of the example sans `once`. I think you'll also have to address how this is any different from dispatch_once or a DSL over the same.

Cheers,

~Robert Widmann

2016/05/17 0:03、Nicholas Maccharoli via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> のメッセージ:

​Hello Swift Evolution,

Its not uncommon to have to do a piece of work only once or on the first iteration of
a loop.
Take for example producing a comma separated string from an Array:

    var result = ""

    let values = [1, 2, 3, 4, 5]

    var gen = values.generate()

    if let first = gen.next() {

        result += "\(first)"

        while let value = gen.next() {

            result += ", "

            result += "\(value)"

        }

    }

Since on the first iteration we want to skip putting a comma in front we use an `if let` to grab the first element and then embed a `while let` inside the `if let` to handle the rest.

Another way to do this could be using a bool to keep track of the first iteration:

    var first = true

    while let value = gen.next() {

        if first {

            result += "\(value)"

            first = false

            continue

        } else {

            result += ", "

            result += "\(value)"

        }

    }

These approaches work, but I think there may be a way to do this with less noise.

If there was a keyword to execute a block of code only on the first iteration of a loop I think that would make code like this more concise.

If there was a keyword like `once` then the same thing could be achieved with something like:

    while let value = gen.next() {

        once {

            result += "\(value)"

            continue

        }

        result += ", "

        result += "\(value)"

    }

How does it sound?

- Nick

_______________________________________________
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

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