For-loop revisited


(Ted van Gaalen) #1

In technical and scientific programming, one often encounters loops
based on iterating and changing numerical values (e.g. floats, integers, doubles)
varying values from positive to negative or reversed with arbitrary
step sizes which also could be positive and negative, and
not bound to or based on collections or arrays..
For example in this function which is part of a Swift/SceneKit app for Apple TV I am
currently constructing:

   // generate a matrix of tiles in space:
  // ( i don't care how many tiles, i even don't know,
  // all i want is a certain area covered with tiles ) :

    func addTileMatrix(z z: Float )
    {
        let w:Float = 20
        let h:Float = 5
        let l:Float = 5
        
        for var x:Float = -60; x < 60; x += w * 1.2
        {
            for var y:Float = -30; y < 60; y += h * 1.2
            {
               let pos = SCNVector3(x: x, y: y, z: z)
                
                let tile = TGStaticTile(pos: pos,
                                w: CGFloat(w), h: CGFloat(h), l: CGFloat(l),
                                color: UIColor.randomColor(), naam: "Tile\(tiles.count + 1)")

                tiles.append(tile)
           }
        }
    }

As you can see, it relies completely on classical C-style for loops. As I see it, in this case
the classical for-loops in this function provide an easily understood, elegant and compact
way to generate the objects I need.

For reasons not all really convincing me, a lot of us want to drop the C-Style for-loop.
It is by some even considered to be error-prone, really? Something so simple as this?
In this perspective, one could argue that nearly all programming statements are error-prone...

Yes I have read Erica Sadun's proposal SE-0007 "Remove C-Style for-loops with conditions and incrementers."
In this document, the first classical for-loop offered for comparison is a really bad pointer based example,
a typical nightmare C or C++ construct.also: imho the offered alternative with for-in.. alternative is
not really much better. (actually imho Swift should not use (even unsafe) pointers at all ,
but simply pass contiguous byte/word arrays only) In most cases however, the usage of
classical for loops can also be reasonably well structured, as in my example above.

Still, if one wants to get rid of the classical for-loop, (and in many cases I do as well)
then there should be equivalents for iterating with non-collection values, like coordinates as in the above example.

Yes, by deploying the Stride library function, it is possible to convert my example
to the one below without the ancient for loop construct like so, as tested in Playground:

   let w:Float = 20 // i need Floats (see the example above)
    let h:Float = 5
    
    for var x:Float in (Float(-60.0)).stride(to: Float(60.0), by: w * 1.2)
    {
        for var y:Float in (Float(-30.0)).stride(to: Float(60.0), by: h * 1.2)
        {
            print("x = \(x) y = \(y)")
        }
    }

The above works but imho it is really ugly. Not really an improvement over
the classical for-loop I think.

(btw. in the above example. the compiler (which I think in most cases is superb!)
mistakingly recommends me to change the for-loop vars x and y to 'let'..)

Imho all this casting between floating point types should be done implicitly, but
I've already brought this topic forward here on the forum.
OK, let's clean it up a little: use a single floating point type here (Double):

let w = 20.0
let h = 5.0
    
for var x in (-60.0).stride(to: 60.0, by: w * 1.2)
{
    for var y in (30.0).stride(to: -60.0, by: -h * 1.2)
    {
        print("x = \(x) y = \(y)")
    }
}

(btw: If I don't put () around -60 , then the compiler complains with
"Unary operator '-' cannot be applied to an operand of type 'StrideTo<Double>' "
Could this be a compiler error? Shouldn't it first instantiate or evaluate the
numerical object,before glueing the .stride() to it? )

Although the for loops now have no mixed numerical types, imho, this still looks ugly.
One has to write too much code and it also needs "indirect thought paths"
like when using Objective C.. If I use for-loops, I don't want to think about library functions
like stride (btw nowhere described in the Swift manual !)

Assuming that the Swift for...in.. can only work with a one dimensional array or collection:
then:
Suppose for a moment that one is iterating with very small incrementing or
decrementing values in a relatively large range: Unless the compiler is smart enough
to recognize this, the stride function will allocate and deliver to the for-loop a huge vector
collection or array, with maybe ten thousands of values.. making it all extremely inefficient.
This is of course an unacceptable disadvantage for often used iterating control statements.

To improve this I'd suggest why not simply let the compiler handle the iteration set up internally
like it has been done for almost half a century in most other programming languages?
I would suggest to add these new for loop variants to Swift, as an integral part of the Swift language.

for var n from 10 to 1 by -1 { } // looping backwards

for var n from -10 to 12 // here, the compiler assumes and increment value of 1, no "by" is needed.

for var x: Float from -60.0 to 120 by 0.01 { } // this one has many iterations!

for var d from 0.0 to abyss by -rate {}

I'd suggest also: All literals and vars behind the "from" should whenever possible be
implicitly converted to the loop var's type (var x here)

There's another huge advantage here. Now that the compiler sees a "from" it would
expect 2 or 3 values instead of a (sometimes huge) one dimensional vector.
(it needs to go through each value in the vector, because the vector contens
are unpredictable.)
Range and increment are now well defined, so the compiler is now able to generate
very efficient low level code which would be so much faster
that allocating and iterating through a vector.

