problem creating string from list

P

Peter Wyzl

I have been playing around with this for a while now and am clearly missing
something...

__SAMPLE__

#!/usr/bin/perl -w
use strict;

my $word = 'foobar';

my $string = substr $word,0,1;
$string = join '|', $string, substr $word,0,$_ for 2..length $word;

print $string;

__END SAMPLE__

prints 'f|fo|foo|foob|fooba|foobar' (sans quotes)

But!

changing to:

$string = join '|', substr$word,0,$_ for 1..length $word;

prints 'foobar'

Why is this discarding all but the last value from the list?

Whilst this:

$string = join '|', substr $word,0,1, substr$word,0,$_ for 2..length $word;

prints 'f'

why is this discarding all but the first value from the list?

Even more puzzling to me is why these are syntax errors:

$string = join '|', (substr $word,0,$_ for 1..length $word);
$string = join '|', [substr $word,0,$_ for (1..length $word)];

(and several other variants with braces)

Specifically I don't understand what the scalar variable $string is doing in
the first version that makes this work. I played with parens trying to
force list context but that is when I hit the syntax errors.

$string = join '|', (substr $word,0,$_ for (1..length $word));

causes the error:
syntax error at test.pl line 7, near "$_ for "

Scratching my head with that one...
 
G

Gunnar Hjalmarsson

Peter said:
I have been playing around with this for a while now and am clearly missing
something...

__SAMPLE__

#!/usr/bin/perl -w
use strict;

my $word = 'foobar';

my $string = substr $word,0,1;
$string = join '|', $string, substr $word,0,$_ for 2..length $word;

print $string;

__END SAMPLE__

prints 'f|fo|foo|foob|fooba|foobar' (sans quotes)

But!

changing to:

$string = join '|', substr$word,0,$_ for 1..length $word;

prints 'foobar'

Why is this discarding all but the last value from the list?

<snip>

To me it's more surprising that your first expression works. Use the
map() function instead:

my $string = join '|', map { substr $word,0,$_ } 1..length $word;
 
G

Gunnar Hjalmarsson

Peter said:
I have been playing around with this for a while now and am clearly missing
something...

__SAMPLE__

#!/usr/bin/perl -w
use strict;

my $word = 'foobar';

my $string = substr $word,0,1;
$string = join '|', $string, substr $word,0,$_ for 2..length $word;

print $string;

You may be missing that what's happening can be written as:

my $string = substr $word,0,1;
for ( 2..length $word ) {
$string = join '|', $string, substr $word,0,$_;
}

or maybe as:

my $string = substr $word,0,1;
$string .= '|' . substr $word,0,$_ for 2..length $word;
$string = join '|', substr$word,0,$_ for 1..length $word;

prints 'foobar'

Why is this discarding all but the last value from the list?

Write it like this, and it gets obvious:

my $string;
for ( 1..length $word ) {
$string = join '|', substr $word,0,$_;
}

But, as I said in another post, use map() instead.
 
A

Anno Siegel

Peter Wyzl said:
I have been playing around with this for a while now and am clearly missing
something...

It seems to me you are conflating the actions of "map" and "for".
__SAMPLE__

#!/usr/bin/perl -w
use strict;

my $word = 'foobar';

my $string = substr $word,0,1;
$string = join '|', $string, substr $word,0,$_ for 2..length $word;

print $string;

__END SAMPLE__

prints 'f|fo|foo|foob|fooba|foobar' (sans quotes)

You appear to think that join() puts together the substrings in one
go, but it doesn't. join() is called repeatedly and each time only
joins two elements (and one '/'): the string constructed so far, and
one new substring. It gets clearer when join() is written with
parentheses like this:

my $string = substr $word,0,1;
$string = join( '|', $string, substr $word,0,$_) for 2..length $word;

Join is called with three arguments each time. You want "map" to
construct the list of substrings and join them in one go:

my $string = join '|', $string, map substr( $word,0,$_), 1 ..length $word;

Now you don't have to initialize the string anymore, "map" (starting at 1
now) produces all the parts, and "join" puts them all together.
But!

changing to:

$string = join '|', substr$word,0,$_ for 1..length $word;

prints 'foobar'

Why is this discarding all but the last value from the list?

