Hi All,
I have been working on this keeper for several
months an though it was time to share and get feedback.
Once you understand OOP (Object Orientated Programming)
and Raku's elegant implementation, it becomes an
extremely powerful tool. And the nice part is that
Raku made it really easy! Like a hash on steroids.
-T
Raku: "class", "object", "method":
Target audience:
those new to Raku (Perl 6), or those familiar with Raku, but not
Raku's Object Orientated Programming. The NativeCall examples may
be of interest to those just starting out with system calls.
Object Oriented Programming (OOP) is an elegant system of declaring
data structures (classes), assigning values based on those data structures
(objects), and accessing data in those objects (methods).
"class" is a "container for methods (functions) and properties
(variables),
and you can create multiple instances [objects] from a single
template.
The rest (implementation) is language dependent.
Also, a class can have *internal* (private) variables that you
can only
access by way of a method - which means that those internal
variables
are not properties (that you can read from and/or write to)." [2]
The following is a discussion of Raku's implementation of OOP.
Discussed are:
class
object
addressing values inside an object
method
using class, object, and method to access system libraries with
NativeCall
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*** class ***
A class is a template of data structures used for creating objects:
For example:
# Create a "class" called "Fruit"
class Fruit {
has Str $.location is rw; # "rw" means Read-Write
has UInt $.apples is rw;
has UInt $.oranges; # <-- note missing "is rw"
has UInt $.bananas is rw;
}
Note that the default is "ro" (read only) and that "is ro" as in
has UInt $.oranges is ro;
is not an acceptable term. When structures are "ro", you can populate
them once with the ".new" command [3]. The examples for "methods"
show this. Also, this (ro) means that "you" can not write to the
variable, but Raku can and often does. NativeCall for example.
And the "." is an operator and is required.
A note on `has` versus `has`:
Functionally, `HAS` is basically the same thing as `has`. The
"offical"
word on the two is
CStructs and CUnions can be in turn referenced by—or embedded
into—a
surrounding CStruct and CUnion. To say the former we use has as
usual,
and to do the latter we use the HAS declaration instead [12]
Just use `has`.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*** object ***
"object" is when you declare a data structure using a "class".
For example (note the defaults):
class Fruit {
has Str $.location is rw;
has UInt $.apples is rw;
has UInt $.oranges is rw;
has UInt $.bananas is rw;
}
# Create an "object" called "$FruitStand"
my $FruitStand = Fruit.new( location => "Cucamonga",
apples => 400,
oranges => 200,
bananas => 50 );
Note that you have to ".new" to create these defaults and that ".new"
over rides the "ro" status.
Tip: you can "push" objects onto an array
my @Stores;
my $FruitStand = Fruit.new( location => "Cucamonga",
apples => 400,
oranges => 200,
bananas => 50 );
push @Stores, $FruitStand;
$FruitStand = Fruit.new( location => "Azusa",
apples => 20,
oranges => 2000,
bananas => 5 );
push @Stores, $FruitStand;
$FruitStand = Fruit.new( location => "Anaheim",
apples => 10,
oranges => 300,
bananas => 55 );
push @Stores, $FruitStand;
say @Stores;
[Fruit.new(location => "Cucamonga", apples => 400, oranges => 200,
bananas => 50)
Fruit.new(location => "Azusa", apples => 20, oranges => 2000,
bananas => 5)
Fruit.new(location => "Anaheim", apples => 10, oranges => 300,
bananas => 55)]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*** addressing values inside an object ***
The dot itself is technically an operator, it means "call the method
'apples' on the preceding object". So $FruitStand.apples translates
to "call the method 'apples' on the object $FruitStand" [11]
Reading from:
say $FruitStand.apples
400
$FruitStand.apples.say
400
# multiple other ways to read the value
print $FruitStand.location ~ "has " ~ $FruitStand.apples ~"
apples in stock\n";
print "Fruitstand in {$FruitStand.location} has
{$FruitStand.apples} apples\n";
print "Fruitstand in ", $FruitStand.location, "has ",
$FruitStand.apples, " apples\n";
print "$FruitStand.location() has $FruitStand.apples() apples in
stock";
Cucamongahas 400 apples in stock
Note: an "oops!". Separate the variables from the string, or else:
say "$FruitStand.location has $FruitStand.apples apples in
stock";
Fruit<79300336>.location has Fruit<79300336>.apples apples in
stock
The "79300336" in the above is the Raku address.
Writing to (must be declared as "rw"):
$FruitStand.apples = 25;
25
Note: another "oops". If you try to write to an "ro" (the default)
variable,
you get:
$FruitStand.oranges = 25
Cannot modify an immutable Int (200)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*** methods ***
A "method" (function) is declared inside a "class" declaration.
Methods are the subroutines of an object.
Please note that you are unable to address a customer method in the
style of `say 0.7.cos`. These are for build-in methods only. You
need to feed the method an object that is declared from the method's
class. You do this with `xxx.new`. See examples 2, 3, and 4 below.
Variable syntax *inside* a class declaration
$. access the variable though a behind the scenes method
$! directly access the variable
Please note:
" class Foo { has $.bar }
that Raku will behind the scenes make a method that's defined as
method bar { return $!bar }
the $!foo variant accesses the variable directly — but it's only
available inside of a class" [7]
self = the is the value method
"ABC".lc
"ABC" would be "self"
Ooos!!! You will get back your Raku pointer's address unless
add the
appropriate "has" variable after it. See example 1
below.
In other words:
"self is the current instance of an object. If I have a class like
class Person { has $.name }
I'll make new people by doing
my $personA = Person.new( name => "John" );
my $personB = Person.new( name => "Alice" );
inside of a class, sometimes you want to reference not the abstract
idea of a person, but a single concrete person you might have a
method like
method introduce { say "Hi, my name is {self.name}" }
(this is equivalent to $.name)" [8]
Example 1: Show the variable "self"
# Create a class called "PrintTest"
class PrintTest {
has Str $.Msg;
method PrintMsg() {
print "self = <" ~ self.Msg ~ ">\n";
print "self = <" ~ self.Str ~ ">\n";
print "self = <" ~ self ~ ">\n";
}
}
# Create an object called $x using the class PrintTest
my $x = PrintTest.new(Msg => "abc");
$x.PrintMsg
self = <abc>
self = <PrintTest<95224840>>
self = <PrintTest<95224840>>
Example 2: Method without a structure
# Just leave off the structure (has) declaration
class Yelling {
has Str $.Msg;
method Yell() {
print self.Str.uc ~ ">\n";
}
}
Example 2: set up an inventory of fruit
# Create a "class" called "Fruit"
class Fruit {
has Str $.location;
has UInt $.apples;
has UInt $.oranges;
has UInt $.bananas;
method PrintFruitInventory() {
print $!location ~ " has the following inventory:\n";
print " apples: " ~ $!apples ~ "\n";
print " oranges: " ~ $!oranges ~ "\n";
print " bananas: " ~ $!bananas ~ "\n\n";
}
}
# create an object called "$FruitStand" from the class "Fruit"
my $FruitStand = Fruit.new( location => "Cucamonga",
apples => 400,
oranges => 200,
bananas => 50 );
$FruitStand.PrintFruitInventory
Cucamonga has the following inventory:
apples: 400
oranges: 200
bananas: 50
Example 3: shows an internal variable:
class BadMath {
has Int $.A;
has Int $.B;
method BadAdd() {
# $Clinker is an internal variable. Note no $. or $!
my $Clinker = (-5.1 .. 5.1).rand.truncate;
return $!A + $!B + $Clinker;
}
}
my $TwoPlusTwo = BadMath.new( A => 2, B=> 2 );
print $TwoPlusTwo.BadAdd ~ "\n";
7
8
2
print BadMath.new( A => 2, B=> 2 ).BadAdd ~ "\n";
4
Example 4: show how to pass an optional parameter
class Angle {
has Numeric $.degrees;
# "$debug" is the optional parameter
method AngleCosine( $debug = False ) {
my Str $SubName = &?ROUTINE.name;
# "self.degrees" is the same as "$!degrees"
my Numeric $radians = self.degrees * π / 180;
my Numeric $cosine = $radians.cos;
if $debug {
print "$SubName debugging:\n";
print " self = <" ~ self.degrees ~ ">\n";
print " radians = <$radians>\n";
print " Cosine of " ~ self.degrees ~ " degrees is <" ~
$cosine ~ ">\n\n";
}
return $cosine;
}
}
my $x = Angle.new( degrees=> 45 );
say $x.AngleCosine;
0.7071067811865476
say $x.AngleCosine( True );
AngleCosine debugging:
self = <45>
radians = <0.7853981633974483>
Cosine of 45 degrees is <0.7071067811865476>
0.7071067811865476
print Angle.new( degrees => 45 ).AngleCosine( True ) ~ "\n";
AngleCosine debugging:
self = <45>
radians = <0.7853981633974483>
Cosine of 45 degrees is <0.7071067811865476>
0.7071067811865476
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*** using class, object, and method to access system libraries ***
NativeCall [5] is Raku's interface to system libraries (.so and .dll).
These
libraries use "C" style calls -- NativeCall accommodates them.
Note that C does not have OOP and that virtually all system calls are
written in C. (Some Windows calls are written in C++.)
What C does have is "structures", which can be though of loosely as
a precursor to OOP and lend easily to Raku's classes:
"In C, a struct might look a little like a class, but is much simpler.
It is really just a record: a collection of fields." [9]
For an in depth examination of NativeCall and C, see
perl6.NativeCall.WinAPI.odt
Example 1: Windows time:
<WinSysTime.pl6>
#!/usr/bin/env raku
#`{
GetSystemTime()
https://docs.microsoft.com/e-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime
[10]
}
use NativeCall;
#`{
First we dupliate the following GetSystemTime's C structure with
a class:
global type ws_systemtime = struct
word16 year
word16 month
word16 dayofweek
word16 day
word16 hour
word16 minute
word16 second
word16 milliseconds
end
}
class tm is repr('CStruct') {
has int16 $.tm_year; # Year
has int16 $.tm_mon; # Month. [0-11]
has int16 $.tm_wday; # Day of week. [0-6]
has int16 $.tm_mday; # Day. [1-31]
has int16 $.tm_hour; # Hours. [0-23]
has int16 $.tm_min; # Minutes. [0-59]
has int16 $.tm_sec; # Seconds. [0-60] (1 leap
second)
has int16 $.tm_msec; # milliseconds [0-999]
#`{
Now we create NativeCalls link to the C function:
importdll kernel32=
windows proc "GetSystemTime"(ref byte)
end
proc start=
tm:=new(ws_systemtime)
getsystemtime(&tm)
println tm.day, tm.month, tm.year
end
"&tm" is C for pointer to tm
Note that "is rw" below tells "tm" to return the value pointed to
by GetLocalTime. It does not mean "read write".
Also, you can not put a space after "native"
}
sub GetLocalTime( tm is rw ) is native( 'kernel32' ) { * };
#`{
Then we create a method to call GetLocalTiem through
NativeCall's sub.
Note that we declare on object ($time) to feed GetLocalTime()
#
method GetLocalTime() returns tm {
# my Str $SubName = &?ROUTINE.name;
my $time = tm.new;
GetLocalTime( $time );
return $time;
}
}
</WinSysTime.pl6>
my $SysTime = tm.new;
$SysTime = tm.GetLocalTime();
print "Local Time HH:MM:SS <" ~ $SysTime.tm_hour ~ ":" ~
$SysTime.tm_min ~ ":" ~ $SysTime.tm_sec ~ ">\n";
print "year = <" ~ $SysTime.tm_year ~ ">\n";
raku WinSysTime.pl6
> Local Time HH:MM:SS <0:17:38>
> year = <2020>
Example 2: Linux time:
<LinSysTime.pl6>
#!/usr/bin/env raku
#`{
Getting the "Time" in C's time.h is a two step process.
1) get the absolute time (seconds since 1970)
2) send the absolute time to conversion routines
https://www.cplusplus.com/reference/ctime/time/
https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/time.h.html
/usr/include/bits/types/time_t.h
/* Return the current time and put it in *TIMER if TIMER is not
NULL. */
extern time_t time (time_t *__timer) __THROW;
/usr/include/bits/types/time_t.h
#ifndef __time_t_defined
#define __time_t_defined 1
#include <bits/types.h>
/* Returned by `time'. */
typedef __time_t time_t;
#endif
https://www.cplusplus.com/reference/ctime/localtime/
/* localtime example */
#include <stdio.h> /* puts, printf */
#include <time.h> /* time_t, struct tm, time, localtime */
int main ()
{
time_t rawtime;
struct tm * timeinfo;
time (&rawtime);
timeinfo = localtime (&rawtime); /* the "&" means the variables
memory address */
printf ("Current local time and date: %s", asctime(timeinfo));
return 0;
}
} # end block commet
use NativeCall;
#`{
First we must create a class to match struct_tm.j
/usr/include/bits/types/struct_tm.h
#ifndef __struct_tm_defined
#define __struct_tm_defined 1
#include <bits/types.h>
/* ISO C `broken-down time' structure. */
struct_tm
{ int tm_sec; /* Seconds. [0-60] (1 leap second) */
int tm_min; /* Minutes. [0-59] */
int tm_hour; /* Hours. [0-23] */
int tm_mday; /* Day. [1-31] */
int tm_mon; /* Month. [0-11] */
int tm_year; /* Year - 1900. */
int tm_wday; /* Day of week. [0-6] */
int tm_yday; /* Days in year. [0-365] */
int tm_isdst; /* DST. [-1/0/1] */
# ifdef __USE_MISC
long int tm_gmtoff; /* Seconds east of UTC. */
const char *tm_zone; /* Timezone abbreviation. */
# else
long int __tm_gmtoff; /* Seconds east of UTC. */
const char *__tm_zone; /* Timezone abbreviation. */
# endif
};
}
class tm is repr('CStruct') {
has int32 $.tm_sec; # Seconds. [0-60] (1 leap second)
has int32 $.tm_min; # Minutes. [0-59]
has int32 $.tm_hour; # Hours. [0-23]
has int32 $.tm_mday; # Day. [1-31]
has int32 $.tm_mon; # Month. [0-11]
has int32 $.tm_year; # Year - 1900. Add 1900 to this to get a
four digit year
has int32 $.tm_wday; # Day of week. [0-6]
has int32 $.tm_yday; # Days in year. [0-365]
has int32 $.tm_isdst; # DST. [-1/0/1]
has int64 $.tm_gmtoff; # Seconds east of UTC.
has byte $.tm_zone; # Timezone abbreviation.
#`{
Next we have to figure out what the mysterious "time_t"
structure is
that figuring out brings tears to the most battle hardened C
programmer.
It is a "long, long int" or int64 (figured out the hard way).
/usr/include/bits/types/time_t.h
#ifndef __time_t_defined
#define __time_t_defined 1
#include <bits/types.h>
/* Returned by `time'. */
typedef __time_t time_t;
#endif
We will declare $rawtime a little later as an int64
}
#`{
Next we create a NativeCall sub to match C's "time" routine
/usr/lib64/libc.so.6 `'c',v6` converts to "libc.so.6"
time (&rawtime);
--> "time" is the name of the routine
--> "&rawtime" is the (C pointer) address structure of
"time_t"
Note: NativeCall converts `'c', v6` into libc.so.6
NativeCall simulate the pointer in &rawtime with "int64
is rw",
(which is why we had to know what "time_t" was).
}
sub time( int64 is rw ) is native( 'c', v6 ) { * }; # "is rw"
makes it a pointer
#`{
Now we create a NativeCall sub to retrieve the local time.
The C call is
timeinfo = localtime (&rawtime);
--> "&rawtime" is the (C pointer) address of "rawtime"
--> "localtime" converts the absolute time into a "tm"
structure that is much more easily read.
}
sub localtime( int64 is rw ) returns tm is native( 'c', v6 ) { *
}; # "is rw" makes it a pointer
# return the absolute time
method GetRawTime( --> int64 ) {
my int64 $rawtime;
time( $rawtime );
return $rawtime;
}
# convert the absolute time into usable structures
method GetLocalTime() returns tm {
my Str $SubName = &?ROUTINE.name;
my $time = tm.new;
my int64 $rawtime = 0;
$rawtime = tm.GetRawTime();
$time = localtime( $rawtime );
return $time;
}
}
my int64 $RawTime = 0;
$RawTime = tm.GetRawTime();
print "raw time = <" ~ $RawTime ~ ">\n";
my $SysTime = tm.new;
$SysTime = tm.GetLocalTime();
print "Local Time HH:MM:SS <" ~ $SysTime.tm_hour ~ ":" ~
$SysTime.tm_min ~ ":" ~ $SysTime.tm_sec ~ ">\n";
print "year = <" ~ $SysTime.tm_year + 1900 ~ ">\n";
</LinSysTime.pl6>
$ LinSysTime.pl6
raw time = <1608970172>
Local Time HH:MM:SS <0:9:32>
year = <2020>
Example 3: Linux: print a list of available printer (network and local)
from CUPS
use NativeCall;
#`{
https://www.cups.org/doc/cupspm.html
int cupsGetDestMediaByName(http_t *http, cups_dest_t *dest,
cups_dinfo_t *dinfo, const char *media, unsigned flags, cups_size_t *size);
Parameters
http Connection to destination
dest Destination
dinfo Destination information
media Media name
flags Media matching flags
size Media size information
Return Value # 1 on match, 0 on failure
}
class CupsDest is repr('CStruct') {
has Str $.name;
has Str $.instance;
has int32 $.is-default;
has Pointer $.options;
}
sub cupsGetDests(Pointer is rw --> int32) is native('cups', v2) {}
my $ptr = Pointer.new;
my $nCount = cupsGetDests($ptr);
# Note: the `^$` makes the integer $nCount look like an array of 0
to ($nCount - 1)
for ^$nCount -> $i {
my $dest = nativecast(CupsDest, Pointer.new($ptr + $i *
nativesizeof(CupsDest)));
print "<" ~ $dest.name ~ ">\n";
}
$ ListPrinters.pl6
<B4350>
<Cups-PDF>
<Cups_PDF_rn6>
<Oki_B4350_on_dev_lp0_rn6>
<Virtual_PDF_Printer>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*** References: ***
[1] Object-oriented programming
https://en.wikipedia.org/wiki/Object-oriented_programming
[2] comp.os.ms-windows.programmer.win32: 12/18/20 2:18 AM, R.Wieser:
[3] routine new
https://docs.raku.org/routine/new
[4] Classes & Objects
https://raku.guide/#_classes_objects
[5] Native calling interface
https://docs.raku.org/language/nativecall
[6] Day 9: Getting Windows Memory Usage with NativeCall
https://raku-advent.blog/2020/12/09/day-11-getting-windows-memory-usage-with-nativecall/
[7] https://webchat.freenode.net/?channels=#raku, guifa2 2020-12-24
21:28:51
[8] https://webchat.freenode.net/?channels=#raku, guifa2 2020-12-24
22:03:47
[9] comp.lang.c: 12/28/20 3:50 AM, Bart
[10]
https://docs.microsoft.com/e-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime
[11] https://webchat.freenode.net/?channels=#raku: guifa2 2020-12-30
18:06:23
[12]
https://docs.raku.org/language/nativecall#Embedding_CStructs_and_CUnions_with_HAS