Anyway yesterday I saw this magazine in a supermarket:
It has three good articles by Dave in it, and, as Dave pointed out on Facebook, the article he wrote about in Learning from Bad Code (if you haven't already read that you may want to do it now as it'll save me copying the code and re-iterating what Dave said about it.)
So it occurred to me that this bad code would provide a good didactic vehicle for introducing Perl 6, especially as Dave had already provided a good quality Perl 5 example with which it can be compared.
So I wrote a Perl 6 version of the Starfield example:
This is approximately the same length as Dave's (I've been concise in some places and verbose in others, it could probably be done in fewer lines if one was so inclined.) I've used the Perl 6 module NCurses which is basically the same as the Perl 5 module in the example except it doesn't provide an object oriented interface.
If you're already familiar with Perl 6 you'll probably already understand the code.
The first thing you may notice is that there is no "use strict" or "use warnings" in the code, this is because that these things are effectively on by default, which means (among other things,) that variables must be declared and types defined before they are used. Perl 6 in general will catch a lot more problems with code at compile time, (and typically provide more helpful error messages,) which can significantly enhance the programmer's experience.
The "use v6" is not strictly necessary here, (infact it is basically ignored by the Perl 6 compiler,) however it is of benefit when you are working in both Perl 6 and Perl 5 as it will give rise to an error such as
Perl v6.0.0 required--this is only v5.20.3When run as Perl 5 code, rather than something more confusing when it fails to parse the Perl 6 code later on.
Rather than use variables for the basic parameters of the program in my example I have used constants defined with the "constant" keyword, this is conceptually similar to "use constant" in Perl 5 in that it introduces a name that can be used in place of some value later in the code, however, unlike "use constant" which creates a constant subroutine which limits it's use in certain places in your code, it introduces a "term" which can be used anywhere that, for example, a literal value can be used.
The names of the constants warrant mention too: Perl 6 allows the use of a hyphen in identifiers (such as variable or subroutine name, or constants as here,) there is a definite preference toward "Kebab Case" as opposed to "Snake Case", as typically found in Perl 5. In some places this may lead to a reduction in brevity (because more white space becomes significant,) but this reflects a general inclination in favour of clarity and consistency over brevity in the design.
I've used objects of the class "Star" to hold the star data and provide the movement behaviour. Dave's choice to use a hash to store the star data is sensible in a short program like this: in Perl 5 you would either have to provide an amount of boilerplate code to define the constructor and the accessors or use a fairly heavyweight external module such as Moose; with Perl 6 you get a powerful and clean object system out of the box: this is all documented in the Object Orientation document, so I'll just describe the things that I have implemented.
The BUILD submethod here isn't strictly necessary as the values could have been supplied as default initializers to the attributes directly, but it makes things clearer in this example. The initialising expressions are quite a common idiom: using ".pick" on a list defined by an appropriate Range; Range is similar to the '...' range operator in Perl 5 but somewhat more generalised and more powerful, they crop up regularly in loop constructs, array slices and so forth.
The observant may have noticed that the attributes of the class are declared as $.x, $.y and $.s but are used as $!x, $!y and $!s in the BUILD. This is quite common within classes: the '.' "twigil" declares the attribute as being public and a public accessor will be created for it, within the methods of the class itself the attribute can be referred to directly with the '!' twigil without going through the public accessor, by default the public accessor is read-only so within the class it can only be assigned to this way. The accessor can be made writeable by applying the "is rw" trait to the definition but this isn't necessary here.
The "move" method uses the "ternary" or "conditional" statement for brevity as opposed to the two separate statements in the original example, this works exactly the same as it does in Perl 5, just the '?' and ':' of Perl 5 have been renamed to '??' and '!!' for reasons of grammatical simplicity.
The array of star objects is declared and initialised in a single statement for brevity: the array is declared as being comprised of Star objects, any attempt to add anything else to it will result in a fatal error, this can reduce complexity and aid debugging in larger programs but isn't really necessary here and could be omitted. Of course arrays without a declared type can hold any types of objects just as in Perl 5.
In order to keep the initialiser to one line and still be readable I have used "gather"/"take": the gather basically returns an iterator with one item for each time a "take" is encountered; the "for" statement modifier here works pretty much the same way as in Perl 5: the "take Star.new" is executed 'numstars' times (i.e 100 times.) The gather's iterator is automatically expanded on assignment to the array.
While Perl 6 does still support the "while(1)" in the Perl 5 (though it would probably be better written "while(True)" as Perl 6 has true booleans,) it is more idiomatic to use "loop" for an infinite loop of this sort. The "loop" statement replaces the three-part or "C-Style" "for" in Perl 5 and its bare usage here is equivalent to "loop (;;)"; the "for" in Perl 6 no longer supports this iterator style.
The inner loop iterating over the array of Star objects illustrates the way the "for" loop statement differs from that in Perl 5 (incidentally the "foreach" synonym no longer exists, Perl 6 code only has "for".) At its simplest the Perl 6 "for" can be thought of as iterating over the supplied list and for each item executing the associated block with the item as an argument; by default the item is passed in the 'topic' variable "$_" much as in Perl 5, however, as in this example, the argument can be named with the argument variable ( or variables separated by a comma,) following the '->'; in the Perl 6 jargon this is known as a "pointy block" and can be thought of as an anonymous subroutine with the specified parameters. the parameter variable doesn't need to be declared with 'my' as you might the target variable in Perl 5 as the declaration is implicit in it being a parameter.
So there you have it, the Perl 6 version is not much longer than the good Perl 5 one and is at least as readable and maintainable It uses the Perl 6 Object Orientation features without adding unwanted cruft to the code so you could extend the behaviour of the stars without changing the body of the code.
Perl 6 not as frightening as you thought it was.
Instead of 「gather / take」 you could just have used
ReplyDelete「do Star.new for ^numstars」
Absolutely, and it's great that there are several ways of doing it, but I wanted to introduce something that was entirely new to Perl 6 (being that the audience I had in my mind was people who are familiar with Perl 5 and not Perl 6.)
DeleteI'll probably rue the choice a few years down the road if people start over-using the "gather/take" when other better options are available, but hey :)