for ... else

264 views
Skip to first unread message

Thomas Wittek

unread,
Mar 2, 2007, 5:19:52 PM3/2/07
to perl6-l...@perl.org
Today I stumbled upon the Perl5 module For::Else.
It adds an else block to a foreach loop:

foreach my $item (@items) {
#process each item
} else {
#handle the empty list case
}

I find it a very nice addition as I've written code like this:

if (@stuff) {
for my $thing (@stuff) {
..
}
} else {
..
}

many times. An else block would save keystrokes and would make the code
more readable. Python also has it.

What do you think?
--
Thomas Wittek
http://gedankenkonstrukt.de/
Jabber: strea...@jabber.i-pobox.net

Luke Palmer

unread,
Mar 2, 2007, 5:36:06 PM3/2/07
to Thomas Wittek, perl6-l...@perl.org
On 3/2/07, Thomas Wittek <ma...@gedankenkonstrukt.de> wrote:
> Today I stumbled upon the Perl5 module For::Else.
> It adds an else block to a foreach loop:
>
> foreach my $item (@items) {
> #process each item
> } else {
> #handle the empty list case
> }
>
> I find it a very nice addition as I've written code like this:
>
> if (@stuff) {
> for my $thing (@stuff) {
> ..
> }
> } else {
> ..
> }
>
> many times. An else block would save keystrokes and would make the code
> more readable. Python also has it.

It will confuse the Python folks to no end (assuming they'd ever touch
anything resembling Perl). That is not what it means in Python. The
python else construct assumes you are looping in order to find
something:

for x in list:
if something(x):
print "Found"
break
else:
print "Not found"

That is, the else block is fired if you get to the end of the list
without breaking.

I'm not sure about either interpretation, but admittedly Python's is
harder to emulate clearly. I wonder if a closure trait should do the
same thing. Or I wonder if there's really a problem here that we're
addressing, or whether we're just adding syntax because we can.

Luke

Paul Seamons

unread,
Mar 2, 2007, 5:28:13 PM3/2/07
to perl6-l...@perl.org
> foreach my $item (@items) {
> #process each item
> } else {
> #handle the empty list case
> }
>
> What do you think?

I'm not sure if I like it, but there have been several times that I would've
used it recently. I think it would certainly have utility.

Paul

Rick Delaney

unread,
Mar 2, 2007, 6:51:46 PM3/2/07
to Luke Palmer, Thomas Wittek, perl6-l...@perl.org
On Mar 02 2007, Luke Palmer wrote:
> Or I wonder if there's really a problem here that we're
> addressing, or whether we're just adding syntax because we can.

This syntax awkwardness comes up a lot in various kinds of reports.

for @records {
.print;
}
if @records {
say "Search returned no records";
}

I've always found it annoying that I have to write @records (or
whatever) twice. I don't know if Thomas's idea is the best possible but
it's not horrible. Hmm, I wonder if I could get it into Template
toolkit.

[% FOREACH records %]
<span>[% whatever %]</span>
[% ELSE %]
<span>Search returned no records.</span>
[% END %]

--
Rick Delaney
ri...@bort.ca

Thomas Wittek

unread,
Mar 2, 2007, 7:39:35 PM3/2/07
to perl6-l...@perl.org
Luke Palmer schrieb:

> That is not what it means in Python.

You trapped me. :) Actually I don't know any python but I've once read a
for/else construct in python. But obviously it doesn't dwIm.

From the Python Reference Manual:

When the items are exhausted (which is immediately when the sequence
is empty), the suite in the else clause, if present, is executed,
and the loop terminates.

> I'm not sure about either interpretation, but admittedly Python's is
> harder to emulate clearly.

I'd like the For::Else behaviour more. Especially as I remember numerous
times writing an if clause to check if a list is empty before processing it.

I don't remember many cases where I wrote something like this:

my $found;
foreach my $item (@items) {
if ($item = 'foobar') {
$found = 1;
last;
}
}
unless ($found) {
..
}

To make it more clear, I could imagine (a subset of) this:

