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




Reply via email to