LanX has asked for the wisdom of the Perl Monks concerning the following question:

Hi

I'm puzzling over the best interface design of a multi-purpose function/operator, where the same function can be used for various purposes:

Lets take a function range() simulating the range operator as example:

 for ( range(1,9) ) { print $i }

 while ( range(1,9) ) {...}

 $f=range(1,9); while ( &$f ) {...}

should all do the same 9 loops.

The first two examples are easy to achieve by evaluating the context (for imposes list, while scalar context).

Unfortunately there is no "function context"...

I was thinking about blessing a scalar such that I can use scalar context for both cases, such that

 $f=range(1,9); while ( $i=$f->next ) {...} works.

Anyhow this is sabotaging performance, since calling methods on objects or ties is quite expensive... and I'm not sure if this double nature of $f could have ugly side effects.

The other approach would be using globs, such that calling *f=range(1,9); while ( $i=f() ) {...} would define a function f(), but ATM I'm not sure if this is even possible...and it shouldn't be working with lexical functions.

My best guess is to return a blessed scalar and to use this  $f=range(1,9)->iterator; while ( $i=&$f ) {...} ...

Any suggestions?

That's all untested code, hope you get the underlying ideas.

Cheers Rolf

PS: Yes I know that the while loops wouldn't work when range(0,9) is called, but thats another issue. I didn't want to over complicate the examples... :)

UPDATE:

After some meditation I think I got an even better idea, I could check for a third parameter to assign the scalar value to, such that in scalar context always a coderef is returned, this would also solve the "0 problem" (see PS):

 for my $i ( range(1,9) ) { print $i }

 while ( range 1,9 => my $i ) {...}

 $f=range(1,9); while ( $f->(my $i)) {...}

UPDATE:

I think I have to replace all mys with state restricting this feature to 5.10. 8(

Replies are listed 'Best First'.
Re: Designing a DWIMish interface for generator function
by ikegami (Patriarch) on Feb 01, 2010 at 03:58 UTC

    Anyhow this is sabotaging performance, since calling methods on objects or ties is quite expensive...

    Ties aren't cheap compared to variable lookups because they involve a sub call. However, they shouldn't be much slower than a sub call.

    Method calls shouldn't be any slower than an sub call because they do the same thing. Even if inheritance is involved, Perl caches the inherited method so it doesn't have to search the inheritance tree for it again.

    Of course, there's no reason to use a method call here. $f->() would work just as well.

    for my $i ( range(1,9) ) { print $i }

    That would take an efficient counting loop (O(1) memory) into an inefficient foreach loop (O(N) memory)

    while ( range(1,9) ) {...}

    You could make this work by storing the current state of the counter in a per-thread hash indexed by the operator instance. The problem is that you'd have no way of resetting the counter if you previously used last to exit the loop.

    On the other hand, the following would be trivial to implement.

    for my $i ( 1..9 ) { print $i } for my $i ( range(1,9) ) { print $i } { my $i; while ( range 1,9 => $i ) {...} } { my $iter = range(1,9); while ( $iter->(my $i) ) {...} }
    sub range { my $start = shift; my $end = shift; if (!@_) { if (wantarray()) { return $start .. $end; } else { my $i = $start; return sub { return undef if $i > $end; $_[0] = $i++; return 1; }; } } if (defined($_[0])) { ++$_[0]; } else { $_[0] = $start; } if ($_[0] > $end) { return $_[0] = undef; } else { return 1; } }
      I have a pretty clear idea of how to implement it, I'm looking for the best interface resp. usage pattern to distinct between returning single steps and an iterator function.

      But I'm confident that the idea in the update is the best way to go.

      But please why don't you consider using while ( range 1,9 => my $i ) {...} instead of { my $i; while ( range 1,9 => $i ) {...} }?

      Cheers Rolf

        You need something external to the loop to keep the state.

        You could use a combination of the address of the calling opcode as a key to get the appropriate state, but you'd have no way to reset to the counter if you did that, so you wouldn't be able to last/return/die from within the loop.

        Just look at each for example. To properly use each, you need to do:

        keys %hash; # Reset iterator while (my ($k, $v) = each(%hash)) { ... }

        And even then, it doesn't nest because the iterator is per-hash, not per-each.

Re: Designing a DWIMish interface for generator function
by Anonymous Monk on Feb 01, 2010 at 02:59 UTC

    You are mixing between PHP & Perl ... and I was not able to understand what you want to do exactly

      what is PHP?

      Cheers Rolf

      UPDATE: xD

        PHP is a scripting language.

        I have never seen the range function in perl , but I already saw it in PHP

        I might be wrong though. but I still do not understand your question