Why Perl isn't easy to learn
Written by MrBlueSky on Thu May 22, 2008 3:54 pm in blog A Programmers Thoughts under Diversions -
183 views
"...Perl is easy to start learning--and easy to keep learning."
- The PerlFAQ
"We think that Perl is an easy language to learn and use, and we hope to convince you that we're right."
- Programming Perl (Larry Wall, Tom Christiansen, Jon Orwant)
I disagree. Perl is not easy to start learning. And it's not easy to use until your at least past the Beginner level. This is not a Bad Thing (tm). There is no reason why a programming language should be easy to learn. History even seems to indicate that easiness is not a Good Thing when it comes to programming languages (PHP, Basic). Why is Perl not easy to get started with?
Context
In Perl, every expression is evaluated in scalar or list context (well, these are the most important 2 contexts), and the resulting value can be very different and sometimes not what you expected.
| Code: |
|
($x) = (1,3,2); # $x = 1 $y = (1,3,2); # $y = 2 @l = (1,3,2); $z = @l; # $z = 3 |
The fact that the expression in a return statement is evaluated in the context in which the function is called can easily throw an unsuspecting newcomer off. This prints "b":
| Code: |
|
sub f() { return undef; } print "a" if ($a = f()); # $a = undef, nothing is printed print "b" if (@b = f()); # @b = (undef) !! prints "b" |
List flattening
When a list is evaluated, each element of the list is evaluated in a list context, and the resulting list value is interpolated into the list just as if each individual element were a member of the list, to paraphrase "Programming Perl" (the Camel Book).
What does this mean? Lists within lists are flattened:
| Code: |
|
my @a = (1,2,3); my @b = (); my @c = (4,5,6); my @d = (@a, @b, @c); |
@d now contains the list (1,2,3,4,5,6), and not ((1,2,3), (), (4,5,6)) as you might expect.
List flattening also occurs with arguments to functions and their return values. All function parameters are passed as one single, flat list of scalars, and multiple return values are likewise returned to the caller as one single, flat list of scalars:
| Code: |
|
sub f { my (@first, @second) = @_; print "First list: @first, "; print "Second list: @second"; } my @list1 = (1,2,3); my @list2 = (2,4,6); f(@list1, @list2); |
This prints "First list: 1 2 3 2 4 6, Second list:".
Now consider a Perl novice who wants to write a functions which substracts the length of the second list it is given from the length of the first. So, given @list1 and @list2, the function should return the value of @list1-@list2.
| Code: |
|
sub f { ... return @list1-@list2; } my @list1 = (1,2,3,4); my @list2 = (2,4); print f( ... ); |
But how to get the two arrays into f() without them becoming flattened to one array? A solution obvious to seasoned Perl programmers, like this:
| Code: |
|
sub f { my ($list1, $list2) = @_; return @$list1-@$list2; } my @list1 = (1,2,3,4); my @list2 = (2,4); print f(\@list1, \@list2); |
isn't exactly, well..., beginner-friendly.
More Than One Way to Write It
Perls motto is TIMTOWTDI, but there is also often more than one way to 'write' (express) the same thing:
| Code: |
|
(one => 1, two => 2, three => 3) is the same as ('one', 1, 'two', 2, 'three', 3) ("one", "two", "three") is the same as qw|one two three| display { find Critter "Fred" } 'Height', 'Weight'; if Critter is a class and find a method which returns an object, this is the same as Critter->find("Fred")->display('Height', 'Weight'); $a->[1]->{b} is the same as ${%{${@$a}[1]}}{b} |
And
| Code: |
|
print while (<STDIN>) |
does the same as
| Code: |
|
while ($x = <STDIN>) { print $x; } |
Symbol Table and Variable Peculiarities
Global variables and local (lexical) variables are totally diffent things. To really understand the differences you need to know about how they are implemented. For example: global variables are implemented using 'typeglobs', which means you can alias them. Local variables are not.
| Code: |
|
$a = 4; $b = 3; { print ($a+$main::a); # prints 8 my $a = 5; print ($a+$main::a); # prints 9 local $b = 1; print ($b); # prints 1 print ($main::b); # prints 1 too!! } print ($main::b); # now prints 3 again!! |
Parsing Peculiarities
The way Perl parses a program is sometimes (often?) confusing when you are not an advanced perl hacker (yet).
| Code: |
|
print (3*4)+2; # prints 12 print 2+(3*4); # prints 14 |
Other Oddities
- Perls prototypes look like, and often behave like, parameter specifications in other languages. But they are not, and they change the way your function calls are parsed and executed.
- A lot of functions and operators use the scratchpad variable $_. AFAIK there is no other language with such a functionality.
| Code: |
|
# print all input with all letters in uppercase while(<STDIN>) { # put input lines in $_ tr/a-z/A-Z/; # change string in $_ print; # print $_ } |
| Code: |
|
($a = 4) = 5; print $a; # prints 5; |
| Code: |
|
print 1 if (defined($a)); $b = $a->{x}; print 2 if (defined($a)); |
This leads to confusion by people who are new to a language.
For example: "use strict" is a compiler pragma, "use CGI" imports the CGI module. <FILE> is a filehandle, <*.txt> returns a list of txt files in the current directory.
Flexibility is nice. And Perl takes this to the extreme. But nice as it may be, flexibility does not necessarally makes a language more transparant and/or easier to master (that's an understatement).
| Code: |
|
sub f() { return \$x; } ${ f() } = 3; # using a block statement instead of variable name print $x; # prints '3' |
0 blog comments below