5 min read

the dot symbol

The dot . appears in different places in the R ecosystem: e.g. purrr, magrittr’s %>%. I will explore and explain what happens if you mix these usages, or nest them, how the dot symbol is special and how it is not.

Basic usage

%>% of magrittr

You should use the dot if the parameter you pipe forward is not the first parameter of your next function or if you use pipe with data.table and use [] function.

lipsum::lipsum(1)%>% 
    stringi::stri_trans_totitle() %>% 
    gsub(' ', '-', .)
## Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
## character(0)

You can also refer to the parameter with . if it is the first, but then you do not have to:

lipsum::lipsum(1) %>%
    stringi::stri_trans_totitle(.) %>% 
    stringr::str_replace_all(., ' ', '-')
## Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
## character(0)

Map of purrr

calendar_consts <- list(
    'num_hours_in_day' = 24,
    'num_days_in_week' = 7
)
map_chr(names(calendar_consts), ~ stringr::str_c(., ': ', calendar_consts[[.]]))
## [1] "num_hours_in_day: 24" "num_days_in_week: 7"

For compact anonymous functions the formula notation with . is a shorthand for the more verbose following known from the base R apply family.

map_chr(
    names(calendar_consts),
    function(name) {stringr::str_c(name, ': ', calendar_consts[[name]])}
)
## [1] "num_hours_in_day: 24" "num_days_in_week: 7"

Of course defining your function outside the call to map is always possible and preferable for more complex functions.

Nested usage

Map within map

consts <- list(
    'calendar' = calendar_consts,
    'geo' = list(
        'num_continents' = 7,
        'num_states_in_US' = 50
    )
)
map(
    consts,
    ~{
        sub_list <- .
        map_chr(
        names(sub_list),
        ~ stringr::str_c(., ': ', sub_list[[.]])
    )}
)
## $calendar
## [1] "num_hours_in_day: 24" "num_days_in_week: 7" 
## 
## $geo
## [1] "num_continents: 7"    "num_states_in_US: 50"

. is actually not that different from ordinary variable names which means that scoping rules apply as usual. In the inner if you refer to . it means the current element in the inner map. You can either save the outer current element to a variable or define the desired environment for your variable name.

map(
    consts,
    ~ map_chr(
        names(.),
        ~ stringr::str_c(., ': ', parent.env(environment())$'.'[[.]])
    )
)
## $calendar
## [1] "num_hours_in_day: 24" "num_days_in_week: 7" 
## 
## $geo
## [1] "num_continents: 7"    "num_states_in_US: 50"

While the above is possible I do not recommend it as it is difficult to read. You could instead do the following:

pasteConstNamesAndValues <- function(const_list) {
    map_chr(
        names(const_list),
        ~ stringr::str_c(., ': ', const_list[[.]])
    )  
}
map(consts, pasteConstNamesAndValues)
## $calendar
## [1] "num_hours_in_day: 24" "num_days_in_week: 7" 
## 
## $geo
## [1] "num_continents: 7"    "num_states_in_US: 50"

Pipe within pipe

I could not came up with a realistic usage for this as pipe is exactly for avoiding nesting…

Mixed usage

Pipe in map

map_chr(
    names(calendar_consts),
    ~ {
        stringr::str_replace_all(., '_', ' ') %>% 
            stringr::str_c(': ', calendar_consts[[.]]) 
    }
)
## [1] "num hours in day: " "num days in week: "

The above does not work as intended as . in calendar_consts[[.]] refers to the variable forwarded by %>%, in this case the already transformed variable name. Luckily we can refer to the current element in map with .x as well.

map_chr(
    names(calendar_consts),
    ~ {
        stringr::str_replace_all(., '_', ' ') %>% 
            stringr::str_c(': ', calendar_consts[[.x]]) 
    }
)
## [1] "num hours in day: 24" "num days in week: 7"

Map in pipe

names(calendar_consts) %>% 
    map(., ~ stringr::str_c(., ': ', calendar_consts[[.]]))
## [[1]]
## [1] "num_hours_in_day: 24"
## 
## [[2]]
## [1] "num_days_in_week: 7"

What if I want to use the forward-piped object inside the body of map, not just mapping over it? Then the two . symbols will really conflict. We can avoid this by extracting the map into a named function:

pasteWithSeparators <- function(const_list, separators) {
    map(
        names(const_list),
        ~ stringr::str_c(., separators, const_list[[.]])
    ) 
}
c(': ', ' -- ') %>% 
    pasteWithSeparators(calendar_consts, .)
## [[1]]
## [1] "num_hours_in_day: 24"   "num_hours_in_day -- 24"
## 
## [[2]]
## [1] "num_days_in_week: 7"   "num_days_in_week -- 7"

But it won’t work if we inline the function:

c(': ', ' -- ') %>% 
    map(
        names(calendar_consts),
        ~ stringr::str_c(., ., calendar_consts[[.]])
    ) 
## [[1]]
## NULL
## 
## [[2]]
## NULL

Apparently by using the pipe you have to formally pass . as a variable to your function otherwise it will be passed as first, not our intention here. The solution is to adding curly braces:

c(': ', ' -- ') %>% 
    {map(
        names(calendar_consts),
        ~ stringr::str_c(., c(': ', ' -- '), calendar_consts[[.]])
    )} 
## [[1]]
## [1] "num_hours_in_day: 24"   "num_hours_in_day -- 24"
## 
## [[2]]
## [1] "num_days_in_week: 7"   "num_days_in_week -- 7"

We still have to figure out how to pass the separators to map as the . will refer to the current element in map. Fortunately we can refer to the parent environment here as well. But this is very difficult to read, so either do not use the pipe in such cases or predefine your functions.

c(': ', ' -- ') %>% 
    {map(
        names(calendar_consts),
        ~ stringr::str_c(., parent.env(environment())$'.', calendar_consts[[.]])
    )} 
## [[1]]
## [1] "num_hours_in_day: 24"   "num_hours_in_day -- 24"
## 
## [[2]]
## [1] "num_days_in_week: 7"   "num_days_in_week -- 7"

Conclusions

. behaves like a normal variable name but comes handy when communicating clear patterns in a compact way.