It assigns to $string length($string) times, each time overwriting the
previous value. "join", being called with two arguments, is really a
no-op there. Only the final assignment of substr( $word, 0, length $word)
remains.
Whilst this:

$string = join '|', substr $word,0,1, substr$word,0,$_ for 2..length $word;

prints 'f'

why is this discarding all but the first value from the list?

Again, the list you think it works on isn't there, you have multiple
assignments. Here it's the four-argument form of substr() that is
fooling you. The line is parsed as

$string = join( '|', substr( $word,0,1, substr($word,0,$_))), "\n") for
2..length $word;

Like before, "join" does nothing. Four-arg substr() returns the (unchanged)
substring described by the first three arguments, and replaces it by the
fourth argument. That means, $word is changed around in the loop. However,
the first character of $word remains "f", and so $string is set to "f"
five times, and that's what it is after the loop.

What a mess...
Even more puzzling to me is why these are syntax errors:

$string = join '|', (substr $word,0,$_ for 1..length $word);
$string = join '|', [substr $word,0,$_ for (1..length $word)];

"for" is a *statement modifier*. It doesn't have a (list) value
like map. Not even the modified statement has a value, it is just
executed multiple times, the way "for" describes. So what should
be the third argument of substr() is a (modified) statement, not
an expression. That is no valid syntax.

You ought to acquaint yourself with some basic debugging techniques
that help finding out what's going on when you get unexpected results.
The most basic one is printing intermediate results. In your first
example

my $string = substr $word,0,1;
$string = join '|', $string, substr $word,0,$_ for 2..length $word;

simply printing the result of each assignment

print( $string = join '|', $string, substr $word,0,$_, "\n") for
2..length $word;

would have told you a lot. The same goes for the other two.

Another technique is (re-)reading the documentation of the functions
you are using. The four-argument form of substr() that tripped you
up is sneaky, but reading about it again might have alerted you.

The last advice is repeated here often, but that's because it is often
neglected.

Anno
 
P

Peter Wyzl

: Peter Wyzl wrote:
: > I have been playing around with this for a while now and am clearly
missing
: > something...
: >
: > __SAMPLE__
: >
: > #!/usr/bin/perl -w
: > use strict;
: >
: > my $word = 'foobar';
: >
: > my $string = substr $word,0,1;
: > $string = join '|', $string, substr $word,0,$_ for 2..length $word;
: >
: > print $string;
:
: You may be missing that what's happening can be written as:
:
: my $string = substr $word,0,1;
: for ( 2..length $word ) {
: $string = join '|', $string, substr $word,0,$_;
: }

Thats the clue I needed! I was considering the 4 argument issue which is
why I came up against the syntax errors while trying the parens.

If I were using it myself I would have probably gone the map route, but it
was more about trying to understand what _was_ and _wasn't_ happening in the
loop.

I also ran into an interesting scoping problem whilst playing with this.

I am well aware that

foreach my $var (@array){
#ya ya
}

has $var limited to the scope of the loop.

I ran into the unexpected situation where I had an unexpectedly short scope
with the same construct:

my $path .= ",DC=$_" for split '\.', $fqdn;

$path was empty after the line, where pre-declaring $path as in

my $path;
$path .= ",DC=$_" for split '\.', $fqdn;

Works fine!

I assume that is more equivalent to

for (split '\.', $fqdn){
my $path .= ",DC=$_";
}

where once out of the loop $path is out of scope.

I can see I have a lot to learn yet... :)
 
A

Anno Siegel

[...]
I ran into the unexpected situation where I had an unexpectedly short scope
with the same construct:

my $path .= ",DC=$_" for split '\.', $fqdn;

$path was empty after the line, where pre-declaring $path as in

my $path;
$path .= ",DC=$_" for split '\.', $fqdn;

Works fine!

That's simple. You can't declare a lexical variable under the influence
of a statement modifier, by penalty of an undefined result.
I assume that is more equivalent to

for (split '\.', $fqdn){
my $path .= ",DC=$_";
}

Not really. Here you have a defined scope, and $path is known within
it, but not outside. That's perfectly legal and reasonable daily
practice.

The scope of a lexical declared in a statement modifier (if it were
allowed), would have to be the surrounding scope. There is no inner
block it could be confined to.

Anno
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,164
Messages
2,570,898
Members
47,439
Latest member
shasuze

Latest Threads

Top