From m|kkm@rt @end|ng |rom protonm@||@com  Thu Jan 11 01:29:58 2024
From: m|kkm@rt @end|ng |rom protonm@||@com (mikkmart)
Date: Thu, 11 Jan 2024 00:29:58 +0000
Subject: [Rd] New syntax for positional-only function parameters?
In-Reply-To: <754821DB-5E12-4AA7-BAF9-B2388DFC9595@gmail.com>
References: <w-ucjjtNdxQhl8Zu_e2FPoUCgc_P2TH5EXER3li56cz76Zt6YjohZrns85_ZaNLITdRwiSkqPGN7tPmheYu-N4kZNX0MhRtgE64ysN8C6Zc=@protonmail.com>
 <754821DB-5E12-4AA7-BAF9-B2388DFC9595@gmail.com>
Message-ID: <kKmFW-reimfGaOU1E8hJgbAcdp-n2nTZ3XUKakDjUBAaeIbVNG1AHp7etcbCCu37cPzhjMGjoipqNKvT5T50uh1CP3HT8OJaRY3nEW2VCwM=@protonmail.com>

Thanks Aidan and Ivan,

> Could you give a little more detail on this [...]? [...] Typically, the
> default scoping rules are sufficient to resolve these [...].

I agree these conflicts can be solved when spotted. And certainly more easily
so if there were a dedicated currying syntax in base R as Ivan mentioned.
However I think both users and package authors would benefit from being able
to prevent the collisions altogether.

To collect some data on the prevalence, I analyzed the 387 installed packages
on my machine, including 23 433 functions. Of those, 2 585 (11%) both accepted
... and had a "mangled" first argument name (one that did not start with a
lower case letter), indicating that the function might have benefited from
the availability of a positional-only parameter syntax.

> This is realistic to implement. In addition to changes in gram.y (or,
> perhaps, to the declare() special interface for giving extra instructions to
> the parser that was suggested for declaring arguments for NSE) to mark the
> formals as positional-only, the argument matching mechanism in
> src/main/match.c:matchArgs_NR will need to be changed to take the flag
> into account.

Thanks, Ivan, for the pointers. Following them I was able to put together a...
let's say proof of concept patch for this, included below. With the patch[1]
we indeed have for example:

g <- function(x, f, /, ...) match.call()
g(1, f, x = 2) == quote(g(1, f, x = 2))

Or:

my_lapply <- function(x, f, /, ...) {
  res <- vector("list", length(x))
  for (i in seq_along(x)) {
    res[[i]] <- f(x[[i]], ...)
  }
  res
}

add <- function(x, y) x + y
my_lapply(1:5, add, x = 1)

Best wishes,

Mikko

[1]: Compiled with `RUN_BISON=1 make all recommended` on Windows, as it took
  me a painful while to figure out.

Index: src/main/gram.y
===================================================================
--- src/main/gram.y     (revision 85797)
+++ src/main/gram.y     (working copy)
@@ -557,6 +557,7 @@
 formlist:                                      { $$ = xxnullformal(); }
        |       SYMBOL                          { $$ = xxfirstformal0($1);      modif_token( &@1, SYMBOL_FORMALS ) ; }
        |       SYMBOL EQ_ASSIGN expr_or_help   { $$ = xxfirstformal1($1,$3);   modif_token( &@1, SYMBOL_FORMALS ) ; modif_token( &@2, EQ_FORMALS ) ; }
+       |       formlist ',' '/'                { $$ = xxaddformal0($1,$3, &@3);   modif_token( &@3, SYMBOL_FORMALS ) ; }
        |       formlist ',' SYMBOL             { $$ = xxaddformal0($1,$3, &@3);   modif_token( &@3, SYMBOL_FORMALS ) ; }
        |       formlist ',' SYMBOL EQ_ASSIGN expr_or_help
                                                { $$ = xxaddformal1($1,$3,$5,&@3); modif_token( &@3, SYMBOL_FORMALS ) ; modif_token( &@4, EQ_FORMALS ) ;}
Index: src/main/match.c
===================================================================
--- src/main/match.c    (revision 85797)
+++ src/main/match.c    (working copy)
@@ -185,10 +185,13 @@
 {
     Rboolean seendots;
     int i, arg_i = 0;
+    int nfargposonly = 0;
     SEXP f, a, b, dots, actuals;

     actuals = R_NilValue;
     for (f = formals ; f != R_NilValue ; f = CDR(f), arg_i++) {
+       /* Get count of positional-only formal arguments */
+       if (TAG(f) == Rf_install("/")) nfargposonly = arg_i + 1;
        /* CONS_NR is used since argument lists created here are only
           used internally and so should not increment reference
           counts */
@@ -218,6 +221,7 @@
     a = actuals;
     arg_i = 0;
     while (f != R_NilValue) {
+       if (arg_i >= nfargposonly) {
       SEXP ftag = TAG(f);
       const char *ftag_name = CHAR(PRINTNAME(ftag));
       if (ftag != R_DotsSymbol && ftag != R_NilValue) {
@@ -241,6 +245,7 @@
                  }
              }
            }
+         }
        }
        f = CDR(f);
        a = CDR(a);
@@ -257,7 +262,7 @@
     a = actuals;
     arg_i = 0;
     while (f != R_NilValue) {
-       if (fargused[arg_i] == 0) {
+       if (fargused[arg_i] == 0 && arg_i >= nfargposonly) {
            if (TAG(f) == R_DotsSymbol && !seendots) {
                /* Record where ... value goes */
                dots = a;
@@ -310,6 +315,10 @@
            seendots = TRUE;
            f = CDR(f);
            a = CDR(a);
+       } else if (TAG(f) == Rf_install("/")) {
+           /* Ignore positional-only marker */
+           f = CDR(f);
+           a = CDR(a);
        } else if (CAR(a) != R_MissingArg) {
            /* Already matched by tag */
            /* skip to next formal */
Index: src/main/unique.c
===================================================================
--- src/main/unique.c   (revision 85797)
+++ src/main/unique.c   (working copy)
@@ -1919,10 +1919,14 @@

     /* Attach the argument names as tags */

-    for (f = formals, b = rlist; b != R_NilValue; b = CDR(b), f = CDR(f)) {
-       SET_TAG(b, TAG(f));
+       int nfargposonly = 0, arg_i = 0;
+    for (f = formals ; f != R_NilValue ; f = CDR(f), arg_i++) {
+       if (TAG(f) == Rf_install("/")) nfargposonly = arg_i + 1;
     }

+    for (f = formals, b = rlist, arg_i = 0; b != R_NilValue; b = CDR(b), f = CDR(f), arg_i++) {
+       if (arg_i >= nfargposonly) SET_TAG(b, TAG(f));
+    }

     /* Handle the dots */



[Rd] New syntax for positional-only function parameters?
mikkmart