for @items -> $item {
say $item;
if $item == 42 {
say "I've found the Answer to Life, the Universe, and Everything!";
last;
}
} start {
say "Mh, I'll look for 42!"
} end {
say "The end has been reached. 42 not found."
} empty {
say "No items."

Larry Wall

unread,
Mar 3, 2007, 12:03:56 PM3/3/07
to perl6-l...@perl.org
On Sat, Mar 03, 2007 at 01:39:35AM +0100, Thomas Wittek wrote:
: Luke Palmer schrieb:

: > That is not what it means in Python.
:
: You trapped me. :) Actually I don't know any python but I've once read a
: for/else construct in python. But obviously it doesn't dwIm.
:
: >From the Python Reference Manual:
:
: When the items are exhausted (which is immediately when the sequence
: is empty), the suite in the else clause, if present, is executed,
: and the loop terminates.
:
: > I'm not sure about either interpretation, but admittedly Python's is
: > harder to emulate clearly.

Well, that's assuming you come at it only with the 'for' loop. But this
is still Perl, so there's still more than one way to do it. :-)

$result = first { .something }, @items
err fail "nothing found";

: I'd like the For::Else behaviour more. Especially as I remember numerous


: times writing an if clause to check if a list is empty before processing it.
:
: I don't remember many cases where I wrote something like this:
:
: my $found;
: foreach my $item (@items) {
: if ($item = 'foobar') {
: $found = 1;
: last;
: }
: }
: unless ($found) {
: ..

: }

If you actually wrote that, then you'll always find that the first
item has the value 'foobar'. :)

: To make it more clear, I could imagine (a subset of) this:


:
: for @items -> $item {
: say $item;
: if $item == 42 {
: say "I've found the Answer to Life, the Universe, and Everything!";
: last;
: }
: } start {
: say "Mh, I'll look for 42!"
: } end {
: say "The end has been reached. 42 not found."
: } empty {
: say "No items."

: }

Well, leaving out the extraneous stuff, here's both kinds of "else" in one:

$result = first 42, (@items || say "No items.")
err say "The end has been reached. 42 not found.";

To get all of it in, you could use something more like:

$result = first Any, gather for @items || say "No items." {
FIRST {

say "Mh, I'll look for 42!"
}

when 42 {


say "I've found the Answer to Life, the Universe, and Everything!";

take $_;
}
LAST {
say "The end has been reached. 42 not found.";
}
}

That's assuming the LAST never runs because the gather never requests
a second take. (And also assuming that when the loop is GC'd it also
doesn't run LAST.) Note that in either case we're using smartmatch,
so you're not going to make the =/== mistake.

Perhaps there should be something like the "first gather" that does
the gather assuming only one thing will be gathered. Unfortunately
"item gather" will still gather everything and make it [list].

Larry

Thomas Wittek

unread,
Mar 3, 2007, 5:17:46 PM3/3/07
to perl6-l...@perl.org
Larry Wall:

> : if ($item = 'foobar') {

== of course ;)

> If you actually wrote that, then you'll always find that the first
> item has the value 'foobar'. :)

> Well, leaving out the extraneous stuff, here's both kinds of "else" in one:


>
> $result = first 42, (@items || say "No items.")
> err say "The end has been reached. 42 not found.";

That's a solution to find the first matching item.
But that's not the only reason to loop through a list.
"else" would be a general purpose solution.

> To get all of it in, you could use something more like:
>
> $result = first Any, gather for @items || say "No items." {
> FIRST {
> say "Mh, I'll look for 42!"
> }
> when 42 {
> say "I've found the Answer to Life, the Universe, and Everything!";
> take $_;
> }
> LAST {
> say "The end has been reached. 42 not found.";
> }
> }

That's, well, elegant! Yes.
Because and but it's tricky.
Nothing where I'd say "wow, thats an easy solution to my problem!".
It's a bit complicated, because you have to understand and combine
several concepts. That's elegant. But not easy, I think.
I think it's not simple enough for this simple kind of problem.

Darren Duncan

unread,
Mar 3, 2007, 5:57:13 PM3/3/07
to perl6-l...@perl.org
At 11:17 PM +0100 3/3/07, Thomas Wittek wrote:
>Larry Wall:
>> : if ($item = 'foobar') {
>
>== of course ;)
>
>> If you actually wrote that, then you'll always find that the first
> > item has the value 'foobar'. :)

Care to try a third time?

I don't think the numeric-casting == will do what you want either.

-- Darren Duncan

Juerd Waalboer

unread,
Mar 3, 2007, 8:50:46 PM3/3/07
to perl6-l...@perl.org
Thomas Wittek skribis 2007-03-03 23:17 (+0100):

> Larry Wall:
> > : if ($item = 'foobar') {
> == of course ;)

Or how about eq? :)
--
korajn salutojn,

