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

Named arguments don't respect canonical `key => value` convention (specific example of `mm-dd-yyyy` routine) #419

Open jubilatious1 opened 9 months ago

jubilatious1 commented 9 months ago

Named arguments don't respect canonical key => value convention (specific example of mm-dd-yyyy routine)

See attempt(s) to specify / SOLIDUS as separator in the mm-dd-yyyy routine, below.

In the Raku REPL (Rakudo 2023.05)

> my %months = [Z=>] <Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec>, 1..12;
{Apr => 4, Aug => 8, Dec => 12, Feb => 2, Jan => 1, Jul => 7, Jun => 6, Mar => 3, May => 5, Nov => 11, Oct => 10, Sep => 9}
> 
> given 'Jan/01/2024' { S{ ^ ( <.alpha>**3 ) (\/)  (\d**1..2)  (\/) (\d**4) } = "$4-{sprintf q[%02d], %months{$0}}-$2".Date.mm-dd-yyyy.say };
01-01-2024
> given 'Jan/01/2024' { S{ ^ ( <.alpha>**3 ) (\/)  (\d**1..2)  (\/) (\d**4) } = "$4-{sprintf q[%02d], %months{$0}}-$2".Date.mm-dd-yyyy().say };
01-01-2024
> given 'Jan/01/2024' { S{ ^ ( <.alpha>**3 ) (\/)  (\d**1..2)  (\/) (\d**4) } = "$4-{sprintf q[%02d], %months{$0}}-$2".Date.mm-dd-yyyy(sep => '/').say };
01-01-2024
> given 'Jan/01/2024' { S{ ^ ( <.alpha>**3 ) (\/)  (\d**1..2)  (\/) (\d**4) } = "$4-{sprintf q[%02d], %months{$0}}-$2".Date.mm-dd-yyyy(sep => "/").say };
01-01-2024
> given 'Jan/01/2024' { S{ ^ ( <.alpha>**3 ) (\/)  (\d**1..2)  (\/) (\d**4) } = "$4-{sprintf q[%02d], %months{$0}}-$2".Date.mm-dd-yyyy("/").say };
01/01/2024
>

lizmat commented 9 months ago

Generally, if a method can only take one (optional) argument, it becomes a positional. And any named arguments are ignored. Another example:

dd Int.new(42);          # 42
dd Int.new(:value(42));  # 0

So do you think that it should

??

jubilatious1 commented 7 months ago

I'm circling back to this Issue because I wanted to put some thought into my answer. In fact I'm used to the R-programming language, which was first released in 1993 (per Wikipedia). According to Wikipedia, R implements the following paradigms:

Multi-paradigm: procedural, object-oriented, functional, reflective, imperative, array

Basically, for R functions named arguments ("parameters") take precedence and can be input in any order. If a named argument is omitted entirely, the default value for that parameter is used. Conversely, if a value is entered sans named argument, a positional indexing scheme is used to match values with their (implied) named arguments.

Integer Vectors

Description: Creates or tests for objects of type "integer".

Usage:

integer(length = 0) as.integer(x, ...) is.integer(x)

Arguments:

length
a non-negative integer specifying the desired length. Double values will be coerced to integer: supplying an argument of length other than one is an error.

x
object to be coerced or tested.

... further arguments passed to or from other methods.

