Giter VIP home page Giter VIP logo

dotdot's Introduction

dotdot

Installation :

devtools::install_github("moodymudskipper/dotdot")

This package proposes an improved assignment using the shorthand ...

library(dotdot)
x <- y <- iris
x$Sepal.Length[5] <- x$Sepal.Length[5] + 3
y$Sepal.Length[5] := .. + 3
identical(x,y)
#> [1] TRUE

z <- factor(letters[1:3])
levels(z) := c(.., "level4")
z
#> [1] a b c
#> Levels: a b c level4

You can think about the .. as the : of the := symbol laid horizontally.

Integration with data.table, tidyverse and other packages using :=

The operator := is used by prominent packages data.table and rlang (mostly through tidyverse functions), but they only use it to parse expressions, due to its convenient operator precedence. It's not actually called.

Thus dotdot is completely tidyverse and data.table compatible, and some adhoc adjustments were made so it even works when the latter are attached after dotdot.

library(data.table)
#> 
#> Attaching package: 'data.table'
#> The following object is masked _by_ 'package:dotdot':
#> 
#>     :=
levels(z) := c(.., "level5")
z
#> [1] a b c
#> Levels: a b c level4 level5
data <- as.data.table(head(iris,2))
data[,new_col := 3] # `:= ` works as if dotdot wasn't attached
data 
#>    Sepal.Length Sepal.Width Petal.Length Petal.Width Species new_col
#> 1:          5.1         3.5          1.4         0.2  setosa       3
#> 2:          4.9         3.0          1.4         0.2  setosa       3

An example of fine integration of the operator being used by dotdot and rlang through dplyr

library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:data.table':
#> 
#>     between, first, last
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
my_data_frame <- iris[3:5]
var = quo(Log.Petal.Width)
my_data_frame := .. %>% mutate(!!var := log(Petal.Width)) %>% head(2)
my_data_frame
#>   Petal.Length Petal.Width Species Log.Petal.Width
#> 1          1.4         0.2  setosa       -1.609438
#> 2          1.4         0.2  setosa       -1.609438

In case you've attached another package containing :=, you can use dotdot_first() to make sure that our := is not masked (It seems to be rare enough though as I couldn't find an example)

Comparison with magrittr's %<>%

The package magrittr contains the operator %<>% which serves a similar role to :=. Let's see how it is similar first, and then how it differs :

These calls have the same effect:

iris$Sepal.Length %<>% log
iris$Sepal.Length %<>% log(.)
iris$Sepal.Length := log(..)

Those as well, but here we see magrittr is less compact and readable.

iris$Sepal.Length[5] %<>% multiply_by(2) %>% add(3)
iris$Sepal.Length[5] %<>% {2*. + 3}
iris$Sepal.Length[5] := 2*.. + 3

Now for the differences, aside from compacity and readability :

  • attaching magrittr means often masking functions likes extract or set_names. dotdot only exports its operator and the dotdot_first function.
  • magrittr operators deal with environment in a way that is much less straightforward, so this won't work :
library(magrittr)
test <- function(some_parameter) {
  some_parameter %<>% {as.character(substitute(.))}
  some_parameter
  }
x <- try(test(foo))
#> Error in eval(lhs, parent, parent) : objet 'foo' introuvable
inherits(x,"try-error")
#> [1] TRUE

While this will work fine:

test <- function(some_parameter) {
  some_parameter := as.character(substitute(..))
  some_parameter
  }
test(foo)
#> [1] "foo"
  • := is also faster than %<>% , though these operations are fast anyway and not likely to be a bottleneck very often if ever:
 b <- x <- y <- z <- 1
 microbenchmark::microbenchmark(
   base      = {b <- b + 1},
   dotdot    = {x := .. + 1},
   magrittr  = {y %<>% add(1)},
   magrittr2 = {z %<>% {. + 1}},
   times = 1e4
)
#> Unit: nanoseconds
#>       expr   min    lq       mean median    uq     max neval  cld
#>       base   200   302   493.4608    401   501   36501 10000 a   
#>     dotdot 10001 11802 14891.2715  12901 14001 2888200 10000  b  
#>   magrittr 61001 63502 79046.3541  65301 69700 4641102 10000    d
#>  magrittr2 45601 47801 58116.3759  49100 51701 3128902 10000   c

Edge cases and good practice

