Giter VIP home page Giter VIP logo

zeallot's Introduction

zeallot

Variable assignment with zeal!

alt text alt text

What's there to be excited about?

zeallot allows multiple, unpacking, or destructuring assignment in R by providing the %<-% operator. With zeallot you can tighten code with explicit variable names, unpack pieces of a lengthy list or the entirety of a small list, destructure and assign object elements, or do it all at once.

Unpack a vector of values.

c(x, y) %<-% c(0, 1)
#> x
#[1] 0
#> y
#[1] 1

Unpack a list of values.

c(r, d) %<-% list(2, 2)
#> r
#[1] 2
#> d
#[1] 2

Destructure a data frame and assign its columns.

c(duration, wait) %<-% head(faithful)

#> duration
#[1] 3.600 1.800 3.333 2.283 4.533 2.883
#> wait
#[1] 79 54 74 62 85 55

Unpack a nested list into nested left-hand side variables.

c(c(a, b), c(c, d)) %<-% list(list(1, 2), list(3, 4))
#> a
#[1] 1
#> b
#[1] 2
#> c
#[1] 3
#> d
#[1] 4

Destructure and partially unpack a list. "a" is assigned to first, but "b", "c", "d", and "e" are grouped and assigned to one variable.

c(first, ...rest) %<-% list("a", "b", "c", "d", "e")
first
#[1] "a"
rest
#[[1]]
#[1] "b"
#
#[[2]]
#[1] "c"
#
#[[3]]
#[1] "d"
#
#[[4]]
#[1] "e"

Installation

You can install zeallot from CRAN.

install.packages("zeallot")

Use devtools to install the latest, development version of zeallot from GitHub.

devtools::install_github("nteetor/zeallot")

Getting Started

Below is a simple example using the purrr package and the safely function.

Safe Functions

The purrr::safely function returns a "safe" version of a function. The following example is borrowed from the safely documentation. In this example a safe version of the log function is created.

safe_log <- purrr::safely(log)
safe_log(10)
#$result
#[1] 2.302585
#
#$error
#NULL

safe_log("a")
#$result
#NULL
#
#$error
#<simpleError in .f(...): non-numeric argument to mathematical function>

A safe function always returns a list of two elements and will not throw an error. Instead of throwing an error, the error element of the return list is set and the value element is NULL. When called successfully the result element is set and the error element is NULL.

Safe functions are a great way to write self-documenting code. However, dealing with a return value that is always a list could prove tedious and may undo efforts to write concise, readable code. Enter zeallot.

The %<-% Operator

With zeallot's unpacking operator %<-% we can unpack a safe function's return value into two explicit variables and avoid dealing with the list return value all together.

c(res, err) %<-% safe_log(10)
res
#[1] 2.302585
err
#NULL

The name structure of the operator is a flat or nested set of bare variable names built with calls to c(). . These variables do not need to be previously defined. On the right-hand side is a vector, list, or other R object to unpack. %<-% unpacks the right-hand side, checks the number of variable names against the number of unpacked values, and then assigns each unpacked value to a variable. The result, instead of dealing with a list of values there are two distinct variables, res and err.

Further Reading and Examples

For more on the above example, other examples, and a thorough introduction to zeallot check out the vignette on unpacking assignment.

Below are links to discussions about multiple, unpacking, and destructuring assignment in R,

Related work

The vadr package includes a bind operation with much of the same functionality as %<-%. As the author states, "[they] strongly prefer there to be a <- anywhere that there is a modification to the environment." If you feel similarly I suggest looking at vadr. Unfortunately the vadr package is not on CRAN and will need to be installed using devtools::install_github().


Thank you to Paul Teetor for inspiring me to build zeallot.

Without his encouragement nothing would have gotten off the ground.

zeallot's People

Contributors

pteetor avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

zeallot's Issues

pick a syntax

A decision needs to be made regarding the syntax allotalot will use.

The original syntax used a . function to produce the name structure on the left-hand side of the assignment expression,

.(a, b) %<-% list(0, 1)
.(a, .(b, c)) %<-% list(0, list(1, 2))

The new syntax uses :'s as a means of listing bare names and {'s as a means of nesting names,

a: b %<-% list(0, 1)
{a: b} %<-% list(0, 1)

a: {b: c} %<-% list(0, list(1, 2))
{a: {b: c}} %<-% list(0, list(1, 2))

