Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Indirect object syntax should be deprecated #384

Closed
lizmat opened this issue Sep 10, 2023 · 22 comments
Closed

Indirect object syntax should be deprecated #384

lizmat opened this issue Sep 10, 2023 · 22 comments
Labels
language Changes to the Raku Programming Language

Comments

@lizmat
Copy link
Collaborator

lizmat commented Sep 10, 2023

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.

@lizmat lizmat added the language Changes to the Raku Programming Language label Sep 10, 2023
@tbrowder
Copy link
Member

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

@2colours
Copy link

@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
Copy link
Contributor

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
Copy link

librasteve commented Sep 11, 2023

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:

  • $x = 0; # assign a value to a variable
  • $x := 0; # bind an object to a container (or a lable if you go \x)
  • $x: 0; # set an attribute value with a method call

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
Copy link

2colours commented Sep 11, 2023

@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
Copy link

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
Copy link

alabamenhu commented Sep 11, 2023

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
Copy link

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
Copy link

bbkr commented Sep 12, 2023

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:

  • Indirect method call is not meant for chaining. Proving that you can write obfuscated code by abusing this feature is not a very strong point for its removal in my opinion.
  • Raku supports UTF-8 out of the box. Do we need to free : to be able to write "Hi" ~ :waving-hand: if we can slap it directly into string "Hi👋"?

@gfldex
Copy link

gfldex commented Sep 17, 2023

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

@2colours
Copy link

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

@jubilatious1
Copy link

  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
Copy link
Contributor

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
Copy link
Collaborator Author

lizmat commented Sep 26, 2023

UPDATE: removed the idea of emojis.

@jubilatious1
Copy link

jubilatious1 commented Sep 26, 2023

@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
Copy link
Member

@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
Copy link

@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
Copy link

@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
Copy link
Contributor

%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
Copy link
Contributor

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 👍

@codesections
Copy link
Contributor

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

@lizmat
Copy link
Collaborator Author

lizmat commented Oct 23, 2023

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

@lizmat lizmat closed this as completed Oct 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
language Changes to the Raku Programming Language
Projects
None yet
Development

No branches or pull requests

9 participants