:= is NOT meant to be a complete replacement of the <- operator, the latter is explicit in the absence of .. , so more readable, is faster (though we're speaking microseconds), and won't clutter your traceback() when debugging.

:= can be used several times in a statement like z <- (x := .. + 1) + (y:= .. +1) but it never makes sense to use it := several times in an assignment such as x := (y := .. + 2) as all the .. will be replaced by the name of the variable on the lhs of the first evaluated := in any case. It can even produce counter intuitive output, see below.

This is all good and explicit :

x <- 4
y <- 7
z <- (x := .. + 1) + (y:= .. +1)
x
y
z

But using several nested := is unuseful and potentially confusing, here the dots will be replaced by x, though one might have expected them to be replaced by y.

x <- 4
y <- 7
x := (y := .. + 2) # same as `x <- (y := x + 2)` 
x
y

Good practice makes things unambiguous :

x <- 4
y <- 7
x <- (y := .. + 2)
x
y

x <- 4
y <- 7
x := (y <- .. + 2)
x
y

dotdot's People

Contributors

moodymudskipper avatar

Stargazers

Jimmy Briggs avatar Benjamin Guinaudeau avatar Russell Dinnage avatar Ryan Holbrook avatar Antoine Bichat avatar muuk avatar Neal Fultz avatar Hongyuan Jia avatar Sono Shah avatar Luke Smith avatar Artem Sokolov avatar John Coene avatar  avatar Nicholas Tierney avatar  avatar Lluís Revilla avatar Jonathan Carroll avatar

Watchers

James Cloos avatar Ajay Mehta avatar  avatar  avatar

dotdot's Issues

Integrate `%.%`, maybe `%L.%` ?

fastpipe seems to address a need, or at least makes people curious, but releasing on CRAN is not a good idea as it conflicts with magrittr (possibly CRAN would refuse it in fact).

But renaming %>>% as %.% and including it in dotdot seems to make sense. := can be seen as a fast generalized %<>%, and %.% a fast generalized %>%.

Because users seemed to like %L>% the fast version might be included as %L.% in dotdot too.

%L>% (using insert_dot()) might be included too but I'm afraid it leads to confusion because it won't be recognized by functional chains

clearer code for `:=`

`:=` <-function (e1, e2) {
  mc <- match.call()
  mc[[1]] <- quote(.Primitive("<-"))
  mc[[3]] <- do.call(substitute, list(mc[[3]], list(.. = mc[[2]])))
  eval.parent(mc)
}

dotdot usage inside `[`?

This is unrelated to assignment and probably out-of-scope for this package, but is it possible to subset with ..? E.g.:

mtcars$mpg[..>20]

Instead of:

mtcars$mpg[mtcars$mpg>20]

`.. :=` taking last call as lhs ?

Could be nice for interactive use but might slow down the function in regular use, though should be minor.

# https://stackoverflow.com/questions/30552847/r-last-call-feature-similar-to-last-value
last.call <- function(n=2) {
        
        f1 <- tempfile()
        try( savehistory(f1), silent=TRUE ) 
        try( rawhist <- readLines(f1), silent=TRUE )
        unlink(f1)
        
        if( exists('rawhist') ) { 
            
            # LOOK BACK max(n)+ LINES UNTIL YOU HAVE n COMMANDS 
            cmds <- expression() 
            n.lines <- max(abs(n)) 
            while( length(cmds) < max(abs(n)) ) { 
                lines <- tail( rawhist, n=n.lines )
                try( cmds <- parse( text=lines ), silent=TRUE ) 
                n.lines <- n.lines + 1 
                if( n.lines > length(rawhist) ) break 
            }
            ret <- rev(cmds)[n] 
            if( length(ret) == 1 ) return(ret[[1]]) else return(ret) 
            
        }
        
        return(NULL)
        
    }


`:=` <- function (e1, e2) {
  mc <- match.call()
  mc[[1]] <- quote(.Primitive("<-"))
  if(mc[[2]] == quote(`..`))
    mc[[2]] <- eval(substitute(substitute(x, list(.. = last.call()[[2]])),
                    list(x = mc[[2]])))
  mc[[3]] <- eval(substitute(substitute(e2, list(.. = mc[[2]])), 
                             list(e2 = mc[[3]])))
  eval.parent(mc)
}

iris2 <- head(iris)
iris2$Species := as.character(..)
.. := toupper(..)

This is just a proof of concept as it doesn't work used several times in a raw. The history should be parsed and explored until an assignment is found with a lhs different from ..

Multiple := in an expression leaves .. unclear.

What is the expected result of:

> x=4 ; y=7; x := (y := .. + 2)

does the .. refer to y or x?

> x
[1] 6
> y
[1] 6

x.

But it means the part of the expression y := .. + 2 is now horribly dependent on an outside context, namely the value of x, or possibly even something further away.

Better to avoid .. and be explicit with whichever of these is wanted:

> x=4 ; y=7; x := (y := y + 2)
> x=4 ; y=7; x := (y := x + 2)

I would think this is also a bit unobvious:

> x=4 ; x := (function(z){z=3;z :=..;z})(..)
> x
[1] 4

since even the .. inside the function evaluates to the LHS of the outside :=. Compare:

> x=4 ; x := (function(z){z=3;z})(..)
> x
[1] 3

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.