The new syntax is thought of as lighter and thus a better option than the original syntax. However, if the new syntax obfuscates assignment expressions the original syntax will be used. If neither syntax is clear enough, a new, third, syntax may be proposed.

collector variable unintentionally unpacks default value

See following examples,

c(a, ...b = list()) %<-% c(1)
# b = list() ? Nope, NULL

c(d, ...e = list(2)) %<-% c(1)
# e = list(2) ? Nope, 2

We would expect both a and d to have a value of 1 and they do. However, b has a NULL value and e has a value of 2. The pair_off variable needs to indicate that a collector variable has a default. Alternatively, collector variable handling needs to be moved out of the multi_assign function and get moved to the pair_off function.

Inconsistent error messages

library(zeallot)
{x} %<-% list(1, 2)
#> Error: too many values to unpack
{x : y : z : a } %<-% list(1, 2)
#> Error: expecting 4 values, but found 2

Move `master` branch to `main`

The master branch of this repository will soon be renamed to main, as part of a coordinated change across several GitHub organizations (including, but not limited to: tidyverse, r-lib, tidymodels, and sol-eng). We anticipate this will happen by the end of September 2021.

That will be preceded by a release of the usethis package, which will gain some functionality around detecting and adapting to a renamed default branch. There will also be a blog post at the time of this master --> main change.

The purpose of this issue is to:

  • Help us firm up the list of targetted repositories
  • Make sure all maintainers are aware of what's coming
  • Give us an issue to close when the job is done
  • Give us a place to put advice for collaborators re: how to adapt

message id: euphoric_snowdog

looking ahead to version 0.0.5

In one month's time remove old syntax, pull changes from error-clean-up branch, bump version to 0.0.5, and submit to CRAN again.

add set of tests for simple expected use cases

The tests file for the assignment operator extensively tests the function. But! since the file is long, writing a second test file to outline and test the expected usage and result of %<-% would be quite useful.

update intro vignette for smiley syntax

  • update code chunks
  • update existing sections
  • add new section highlighting the syntax

While the code chunks in the introductory vignette have been updated and use the new syntax, the existing sections still reference . and need fixing. Adding a section to highlight and explain the new syntax is also necessary.

I don’t understand destructuring

Why does data frame need a method? That seems weird to me (especially since the default method throws an error - how do regular lists get handled?)

Assign multiple types?

####Neither the README nor unpacking assignment vignette make it explicit you can use %<-% to assign different types in one expression. I ran the following experiment and it appears to be the case.

library(zeallot)

c(a, b, c, d, e) %<-% list(1.0, 1L, "1", factor(1), data.frame(x = 1))
lapply(list(a = a, b = b, c = c, d = d, e = e), class)
#> $a
#> [1] "numeric"
#> 
#> $b
#> [1] "integer"
#> 
#> $c
#> [1] "character"
#> 
#> $d
#> [1] "factor"
#> 
#> $e
#> [1]  #"data.frame"

Is this expected behavior for how %<-% operates? If so, it should be made more clear.

Better tracebacks with rlang

I've seen a number of people mention the verbose traceback when using %<-%. Using rlang's trace_back() would cut down on spurious calls by adjusting the bottom of the trace.

remove tests for old operator

There are a couple tests which check if the old syntax is still caught. These need to be removed as the old syntax is being phased out.

README typo

There's a small typo in the final example code snippet. The <- needs to be %<-%.

Could an second variation on the operator be created so it doesn't with %<-% from future

Since %<-% is also used by future and the usage of namespaces with operators is a little messy zeallot::%<-%(c(a, b), c(1, 2)) to differentiate between the different overrides of the operator, could a second alternative variation be created. It's a really useful operator, adding one of the pieces of functionality that r misses vs python. For my own use I've cloned and changed the operator to %<-c%, but I guess anything would work.

...x can be empty

> c(...x, y) %<-% list(3)
Error: invalid `%<-%` right-hand side, incorrect number of values

Can we have a feature where x can be empty?

right operator

Add %->% operator.

c(1, 2) %->% c(x, y)

Currently magrittr chains must be wrapped in parentheses, see issue tidyverse/magrittr#137.

c(x, ...rest) %<-% (
  1:5 %>%
  lapply(function(i) i + 5)
)

A right operator would allow us to drop the parentheses and give the chain a cleaner look.

1:5 %>%
  lapply(function(i) i + 5) %->%
  c(x, ...rest)