juerd waalboer: perl hacker <ju...@juerd.nl> <http://juerd.nl/sig>
convolution: ict solutions and consultancy <sa...@convolution.nl>

Ik vertrouw stemcomputers niet.
Zie <http://www.wijvertrouwenstemcomputersniet.nl/>.

Herbert Breunung

unread,
Mar 4, 2007, 6:40:30 AM3/4/07
to perl6-l...@perl.org

> Von: Thomas Wittek <ma...@gedankenkonstrukt.de>

> That's, well, elegant! Yes.
> Because and but it's tricky.
> Nothing where I'd say "wow, thats an easy solution to my problem!".
> It's a bit complicated, because you have to understand and combine
> several concepts. That's elegant. But not easy, I think.
> I think it's not simple enough for this simple kind of problem.
>

exactly, my full support to thomas with this, perl was always about
keep simple things simple too.

herbert breunung
proton-ce.sf.net
_____________________________________________________________________
Der WEB.DE SmartSurfer hilft bis zu 70% Ihrer Onlinekosten zu sparen!
http://smartsurfer.web.de/?mc=100071&distributionid=000000000066

Jonathan Lang

unread,
Mar 4, 2007, 6:59:07 AM3/4/07
to herbert breunung, perl6-l...@perl.org
herbert breunung wrote:
> > Von: Thomas Wittek <ma...@gedankenkonstrukt.de>
> > That's, well, elegant! Yes.
> > Because and but it's tricky.
> > Nothing where I'd say "wow, thats an easy solution to my problem!".
> > It's a bit complicated, because you have to understand and combine
> > several concepts. That's elegant. But not easy, I think.
> > I think it's not simple enough for this simple kind of problem.
>
> exactly, my full support to thomas with this, perl was always about
> keep simple things simple too.

Seconded. I would favor allowing an "else" block to be attached
following any loop block, with the semantics being that the else block
only gets triggered if the loop block doesn't run at least once. I'd
do this instead of a block trait (such as FIRST or LAST) because of
the either-or relationship between the loop block and the else block.

--
Jonathan "Dataweaver" Lang

Jonathan Lang

unread,
Mar 4, 2007, 7:03:26 AM3/4/07
to p6l
Jonathan Lang wrote:
> Seconded. I would favor allowing an "else" block to be attached
> following any loop block, with the semantics being that the else block
> only gets triggered if the loop block doesn't run at least once. I'd
> do this instead of a block trait (such as FIRST or LAST) because of
> the either-or relationship between the loop block and the else block.

The one exception would be the "repeat" statement, because attaching
an "else" block to it would be as confusing semantically as attaching
an "else" block to an "unless" statement.

--
Jonathan "Dataweaver" Lang

Thomas Wittek

unread,
Mar 4, 2007, 7:03:51 AM3/4/07
to perl6-l...@perl.org
Darren Duncan schrieb:

Shame on me. I confused my examples. Read the 42 thing and wrote above.
Must be > of course, to leave the gag running ;)
Next time I should write my messages with "use warnings;"...

Andy Armstrong

unread,
Mar 4, 2007, 1:08:09 PM3/4/07
to Thomas Wittek, perl6-l...@perl.org
On 3 Mar 2007, at 00:39, Thomas Wittek wrote:
> I'd like the For::Else behaviour more. Especially as I remember
> numerous
> times writing an if clause to check if a list is empty before
> processing it.

That's crazy. If the list is empty foreach still does the right thing
- there's no benefit in guarding a foreach with a conditional.

> I don't remember many cases where I wrote something like this:
>
> my $found;
> foreach my $item (@items) {
> if ($item = 'foobar') {
> $found = 1;
> last;
> }
> }
> unless ($found) {
> ..
> }

I'd say that or a close variation of it was a pretty common idiom.

--
Andy Armstrong, hexten.net

Steve Lukas

unread,
Mar 4, 2007, 3:12:48 PM3/4/07
to perl6-language
I vote against this proposal.

More exceptional rules in a language are bad in itself.
Those exceptions force people to more to learn more stuff
and lead to confusion for those who don't know every detail
of this language. So, there should be an important reason
for that or it's a silly idea.

I think your proposal is conflicting with the common meaning
of 'else' and I fear that this would discourage many
professional programmers that are used to other languages,
to give perl6 a try. In other words: Features are good but
breaking conventions is bad.

On the other hand, there is no important reason for it because C<

for @rray -> $el {}
if ! @rray {}