As you can see, these for-loop variants are really clean and easy to understand.
Even those with little programming experience will grasp immediately what is
going on here!

Swift was / is also intended to offer an easy entry level for
those new to programming, much easier that starting with Objective C.
So, I'd kindly suggest keep it simple, whenever possible.

Kind Regards
Ted


(Dave Abrahams) #2

Please file a bug report!

···

on Thu Feb 25 2016, ted van gaalen <swift-evolution@swift.org> wrote:

(btw: If I don't put () around -60 , then the compiler complains with
"Unary operator '-' cannot be applied to an operand of type 'StrideTo<Double>' "
Could this be a compiler error? Shouldn't it first instantiate or evaluate the
numerical object,before glueing the .stride() to it? )

--
-Dave


#3

OK, let's clean it up a little: use a single floating point type here (Double):

let w = 20.0
let h = 5.0

for var x in (-60.0).stride(to: 60.0, by: w * 1.2)
{
   for var y in (30.0).stride(to: -60.0, by: -h * 1.2)
   {
       print("x = \(x) y = \(y)")
   }
}

For what it's worth, you can keep the Floats and things can still be simple:

    let w: Float = 20
    let h: Float = 5
    for x in Float(-60).stride(to: 60, by: w * 1.2) {
        for y in Float(30).stride(to: -60, by: -h * 1.2) {
            print("x = \(x) y = \(y)")
        }
    }

Inference makes it possible to use integer literals and avoid explicit casts most places.

(To be honest, I'm not sure why the `Float(-60)` and `Float(30)` are required. I would assume that the presence of `w` and `-h` as `by:` arguments would allow the other arguments to be inferred.)

Stephen


(Pyry Jahkola) #4

Doesn't seem like a bug to me. In general, we want to allow an expression like -rect.width to return the negative width, and not apply the unary operator to rect first. Same goes to -array.reduce(0, combine: +), for example.

Hence, member access and function application bind tighter than prefix operators.

— Pyry

···

On 26 Feb 2016, at 04:26, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Thu Feb 25 2016, ted van gaalen <swift-evolution@swift.org> wrote:

(btw: If I don't put () around -60 , then the compiler complains with
"Unary operator '-' cannot be applied to an operand of type 'StrideTo<Double>' "
Could this be a compiler error? Shouldn't it first instantiate or evaluate the
numerical object,before glueing the .stride() to it? )

Please file a bug report!


(Haravikk) #5

for var n from 10 to 1 by -1 { } // looping backwards

This is currently possible with:

  for var n in (1 … 10).reverse()

Which I find pretty simple personally, though being able to do 10 … 1 would be nice.

for var n from -10 to 12 // here, the compiler assumes and increment value of 1, no "by" is needed.

The following already does this one as-is:

  for var n in -10 … 12

for var x: Float from -60.0 to 120 by 0.01 { } // this one has many iterations!
for var d from 0.0 to abyss by -rate {}

These two definitely make more of a case for this. I’m not so sure about introducing new keywords for this though.

Personally I think that what we need is a more flexible type for the … and ..< operators that is simple two related values in any order, which Range could be instantiated from with more strict conditions (must be low to high) for things like grabbing arrays slices and so-on where the order matters. The basic non-Range would then be optimised for use with loops to give us the greater flexibility without having to do stuff like using .reverse().

Plus ideally the new type would be capable of representing a full range of values; I really don’t like that Range<UInt8> is currently limited to 0-254 inclusive, since the endIndex is always exclusive, but that’s another topic really.

···

On 26 Feb 2016, at 02:11, ted van gaalen via swift-evolution <swift-evolution@swift.org> wrote:


(Dave Abrahams) #6

You're right, thanks.

···

On Feb 26, 2016, at 12:42 AM, Pyry Jahkola <pyry.jahkola@iki.fi> wrote:

On 26 Feb 2016, at 04:26, Dave Abrahams via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

on Thu Feb 25 2016, ted van gaalen <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

(btw: If I don't put () around -60 , then the compiler complains with
"Unary operator '-' cannot be applied to an operand of type 'StrideTo<Double>' "
Could this be a compiler error? Shouldn't it first instantiate or evaluate the
numerical object,before glueing the .stride() to it? )

Please file a bug report!

Doesn't seem like a bug to me. In general, we want to allow an expression like -rect.width to return the negative width, and not apply the unary operator to rect first. Same goes to -array.reduce(0, combine: +), for example.

Hence, member access and function application bind tighter than prefix operators.


(Ted van Gaalen) #7

Hello Haravikk,

Thank you. Yes I know this is possible,
but my main point is that for..in.. always expects an instance of some sort of a collection.

e.g for this for-loop:

for var x in -60.0.stride(to: 60.0, by: 0.001)

What I don’t know: (can’t find yet)

Will in this case a large collection with 120000 elements of Double be created by .stride?

or

Does .stride(), which in the end uses a descendant of SequenceType, just calculate a new value each time for..in.. uses .next() on this collection?

I hope the latter is true.

Can someone clarify this perhaps?

Still , I’d rather see a for variant with “from”

e.g.

    for x from xmin to xmax by xstep { }

    for x from xmax to xmin by -xstep { }

    for apple from 1 to applesInTruck { }

No need for collections in these cases,

Shouldn't be that hard to implement?

kind regards
Ted

ted van gaalen

···

On 26.02.2016, at 12:45, Haravikk <swift-evolution@haravikk.me> wrote:

On 26 Feb 2016, at 02:11, ted van gaalen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

for var n from 10 to 1 by -1 { } // looping backwards

This is currently possible with:

  for var n in (1 … 10).reverse()

Which I find pretty simple personally, though being able to do 10 … 1 would be nice.

for var n from -10 to 12 // here, the compiler assumes and increment value of 1, no "by" is needed.

The following already does this one as-is:

  for var n in -10 … 12

for var x: Float from -60.0 to 120 by 0.01 { } // this one has many iterations!
for var d from 0.0 to abyss by -rate {}

These two definitely make more of a case for this. I’m not so sure about introducing new keywords for this though.

Personally I think that what we need is a more flexible type for the … and ..< operators that is simple two related values in any order, which Range could be instantiated from with more strict conditions (must be low to high) for things like grabbing arrays slices and so-on where the order matters. The basic non-Range would then be optimised for use with loops to give us the greater flexibility without having to do stuff like using .reverse().

Plus ideally the new type would be capable of representing a full range of values; I really don’t like that Range<UInt8> is currently limited to 0-254 inclusive, since the endIndex is always exclusive, but that’s another topic really.


(Vanderlei Martinelli) #8

Well... I'm against the removal of C style for loop, but it is gone. (I do
not think the `for ... in ...` is a replacement.) And I'm against the
removal of `++` and `--` operator as well. For years to come whenever
anyone asks me about it I'm going to say that I was against the removal. :wink:

I like the `for ... from ... to .. by ...` syntax. This would be a
replacement for the C style loop, not the `for ... in ... ` that, as I see,
is a very welcome addition but not a replacement.

-Van

···

On Fri, Feb 26, 2016 at 1:07 PM, Ted F.A. van Gaalen via swift-evolution < swift-evolution@swift.org> wrote:

Hello Haravikk,

Thank you. Yes I know this is possible,
but my main point is that for..in.. always expects an instance of
some sort of a collection.

e.g for this for-loop:

for var x in -60.0.stride(to: 60.0, by: 0.001)

What I don’t know: (can’t find yet)
>> Will in this case a large collection with 120000 elements of Double be
created by .stride?
or
>> Does .stride(), which in the end uses a descendant of SequenceType,
just calculate a new value each time for..in.. uses .next() on this
collection?
I hope the latter is true.

Can someone clarify this perhaps?

Still , I’d rather see a for variant with “from”

e.g.

    for x from xmin to xmax by xstep { }

    for x from xmax to xmin by -xstep { }

    for apple from 1 to applesInTruck { }

No need for collections in these cases,

Shouldn't be that hard to implement?

kind regards
Ted

ted van gaalen

On 26.02.2016, at 12:45, Haravikk <swift-evolution@haravikk.me> wrote:

On 26 Feb 2016, at 02:11, ted van gaalen via swift-evolution < > swift-evolution@swift.org> wrote:

for var n from 10 to 1 by -1 { } // looping backwards

This is currently possible with:

for var n in (1 … 10).reverse()

Which I find pretty simple personally, though being able to do 10 … 1
would be nice.

for var n from -10 to 12 // here, the compiler assumes and increment
value of 1, no "by" is needed.

The following already does this one as-is:

for var n in -10 … 12

for var x: Float from -60.0 to 120 by 0.01 { } // this one has many
iterations!
for var d from 0.0 to abyss by -rate {}

These two definitely make more of a case for this. I’m not so sure about
introducing new keywords for this though.

Personally I think that what we need is a more flexible type for the … and
..< operators that is simple two related values in any order, which Range
could be instantiated from with more strict conditions (must be low to
high) for things like grabbing arrays slices and so-on where the order
matters. The basic non-Range would then be optimised for use with loops to
give us the greater flexibility without having to do stuff like using
.reverse().

Plus ideally the new type would be capable of representing a full range of
values; I really don’t like that Range<UInt8> is currently limited to 0-254
inclusive, since the endIndex is always exclusive, but that’s another topic
really.

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


(David Waite) #9

>> Does .stride(), which in the end uses a descendant of SequenceType, just calculate a new value each time for..in.. uses .next() on this collection?

this.

    for x from xmin to xmax by xstep { }

    for x from xmax to xmin by -xstep { }

    for apple from 1 to applesInTruck { }

No need for collections in these cases,

As the thread for removal of C-style for showed in benchmarks, using a range or stride does not have a performance impact under optimization. Such new syntax would need to stand on its own as a second alternative to using ranges/strides.

Considering that it would require reserving three new keywords (‘from’, ‘to’, and ‘by’) this will be a hard argument to make.

-DW

···

On Feb 26, 2016, at 9:07 AM, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org> wrote:


(Ted van Gaalen) #10

I also do like the for..in.. naturally: when collections are involved.
like:
    for croc in crocodiles
    for candidate in presidentialCandidates.sort( { $0.IQ > $1.IQ} ) //...
etc.

I also see no reason to remove ++ and —
but i can live with it.

Ted

···

On 26.02.2016, at 17:21, Vanderlei Martinelli <vmartinelli@alecrim.com> wrote:

