On 08-12-2020 12:46, Gabor Grothendieck wrote:> Duncan Murdoch: >> I agree it's all about call expressions, but they aren't all being >> treated equally: >> >> x |> f(...) >> >> expands to f(x, ...), while >> >> x |> `function`(...) >> >> expands to `function`(...)(x). This is an exception to the rule for > > Yes, this is the problem. It is trying to handle two different sorts of right > hand sides, calls and functions, using only syntax level operations and > it really needs to either make use of deeper information or have some > method that is available at the syntax level for identifying whether the > right hand side is a call or function. In the latter case having two > operators would be one way to do it. > > f <- \(x) x + 1 > x |> f() # call > x |:> f # function > x |:> \(x) x + 1 # function > > In the other case where deeper information is used there would only be one > operator and it would handle all cases but would use more than just syntax > level knowledge. > > R solved these sorts of problems long ago using S3 and other object oriented > systems which dispatch different methods based on what the right hand side is. > The attempt to avoid using the existing or equivalent mechanisms seems to have > led to this problem. >I think only allowing functions on the right hand side (e.g. only the |> operator and not the |:>) would be enough to handle most cases and seems easier to reason about. The limitations of that can easily be worked around using existing functionality in the language. The problem with only allowing x |> mean and not x |> mean() is with additional arguments. However, this can be solved with a currying function, for example: x |> curry(mean, na.rm = TRUE) The cost is a few additional characters. In the same way it is possible to write a function that accepts an expression and returns a function containing that expression. This can be used to have expressions on the right-hand side and reduces the need for anonymous functions. x |> fexpr(. + 10) dta |> fexpr(lm(y ~ x, data = .)) You could call this function .: x |> .(. + 10) dta |> .(lm(y ~ x, data = .)) Dummy example code (thanks to a colleague of mine) fexpr <- function(expr){ expr <- substitute(expr) f <- function(.) {} body(f) <- expr f } . <- fexpr curry <- function(fun,...){ L <- list(...) function(...){ do.call(fun, c(list(...),L)) } } `%|>%` <- function(e1, e2) { e2(e1) } 1:10 %>% mean c(1,3,NA) %|>% curry(mean, na.rm = TRUE) c(1,3,NA) %|>% .( mean(., na.rm = TRUE) ) %>% identity c(1,3,NA) %|>% .( . + 4) c(1,3,NA) %|>% fexpr( . + 4) c(1,3,NA) %|>% function(x) mean(x, na.rm = TRUE) %>% fexpr(. + 1) -- Jan
On 09/12/2020 9:55 a.m., Jan van der Laan wrote:> > I think only allowing functions on the right hand side (e.g. only the |> > operator and not the |:>) would be enough to handle most cases and seems > easier to reason about. The limitations of that can easily be worked > around using existing functionality in the language.I agree that would be sufficient, but I don't see how it makes reasoning easier. The transformation is trivial, so I'll assume that doesn't consume any mental energy compared to understanding what the final expression actually does. Using your currying example, the choice is between x |> mean(na.rm = TRUE) which transforms to mean(x, na.rm = TRUE), or your proposed x |> curry(mean, na.rm = TRUE) which transforms to curry(mean, na.rm = TRUE)(x) To me curry(mean, na.rm = TRUE)(x) looks a lot more complicated than mean(x, na.rm = TRUE), especially since it has the additional risk that users can define their own function called "curry". Duncan Murdoch> > The problem with only allowing > > x |> mean > > and not > > x |> mean() > > is with additional arguments. However, this can be solved with a > currying function, for example: > > x |> curry(mean, na.rm = TRUE) > > The cost is a few additional characters. > > In the same way it is possible to write a function that accepts an > expression and returns a function containing that expression. This can > be used to have expressions on the right-hand side and reduces the need > for anonymous functions. > > x |> fexpr(. + 10) > dta |> fexpr(lm(y ~ x, data = .)) > > You could call this function .: > > x |> .(. + 10) > dta |> .(lm(y ~ x, data = .)) > > > Dummy example code (thanks to a colleague of mine) > > > fexpr <- function(expr){ > expr <- substitute(expr) > f <- function(.) {} > body(f) <- expr > f > } > . <- fexpr > > curry <- function(fun,...){ > L <- list(...) > function(...){ > do.call(fun, c(list(...),L)) > } > } > > `%|>%` <- function(e1, e2) { > e2(e1) > } > > > 1:10 %>% mean > c(1,3,NA) %|>% curry(mean, na.rm = TRUE) > c(1,3,NA) %|>% .( mean(., na.rm = TRUE) ) %>% identity > c(1,3,NA) %|>% .( . + 4) > c(1,3,NA) %|>% fexpr( . + 4) > c(1,3,NA) %|>% function(x) mean(x, na.rm = TRUE) %>% fexpr(. + 1) > > -- > Jan > > ______________________________________________ > R-devel at r-project.org mailing list > https://stat.ethz.ch/mailman/listinfo/r-devel >