>
should work. It's short and easy to understand.
So, I think there is no problem at all with the
currently specced syntax.

Kind Regards
Stefan


---------------------------------
It's here! Your new message!
Get new email alerts with the free Yahoo! Toolbar.

Daniel Brockman

unread,
Mar 4, 2007, 3:55:45 PM3/4/07
to perl6-l...@perl.org
What about this?

given @foo {
for $_ -> $x { ... }
when .empty { ... }
}

You can reverse the order if you want:

given @foo {
when .empty { ... }
for $_ -> $x { ... }
}

I don't like C<$_>, but I can't think of a way to get rid of it.

--
Daniel Brockman <dan...@brockman.se>

Jonathan Lang

unread,
Mar 4, 2007, 4:25:47 PM3/4/07
to Daniel Brockman, perl6-l...@perl.org
Daniel Brockman wrote:
> What about this?
>
> given @foo {
> for $_ -> $x { ... }
> when .empty { ... }
> }
>
> You can reverse the order if you want:
>
> given @foo {
> when .empty { ... }
> for $_ -> $x { ... }
> }

Actually, you'd be better off using the second order; the "when"
statement would drop you out of the "given" block if triggered, and
you'd never get to the loop. In the first order, you'd be triggering
both the loop and the "when" every time the "given" block executes.

--
Jonathan "Dataweaver" Lang

Smylers

unread,
Mar 4, 2007, 4:35:13 PM3/4/07
to perl6-l...@perl.org
Andy Armstrong writes:

> On 3 Mar 2007, at 00:39, Thomas Wittek wrote:
>
> > I'd like the For::Else behaviour more. Especially as I remember
> > numerous times writing an if clause to check if a list is empty
> > before processing it.
>
> That's crazy. If the list is empty foreach still does the right thing
> - there's no benefit in guarding a foreach with a conditional.

The purpose of the C<if> test is not to guard the loop but to attach an
C<else> clause to (hence the above mention of C<For::Else>), so that
some code can be run only in the case where the list is empty.

I have many times wanted this: either list the results or display a
message saying that there aren't any results; process each invoice, or
throw an error complaining there aren't any invoices.

Oooh, "or", now there's an idea. In Perl 6 the return value of C<for>
is the list of values that successfully completed an iteration. In the
case of their not being any items in the list to start with that will be
an empty list, which is false. So I think this will work:

for @invoice
{
.process;
} or fail 'No invoices to process';

> > I don't remember many cases where I wrote something like this:
> >
> > my $found;
> > foreach my $item (@items) {
> > if ($item = 'foobar') {
> > $found = 1;
> > last;
> > }
> > }
> > unless ($found) {
> > ..
> > }
>
> I'd say that or a close variation of it was a pretty common idiom.

I've many times wanted a better way of doing that too. Basically you
want an C<else> to attach to the C<if> but only be activated if none of
the. Larry's suggestion of using C<first> for this looks good.

Smylers

Thomas Wittek

unread,
Mar 4, 2007, 5:00:37 PM3/4/07
to perl6-l...@perl.org
Steve Lukas schrieb:

> More exceptional rules in a language are bad in itself.
> Those exceptions force people to more to learn more stuff

I guess that most people would understand the for/empty(/start/end) code
without having ever written any line of Perl.
They won't understand first/Any/gather directly, I think.

> On the other hand, there is no important reason for it because C<
>
> for @rray -> $el {}
> if ! @rray {}
>
> should work. It's short and easy to understand.

Agree, this looks good indeed.

Smylers

unread,
Mar 4, 2007, 6:24:10 PM3/4/07
to perl6-l...@perl.org
Steve Lukas writes:

> On the other hand, there is no important reason for it because C<
>
> for @rray -> $el {}
> if ! @rray {}
>
> >
> should work. It's short and easy to understand.

But it involves repeating C<@rray> -- which for more complex expressions
(results from function calls, delving deep into very nested data
structures, whatever) could be tedious. Repetition is certainly poor
style and makes the code more prone to errors being introduced.

Also, it relies on the contents of C<@rray> not being cleared inside the
loop.

Smylers

Rick Delaney

unread,
Mar 4, 2007, 8:14:42 PM3/4/07
to Smylers, perl6-l...@perl.org
On Mar 04 2007, Smylers wrote:
> for @invoice
> {
> .process;
> } or fail 'No invoices to process';

If that actually works then I'm happy.