Well... I'm against the removal of C style for loop, but it is gone. (I do not think the `for ... in ...` is a replacement.) And I'm against the removal of `++` and `--` operator as well. For years to come whenever anyone asks me about it I'm going to say that I was against the removal. :wink:


(Ted van Gaalen) #11

Thanks for clarifying, David

However, to me, it still remains awkward and indirect to
use collections in cases where they are not necessary at all.

About reserved words:
The sub-keywords (‘from’, ‘to’, and ‘by’)
are context dependent and will almost certainly
not be used on their own, rather always together
with a primary keyword.

An advanced compiler should be able to
determine from the context of a statement
if words used in that statement act as keywords or not!

For instance the PL/1 language from < 1970 , far ahead of its time, **1**
in spite of being very extensive, has no reserved words at all.

Most people will not use reserved words as identifiers because this is confusing.

I will write a Swift proposal next week for
for .. from .. to.. [by.. ] ,
in spite of all of its functionality being
already present in the classical for ;; loop.

I still cannot find a reason why it should be removed.
As with any other language construct,
it really depends on how one uses it.
Like with any other language elements in Swift,
it’s very easy to create a mess, no one can prevent this.

Of course, I would use a for..in..
if dealing with a collection: using a
classic for ;; for that is clearly not an advantage.

TedvG

  **1** As a source of inspiration, one might find this interesting:
https://en.wikibooks.org/wiki/Software_Engineers_Handbook/Language_Dictionary/PLI#Looping_Statements

Ted

···

On 26.02.2016, at 21:52, David Waite <david@alkaline-solutions.com> wrote:

On Feb 26, 2016, at 9:07 AM, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org> wrote:

Does .stride(), which in the end uses a descendant of SequenceType, just calculate a new value each time for..in.. uses .next() on this collection?

this.

   for x from xmin to xmax by xstep { }

   for x from xmax to xmin by -xstep { }

   for apple from 1 to applesInTruck { }

No need for collections in these cases,

As the thread for removal of C-style for showed in benchmarks, using a range or stride does not have a performance impact under optimization. Such new syntax would need to stand on its own as a second alternative to using ranges/strides.

Considering that it would require reserving three new keywords (‘from’, ‘to’, and ‘by’) this will be a hard argument to make.

-DW


(Ted van Gaalen) #12

Goedemorgen David, Chris & Other respected,

      for v from v1 to v2 by v3 { }

Yes, i do see this for variant as an *additional* alternative to ranges/strides.

Regarding performance, I was under the false impression that for a stride
a complete collection was generated, but luckily this is not the case, as for each
next() a new value is calculated, not a collection element is returned

In the mean time I’ve made a new Strider variant, as described in detail
with source code in another post I’ve submitted yesterday:

for v in Strider( from: v1, to: v2, by: v3, tolerance: vtol) { Aragorn(v) }

