Think Python, But Perl 6 - Chapter 3: Functions
On to chapter 3 of Allen B. Downey's Think Python book, as interpreted through Perl 6.
Function Calls
Looks pretty much the same. Perl 6 function calls don't require parentheses. Use them or don't. If Perl 5 is anything to go by, not using parens will occasionally have surprising results.
We've already used a lot of method calls. Objects are more fundamental in Perl 6 than in Python, and definitely more than Perl 5. A bit more like Ruby.
Type Conversion
Python has int(var)
, Perl 6 has $var.Int()
.
> '32'.Int
32
> 'Hello'.Int
Cannot convert string to number: base-10 number must begin with valid digits or '.' in '⏏Hello' (indicated by ⏏)
Int
truncates, not round. We're used to that by now.
> 3.99999.Int
3
> -2.3.Int
-2
Rat
converts integers and strings to Rational numbers, but Perl 6 will generally try to do the right thing if you start doing numbery things to a string.
> '32.1' * 2
64.2
So conversion between numeric types is not important as often in Perl 6.
Math Functions ... Methods ... Callable things
At least some of Python's math
functionality is core functionality:
> sin(0.7)
0.644217687237691
... or is is built into the object?
> 0.7.sin
0.644217687237691
I'm not really sure. Maybe both. Maybe neither. Further study on this is a good idea. And as long as I'm thinking about "further study" I may as well find out whether there's a hardcoded constant for π. Not today though.
> my $pi = 3.1415926;
3.1415926
> my $degrees = 45;
45
> my $radians = $degrees / 360 * 2 * $pi;
0.78539815
> sin $radians;
0.707106771713121
Checking the result.
> sqrt(2) / 2;
0.707106781186548
Hm. Really really close, but hm.
Composition
They're just talking about composing expressions from others by using functions.
> my $x = sin( $degrees / 360 * 2 * $pi );
0.707106781186584
> $x = exp(log($x + 1));
1.70710678118658
What does it look like when you try to assign to something that's not okay as an lvalue?
> my $minutes = $hours * 60;
60
> $hours * 60 = $minutes;
Cannot assign to a non-container
Adding new functions
Oh hey. Things started to get interesting.
> sub print-lyrics() {
Unable to parse expression in block; couldn't find final '}'
Hm. Well that's something to look at for the REPL. Anyways. How about creating the function in a file?
use v6;
sub print-lyrics() {
say "I'm a lumberjack and I'm okay.";
say "I sleep all night and I work all day."
}
print-lyrics();
Okay maybe not that interesting quite yet.
$ perl6 lyrics.p6
I'm a lumberjack and I'm okay.
I sleep all night and I work all day.
Let's make a simple sub for examining functions.
> sub yo() { say "Yo!" }
sub yo() { ... }
> yo.WHAT
Yo!
Well that doesn't work. It just calls &yo
. Oh right. What happens if I use the sigil?
> &yo.WHAT
Sub()
Okay. That works.
Using one function inside another is easy.
> sub yo-yo { yo; yo; }
sub yo-yo() { ... }
> yo-yo
Yo!
Yo!
Definitions and uses
Here's the whole program, Perl 6 style.
use v6;
sub print-lyrics() {
say "I'm a lumberjack and I'm okay.";
say "I sleep all night and I work all day."
}
sub repeat-lyrics() {
print-lyrics();
print-lyrics();
}
repeat-lyrics();
Exercise 1
Move the last line of this program to the top, so the function call appears before the definitions. Run the program and see what error message you get.
No errors. I removed the parens to see if that caused any change.
use v6;
repeat-lyrics;
sub print-lyrics() {
say "I'm a lumberjack and I'm okay.";
say "I sleep all night and I work all day."
}
sub repeat-lyrics() {
print-lyrics;
print-lyrics;
}
Nope. It worked smoothly.
Exercise 2
Move the function call back to the bottom and move the definition of
print-lyrics
after the definition ofrepeat-lyrics
. What happens when you run this program?
What happens is that perl6
runs it as expected without issue. Nothing to see here.
Flow of execution
Bit of common sense advice about following program flow of execution when reading code.
Parameters and arguments
use v6;
sub say-twice($bruce) {
say $bruce;
say $bruce;
}
say-twice 'Spam';
say-twice 17;
my $pi = 3.1415926539;
say-twice $pi;
A moment of silence as we enjoy the fact that we can get to function arguments in Perl 6 without unrolling @_
or grabbing a module from CPAN. Okay, moment's over.
$ perl6 say-twice.p6
Spam
Spam
17
17
3.1415926539
3.1415926539
Show that argument is evaluated before being passed to the function.
# ...
say-twice 'Spam ' x 4;
Yep.
...
Spam Spam Spam Spam
Spam Spam Spam Spam
Variables and parameters are local
Variables are available only in their scope.
> sub say-twice($bruce) { say $bruce; say $bruce; }
sub say-twice($bruce) { ... }
> sub cat-twice($part1, $part2) { my $cat = $part1 ~ $part2; say-twice $cat; }
sub cat-twice($part1, $part2) { ... }
> my $line1 = 'Bing tiddle ';
Bing tiddle
> my $line2 = 'tiddle bang.';
tiddle bang.
> cat-twice($line1, $line2);
Bing tiddle tiddle bang.
Bing tiddle tiddle bang.
> say $cat;
Variable $cat is not declared
Not too surprising there.
Stack diagrams
Kind of useful, but the specific example would need to be reworked. Perl 6 catches simple name errors during compilation rather than run time.
> sub say-thrice($bruce) { say $cat }
Variable $cat is not declared
Fruitful functions and void functions
"Fruitful functions" is what the book calls functions with a return value. Again, the specific example works a bit different than in Python.
> my $result = say-twice('Spam')
Spam
Spam
> $result
True
> $result.WHAT
Bool()
say
returns a value, say-twice
returns the last value generated. In this case, there was no issue. say
returned True
and so did say-twice
.
Why functions?
Explaining the utility of functions from a program architecture perspective.
Importing with from
Hm. It'd be nice to take a moment to think about use
, but I'm not sure which modules are okay to import. I'll just leave this one alone for now.
Exercises
Exercise 3
Strings have a method called
chars
that returns the number of characters in the string, so the value of'Brian'.chars
is5
.Write a function named
right-justify
that takes a string named$s
as a parameter and prints the string with enough leading spaces so that the last letter of the string is in column70
of the display.
use v6;
my $name = 'Brian';
say right-justify( $name );
sub right-justify($s) {
' ' x (70 - $s.chars) ~ $s;
}
Exercise 4
A function object is a value you can assign to a variable or pass as an argument. For example,
do-twice
is a function that takes a function object as an argument and calls it twice:
use v6;
sub do-twice(&f) {
f();
f();
}
Here's an example that uses do-twice
to call a function named say-spam
twice:
sub say-spam() {
say 'spam';
}
do-twice(&say-spam);
- Type this example into a script and test it.
- Modify
do-twice
so that it takes two arguments, a function and a value, and calls the function twice, passing the value as an argument. - Write a more general version of
say-spam
, calledsay-twice
, that takes a string as a parameter and prints it twice. - Use the modified version of
do-twice
to callsay-twice
twice, passing'spam'
as an argument. - Define a new function called
do-four
that takes a function and a value and calls the function four times, passing the value as a parameter. There should be only two statements in the body of this function, not four.
Exercise 4-2
use v6;
sub do-twice(&f, $arg) {
f($arg);
f($arg);
}
sub uc-it($s) {
say $s.uc;
}
do-twice(&uc-it, 'spam');
Exercise 4-3 and 4-4
use v6;
sub do-twice(&f, $arg) {
f($arg);
f($arg);
}
sub say-twice($s) {
say $s;
say $s;
}
do-twice(&say-twice, 'spam');
Exercise 4-5
use v6;
sub do-twice(&f, $arg) {
f($arg);
f($arg);
}
sub do-four(&f, $arg) {
do-twice(&f, $arg);
do-twice(&f, $arg);
}
sub say-twice($s) {
say $s;
say $s;
}
do-four(&say-twice, 'spam');
Exercise 5
This exercise can be done using only the statements and other features we have learned so far.
Write a function that draws a grid like the following:
+ - - - - + - - - - +
| | |
| | |
| | |
| | |
+ - - - - + - - - - +
| | |
| | |
| | |
| | |
+ - - - - + - - - - +
Write a function that draws a similar grid with four rows and four columns.
Exercise 5-1
The challenge in these exercises is not using the basic language structures I'm already familiar with.
use v6;
sub do-twice(&f) {
f();
f();
}
sub do-four(&f) {
do-twice(&f);
do-twice(&f);
}
sub draw-intersection() {
print "+ ";
}
sub draw-row-edge() {
print "- ";
}
sub draw-cell-row-edge() {
draw-intersection();
do-four(&draw-row-edge);
}
sub end-line() {
say;
}
sub draw-grid-row-edge() {
do-twice(&draw-cell-row-edge);
draw-intersection();
end-line();
}
sub draw-column-edge() {
print "| ";
}
sub draw-cell-space() {
print " ";
}
sub draw-cell-spaces() {
draw-column-edge();
do-four(&draw-cell-space);
}
sub draw-grid-spaces() {
do-twice(&draw-cell-spaces);
draw-column-edge();
end-line();
}
sub draw-cell-row() {
draw-grid-row-edge();
do-four(&draw-grid-spaces);
}
sub draw-grid() {
do-twice(&draw-cell-row);
draw-grid-row-edge();
}
draw-grid();
Exercise 5-2
Write a function that draws a similar grid with four rows and four columsn.
I refactored a little bit, but I'm not sure it was actually helpful with the limited toolkit (general programming, not Perl 6) allowed for the reader at this point.
use v6;
sub do-twice(&f) {
f();
f();
}
sub do-four(&f) {
do-twice(&f);
do-twice(&f);
}
sub do-one-four(&first, &second) {
first();
do-four(&second);
}
sub draw-intersection() {
print "+ ";
}
sub draw-row-edge() {
print "- ";
}
sub draw-cell-row-edge() {
do-one-four(&draw-intersection, &draw-row-edge);
}
sub end-line() {
say;
}
sub draw-grid-row-edge() {
do-twice(&draw-cell-row-edge);
draw-intersection();
end-line();
}
sub draw-column-edge() {
print "| ";
}
sub draw-cell-space() {
print " ";
}
sub draw-cell-spaces() {
do-one-four(&draw-column-edge, &draw-cell-space);
}
sub draw-grid-spaces() {
do-twice(&draw-cell-spaces);
draw-column-edge();
end-line();
}
sub draw-cell-row() {
do-one-four(&draw-grid-row-edge, &draw-grid-spaces);
}
sub draw-two-grid() {
do-twice(&draw-cell-row);
draw-grid-row-edge();
}
sub draw-four-edge() {
do-four(&draw-cell-row-edge);
draw-intersection();
end-line();
}
sub draw-four-spaces() {
do-four(&draw-cell-spaces);
draw-column-edge();
end-line();
}
sub draw-four-row() {
do-one-four(&draw-row-edge, &draw-four-spaces);
}
sub draw-four-grid() {
do-four(&draw-four-row);
draw-four-edge();
}
draw-four-grid();
I'm glad you guys are enjoying yourself. Having fun is the best parts of coding.
May I make a request though? These posts are quite long, could you put much of them in the "extended" section to keep the front page more viewable? Thanks!
Thank you! Yes, I can do that.
Exercise 3 is slightly wrong. It doesn't take into account that Unicode characters have different widths. This is of-course a problem with the question, not with your answer.
It's a little bit of both, actually. `elems` is a typo on my part. The actual code I wrote used `chars`. Before I go editing that post: would that make it more Unicode-friendly, or would I only muck it up more by mixing the humble ASCII space with a Unicode string?
Thanks again. Actually the more I think of it, the more I think it might actually make a nice tiny website. Hosted on github pages for example?
Good luck!
When I get far enough along to turn my notes into thoughts, I plan to put those thoughts on my own site. My Perl 6 efforts at this point are too jumbled even for that.
The REPL has many weaknesses, but there are improvements most months. For example, on Jan 30, timotimo, who did the levenshtein code ("did you mean 'principal'?", which incidentally doesn't yet work in the REPL) said "not [too] terribly long until i can run the modified perl6 repl with ipythoni think" and part of doing that would be to resolve the issue of prompting for further lines of input when appropriate.
You didn't write anything about flow of execution. The Perl 6 concurrency features designed to date are entirely implicit (transparent, ie happen automatically) so can safely be ignored. But I really like Phasers for things like now - INIT now.
"Show that argument is evaluated before being passed to the function." This isn't always going to be the case. Perl 6 supports both eager and lazy evaluation (and some in between levels). Otoh it DWIMs so you can ignore this for now if you prefer.
If you're using Rakudo Star, you should in theory be able to use any of the included modules because the Star distro only includes modules that are passing their test suite with the Rakudo compiler with which they are bundled. Otoh these modules and their test suites are often pretty basic so ymmv.
Thank you for documenting your look at Perl 6 this way. I'm gonna go make sure #perl6 pay attention if they haven't already seen your posts.
@Brian: When using fixed-width fonts, some characters may take more or less than one column to display, such as some control characters and East Asian characters. This is described in UAX #11: East Asian Width. Perl 6 doesn't have a solution to this problem in the spec, nor does any other language with which I'm familiar. It also doesn't yet have a solution in a Perl 6 module, although I'm thinking of writing one. Other languages generally have a solution in an external library or module. For example, Perl 5 has the Unicode::GCString module that provides the
columns
method. Regardless, I agree with @Brad that this is a problem with the question, which I'm sure wasn't taking variable-width characters into account.If I was writing "Think Perl 6" I would probably change some of the approach. For starters, I would need to talk about objects a lot earlier than in the Python version. I would also probably fiddle with the wording of things like "show that X happens before Y", but gently. The main utility of the "Think" books - beyond the enormous convenience of an open license - is that they introduce topics in bite-sized pieces, building up impressively without making the learner's brain explode at any point. I would try to emulate that.