Think Python in Perl 6 Chapter 4: Interface Design

It is time for Chapter 4 of Allen Downey’s Think Python refashioned in Perl 6. I just needed some time to make a simple wrapper for his Swampy framework. Feel free to install and play along, but don’t rattle Swampy.pm6 too much. I have only implemented enough to finish this chapter.

Then again, go ahead.

TurtleWorld

First is mypolygon.pl6, getting the hang of the interface.

use v6;
use Swampy;

my $turtle = make-turtle();
say $turtle;
draw-it();

Didn’t actually draw anything, but it did fire up the TurtleWorld display. It even printed out a little bit.

$ perl6 mypolygon.pl6
t1
Python code is in mypolygon.pl6.py

Here are a couple implementation details for you. The turtle has a simple name: t1. And the Python code associated with mypolygon.pl6 will go in mypolygon.pl6.py.

from swampy.TurtleWorld import *

world = TurtleWorld()
t1 = Turtle()

wait_for_user()

Only the Python code relevant to interacting with the swampy library will be written to disk. I expect this means some of the Python code will be tediously long when we get into looping and such. Not concerned about it today. I am amazed it works at all.

Now add the initial sample drawing instructions.

use v6;
use Swampy;

my $bob = make-turtle();
say $bob;

fd($bob, 100);
lt($bob);
fd($bob, 100);

draw-it();

Run it. Woo! A right angle!

Now modify the program to draw a square.

Okay okay. I already know one way to do this because of my Swampy.pm6 adventure.

use v6;
use Swampy;

my $bob = make-turtle();

for 1..4 {
    fd($bob, 100);
    lt($bob);
}

draw-it();

The book suggestion is pretty much the same. Simple loops over ranges where you ignore the counter.

use v6;

for 1..4 {
    say "Hello!";
}

Welcome to looping, kids. It doesn’t look too painful in Perl 6, does it?

Exercises

Exercise 1

Write a function called square that takes a parameter named $t, which is a turtle. It should use the turtle to draw a square.

Write a function call that passes $bob as an argument to square, and then run the program again.

Stand back. I’ve got this.

use v6;
use Swampy;

sub square($t) {
    for 1..4 {
        fd($t, 100);
        lt($t);
    }
}

my $bob = make-turtle();
square($bob);
draw-it();

Exercise 2

Add another parameter, named $length, to square. Modify the body so length of the sides is $length, and then modify the function call to provide a second argument. Run the program again. Test your program with a range of values for $length.

Still not too exciting.

use v6;
use Swampy;

sub square($t, $length) {
    for 1..4 {
        fd($t, $length);
        lt($t);
    }
}

my $bob = make-turtle();

square($bob, 50);
square($bob, 75);
square($bob, 100);

draw-it();

The functions lt and rt make 90 degree turns by default, but you can provide a second argument that specifies the number of degrees. For example, lt($bob, 45) turns $bob 45 degrees to the left.

Make a copy of square and change the name to polygon. Add another parameter named $n and modify the body so it draws an n-sided regular polygon. Hint: the exterior angles of an n-sided regular polygon are 360/n degrees.

BRB, updating Swampy.pm6.

Okay, done.

# ... 

sub polygon($t, $length, $n) {
    my $angle = 360 / $n;
    for 1..$n {
        fd($t, $length);
        lt($t, $angle);
    }
}

my $bob = make-turtle();

polygon($bob, 20, 8);

draw-it();

Exercise 4

Write a function called circle that takes a turtle, $t, and radius, $r, as parameters and that draws an approximate circle by invoking polygon with an appropriate length and number of sides. Test your function with a range of values of $r.

Hint: figure out the circumference of the circle and make sure that $length * $n = $circumference.

Another hint: if $bob is too slow for you, you can speed him up by changing $bob.delay, which is the time between moves, in seconds. $bob.delay = 0.01 ought to get him moving.

Um. That’s right. $bob is actually an object over in Python land. Well, I’m not that far in Perl 6 yet. Let me just add a set-delay($t, $delay) sub.

# ...
my $pi = 3.1415926;

sub circle($t, $r) {
    my $circumference = 2 * $pi * $r;
    my $n = 30;
    my $length = $circumference / $n;
    polygon($t, $length, $n);
}

my $bob = make-turtle();

set-delay($bob, 0.01);
circle($bob, 100);
circle($bob, 10);
circle($bob, 20);
circle($bob, 80);
circle($bob, 60);

