Raku / problem-solving

🦋 Problem Solving, a repo for handling problems that require review, deliberation and possibly debate
Artistic License 2.0
70 stars 16 forks source link

Indirect object syntax should be deprecated #384

Closed lizmat closed 1 year ago

lizmat commented 1 year ago

I think not many people are using it, and it is one of the Perl ideas that didn't age well. How many people know what

dd chop 42:

will output? Did you without trying?

My proposal would be to deprecate the indirect object syntax in the legacy grammar, and completely disallow it in the Raku grammar for language version 6.e (FOUR) and higher.

This would free up the colon for other purposes.

tbrowder commented 1 year ago

Why is the colon even there? Whether I do "chop 42" or "chop 42:" in the REPL I get the same result.

2colours commented 1 year ago

@tbrowder that's because there is a subroutine &chop and a Cool method chop. chop 42: is basically sugar for 42.chop, using the method.

codesections commented 1 year ago

When I first considered this issue, I was somewhat torn. But, after having given the matter some more serious thought, I've come to really like that syntax and to strongly feel that it ought not to be deprecated. Here's why:

I'm very glad that we can write both chop $foo and $foo.chop; even though they return the same value, they emphasize different points to the reader. (Just as "The boy was bitten by the dog" and "The dog bit the boy" communicate the same literal info but with different emphasis; order matters). Depending on my intent, one order might be much clearer than the other.

Of course, as @2colours notes, for chop we don't need the : invocant marker to be able to use the best order – Raku has both a chop sub and a chop method. But consider a case where that isn't true. I can write $promise.vow just fine, but I cannot write vow $promise – Raku doesn't have a vow subroutine. But, thanks to this syntax, I can write vow $promise:, which means I can still select the order that best communicates my intent.

