[swift-evolution] For-loop revisited

Ted F.A. van Gaalen tedvgiosdev at gmail.com
Tue Mar 8 17:01:28 CST 2016


@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,
                    to: 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





 


> On 08.03.2016, at 18:29, Maximilian Hünenberger <m.huenenberger at 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 at 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 at alkaline-solutions.com> wrote:
>>> 
>>> 
>>>> On Feb 26, 2016, at 9:07 AM, Ted F.A. van Gaalen via swift-evolution <swift-evolution at 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 at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution



More information about the swift-evolution mailing list