Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Using perlclass 'methods'

by drudgesentinel (Initiate)
on Jul 18, 2024 at 04:06 UTC ( [id://11160666]=perlquestion: print w/replies, xml ) Need Help??

drudgesentinel has asked for the wisdom of the Perl Monks concerning the following question:

Hello! I'm quite new to Perl 5, and often struggle with the concept of references (as other languages I've dabbled in do not use these)

I'm going through a Ruby OOP book that I like and am trying to use the experimental 'class' features to familiarize myself with the language more (I'm learning it to work on a project with a friend)

In the following snippet, the code for the ratio method was working fine. However, with the addition of the gear_inches method, I get a cryptic (to me) error: Can't use string ("26") as a symbol ref while "strict refs" in use at gear1.pm line 15.

This seems to imply that when I create the new class instance at the end, the rim parameter value of 26 is interpreted as a symbolic reference when the 'gear_inches' function is called. Upon reading up on symbolic references: https://perldoc.perl.org/perlref#Symbolic-references

It seems that Perl is saying that '26' (the value of the scalar) is taken to be the name of a variable. I have no idea how this could possibly be given the simplicity of the code. Every problem that produced an error message I couldn't make sense of previously involved de referencing, a concept with which I've not previously dealt. I guess this is probably also that somehow (lol?) Thank you for your time!
use v5.40.0; use experimental 'class'; class Gear { field $chainring :param :reader; field $cog :param :reader; field $rim :param :reader; field $tire :param :reader; method ratio { return $chainring/$cog; } method gear_inches { return ratio * $rim + ($tire * 2); } } say Gear->new(chainring=>52, cog=>11, rim=>26, tire=>1.5)->gear_inches +;

Replies are listed 'Best First'.
Re: Using perlclass 'methods'
by eyepopslikeamosquito (Archbishop) on Jul 18, 2024 at 08:43 UTC

    In case it helps, I ran six versions of the OP's original program on Linux, with results shown below:

    # Ran this little test program on Linux with perl -v printing: # This is perl 5, version 40, subversion 0 (v5.40.0) built for x86_64- +linux use v5.40.0; use feature 'class'; use experimental 'class'; class Gear { field $chainring :param :reader; field $cog :param :reader; field $rim :param :reader; field $tire :param :reader; method ratio { return $chainring/$cog; } method gear_inches { # this one failed: # Can't locate object method "ratio" via package "self" # return (self->ratio() * $rim) + ($tire * 2); # this one also failed: # Can't locate object method "ratio" via package "self" # return (self->ratio * $rim) + ($tire * 2); # this one worked, printing: 125.909090909091 # return ($self->ratio * $rim) + ($tire * 2); # this one also worked, printing: 125.909090909091 return ($self->ratio() * $rim) + ($tire * 2); # this one failed: # Cannot invoke method "ratio" on a non-instance # return (ratio() * $rim) + ($tire * 2); # this one failed: # Can't use string ("26") as a symbol ref while "strict refs" in + use # return (ratio * $rim) + ($tire * 2); # update: this one, suggested by [hippo]++, also works # return $self->ratio * $rim + $tire * 2; } } say Gear->new(chainring=>52, cog=>11, rim=>26, tire=>1.5)->gear_inches +;

    See Also

    👁️🍾👍🦟
