Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

OO design: returning string and/or special value

by wanna_code_perl (Friar)
on Oct 07, 2019 at 20:04 UTC ( [id://11107157]=perlquestion: print w/replies, xml ) Need Help??

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

Fellow monks!

I have a class with a $obj->method call that returns a value. (Earth shattering, no?) The vast majority of the time, the caller is going to want that value in plain old string format. A small (5%) percentage of calls, they want another, let's say special scalar format. Crucially, sometimes they want both string and special formats from the same call, and calls take around a second, and are non-idempotent. It is possible to convert from special to string after the fact, but not the other way around.

So for my first cut, I had a return object that is initialized with the special representation of a return value, with two methods: $ret->string and $ret->special. Simple enough. But it felt wrong to force the caller to remember to append ->string almost everywhere, so I decided to play around with overload, and was quickly reminded of the pain and potential pitfalls associated with overrides to "just" stringify an object. To wit, one has to overload the following "minimal" set, according to overload:

+ - * / % ** << >> x <=> cmp & | ^ ~ &. |. ^. ~. atan2 cos sin exp log sqrt int "" 0+ bool ~~

I doubt users are going to need trig functions in my case, but everything else is possible, and for the most part, likely.

As an aside, I've always been curious as to why so many overloads are necessary for basic stringification. Could q("") or perhaps even a q(scalar) not have been enough in many cases? Let all of the others automatically derive from that? For example, if my object already stringifies to "apple", why do I also need to define "cmp" to know if $obj lt 'banana' is true? But that's probably a separate question.

So I'd like some other opinions on this design. Having users do $ret->string in 95% of cases is undesirable, but isn't the end of the world.

I'm currently leaning toward returning both, i.e.: my ($string, $special) = $obj->method, (with my $string = $obj->method working in scalar context), but am very much open to other options.

The reason I'm here, though, is to ask: how would you design something like this (i.e., the first paragraph), and why?

As respects performance, an extra subroutine call or two isn't going to matter, as $obj->method is not CPU bound and takes around a second to return.

Replies are listed 'Best First'.
Re: OO design: returning string and/or special value
by stevieb (Canon) on Oct 07, 2019 at 20:15 UTC

    You don't specify exactly what "special" is. That makes it difficult to come up with a solution to the return value.

    You also don't give an example of the subroutine in question. Does it accept parameters? If so, what type?

    You could demand that a user send in nothing to get a string return, and if the user sends in a reference to something, you could generate data within the reference for something "special".

    That, or require a hash as arguments: $o->method(want => 'special');, whereby if no args are present, you return a string.

    More information is necessary that describes what you're doing in behind the scenes, why you want to return different types of information and an example of what you have now.

      Hi stevieb! Thanks for your input. Answers to most of your questions:

      You don't specify exactly what "special" is.

      See first paragraph of the OP. special is also a scalar, just a different application-specific format. It's possible to programmatically convert special to string, but not vice-versa. Said formats are well-understood in my problem domain, and complex enough to take several paragraphs to explain and justify, so I opted not to, trusting my fellow monks would trust me to accurately represent my underlying requirements.

      I did give examples of the subroutine, and those examples did not accept arguments. That was accurate.

      You could demand that a user send in nothing to get a string return, and if the user sends in a reference to something, you could generate data within the reference for something "special".

      That, or require a hash as arguments: $o->method(want => 'special');, whereby if no args are present, you return a string.

      These are reasonable suggestions. The second one, I almost went with, but then thought the list context idea was superior, especially when you remember the caller sometimes needs both the string and special formats, so you'd need want => 'both' as well, or something to that effect.

      More information is necessary that describes what you're doing in behind the scenes, why you want to return different types of information and an example of what you have now.

      I'm returning differing string formats because that's the way my particular problem domain works, and what the requirements demand. Callers will normally (95%) want the simplified string representation, but will sometimes want the more complex special format, and sometimes want both.

      I'm sorry I wasn't as concrete as you would like. What follows is the most reasonable minimal example I can come up with. It mimics my current calling conventions and return types, and that's about all I can reasonably demonstrate. The relevant parts of the real class are about 800 lines worth of proprietary algorithm that I can't share without breaking an MNDA, even if you wanted to wade through it, which I wouldn't recommend. :-)

      use Test::More; package Foo { sub new { bless { }, shift }; sub method { my ($self, $arg) = @_; # Network operation followed by internal processing, but # for the sake of example, say we've already done all # that and it's in $arg. (Normally method takes no args, # and is non-idempotent): wantarray ? (_special_to_string($arg), $arg) : _special_to_string($arg); } sub _special_to_string { # Really requires 100-odd lines of transformation code, # split among several other internal subs, but for now: lc(shift); } } my $arg = 'Really Special'; my $foo = Foo->new; is scalar $foo->method($arg), lc($arg), 'explicit scalar context'; my $string = $foo->method($arg); is $string, lc($arg), 'scalar assignment'; my ($plain, $special) = $foo->method($arg); is_deeply [ $foo->method($arg) ], [ lc($arg), $arg ], 'list'; __END__ ok 1 - explicit scalar context ok 2 - scalar assignment ok 3 - list
Re: OO design: returning string and/or special value
by Fletch (Bishop) on Oct 07, 2019 at 20:10 UTC

    Maybe Contextual::Return ?

    Update: Not that that's not similar / the same thing you'd started to do with the overloading, just that it's a (possibly) more tested implementation.

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      Ah Contextual::Return. I've had some interesting results with it in the past, as well as interesting feedback. It seems I went with Want in that case, though I don't have the foggiest clue what I was working on back then. But what do you think either module would get me, versus the basic approach (below)? I'm not sure how that would actually get me an overloaded result as you (I think) suggest, just a return based on different contexts?

      wantarray ? (_special_to_string($arg), $arg) : _special_to_string($arg);

      Remembering both string and special are both scalars (strings, in fact), how (in high level terms at least) would you tackle this with either C::R or Want?

        OK I think I misread / elided some of your OP where you said that both values were strings. In that case C::R probably isn't what you're wanting, unless you wanted to return a magical hashref that had both values that stringified to the string bit (disclaimer I've never really used C::R in anger, just aware of it's presence).

        use Contextual::Return; sub blahblah { ## ... return ( STR { _special_to_string( $retval ); } HASHREF { +{ special => $retval, string => _special_to_string( $re +tval ) }; } ); } my $foo = blahblah( @some_args ); say "Foo: $foo"; say "Special from foo: ", $foo->{special};

        There's also Object::Result which is a variation on the theme which is aimed at making it "easier" to return an instance with arbitrary methods; I believe it's mentioned/recommended in the updated PBP course, for whatever that's worth.

        After thinking over it a bit more I think I'd lean towards just a hashref, maybe Object::Result; and eschew overloading or other magic. If you name the slot with the plain string well you probably win in clarity what you lose in extra typing (say $result->{message}).

        Then again I drove 900+ miles this weekend so take that with a large grain of salt.</handwaving>

        The cake is a lie.
        The cake is a lie.
        The cake is a lie.

Re: OO design: returning string and/or special value
by daxim (Curate) on Oct 08, 2019 at 10:38 UTC
Re: OO design: returning string and/or special value
by jcb (Parson) on Oct 08, 2019 at 03:44 UTC

    I have used overloading on several classes with few problems. For your case, try use overload '""' => 'string', fallback => 1; in the class of the object that you return from ->method.

Re: OO design: returning string and/or special value
by swl (Parson) on Oct 07, 2019 at 22:23 UTC

    I'm with 1nickt's comment, in that using context to determine the result for this is somewhat fraught.

    Assignment to an array or hash will need scalar context in some cases, which is as much typing as a method argument, and will lead to later programmers wondering what is going on.

    my %hash1 = (some_key => $object->method); my %hash2 = (some_key => scalar $object->method);

    More generally, though, wantarray works well for returning flat lists or references to lists (although this approach also needs explicit scalar context sometimes).

      True, hash/array assignment and other such things can trip sleepy programmers up. It's very unlikely to ever be called in this manner in my case, but certainly not out of the question. What I do wonder though, is whether that necessarily means "don't use wantarray," or if it's reasonable to trust an experienced Perl programmer to understand list and scalar context in a documented method, to avoid other potential bugs and reduce the average cognitive load of the programmer in question. What do you think?

        I use wantarray in my code, but with the aim of doing so consistently, i.e. related methods all use it or all don't. Returning list refs is generally faster, especially if the list is large, so in some cases it is added as an optimisation that will not break (or will break less) code elsewhere in the system. This code is not on CPAN, though, so I have more freedom in doing so.

Re: OO design: returning string and/or special value
by 1nickt (Canon) on Oct 07, 2019 at 21:25 UTC

    Hi, if you don't want to require the caller of your method to provide params (?!) you should just return all the data and let the caller consume the parts that are interesting. Return a hashref if the caller is another Perl routine, or a JSON obj if there's data interchange. Steer clear of wantarray and conditional/inconsistent return values in general.

    Hope this helps.


    The way forward always starts with a minimal test.

      I'm trying to go with the good old Larry Wall-ism and "make the easy things easy, and the hard things possible". I need the basic string representation 95% of the time, so making the caller remember to write $obj->method->{string} (hashref) or $obj->method->string (object return) or similar is the sort of thing I'm trying to steer away from if reasonably possible.

      I grant you wantarray can occasionally be mildly surprising in certain edge cases, but I disagree that one should always steer clear of it. In my particular case I don't see how different contexts would confuse an average Perl programmer to any significant degree, even if they don't RTFM. If you want to explain your objection to wantarray in this case, though, I'd be grateful for another perspective on that! See my reply to stevieb for some example code.

      if you don't want to require the caller of your method to provide params (?!)

      What's surprising about a method without params? Or were you just emphatically confirming whether my method did or did not require params? (It doesn't).

        Nothing surprising about a method that does not take params, if the method always returns the same thing. For a method that does not always return the same thing (not a good idea anyway), not providing a way for the caller to specify what is wanted via a param seems solidly in the realm of misplaced optimization.

        You asked for suggestions on how to design for your need. My experience has shown me that clarity, simplicity and consistency are above all. So I shared my suggestion. I'm not interested in debating things like "should I have a type constraint that only warns on a bad value" or "should I have a method return one of three things but take no parameters," to be honest. Those questions are long settled, in my view. While TIMTOWTDI is still true, there's usually a standard way to do most basic programming tasks in Perl, these days, or at least some standard ways to *not* do them.


        The way forward always starts with a minimal test.
Re: OO design: returning string and/or special value (peephole Object)
by Anonymous Monk on Oct 08, 2019 at 01:17 UTC

    The reason I'm here, though, is to ask: how would you design something like this (i.e., the first paragraph), and why?

    :)

    I'd perform some OOAD

    You can't analyze design through the peephole that is your question; its too abstract?

    A method that returns a String frog or Squirrel nuts

    Sometimes a user may want just the String, but sometimes a user wants the nuts

    Its almost as if its a good idea to return some kind of Object thats a string by default, but also an object you can ->chew cause its nuts

    Sounds like a different kind of contextual return to me

    As a case study on contextual return we can learn from the functional/methodial CGI's param , the classic demonstration that context sensitive return is too much cognitive load for the new and old alike, providing millions of pitfalls, and zero benefits

    ->non_idempotent_string

    ->non_idempotent_nuts

    ->non_idempotent_string_nuts

    sub non_dempotent_string { my( $string, undef ) = shift->non_idempotent_string_nuts (@_); return $string; }

    I think thats it, more methods .... is it?

Re: OO design: returning string and/or special value
by pwagyi (Monk) on Oct 10, 2019 at 08:12 UTC

    If you have a method that returns SCALAR, in this case, I assume it should also be an object. Converting that object to string format should be responsibility of that object's class. not method() that returns that SCALAR responsibility.

      Scalars should be objects because?
        Since OP does not specify anything, I'm making assumption, anything non-simple SCALAR (number/string) should be already objectified if OP is using OOD approach.
A reply falls below the community's threshold of quality. You may see it by logging in.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11107157]
Approved by jcb
Front-paged by haukex
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-04-19 23:42 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found