%<=%

A thought: Instead of %<-%, how about %<=%?

Advantages:

  • the two strokes in = (versus single stroke in -) visually reflects the notion of multiple (versus single) assignment
  • won't conflict with the implicit-future construct in the future package

(Could %<=% introduce a worse clash than that with future::`%<-%`?)

Formally support and test R 3.1 and greater?

This basically just requires adding this to your travis:

r:
- 3.1
- 3.2
- oldrel
- release
- devel

And this to DESCRIPTION:

Depends:
    R (>= 3.1)

And then fixing any small issues that arise.

Then I would feel confident about re-exporting %<-% from purrr.

%<-% and %>%

Would it be possible to make %<-% work with %>% chains on the left hand side?

Destructuring Assignment by Name

In Javascript we can do:

var o = {p: 42, q: true};
var {p: foo, q: bar} = o;
 
console.log(foo); // 42 
console.log(bar); // true

let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}
a; // 10 
b; // 20 
rest; // { c: 30, d: 40 }

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring

I realize that in R, while named list resembles a hash map, it is indeed ordered.

Maybe something like this?

list(a, b) %<-% list(b = 2, a = 1)
# a = 1 
# b = 2

improving traceback of rhs errors

Run the following example and you will find that traceback() is pretty unhelpful.

library(zeallot)
c(foo, bar) %<-% list(log("foo"), "bar")
traceback()

Explore ways to make this better, if possible.

Alternative syntax?

I think c(a, b, c) <- f() would be a nice alternative to {a : b : c} <- f().

A named unpacking function sans %->%?

I really like this example from the vignette:

mtcars %>%
  subset(hp > 100) %>%
  aggregate(. ~ cyl, data = ., FUN = . %>% mean() %>% round(2)) %>%
  transform(kpl = mpg %>% multiply_by(0.4251)) %->% 
  c(cyl, mpg, ...rest)

But I think it would be cool, readable, and writable if we could do something like this:

mtcars %>%
  subset(hp > 100) %>%
  aggregate(. ~ cyl, data = ., FUN = . %>% mean() %>% round(2)) %>%
  transform(kpl = mpg %>% multiply_by(0.4251)) %>% 
  unpack(cyl, mpg, ...rest)

collector variables and vectors

When collecting values from a vector, the collected values are returned as a list.

a: ...rest %<-% 1:5
a
#> 1
rest
#> list(2, 3, 4, 5)

I would expect rest to equal 2:5, but currently rest is assigned list(2, 3, 4, 5).

Provide partition

Which would work like combination of head and tail - would be a good demo

Can we get %<->%?

What do you think of an operator that's a combination of magritter::`%<>%` and zeallot::`%<-%`

example usage might be

x <- letters[1:4]
y <-  c("w", "x", "y", "z")
f <- c(1,1,2,2)

c(x, y) %<->% lapply(z, function(z) split(z, f))

I'm imagining that the operator would take the left hand side c(x,y) and behind the scenes would call list(x = x, y = y) and pass that list as the first argument to the right hand side function as ., with standard magrittr rules for . placement and evaluation.

Support for partial assignment

list from the gsubfn package supports assigning only some return values while dropping others:

# assign Green and Blue (but not Red) components
list[ , Green, Blue]  <- col2rgb("aquamarine")

Can such functionality be implemented in zeallot as well?

As a work-around, I can assign unused return arguments to dummy variables. But as direct assignment of multiple return values is syntactic sugar, I would prefer as sweet a syntax as possible for the price of non-standard assignment and the extra package dependency.

Regards
Christian

add rest prefix ("...") to smiley syntax

The rest prefix will allow a variable to consume part of the list of values. In the example below "..." is used to consume the remaining elements of the list,

a: ...b %<-% list(1, 2, 3, 4, 5)
a  # 1
b  # list(2, 3, 4, 5)

The rest prefix may also be used to consume a portion of values,

a: ...b: c %<-% list(1, 2, 3, 4, 5)
a  # 1
b  # list(2, 3, 4)
c  # 5

Assign to existing lists?

Great package, I've missed this a lot after python/haskell.

Just tried something crazy and found it didn't work:

l <- some_existing_list
c(l$a, l$b, l$c) %<-% some_func(x, y)

But it would be nice to be able to assign to absolutely anything on the left hand side without using intermediary variables, no idea how hard that is to implement!