draw-it();

Well that was fun. And circle.p6.py has 309 lines.

Exercise 5

Make a more general version of circle called arc that takes an additonal parameter $angle, which determines what fraction of a circle to draw. $angle is in units of degrees, so when $angle=360, arc should draw a complete circle.

Can’t just draw a polygon for this one.

# ...
sub arc($t, $r, $angle) {
    my $arc-length = 2 * $pi * $r * $angle / 360;
    my $step-count = 30;
    my $step-length = $arc-length / $step-count;
    my $step-angle = $angle / $step-count;

    for 1..$step-count {
        fd($t, $step-length);
        lt($t, $step-angle);
    }
}

my $bob = make-turtle();

set-delay($bob, 0.01);

arc($bob, 10, 90);
arc($bob, 20, 90);
arc($bob, 30, 90);
arc($bob, 40, 90);
arc($bob, 50, 90);
arc($bob, 60, 90);
arc($bob, 70, 90);
arc($bob, 80, 90);
arc($bob, 90, 360);

draw-it();

Don’t use a loop. Don’t use a loop. Don’t use a loop. When’s the book going to get to loops?

Encapsulation

Nothing fancy. Just talking about how functions can keep your app tidy.

Generalization

There’s a bit in here about keyword arguments. In Perl 6, it looks like you must specify the arguments that accept keywords:

sub polygon($t, :$length, :$n) {
}

Calling this keyword-enabled polygon looks quite a bit different.

polygon($bob, :length(50), :n(6));

That will take some getting used to. I am so accustomed to hash-style passing in Perl 5 and Ruby. Still, keyword arguments are quite handy for functions that accept a lot of options. I’ll want to revisit them later.

Interface Design

Fine-tuning the logic of circle.

sub circle($t, $r) {
    my $circumference = 2 * $pi * $r;
    my $n = ($circumference / 3).Int + 1;
    my $length = $circumference / $n;
    polygon($t, $length, $n);
}

Refactoring polygon into the more general polyline.

sub polyline($t, $n, $length, $angle) {
    for 1..$n {
        fd($t, $length);
        lt($t, $angle);
    }
}

Changing our shape functions to take advantage of that refactoring.

use v6;
use Swampy;

my $pi = 3.1415926;

sub polyline($t, $n, $length, $angle) {
    for 1..$n {
        fd($t, $length);
        lt($t, $angle);
    }
}

sub polygon($t, $length, $n) {
    my $angle = 360 / $n;
    polyline($t, $n, $length, $angle);
}

sub square($t, $length) {
    polygon($t, $length, 4);
}

sub arc($t, $r, $angle) {
    my $arc-length = 2 * $pi * $r * $angle / 360;
    my $step-count = 30;
    my $step-length = $arc-length / $step-count;
    my $step-angle = $angle / $step-count;
    polyline($t, $step-count, $step-length, $step-angle);
}

sub circle($t, $r) {
    arc($t, $r, 360);
}

my $bob = make-turtle();

set-delay($bob, 0.01);

polyline($bob, 5, 50, 45);
lt($bob, 45);
fd($bob, 100);
polygon($bob, 75, 6);
lt($bob, 45);
square($bob, 100);
lt($bob, 45);
arc($bob, 50, 180);
lt($bob, 45);
circle($bob, 75);

A Development Plan

I’ve heard of those.

Docstring

How about POD?

I’ll keep it simple, since p6doc <filename> doesn’t seem to work as well as perldoc <filename>.

=begin pod

=NAME polygon.p6

=begin DESCRIPTION 

Shape-drawing functions used with Swampy.pm6, written while I read "Think Python"
but write Perl 6.

=end DESCRIPTION

=begin SUBROUTINE C<polyline($t, $n, $length, $angle)>

Draws C<$n> line segements with the given C<$length> and C<$angle>. C<$t> is
a turtle.

=end SUBROUTINE

=end pod

I don’t even know. It would probably be better to stick with the POD notation that is more familiar to Perl 5 folks. Then again, this is mostly for my own entertainment.

Exercises

For these exercises I pretty much just translated the Python code I already had sitting around into Perl 6.

Exercise 4-1

Not much Python-specific here.

Exercise 4-2

Write an appropriately general set of functions that can draw flowers as in Figure 4.1

Just dumping everything into polygon.p6 because I’m in a hurry.

