TODO: start with a much simpler bare-bones example!
The following program shows how a dependency handler might look in Perl 6. It showcases custom constructors, private and public attributes, methods and various aspects of signatures. It's not very much code, and yet the result is interesting and, at times, useful.
like many other languages,
class keyword to introduce a new class.
The block that follows may contain arbitrary code,
just as with any other block,
but classes commonly contain state and behavior declarations.
The example code includes attributes (state),
introduced through the
and behaviors introduced through the
Declaring a class creates a type object which,
is installed into the current package (just like a variable declared with
This type object is an "empty instance" of the class.
You've already seen these in previous chapters.
types such as
Str refer to the type object of one of the Perl 6 built- in classes.
The example above uses the class name
Task so that other code can refer to it later,
such as to create class instances by calling the
Type objects are undefined,
in the sense that they return
False if you call the
.defined method on them.
You can use this method to find out if a given object is a type object or not:
The first three lines inside the class block all declare attributes (called fields or instance storage in other languages).
These are storage locations that every instance of a class will obtain.
Just as a
my variable can not be accessed from the outside of its declared scope,
attributes are not accessible outside of the class.
This encapsulation is one of the key principles of object oriented design.
The first declaration specifies instance storage for a callback -- a bit of code to invoke in order to perform the task that an object represents:
& sigil indicates that this attribute represents something invocable.
! character is a twigil,
or secondary sigil.
A twigil forms part of the name of the variable.
In this case,
! twigil emphasizes that this attribute is private to the class.
The second declaration also uses the private twigil:
The "subclass" language is slightly wrong, but I don't know if we want to get into allomorphism here. Is there a rephrasing that's more correct?
this attribute represents an array of items,
so it requires the
These items each specify a task that must be completed before the present one can complete.
the type declaration on this attribute indicates that the array may only hold instances of the
Task class (or some subclass of it).
The third attribute represents the state of completion of a task:
This scalar attribute (with the
$ sigil) has a type of
Instead of the
this twigil is
While Perl 6 does enforce encapsulation on attributes,
it also saves you from writing accessor methods.
! with a
. both declares the attribute
$!done and an accessor method named
It's as if you had written:
Note that this is not like declaring a public attribute, as some languages allow; you really get both a private storage location and a method, without having to write the method by hand. You are free instead to write your own accessor method, if at some future point you need to do something more complex than return the value.
Note that using the
. twigil has created a method that will provide with readonly access to the attribute.
If instead the users of this object should be able to reset a task's completion state (perhaps to perform it again),
you can change the attribute declaration:
is rw trait causes the generated accessor method to return something external code can modify to change the value of the attribute.
While attributes give objects state,
methods give objects behaviors.
new method temporarily; it's a special type of method.
Consider the second method,
which adds a new task to this task's dependency list.
In many ways,
this looks a lot like a
there are two important differences.
declaring this routine as a method adds it to the list of methods for the current class.
Thus any instance of the
Task class can call this method with the
. method call operator.
a method places its invocant into the special variable
The method itself takes the passed parameter--which must be an instance of the
pushes it onto the invocant's
The second method contains the main logic of the dependency handler:
It takes no parameters,
working instead with the object's attributes.
it checks if the task has already completed by checking the
there's nothing to do.
the method performs all of the task's dependencies,
for construct to iterate over all of the items in the
This iteration places each item--each a
Task object--into the topic variable,
. method call operator without specifying an explicit invocant uses the current topic as the invocant.
Thus the iteration construct calls the
.perform() method on every
Task object in the
@!dependencies attribute of the current invocant.
After all of the dependencies have completed,
it's time to perform the current
Task's task by invoking the
&!callback attribute directly; this is the purpose of the parentheses.
the method sets the
$!done attribute to
so that subsequent invocations of
perform on this object (if this
Task is a dependency of another
for example) will not repeat the task.
Perl 6 is rather more liberal than many languages in the area of constructors.
A constructor is anything that returns an instance of the class.
constructors are ordinary methods.
You inherit a default constructor named
new from the base class
but you are free to override
as this example does:
* in the
The biggest difference between constructors in Perl 6 and constructors in languages such as C# and Java is that rather than setting up state on a somehow already magically created object,
Perl 6 constructors actually create the object themselves.
This easiest way to do this is by calling the
also inherited from
bless method expects a positional parameter--the so-called "candidate"--and a set of named parameters providing the initial values for each attribute.
The example's constructor turns positional arguments into named arguments,
so that the class can provide a nice constructor for its users.
The first parameter is the callback (the thing to do to execute the task).
The rest of the parameters are dependent
The constructor captures these into the
@dependencies slurpy array and passes them as named parameters to
bless (note that
:&callback uses the name of the variable--minus the sigil--as the name of the parameter).
After creating a class, you can create instances of the class. Declaring a custom constructor provides a simple way of declaring tasks along with their dependencies. To create a single task with no dependencies, write:
An earlier section explained that declaring the class
Task installed a type object in the namespace.
This type object is a kind of "empty instance" of the class,
specifically an instance without any state.
You can call methods on that instance,
as long as they do not try to access any state;
new is an example,
as it creates a new object rather than modifying or accessing an existing object.
Unfortunately, dinner never magically happens. It has dependent tasks:
Notice how the custom constructor and sensible use of whitespace allows a layout which makes task dependencies clear.
perform method call recursively calls the
perform method on the various other dependencies in order,
giving the output:
making some money going to the store buying food cleaning kitchen making dinner eating dinner. NOM!
Object Oriented Programming provides the concept of inheritance as one of the mechanisms to allow for code reuse. Perl 6 supports the ability for one class to inherit from one or more classes. When a class inherits from another class that informs the method dispatcher to follow the inheritance chain to look for a method to dispatch. This happens both for standard methods defined via the method keyword and for methods generated through other means such as attribute accessors.
Now any object of type Programmer can make use of the methods and accessors defined in the Employee class as though they were from the Programmer class.
Hey! The above document had some coding errors, which are explained below:
- Around line 1:
Unknown directive: =head0
- Around line 3:
A non-empty Z<>
- Around line 142:
=end for without matching =begin. (Stack: [empty])
- Around line 288:
=end for without matching =begin. (Stack: [empty])
- Around line 601:
=end for doesn't match =begin programlisting. (Stack: =begin programlisting; =begin programlisting)
- Around line 685:
'=end' without a target? (Should be "=end programlisting")
- Around line 691:
=end authors doesn't match =begin programlisting. (Stack: =begin programlisting; =begin programlisting)