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 of repeat-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 is 5.

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 column 70 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);
  1. Type this example into a script and test it.
  2. 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.
  3. Write a more general version of say-spam, called say-twice, that takes a string as a parameter and prints it twice.
  4. Use the modified version of do-twice to call say-twice twice, passing 'spam' as an argument.
  5. 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();

9 Comments

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!

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.

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!

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.

Leave a comment

About Brian Wisti

user-pic I’m a geek in Seattle. I write code. I enjoy writing about code, regardless of whether anyone else reads it. I also knit.