# ...
sub petal($t, $r, $angle) {
    for 1..2 {
        arc($t, $r, $angle);
        lt($t, 180-$angle);
    }
}

sub flower($t, $n, $r, $angle) {
    for 1..$n {
        petal($t, $r, $angle);
        lt($t, 360/$n);
    }
}

sub move($t, $length) {
    pu($t);
    fd($t, $length);
    pd($t);
}

my $bob = make-turtle();
set-delay($bob, 0.01);

move($bob, -100);
flower($bob, 7, 60, 60);
move($bob, 100);
flower($bob, 10, 40, 80);
move($bob, 100);
flower($bob, 10, 140, 20);
draw-it();

Exercise 4-3

Write an appropriately general set of functions that can draw shapes as in Figure 4.2

This one took me a couple of tries, because I forgot a semi-colon. Somewhere a Pythonista is feeling smug.

# ...
sub isosceles($t, $r, $angle) {
    my $y = $r * sin($angle * $pi / 180);

    rt($t, $angle);
    fd($t, $r);

    lt($t, 90+$angle);
    fd($t, 2*$y);

    lt($t, 90+$angle);
    fd($t, $r);

    lt($t, 180-$angle);
}

sub segmented-pie($t, $n, $r) {
    my $angle = 360 / $n;

    for 1..$n {
        isosceles($t, $r, $angle / 2);
        lt($t, $angle);
    }
}

sub draw-pie($t, $n, $r) {
    segmented-pie($t, $n, $r);
    move($t, $r*2 + 10);
}

my $bob = make-turtle();
set-delay($bob, 0.01);

pu($bob);
bk($bob, 130);
pd($bob);

draw-pie($bob, 5, 40);
draw-pie($bob, 6, 40);
draw-pie($bob, 7, 40);
draw-pie($bob, 8, 40);
kill-turtle($bob);
dump-canvas();

draw-it();

Next is an alphabet exercise. I know it will be fairly time consuming, so I will post what I have so far and come back to finish the chapter when I have the time. Right now there is a Mojolicious project I’m supposed to be working on for someone.

8 Comments

In Perl 6, it looks like you must specify the arguments that accept keywords ... I am so accustomed to hash-style passing

I think a half dozen or more parts of the canonical and complete spec doc for subs are relevant. What about this?


sub foo {...}

This is equivalent to one of:


sub foo () {...}
sub foo (*@_) {...}
sub foo (*%_) {...}
sub foo (*@_, *%_) {...}

depending on whether either or both of those variables are used in the body of the routine.

Hi Brian,

Methinks another comment of mine (about passing arbitrary keyword args not already individually specified in a routine's formal parameter list) got sent to spam.

I've been enjoying your posts and look forward to them restarting after you've had the appropriate amount of fun with Mojolicious. :)

Hi Brian,

If by hash-style passing you mean this:


rn: sub foo(:$bar) { say $bar; }; foo(bar => "Hello!")
[08:02am] p6eval: rakudo 0f9703, niecza v24-24-gbdc3343: OUTPUT«Hello!␤»

That works fine in p6 (as this example shows).

Fwiw, on Mojolicious and Perl 6...

From #perl6 a few weeks ago ( http://irclog.perlgeek.de/perl6/2013-02-07#i_6422674 ):

japhb: "if/when Rakudo is running on JVM, would you be willing at that point to do the Mojo porting in earnest? If not, what would you still be waiting on?"

A constructive discussion ensued that enumerated the pieces sri could think of necessary to do a "real deal" Mojolicious port to Perl 6.

The next day japhb said "I'm looking at building a list of modules and functionality that should be higher-priority for us to port from Perl 5".

And thus was born: https://github.com/perl6/perl6-most-wanted

#perl6 especially. I think the current vibe is due to Audrey's influence even though it's 5 years since she dropped back to lurk/msg/Ofun mode, amplified by the sense of excitement building as 6.0.0 approaches.

(For thos reading along, you don't have to get on channel -- there's the web log per the link in my last comment. Additionally, I continually add selected lines to produce a "summary" that is about 10%-30% as long as the full log. For example, here is yesterday's "summary": http://irclog.perlgeek.de/out.pl?channel=perl6;date=yesterday;summary=1 )

And, in general IRC news, Freenode has become the world's #1 IRC network -- http://royal.pingdom.com/2012/04/24/irc-is-dead-long-live-irc/

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.