Re: Using perlclass 'methods'
by haj (Vicar) on Jul 18, 2024 at 07:32 UTC

    A method in Perl always needs to be told on which object instance it should operate. Your call to ratio is missing that, it should be $self->ratio - and that's it. $self is a variable which is automatically available in a method and it holds the "current" object, which is what you want.

    The error message is indeed cryptic for the modern Perl style you are using. It comes from the way Perl passes arguments to its subroutines (and methods): The object on which a method operates is simply passed as its first parameter. So, if you call ratio without the invocant, then the method will interpret the first parameter 26 as the object. The message would be clearer written as say Can't use string ("26") as an object. The current text and its reference to "strict refs" comes from an old feature of Perl which should not be used at all in new code... so maybe we could convince the Perl devs to change that text.

    Edit: The code had a typo (self->ratio should read $self->ratio).

      This is not the correct explanation. Deparse helps:

      unexpected OP_METHSTART at /usr/share/perl5/B/Deparse.pm line 1674. unexpected OP_METHSTART at /usr/share/perl5/B/Deparse.pm line 1674. sub BEGIN { require v5.38.0; () } use warnings; use strict; no feature ':all'; use feature ':5.38'; use experimental ('class'); BEGIN {${^WARNING_BITS} = "\x55\x55\x55\x55\x55\x55\x55\x55\x55\x55\x5 +5\x55\x55\x55\x55\x55\x55\x55\x55\x51\x55"} use feature 'class'; { package Gear; ; sub ratio : method { XXX; return $chainring / $cog; } sub gear_inches : method { XXX; return ratio(*$rim + $tire * 2); } } say 'Gear'->new('chainring', 52, 'cog', 11, 'rim', 26, 'tire', 1.5)->g +ear_inches;

      (I had to modify the code a bit because I don't have 5.40)

      So it takes everything that comes after the method name as the argument, so the * is interpreted as a glob dereference.

      Note that B::Deparse also struggles with the new class feature.

        Thanks for the fix!

        This is also a showcase some quirks of the Perl language:

        • Parentheses are optional after method and subroutine calls. A method needs at least one parameter, and it was not provided with the $self-> syntax, so Perl picks up whatever comes next.
        • The * is a valid (but fortunately rarely used these days) sigil. It is not a multiplication here, but indicates a glob reference.
        • Really quirky: Spaces are allowed between a sigil and its identifier. So the identifier of * $rim is $rim, which is a symbolic reference.

        use v5.40.0 implies "strict refs", so Perl complains about *$rim even before trying to use it as the invocant of the method (as I originally thought).

      I don't have this new perl version to experiment. But I would expect that WITHIN a class calling a method name would automatically assume it is $self->ratio() (C++ and Java do that). If this is not the case, then why not dispensing the "usual" message: Undefined subroutine &main::ratio. It seems to me this class feature is half-baked and with old materials. And will lead to confusion in debugging.

        It seems to me this class feature is half-baked

        There is a reason why it is experimental. I understand the rationale given by those who have been advocating it but I only partially agree with it. There doesn't seem to be anybody suggesting that this new feature should be used in production.

        I am more than happy to keep using "classic" OOP and reach for one of the light frameworks available on CPAN on those occasions which warrant something a little more sugary.


        🦛

        C++ and Java do that, but Python does not. Python was the main inspiration for Perl's OOP, and it also requires methods to be invoked via self, even within the class.

        Simple things first: It is not the message Undefined subroutine &main::ratio because we are not in package main but in a class Gear where the method is defined.

        Whether Perl could imply $self->ratio when a method is called without an invocant is an interesting question. I guess it is a real challenge to the interpreter: It needs to figure out that ratio is a method and not a sub. As far as I know, this is not possible today. Whether this insurmountable, I can't say. Perhaps we should open an issue for Perl?

        And... of course the class feature is half baked. It is rather new and will take time to mature. For this issue, it behaves like all other OO systems in Perl. It could do different because it has the class and method contexts which the other systems don't have. Perhaps it will, at some point in the future?

Re: Using perlclass 'methods'
by Anonymous Monk on Jul 18, 2024 at 06:31 UTC
    method gear_inches { return ratio * $rim + ($tire * 2); }

    Try instead (call it incomplete implementation or anything else) ...

    method gear_inches { return ($self->ratio() * $rim) + ($tire * 2); }

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11160666]
Approved by GrandFather
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others surveying the Monastery: (3)
As of 2024-09-14 00:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    The PerlMonks site front end has:





    Results (21 votes). Check out past polls.

    Notices?
    erzuuli‥ 🛈The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.