which is also implemented (as the other stride(s) as a Double extension function.
Also able to move backwards.

In essence, I can live with using a strider construct, because it can do everything
the classical ‘for ; ;’ loop does.
(although the classical for-loop is way much more efficient)

And also working with pseudo-collections as Strides are has advantages as well

(still:
Laying under my car and trying with one hand to find
my for-screwdriver. "Sh..! Hey! Who took my really handy wrench
out of my shiny new Swift toolbox !?! Because he/she thought
wrongly I didn’t need it anymore? And also without offering a better alternative?” )

I still think its an important Swift improvement to also have:

      for v from v1 to v2 by v3 { }

and perhaps add for real numbers, this tolerance parameter like so:

      for v from v1 to v2 by v3 tolerance vtol { }’

Not only because it is easier to think and write,
especially for those new to Swift,

@Chris: !
but also because the Swift compiler can really boil this
down to a very efficient loop structure wit nearly no calls involved.
instead of having to get values from a generator all the time.

I’d estimate this would at least save about 10 calls or so,
but I am not sure of that, as I have no experience
with working on compilers, parsers and the like.

I assume that our compiler is not intelligent enough to
optimize this strider logic away, because it simply cannot know
in advance what values to expect from the strider sequence.

I do also still think the compiler should treat tolerance requests
for the same reason of a significant performance improvement.
(As in a previous thread I’ll pick up again)

About reserved words: (will also post this separate)
In short:
                 -= there should be none =-
A compiler should have enough intelligence to determine
from the statement’s context if a word is used as a keyword
or not. Like e.g. in PL/1 as I posted here before.

···

----
Lunch break Going to get some coffee to get over
what I saw on BBC/CNN TV…
Some things are really hard to believe.
So, to my dear American colleagues and friends:
for _ in 1..100; { print(“Please! “}
don’t vote for someone like that.
It is already bad enough that he came that far.
https://www.youtube.com/watch?v=FK3TIYG9mqM
  ----

kind regards / met vriendelijke groeten
TedvG

On 26.02.2016, at 21:52, David Waite <david@alkaline-solutions.com> wrote:

On Feb 26, 2016, at 9:07 AM, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org> wrote:

Does .stride(), which in the end uses a descendant of SequenceType, just calculate a new value each time for..in.. uses .next() on this collection?

this.

   for x from xmin to xmax by xstep { }

   for x from xmax to xmin by -xstep { }

   for apple from 1 to applesInTruck { }

No need for collections in these cases,

As the thread for removal of C-style for showed in benchmarks, using a range or stride does not have a performance impact under optimization. Such new syntax would need to stand on its own as a second alternative to using ranges/strides.

Considering that it would require reserving three new keywords (‘from’, ‘to’, and ‘by’) this will be a hard argument to make.

-DW


#13

I'm a bit late to the discussion but how about something like this:

for x in (0..<5).by(0.3) { ... }
// or
for x in (0..<5).strideBy(0.3) { ... }
// or
for x in stride(0..<5, by: 0.3) { ... }

Greetings
- Maximilian

···

Am 27.02.2016 um 00:31 schrieb Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org>:

Thanks for clarifying, David

However, to me, it still remains awkward and indirect to
use collections in cases where they are not necessary at all.

About reserved words:
The sub-keywords (‘from’, ‘to’, and ‘by’)
are context dependent and will almost certainly
not be used on their own, rather always together
with a primary keyword.

An advanced compiler should be able to
determine from the context of a statement
if words used in that statement act as keywords or not!

For instance the PL/1 language from < 1970 , far ahead of its time, **1**
in spite of being very extensive, has no reserved words at all.

Most people will not use reserved words as identifiers because this is confusing.

I will write a Swift proposal next week for
for .. from .. to.. [by.. ] ,
in spite of all of its functionality being
already present in the classical for ;; loop.

I still cannot find a reason why it should be removed.
As with any other language construct,
it really depends on how one uses it.
Like with any other language elements in Swift,
it’s very easy to create a mess, no one can prevent this.

Of course, I would use a for..in..
if dealing with a collection: using a
classic for ;; for that is clearly not an advantage.

TedvG

  **1** As a source of inspiration, one might find this interesting:
https://en.wikibooks.org/wiki/Software_Engineers_Handbook/Language_Dictionary/PLI#Looping_Statements

Ted

On 26.02.2016, at 21:52, David Waite <david@alkaline-solutions.com> wrote:

On Feb 26, 2016, at 9:07 AM, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org> wrote:

Does .stride(), which in the end uses a descendant of SequenceType, just calculate a new value each time for..in.. uses .next() on this collection?

this.

   for x from xmin to xmax by xstep { }

   for x from xmax to xmin by -xstep { }

   for apple from 1 to applesInTruck { }

No need for collections in these cases,

As the thread for removal of C-style for showed in benchmarks, using a range or stride does not have a performance impact under optimization. Such new syntax would need to stand on its own as a second alternative to using ranges/strides.

Considering that it would require reserving three new keywords (‘from’, ‘to’, and ‘by’) this will be a hard argument to make.

-DW

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


(Ted van Gaalen) #14

@Sebastien: Note: your previous email was received by me only and not CC-ed to swift-evolution.

Hello Maximillian

Thanks, but your examples are still for..in.. using pseudo-collections.

My point was, and still is, having a clean and fast iteration statement in Swift,
easy to use, without the need to use (pseudo) collections, as with .stride.

Again, if you use for..in.. on collections, that is where the for..in..
really shines! I use it all the time when I have to handle
collection like arrays dictionaries. etc.. So far so good.

However, in the real world, especially when working with technical
and scientific data and for instance in time critical applications
like 3D presentations fast iterations become a necessity.
E.g. I am currently writing an Apple TV 3D app based on SceneKit
with lots of SCNNode objects. For instance about 300 (geometric)
blocks for each which I have to update it’s 3D positions as fast as
possible. x,y, and z coordinates in (now 3 nested for-loop)
If I have to do that with .stride. the overhead time is simply too much!
Also because it needs to be don within the rendering interval time.
Using for..in.. loops would make it exceed this small time frame.

(as a side note I might assume (but assuming is a bad
thing in software engineering) that many colleagues here
do not build apps such as described in the previous paragraph,
(i hope i am wrong) and therefore do not miss these for
statements, not to mention the classical for-loop. )

Here follows an example, clearly showing the difference:
The version without a GeneratorType/SequenceType
is more than twice as fast! Take a look at the coding,
it should then be clear why.

As tested in Xcode Playground:

import Foundation

// I wrote this function not only to test but also
// implemented floating point comparison tolerance
// and backwards striding: it is still based
// on SequenceType each time a value is
// fetched it has to go through his generator
// instances, no wonder it it runs twice as slow.

public struct StriderGenerator : GeneratorType
{
    private let low: Double
    private let high: Double
    private var step : Double
    private var tol : Double

    private var iterator = 0

    private let moveForward: Bool
    
    private var done = false
   
    public init(from: Double, to: Double, by: Double, tolerance: Double)
    {
        step = by
        if from < to
        {
            low = from
            high = to
            moveForward = true
        }
        else
        {
            low = to
            high = from
            moveForward = false
        }
        self.tol = tolerance * 0.5 // center it.
    }
    
    /// return next value or nil, if no next
    /// element exists.
    
    public mutating func next() -> Double?
    {
        let current:Double
        if done
        {
            return nil
        }
        
        if moveForward
        {
            current = low + Double(iterator) * step
        }
        else
        {
            current = high - Double(iterator) * step
        }
        iterator += 1
        
        // done if exceeding low or highlimits + tolerance
        
        done = current > high + tol ||
               current < low - tol
        
        if done
        {
            return nil
        }
        else
        {
            return current
        }
    }
}

public struct Strider : SequenceType // Aragorn
{
    private let start: Double
    private let end: Double
    private let step: Double
    private let tol: Double

    init(from: Double, to: Double, by: Double, tolerance : Double)
    {
        _precondition(by > 0.0 ,
            "Init of struct Strider: 'by:...' value must be > 0.0.")
        _precondition(abs(by) > tolerance,
            "Init of struct Strider: 'by:...' value must be > tolerance.")
        _precondition(tolerance >= 0.0,
            "Init of struct Strider: tolerance:... value must be >= 0.0")
        
        start = from
        end = to;
        step = by
        tol = tolerance
    }
    
    /// Return a *generator* over the elements of this *sequence*.
    
    public func generate() -> StriderGenerator
    {
        return StriderGenerator(from: start, to: end, by: step, tolerance: tol)
    }
}

public extension Double
{
    
    public func strider(to to: Double, by: Double, tolerance: Double ) -> Strider
    {
        return Strider( from: self, to: to, by: by, tolerance: tolerance)
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// This function written by me yesterday, is a very simple iterator
// without unnecessary calls to an instance of an iterator. sequence type.
// why should it? All direct logic is completely within the function.
// this function is more than twice as fast as the one above.
// Just pass it a block, continue and break are not yet implemented
// but could be an extra block parameter.

func iterate(from from: Double,
                    by: Double,
                 block: (v: Double) -> Void)
{
    let low: Double
    let high: Double
    
    var current = from
    
    let moveForward = from <= to
    if moveForward
    {
        low = from
        high = to
    }
    else
    {
        low = to
        high = from
    }
    
    var iterator = 0

    while current >= low &&
          current <= high
    {
        block(v: current) // <<<<<<<<<<<<<<<<<<
        
        iterator += 1 // ++ !
        if moveForward
        {
            current = low + Double(iterator) * by
        }
        else
        {
            current = high - Double(iterator) * by
        }
    }
}

/////////////////////////////////////////////////////////////////////////////////////////////////////
// Here are test of both methods: they are trivial
// sum up all iteration values.

func testWithStrideFor() // Test with swift lib strider function
{
    var total = 0.0
    
    for value in 0.0.strider(to: 100.0, by: 0.01, tolerance: 0.0001 )
    {
        total += value
    }
    print("total\(total)")
}

func testWithTedsFor() // Test with my iterate function above:
{
    var total = 0.0
    
    iterate(from: 0.0, to: 100.0, by: 0.01, block:
        {
            (value: Double) in
            total += value
        }
    )
    print("total\(total)")
}

// Here both methods are compared

func test()
{
    
    let start1 = NSDate();
    testWithStrideFor()
    let end1 = NSDate();
    
    let elapsed1 = end1.timeIntervalSinceDate(start1)
    
    let start2 = NSDate();
    testWithTedsFor()
    let end2 = NSDate();
    
    let elapsed2 = end2.timeIntervalSinceDate(start2)
    
    print("Test result (in seconds): ")
    print("Time test1: with Strider = \(elapsed1)")
    print("Time test2: without Strider = \(elapsed2)")
    print("The difference: = \(abs(elapsed1 - elapsed2))")
}

test()
////////////////////////////////////////////////////////////////////////////////////////////////
// test results:
total500050.0
total500050.0
Test result (in seconds):
Time test1: with Strider = 10.1978410482407
Time test2: without Strider = 4.94159704446793
The difference: = 5.25624400377274

The latter iterator function without the strider is
is more than twice as fast, which is not exactly
a miracle.

It is highly probable that if both methods were precompiled,
possibly optimized by hand, and integrated in the
Swift language, they would be even much faster.
Especially the method without the strider, because
it has about 60% less coding, and doesn’t go shopping
each time around to get the next() value.

however please implement

for v from v1 to v2 by vstep

simple, isn’t it?

I not really happy writing all this, but it seems to be necessary
because a replacement is needed for the classical for ;;
statement which wil be removed, without offering a
decent alternative!
I use them at many places and I have to
unnecessarily convert them

Why not simply leave them in as they do not
stand other Swift elements in the way.
If you don’t like the for ;; or even i++
just don’t use them. it’s as easy as that.

Kind regards
Ted

···

to: Double,

On 08.03.2016, at 18:29, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

I'm a bit late to the discussion but how about something like this:

for x in (0..<5).by(0.3) { ... }
// or
for x in (0..<5).strideBy(0.3) { ... }
// or
for x in stride(0..<5, by: 0.3) { ... }

Greetings
- Maximilian

Am 27.02.2016 um 00:31 schrieb Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org>:

Thanks for clarifying, David

However, to me, it still remains awkward and indirect to
use collections in cases where they are not necessary at all.

About reserved words:
The sub-keywords (‘from’, ‘to’, and ‘by’)
are context dependent and will almost certainly
not be used on their own, rather always together
with a primary keyword.

An advanced compiler should be able to
determine from the context of a statement
if words used in that statement act as keywords or not!

For instance the PL/1 language from < 1970 , far ahead of its time, **1**
in spite of being very extensive, has no reserved words at all.

Most people will not use reserved words as identifiers because this is confusing.

I will write a Swift proposal next week for
for .. from .. to.. [by.. ] ,
in spite of all of its functionality being
already present in the classical for ;; loop.

I still cannot find a reason why it should be removed.
As with any other language construct,
it really depends on how one uses it.
Like with any other language elements in Swift,
it’s very easy to create a mess, no one can prevent this.

Of course, I would use a for..in..
if dealing with a collection: using a
classic for ;; for that is clearly not an advantage.

TedvG

  **1** As a source of inspiration, one might find this interesting:
https://en.wikibooks.org/wiki/Software_Engineers_Handbook/Language_Dictionary/PLI#Looping_Statements

Ted

On 26.02.2016, at 21:52, David Waite <david@alkaline-solutions.com> wrote:

On Feb 26, 2016, at 9:07 AM, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org> wrote:

Does .stride(), which in the end uses a descendant of SequenceType, just calculate a new value each time for..in.. uses .next() on this collection?

this.

   for x from xmin to xmax by xstep { }

   for x from xmax to xmin by -xstep { }

   for apple from 1 to applesInTruck { }

No need for collections in these cases,

As the thread for removal of C-style for showed in benchmarks, using a range or stride does not have a performance impact under optimization. Such new syntax would need to stand on its own as a second alternative to using ranges/strides.

Considering that it would require reserving three new keywords (‘from’, ‘to’, and ‘by’) this will be a hard argument to make.

-DW

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


(Taras Zakharko) #15

There is no reason why collection-based iteration can’t be as fast as a classical C for loop. The compiler should be able to optimise all the overhead away. , even unroll shorter loops. Maybe it doesn’t do it yet. I’d rather see resources invested into improving the compiler to inline/unroll code better where appropriate rather then introducing additional syntax to support a marginal use case.

— Taras

···

On 09 Mar 2016, at 00:01, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org> wrote:

However, in the real world, especially when working with technical
and scientific data and for instance in time critical applications
like 3D presentations fast iterations become a necessity.


(David Owens II) #16

And yet, developers spend the vast majority of their time running and validating code in non-optimized builds.

I'd also refer you to the many, many talks about so called zero-cost abstractions in C++ causing significant overhead in real-time apps/games. I'm still amazed that Swift's only out here is to write a while loop here.

···

Sent from my iPhone

On Mar 9, 2016, at 4:21 AM, Taras Zakharko via swift-evolution <swift-evolution@swift.org> wrote:

On 09 Mar 2016, at 00:01, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org> wrote:

However, in the real world, especially when working with technical
and scientific data and for instance in time critical applications
like 3D presentations fast iterations become a necessity.

There is no reason why collection-based iteration can’t be as fast as a classical C for loop. The compiler should be able to optimise all the overhead away. , even unroll shorter loops. Maybe it doesn’t do it yet. I’d rather see resources invested into improving the compiler to inline/unroll code better where appropriate rather then introducing additional syntax to support a marginal use case.

— Taras

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


(Goffredo Marocchi) #17

Sometimes programmers directives and runtime knowledge are essential though and the compilers should be optimised but not held to a practically impossible standards. Beside not letting their best people having the best manufacturing process (as well as a nice dose of politics), there is a reason why architectures like IA-64 (which still intrigue me :)) had competitive problems against archs which trusted runtime decisions a lot more.

···

Sent from my iPhone

On 9 Mar 2016, at 12:21, Taras Zakharko via swift-evolution <swift-evolution@swift.org> wrote:

On 09 Mar 2016, at 00:01, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org> wrote:

However, in the real world, especially when working with technical
and scientific data and for instance in time critical applications
like 3D presentations fast iterations become a necessity.

There is no reason why collection-based iteration can’t be as fast as a classical C for loop. The compiler should be able to optimise all the overhead away. , even unroll shorter loops. Maybe it doesn’t do it yet. I’d rather see resources invested into improving the compiler to inline/unroll code better where appropriate rather then introducing additional syntax to support a marginal use case.

— Taras

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


(Ted van Gaalen) #18

Hello Taras
If I am not mistaken, the compiler cannot optimize:

   for element in collection {}

simply because the contents of the collection are
unknown at compile time.

However, it might be able to do so in this cases where
the contents of the collection are known at compile time, e.g. :

  1. for str in [“ape”,”tiger”,”dolphin”,”salmon”] {}

  2. for n in [1, 12, 34, 65, 78, 3] {}

or perhaps also for collections declared with ‘let’:

  3. let words = [“this”,”collection”,”does”,”not”,”change”]
        for str in words {}

and also when the compiler can determine the sequence
optimize collection usage completely out of the way e.g.:
  
  4. for v in stride(from: v1 to v2 by vstep)

like it can do also with:

  5. for i in v1…v2 // etc.

Ergo: In cases where the contents of a collection is unknown at compile
time the compiler always needs to include instructions to traverse
through a collection. As in my example test, this consumes twice
as much time as a plain loop without a collection.

Even when optimized by a smart compiler, or even
hand-coded. I’d estimate that the difference at run time
wil be also be something in the order of 2:1, may even 3:1
because the compiled for..in.. it needs the collection processing
instructions in all cases where collection contents cannot be
determined at compile time.

I have sugessted these for variants like so:

   for x from xmin to xmax by xstep { }

   for x from xmax to xmin by -xstep { }

because:
- no collections are involved/processed in these cases in the first place,
   
- the compiler can optimize it very easily.
   in fact that logic is (still) already present in the
   Swift compiler to compile all for ;; variants.

- Semantic: to have a clear distinction that one is not working with collections here.

Certainly not the least:

- For the sake of clearness and readability.

Marginal use of for ;; or a new Swift equivalent of it?

What makes you think so?

That is definitely not the case!. Please read a lot of source code
in Fortran, C# Delphi, PL/1 C, C++ , Pascal etc. sources
for scientific and technical programming and then try
to imagine how you would rewrite these in Swift with
for ;; removed?
Currently there is no proper equivalence.

I am a bit worried that there is a tendency to remove
statement elements for Swift, as one cannot be sure
if these are useful or not for specific interest groups.

Swift is (and hopefully remains to be) a general purpose
language, Also, so not especially OOP or FP, both
inclinations should be first-class citizens.

TedvG

···

On 09.03.2016, at 13:21, Taras Zakharko <taras.zakharko@uzh.ch> wrote:

On 09 Mar 2016, at 00:01, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org> wrote:

However, in the real world, especially when working with technical
and scientific data and for instance in time critical applications
like 3D presentations fast iterations become a necessity.

There is no reason why collection-based iteration can’t be as fast as a classical C for loop. The compiler should be able to optimise all the overhead away. , even unroll shorter loops. Maybe it doesn’t do it yet. I’d rather see resources invested into improving the compiler to inline/unroll code better where appropriate rather then introducing additional syntax to support a marginal use case.

— Taras


(Ted van Gaalen) #19

Hi Goffredo,
sorry, I don’t understand you msg very well,
i now assume you state that compilers cannot be improved beyond practical limits?
TedvG

···

On 09.03.2016, at 14:25, Goffredo Marocchi <panajev@gmail.com> wrote:

Sometimes programmers directives and runtime knowledge are essential though and the compilers should be optimised but not held to a practically impossible standards. Beside not letting their best people having the best manufacturing process (as well as a nice dose of politics), there is a reason why architectures like IA-64 (which still intrigue me :)) had competitive problems against archs which trusted runtime decisions a lot more.

Sent from my iPhone

On 9 Mar 2016, at 12:21, Taras Zakharko via swift-evolution <swift-evolution@swift.org> wrote:

On 09 Mar 2016, at 00:01, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org> wrote:

However, in the real world, especially when working with technical
and scientific data and for instance in time critical applications
like 3D presentations fast iterations become a necessity.

There is no reason why collection-based iteration can’t be as fast as a classical C for loop. The compiler should be able to optimise all the overhead away. , even unroll shorter loops. Maybe it doesn’t do it yet. I’d rather see resources invested into improving the compiler to inline/unroll code better where appropriate rather then introducing additional syntax to support a marginal use case.

— Taras

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


(Taras Zakharko) #20

Hello Taras
If I am not mistaken, the compiler cannot optimize:

  for element in collection {}

simply because the contents of the collection are
unknown at compile time.

The progression

  for(int i=0; i<=n; i++) {...}

is also unknown at compile time. Compiler needs to generate a series of instructions that increment a loop counter and check whether the exit condition has been reached.

Now, in case of a higher abstraction such as

  for i in stride(0, n, by=1) {...}

there is indeed more code being generated, but the structure of the loop is exactly the same. Specifically, the above loop can essentially be rewritten in C as

for(int i = get_initial_value(), !should_exit_on_counter(i), i = next_value(i)) {...}

There is a piece of code that increments a loop counter and a piece of code that checks the exit condition. In an unoptimised build, the overhead is indeed significant because of all the extra function calls. However, an optimising compiler can easily

a) detect that the iterator does not escape the loop (and therefore eliminate refcounting for it)
b) inline the methods that implement loop increment and exit condition check
c) infer (e.g. via SSA form transformations) that i and the internal counter used by the iterator variable are always identical and eliminate any unnecessary assignments