(This issue doesn't come up all that often when dealing with built-in Raku methods, since so many of them have corresponding subs. But module code generally does not provide sub versions of methods, and doing so would add significant amounts of boilerplate.)

Finally, please note that the : syntax is the logical counterpoint to the .& operator, which I definitely use all the time (e.g., a test such as $result.&is: 42). Since Raku has a way to call subs as though they were methods, it's consistent for Raku to also have a way to call methods as though they were subs.

Given all that, I don't think the : syntax should be deprecated. That said, I agree that it's not widely used. But I think that's a documentation failure and/or a consequence of Raku supplying so many corresponding sub/method pairs. Imo, we should work to make this syntax more widely known and to explain its power rather than remove it.

librasteve commented 1 year ago

In baby raku I may start with...

class A {
    has $.x;
}

A.new.x = 42;

But in grown up raku I know about SOLID and so I want...

class A {
    has $!x is built;

    multi method x { $!x }
    multi method x($val) { $!x = $val }
}

A.new.x: 42;

(a post on the topic)

So, with this in mind, I tend to think of the raku "indirect method syntax" as one of the trio:

Of these, I think that the : variant is often the best approach for OO production code.

So, since Larry cared a lot about the colon, I suspect that he wanted to apply it where it would do the most good - to make setter methods as natural and friendly as assignment.

EDIT: Oops, I misunderstood - didn't ever try doing it. Leaving this in so that replies are not stranded. But generally I have no strong view of this proposal then.

2colours commented 1 year ago

@librasteve I think you misunderstand. The case lizmat brought up is x A.new(): 42 (EDIT: the parens are required here, which kinda makes sense), not A.new.x: 42. The one where the "verb" comes first and the first argument is the self-object itself, followed by a colon.

For what it's worth, I don't feel strongly about this one, in either direction. There are people (@alabamenhu, for example) who indicated that they are using this syntax. I don't think it really has a "killer" use case but what harm does it do? What justifies removing it, if it even displeases some long-standing users? I'm not implying that there cannot be a good reason but what is it actually?

2colours commented 1 year ago

Actually, I think it's mostly evolutionary that this isn't used. I had to try hard how to properly rewrite a method chain like 'foo'.comb.head and I could finally come up with head (comb 'foo':): where both colons and the parens are all very important. And then there aren't a lot of people who like to structure their code around very specific structures, like flat method calls.

alabamenhu commented 1 year ago

I effectively concur with basically everything that @codesections said — his thinking almost directly mirrors mine.

There are times when any of the following may make the most sense.

if $gnarly-condition {    # 1: if/then block
    die;
}

die if $gnarly-condition; # 2: postfix if

$gnarly-condition or die; # 3: infix or

In (1) we want to draw attention to the gnarly condition. But it's very control flowy, and looks great when deep in an algorithm. Nonetheless, there are other times when we might want to draw attention to the effect more, like in (2). It shouts very loud that there is a potential for death. The cause of it is secondary to the effect. Others may still prefer the more assertive Perlism in (3). All of these structures have perhaps more well defined uses in other ways, but even in this small area, we have a variety of doing things, and we have traditionally celebrated this because TIMTOWTDI. I think up to here we're all in agreement.

For me, the colon syntax takes this idea, but applies it to the procedural/OO paradigms. Let's say I need to do some ops to an object:

@arr.push: 1;        # OO style, focus on object
@arr.push(2);

.push: 1 given @arr; # OO style, focus on actions
.push(2) given @arr; 

given @arr {         # OO style, really focus on actions
    .push: 1;
    .push(2);
}

push @arr, 1;        # procedural style, focus on actions
push @arr, 2;   

push @arr: 1;        # procedural style, enabled via OO
push @arr: 2;        # enabled via Array.push($)

@arr.&push: 1;       # OO style
@arr.&push(2);       # enabled via sub push(@,$)

It's these those last two that I want to focus on. The "true" OO and imperative styles require the developer to have contemplated those kinds of uses. The final two enable procedural and OO, without really any extra work. And let's face it, in the examples above, you probably had to look twice to catch that we used a colon rather than a comma: it's designed to fit in that nicely. And it's a nice mnemonic that we're actually calling a method. The signatures might as well be

#               ↱ comma, because @arr is just a normal argument
sub    push(@arr, $x) { … }
method push(@arr: $x) { … }
#               ↳ colon, because @arr is the base object (=self, by default)

Remove the parentheses from the sub there and you get, well, the parentheses-less call that we all know and love:

sub push(@arr, $x) { … }
    push(@arr, $x) # sub call
    push @arr, $x  # also sub call

Remove them from the method and you get...

method push(@arr: $x) { … }
       push(@arr: $x) 
       push @arr: $x; 

Aka, the indirect object syntax. It's really a pretty brilliant way of how Raku is just strangely consistent. It's true that the chaining syntax isn't the best for indirect when parentheses are excluded, but that's the same for method calls where they're really only useful for final methods in a chain. With parentheses, they're not too bad, even for @2colours 's example without arguments (which is probably where I almost never use indirect): head(comb('foo':):). On the plus side, it does smile at you 🙂

Let's think about something like 'foobar'.substr(1,4).contains('a',2). You'd get contains(substr('foo': 0, 2): 'a', 2) and you can see how when arguments are involved it's a lot more palatable of a syntax. Although I don't think I've ever actually chained them outside of just now, it looks a lot like a destructured signature which… is kinda cool)

The main reason I haven't used it as much lately is simply because Comma regrettably doesn't support it and treats it as a syntax error. I used it much more before when I used a plain old text editor and I think Atom's highlighter also supported it (or at least, faked it well enough).

It would be quite sad to see such a beauty to deprecated.

2colours commented 1 year ago

I had to go back twice to even notice the difference. So this one uses the parens for argument passing, not grouping. Frankly, I think the data ---> transformations flow (so practically the method-ish syntax) is much more readable even with the arguments and even against a real subroutine call, not the colon syntax.

But this is really all we can talk about. Preferences, opinions, evaluating whether learning something is a bug or a feature... it would be fairly inconsistent to be opinionated about method call syntax of all things, to be honest, and there doesn't seem to be a great incentive to do some AlexDaniel-style cleanup to create some Dart or Go-style language.

What I can kind of see, as a kind of compromise between the minimalists and the baroque enjoyers that could be applied to this as well, is the outsourcing of certain features into Slangs. That seems more and more feasible for the upcoming times but it also has many social implications, like the possible fragmentation of the language, so the choices would have to be made rather carefully.

bbkr commented 1 year ago

I have one example against this change:

$ raku -e 'my @a = 1,2,3,4; push @a: 5; say @a'
[1 2 3 4 5]
$ raku -e 'my @a = 1,2,3,4; push @a, 5; say @a'
[1 2 3 4 5]

But...

$ raku -e 'my @a = 1,2,3,4; say classify @a: *.is-prime;'
{False => [1 4], True => [2 3]}
$ raku -e 'my @a = 1,2,3,4; say classify @a, *.is-prime;'
{1 => [WhateverCode.new]} # ooops!

My point is that : allows to "subify" every method in safe manner. One can write consistent code in INTENT SUBJECT: PARAMS style, without worrying if predefined sub is declared and how predefined sub is declared.

As for OP arguments:

gfldex commented 1 year ago

If the feature stays, please file a docs issue for https://docs.raku.org/type/Method

2colours commented 1 year ago

A doc issue of what sort? I don't think this has anything to do with a type documentation.

jubilatious1 commented 1 year ago
  1. Does "Indirect object syntax" include/affect file test operators, like :e for exists?

  2. Why emojis? Don't these have Unicode Identifiers already? And why not something like \:waving-hand: or :waving-hand\: ?

It certainly seems to me that the colon has become a major focus of the Raku language, and I don't really understand the need to induce cognitive dissonance by creating a special 'emoji-colon' syntax.

@TimToady

codesections commented 1 year ago

Does "Indirect object syntax" include/affect file test operators, like :e for exists?

No, that's an adverb, to stick with the grammar metaphor. Or, less metaphorically, a named argument. "Indirect object syntax" (less metaphorically, the invocant marker) just refers to the postfix : that transforms what would have been an argument of a sub into the invocant of a method. E.g., in say 42, say is a sub and 42 is an argument; but in say 42: say is a method and 42 is the invocant – just as if you'd written 42.say.

Why emojis?

Yeah, I also wouldn't be a fan of that use of the colon. I haven't said much about that because I view it as a separate issue from whether this syntax should be deprecated – which, imo, it shouldn't be.

lizmat commented 1 year ago

UPDATE: removed the idea of emojis.

jubilatious1 commented 1 year ago

@codesections said: Yeah, I also wouldn't be a fan of that use of the colon. I haven't said much about that because I view it as a separate issue from whether this syntax should be deprecated – which, imo, it shouldn't be.

I understand from old Perl posts that Indirect Object Syntax was a nightmare. So naturally I presumed this was a settled issue when designing Perl6/Raku.

https://perldoc.perl.org/perlglossary#indirect-object https://www.effectiveperlprogramming.com/2020/06/turn-off-indirect-object-notation/ http://www.modernperlbooks.com/mt/2009/08/the-problems-with-indirect-object-notation.html https://archive.shadowcat.co.uk/blog/matt-s-trout/indirect-but-still-fatal/

Are there sanity checks on Indirect Object Syntax? For example (taking the dative case ), you probably should only allow indirect Object Syntax on containers, and disallow Indirect Object Syntax for := binding.

Have these sanity checks been done?

tbrowder commented 1 year ago

@tbrowder that's because there is a subroutine &chop and a Cool method chop. chop 42: is basically sugar for 42.chop, using the method.

Thanks.

jubilatious1 commented 1 year ago

@codesections

I'm misremembering my syntaxes. Do :adverbs have to be preceded by a space? Do Indirect Object Notation colons: have to be followed by a space?

How do things like hash "adverbs" get parsed, if there's no surrounding curlies, brackets, parens, etc?

[2] > my %fruit = apple => Any, orange => 10;
{apple => (Any), orange => 10}
say %fruit<apple>:p
apple => (Any)

I guess the question is whether a new user will be able to understand:

%fruit<apple>:p

as:

%fruit<apple> :p

and not:

%fruit<apple>: p

[...the last one looking awfully like Indirect Object Notation].

jubilatious1 commented 1 year ago

@gfldex You can search for "indirect" on the docs page below (maybe it deserves a separate entry, @finanalyst ?)

https://docs.raku.org/language/objects

But I agree with @alabamenhu and @codesections , the Indirect Object Feature seems to be a vast improvement over the Perl5 implementation, therefore it should stay.

codesections commented 1 year ago

%fruit<apple>: p

[...the last one looking awfully like Indirect Object Notation].

If anything, that example looks even more like precedence drop, which would still be an issue even if we deprecated the indirect-object syntax.

Also, I find the, the chart right above that precedence drop link fairly helpful:

# Method invocation. Object (instance) is $person, method is set-name-age 
$person.set-name-age('jane', 98);   # Most common way 
$person.set-name-age: 'jane', 98;   # Precedence drop 
set-name-age($person: 'jane', 98);  # Invocant marker 
set-name-age $person: 'jane', 98;   # Indirect invocation 

In particular, I like "indirect invocation" better that "indirect object syntax" – for the same reason that I prefer "named argument" to "adverb". In both cases, the grammar analogy can be helpful, but it can also (imo) be confusing to people coming from non-Perl-family languages; I think we'd be better off primarily using the non-grammatical term and subsequently introducing the grammatical term as a potentially helpful metaphor.

codesections commented 1 year ago

FYI, the discussion in this thread gave me the idea for my talk at this year's Raku Conf. And part of that talk will involve discussing the indirect-invocation syntax and how to use it to write clearer code. So, thanks, all :+1:

codesections commented 1 year ago

For reference: Raku/doc#4381 is at least a partial attempt to document the existing indirect-invocation syntax on the Language/syntax page.

lizmat commented 1 year ago

It would seem that indirect object syntax is NOT going to be deprecated. So closing this issue.