- So if I'm in the `R`-Console (a.k.a. REPL), and I enter `as.integer(x=pi)` I get the same return as `as.integer(pi)`
```r
> as.integer(x=pi)
[1] 3
> as.integer(pi)
[1] 3
> 

Wondering if there's a way to get @lizmat 's Raku example (and others, in general) to return the following?

dd Int.new(42);          # 42
dd Int.new(:value(42));  # 42
ab5tract commented 7 months ago

It's an interesting idea to potentially use named arguments to provide values to positionals that were not provided.

So in the case of

dd Int.new(42);          # 42
dd Int.new(:value(42));  # 42

42 would be grabbed from $value.value.

However this conflicts with some existing truisms:

The other option -- creating named argument variants for every single method and routine in CORE-setting -- seems to me to be way too large a project for what would be gained, but it wouldn't necessarily be impossible.

vrurg commented 7 months ago

It's an interesting idea

It's a horrible idea.

First of all, the code that implements this feature would have to live in signature binding. That means extra performance penalty paid on every routine call.

Second, there is no way to have it done at compile time because with dynamic dispatch we can't be sure that there would be no candidates with both positional and named matching.

Third, building the dispatch order is sufficiently complicated task already. I'm afraid, the feature would turn it into a complete mess.

niner commented 7 months ago

I wouldn't call it horrible. I think it's simply too late for a change like this. In Raku positional and named arguments are very different things. While I also sometimes wondered why the distinction has to be as strict as it is, the fact of the matter is, well, that it is. And since there is a strict distinction, they have gotten different properties (like positional defaulting to mandatory while nameds are optional by default and methods ignore unknown named arguments) and APIs have been designed around these properties.

Apart from the implementation side that Vadim mentioned (and that would indeed be horrible to get right) this feels like a change to the language that while it may look innocent and straight forward at first glance would change the language too drastically and violate culture that has been built on this nature. Working around this language design by adding lots of multi candidates to core methods would only be confusing. People would look at examples using core methods, see that each apparently doesn't care about whether you give it that positional or a named and conclude that this is just how the language works which would obviously be wrong.

ab5tract commented 7 months ago

It's an interesting idea

It's a horrible idea.

As a fan of horror, I don't think these two things are always mutually exclusive :)

That said, I expected strong and coherent arguments against it as provided by @vrurg and @niner. It's simply not adoptable at this stage of the game.

One more note regarding the alternative for documenting positional args as per my examples in #423 : you can reduce the visual overhead at the callsite by using no strict:

no strict; $object.method($arg1 = 55, $arg2 = 77);

This is admittedly another horrible idea, but one that might also be interesting to some ;)

niner commented 7 months ago

There's also inline comments:

$object.method(#`[arg1] 55, #`[arg2]  77)
gfldex commented 7 months ago

I would like the remind all interested parties, that extra named arguments are not ignored:

my method m(){}
&m.signature.say;
# (Mu: *%_)

They are stored in the implicite slurpy %_. The purpose is the ability to pass extra arguments on when an object is delegating to a different method or object by using |%_. Sadly, this is not used often in the wild.

jubilatious1 commented 7 months ago

@gfldex wrote:

I would like the remind all interested parties, that extra named arguments are not ignored...

Disturbing. Valid named arguments are ignored but random "extra" text input is accepted.

This is how the R-programming language does it:

https://cran.r-project.org/doc/manuals/r-release/R-intro.html#The-three-dots-argument

ab5tract commented 6 months ago

@gfldex wrote:

I would like the remind all interested parties, that extra named arguments are not ignored...

This is a valid point but one which I didn't want to muddy the discussion with mentioning it. I assume most other people in the discussion were also aware but were using the term "ignored" in the sense that superfluous named arguments in a method call do not cause the dispatch of that call to fail.

They are also ignored in the sense that one needs to manually do something with them in order for them to have any impact.

Additionally they are ignored culturally inasmuch as in general very few users seem to find this feature particularly helpful or necessary. Mostly it's encountered as a WAT when typos occur in named arguments while dispatch proceeds unconcernedly.

EDIT: It is also, of course, the feature that would enable my "diabolical dispatcher" ;)

Disturbing. Valid named arguments are ignored but random "extra" text input is accepted.

I'm not sure how to parse this phrasing. What do you mean by valid named arguments being ignored?

jubilatious1 commented 6 months ago

@ab5tract:

 ~ % raku
Welcome to Rakudo™ v2023.05.
Implementing the Raku® Programming Language v6.d.
Built on MoarVM version 2023.05.

To exit type 'exit' or '^D'
[0] > my %months = [Z=>] <Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec>, 1..12;
{Apr => 4, Aug => 8, Dec => 12, Feb => 2, Jan => 1, Jul => 7, Jun => 6, Mar => 3, May => 5, Nov => 11, Oct => 10, Sep => 9}
[1] > my regex D { ( <.alpha>**3 ) (\/)  (\d**1..2)  (\/) (\d**4) };
regex D { ( <.alpha>**3 ) (\/)  (\d**1..2)  (\/) (\d**4) }
[2] > say $/ if 'Jan/01/2024'.match(/ <D> /);
「Jan/01/2024」
 D => 「Jan/01/2024」
  0 => 「Jan」
  1 => 「/」
  2 => 「01」
  3 => 「/」
  4 => 「2024」