All of these optimisations are fairly trivial and have existed for quite some time in modern compilers. At this point the code of the abstract collection-driven loop becomes identical to an optimised for loop. Furthermore, the compiler can use additional heuristics to unroll or otherwise optimise the loop (such as moving the loop counter to a register).

Sometimes programmers directives and runtime knowledge are essential though and the compilers should be optimised but not held to a practically impossible standards.

I do not think that expecting the compiler to optimise away a simple iterator is a practically impossible standard. There are compilers out there that perform much more impressive feats (such as autovectorisation). In fact, I am quite sure that GCC and clang will optimise away the for loop that uses simple functions for exit condition/increment.

I certainly agree with you that a compiler can’t do everything — but thats what the while loop is for.

···

On 09 Mar 2016, at 14:52, Ted F.A. van Gaalen <tedvgiosdev@gmail.com> wrote:
On 09 Mar 2016, at 14:25, Goffredo Marocchi <panajev@gmail.com> wrote:

On 09 Mar 2016, at 16:11, David Owens II <david@owensd.io> wrote:

And yet, developers spend the vast majority of their time running and validating code in non-optimized builds.

This is a good point! But I believe that this can be alleviated somehow by performing partial optimisations on loops even in the debug mode (e.g. refcounting elimination, inlining)

— Taras