Improve step-in functionality during debugging

First of all, I really love zeallot! Simple, basic, coherent, very useful.

However, during debugging in e.g. RStudio I often step through code and step into functions, which is easily done just pressing Alt+F4. However, if I want to step into function f below I enter into a bunch of tryCatch statement and it is very hard to reach into f(x).

c(a, b) %<-% f(x)

Would it be possible to simplify the function chain so that one quickly enters the internals of f(x)? Or another way that I can enter into f(x)? I would prefer not having to stop the program, enter a browser() statement and rerun, as I often do now when debugging my Shiny app.

no visible binding for global variable for CRAN check

when using zeallot, package can't pass CRAN check:

c(d_fit, info_peak) %<-% rough_fitting(INPUT)
# checking R code for possible problems ... NOTE
opt_season: no visible binding for global variable 'd_fit'
opt_season: no visible binding for global variable 'info_peak'

Undefined global functions or variables:
d_fit info_peak

Any suggestions?

First example of vignette returning an error

The first example of the vignette
c(lat, lng) %<-% list(38.061944, -122.643889)
returns
Erreur : unexpected call c
I'm using MacOS X Sierra and R version 3.3.3 (same error with 3.3.2)

!!! tidy eval in LHS of %<-% ?

Something like this:

assign all dataframe column as variables to the current environment
c(!!! names(mtcars)) %<-% mtcars

Multiple assignment of ggplot2 objects

Nice package, thanks for bringing multiple assignments into R I like that feature of python.

I have read this issue thomasp85/patchwork#32, and I tried to create a plot and then use multiple assignments to reorder the appearance using the mentioned package. However %<-% couldn't would it be possible to work like this:

c(setosa, versicolor, virginica) %<-% ggplot(iris) + 
    geom_point(aes(Sepal.Length, Sepal.Width)) + 
    facet_wrap(~Species)
Error: invalid `%<-%` right-hand side, incorrect number of values

Maybe the issue is how to handle gg and ggplot classes, but I am not enough familiar with gg classes to propose a solution (or to know if it is feasable).

Again thanks
(I was using the version of zeallot from CRAN, zeallot_0.1.0 ggplot2_2.2.1)

Working in a for loop

It would be nice to get multi assign in for loops also (similar to tuple unpacking in python). Here is a mockup implementation. The syntax of this is kinda clunky, it would be nicer if it better mirrored the semantics of an actual R for loop, but that will require greater R jedi skills than I currently posses.

library(zeallot)
pfor <- function(loop_construct, expr) {
  loop_construct <- substitute(loop_construct)
  expr <- substitute(expr)
  loop_list <- eval(loop_construct[[3]], parent.frame())
  for(i in seq_along(loop_list)) {
    loop_construct[[3]] <- loop_list[[i]]
    eval(loop_construct, envir = parent.frame())
    eval(expr, envir = parent.frame())
  }
}

pfor (c(a, b) %<-% list(list(1, 2), list(3, 4)),
  print(c(a, b)))
#> [1] 1 2
#> [1] 3 4

Created on 2018-02-22 by the reprex package (v0.2.0).

collecting the empty list with collector variables

@pteetor , right now the following syntax throws an error,

{a : ...b} %<-% list(1)
#> Error: cannot de-structure numeric 

Would assigning list() to b instead of throwing a be a benefit?

The syntax would then work as follows,

{a : ...b} %<-% list(1)
a
#> 1
b
#> list()

Off the top of my head I could see this proving useful for base cases.

WHILE this, rest %<-% CALL() {
    IF rest IS EMPTY {
        // base case
    }
    // else case
}

Safe functions in purrr suggest this syntax is unnecessary because the programmer never has to worry about only getting one value back. There are always two values returned, but one is NULL. A secondary vignette outlining how to write functions with the %<-% operator in mind might be better than adjusting the syntax.

`is_list` util raises warning

Came up during this example,

library(rlang)
c(expr, env) %<-% quo(foo)

I expect an error, but the warning is not expected and is a bug. The is_list() util should check that the first (and only?) class is "list".

Set := to %<-%

Example

`:=` <- zeallot::`%<-%`

c(a, b) := c("this", "that")

a
# [1] "this"
b
# [1] "that"

There is no right-hand equivalent, but the := is a valid infix operator and would save a bit of typing. As an example, packages rlang and data.table export their own defined := infix operator.

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.