[2] > say S{^ <D> } = "$/.<D>.[4]-{sprintf q[%02d], %months{$/.<D>.[0]}}-$/.<D>.[2]".Date.mm-dd-yyyy given 'Jan/01/2024';
01-01-2024
[2] > say S{^ <D> } = "$/.<D>.[4]-{sprintf q[%02d], %months{$/.<D>.[0]}}-$/.<D>.[2]".Date.mm-dd-yyyy("/") given 'Jan/01/2024';
01/01/2024
[2] > say S{^ <D> } = "$/.<D>.[4]-{sprintf q[%02d], %months{$/.<D>.[0]}}-$/.<D>.[2]".Date.mm-dd-yyyy(sep => "/") given 'Jan/01/2024';
01-01-2024
[2] >

Elsewhere in this thread, @lizmat has asked if I want Raku to DWIM. Yes, if I take the time to explicitly write sep => "/" then I expect Raku to match up the named-argument in the signature with the value I've given it.

As it currently stands, Raku ignores "valid named-arguments" given in the form (example): .mm-dd-yyyy(sep => "/").

Cheers.

@pmichaud @thoughtstream @TimToady

coke commented 6 months ago

~As mentioned on another thread, tagging those three who were heavily involved 20 years ago is probably not helpful for getting your topic reviewed.~ Let's sort that out on the other thread

I am confused. Before lizmat's commit, why are you expecting sep to work as a named argument? The docs and the source both have it as a positional parameter (which is why your second example above works).

jubilatious1 commented 6 months ago

@coke , honestly this is the first I've seen/heard about @lizmat 's commit.

If this is something in the works, then thank you @lizmat !!

I'm honestly confused. If I tell a family member: "Please bring me the key-ring in the living room" and that person can't find it--then it's up to me to clarify. If I say "Please bring me the key-ring in the living room on the mantelpiece in the ceramic bowl" then I'd expect the additional specificity to give me a more successful outcome.

Same with programming. key-ring( location => "living-room", furniture => "mantlepiece", container => "ceramic-bowl") is waaaay more specific than key-ring($_), so I would expect a more specific result. Especially for functions/routines/methods with a single possible named-argument (e.g. sep in yyyy-mm-dd), I expect the language to be able to accept/understand sep => "/", etc.:

https://docs.raku.org/routine/yyyy-mm-dd

@coke , this is primarily my experience with another (non Perl-family) language. Here's an excerpt from a mailing-list email sent way back in 1993:

About a year ago Robert Gentleman and I considered the problem of obtaining decent statistical software for our undergraduate Macintosh lab. After considering the options, we decided that the most satisfactory alternative was to write our own. We started by writing a small lisp interpreter. Next we expanded its data structures with atomic vector types and altered its evaluation semantics to include lazy evaluation of closure arguments and argument binding by tag as well as order. Finally we added some syntactic sugar to make it look somewhat like S. We call the result "R".

https://blog.revolutionanalytics.com/2017/10/updated-history-of-r.html

It would seem self-evident some 30-odd years later that "lazy evaluation of closure arguments and argument binding by tag as well as order" is the right way to implement signatures (i.e. closure arguments).

Thank you for your time and attention.

@Util @doomvox @fecundf

coke commented 6 months ago

Apologies, I mistook this for "I understood it was intended to work this way", not "I would like it to work this way." In the current design, positional arguments and named arguments are two different things, and are not interchangeable.

https://docs.raku.org/language/signatures#Positional_vs%2E_named_arguments

pmichaud commented 6 months ago

I seem to recall (but my recollection may be faulty here) that when looking at the dispatch algorithm @jnthn and I decided that it wouldn't be feasible, or at least REALLY difficult, to have named arguments bind to positional parameters of the same name, especially in light of multiple dispatch, role composition, inheritance, etc.

Another challenge becomes that if implemented, the names of positional parameters effectively become part of the public interface/contract, which has implications for software evolution and iterations of any module or routine primarily using positional parameters.

So, my memory is that this is something was explicitly considered, discussed at length, and declined to support because of the performance, implementation, and software design concerns involved. Hope this bit of memory helps.

Pm