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 tosquare
, 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
, tosquare
. 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
andrt
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 topolygon
. 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 invokingpolygon
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
calledarc
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.
I think a half dozen or more parts of the canonical and complete spec doc for subs are relevant. What about this?
This is equivalent to one of:
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).
Right you are! I'm not sure where I got confused on the syntax.
It may be a while before I pick this specific topic back up. Lots of digging into both Mojolicious and Perl 6 as attention span demands.
There's the issue that I'm capable of much more polished and well-researched writing than this. Any further posts in this style will just be an embarrassment.
Future content might go elsewhere, though. This spam trap is starting to annoy me. If so, I'll at least post about where the other content is to be found.
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
What the what?
I have *got* to start spending time on IRC. Thanks for sharing.
#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/