--
Rick Delaney
ri...@bort.ca

Jonathan Lang

unread,
Mar 4, 2007, 9:37:34 PM3/4/07
to p6l
Rick Delaney wrote:
> Smylers wrote:
> > for @invoice
> > {
> > .process;
> > } or fail 'No invoices to process';
>
> If that actually works then I'm happy.

It's dependent on .process not returning a false on the final iteration.

--
Jonathan "Dataweaver" Lang

Larry Wall

unread,
Mar 4, 2007, 11:54:15 PM3/4/07
to p6l
On Sun, Mar 04, 2007 at 06:37:34PM -0800, Jonathan Lang wrote:
: Rick Delaney wrote:
: >Smylers wrote:
: >> for @invoice
: >> {
: >> .process;
: >> } or fail 'No invoices to process';
: >
: >If that actually works then I'm happy.
:
: It's dependent on .process not returning a false on the final iteration.

Er, these days 'for' is more like 'map', and hence returns a list.
So it's dependent on at least one iteration returning a non-() value.
In fact, if the final iteration returned False, the list would be
considered true.

Larry

Tom Lanyon

unread,
Mar 5, 2007, 12:26:16 AM3/5/07
to p6l

The situation the thread is referring to has @invoice empty so no
iterations are taking place and all this is a non-issue. :)

--
Tom Lanyon

Larry Wall

unread,
Mar 5, 2007, 12:29:12 AM3/5/07
to Tom Lanyon, p6l

You would still get a false positive on a non-null list if all of
the iterations return ().

Larry

Tom Lanyon

unread,
Mar 5, 2007, 12:43:16 AM3/5/07
to Tom Lanyon, p6l

I stand corrected. Is there a way to avoid the false positive case?

Sounds like the following will work, but it doesn't seem 'nice'.

for @invoice
{
.process;
1;


} or fail 'No invoices to process';

--
Tom Lanyon

Larry Wall

unread,
Mar 5, 2007, 1:24:43 AM3/5/07
to p6l
On Mon, Mar 05, 2007 at 04:13:16PM +1030, Tom Lanyon wrote:
: Sounds like the following will work, but it doesn't seem 'nice'.

:
: for @invoice
: {
: .process;
: 1;
: } or fail 'No invoices to process';

Still think if there's no invoices it logically should be tested first.
If you don't want to repeat mentioning the array, how 'bout:

@invoice or fail 'No invoices to process'
==> for @() {
.process
}

or equivalently

@invoice or fail 'No invoices to process'
==> map {
.process
}

all assuming you don't like my original

for @invoice || fail 'No invoices to process'
{
.process
}

Larry

Tom Lanyon

unread,
Mar 5, 2007, 1:42:27 AM3/5/07
to p6l
Larry Wall wrote:
> Still think if there's no invoices it logically should be tested first.
> If you don't want to repeat mentioning the array, how 'bout:
>
> @invoice or fail 'No invoices to process'
> ==> for @() {
> .process
> }
>
> or equivalently
>
> @invoice or fail 'No invoices to process'
> ==> map {
> .process
> }
>
> all assuming you don't like my original
>
> for @invoice || fail 'No invoices to process'
> {
> .process
> }
>
> Larry
>

Ah, I missed that last one when you originally posted it.

All three of those are quite sufficient solutions in my mind.

p.s. Could the latter 'for @foo || fail { }' cause confusion with 'for
@foo {} or fail' ?

--
Tom Lanyon

Jonathan Lang

unread,
Mar 5, 2007, 10:09:50 AM3/5/07
to p6l

These should work, as long as you don't replace "fail 'no invoices to
process'" with something that returns a non-empty list.

That said, you could:

{
for =<> || fail 'no input'
{
.process
}
CATCH {
do_something_else if 'no input'
}
}

...where the 'no input' string is there only so that failures
generated by .process will propagate correctly.

--

This solution isn't generalizable beyond "for", though: you wouldn't
be able to use it for "while, "until", or "loop", all of which could
potentially follow the logic of "do something special if the loop
fails to run even once". Hmm...

while test -> $result {
do_something;
FIRST { do_something_else unless $result }
}

...except that I don't think that FIRST would run unless $result is true.

I suppose that you _could_ write:

if test {
repeat {
do_something
} while test
} else {
do_something_else
}

You'd be having to spell out the test twice, though.

--
Jonathan "Dataweaver" Lang

Reply all
Reply to author
Forward
0 new messages