We are experiencing a programming revolution, with the democratization of artificial intelligence, But also with the creation and improvement of more traditional software tools to improve your code: local, free, deterministic.
In this message we will introduce you
- π¦ lintrThrough Michael Chirico and many othersan R package that detects many ways to improve your code;
- π»$ Airby Lionel Henry and Davis Vaughan, a fast CLI (command-line interface) for automatically and almost instantly formatting R code;
- π»$ jarlby Etienne Bacher, another quick CLI (command line interface) tool to find and automatically repair ribbons;
- π¦ flirtby Etienne Bacher, an R package to efficiently rewrite code patterns, both built-in and custom.
With these four wonderful tools, you can effortlessly improve your code, your colleagues’ code… and even AI-suggested code. With a little more effort, you can even internalize best practices in the future and write better code from scratch!
A sample script
Let’s start with a script that has a few problems… Can you spot them?
lleno <-!any(is.na(x))
ok<- !(x[1] == y[1])
if (ok) z<- x + 1
if (z>3) stop("ouch")The R console versus the terminal
Note that in this post some tools are used in the R console, but others are used in the terminal, which you may also know as command line or shell.
Find out what you can improve with the {lintr} R package π¦
A first instinct might be to run the lintr package on the script. The lint() function performs static analysis and highlights potential problems in your R code, including formatting and programming suggestions.
lintr::lint("test.R", linters = lintr::all_linters())#> index.Rmd:148:32: warning: [nonportable_path_linter] Use file.path() to construct portable file paths.
#> flir::fix("test.R", linters = "flir/rules/custom/stop_abort.yml")
#> ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#> test.R:1:7: style: [infix_spaces_linter] Put spaces around all infix operators.
#> lleno <-!any(is.na(x))
#> ^~
#> test.R:1:10: warning: [any_is_na_linter] anyNA(x) is better than any(is.na(x)).
#> lleno <-!any(is.na(x))
#> ^~~~~~~~~~~~~
#> test.R:2:3: style: [infix_spaces_linter] Put spaces around all infix operators.
#> ok<- !(x[1] == y[1])
#> ^~
#> test.R:2:6: warning: [comparison_negation_linter] Use x != y, not !(x == y).
#> ok<- !(x[1] == y[1])
#> ^~~~~~~~~~~~~~~
#> test.R:2:11: style: [implicit_integer_linter] Use 1L or 1.0 to avoid implicit integers.
#> ok<- !(x[1] == y[1])
#> ~^
#> test.R:2:19: style: [implicit_integer_linter] Use 1L or 1.0 to avoid implicit integers.
#> ok<- !(x[1] == y[1])
#> ~^
#> test.R:3:10: style: [infix_spaces_linter] Put spaces around all infix operators.
#> if (ok) z<- x + 1
#> ^~
#> test.R:3:19: style: [implicit_integer_linter] Use 1L or 1.0 to avoid implicit integers.
#> if (ok) z<- x + 1
#> ~^
#> test.R:4:6: style: [infix_spaces_linter] Put spaces around all infix operators.
#> if (z>3) stop("ouch")
#> ^
#> test.R:4:8: style: [implicit_integer_linter] Use 3L or 3.0 to avoid implicit integers.
#> if (z>3) stop("ouch")
#> ~^
#> test.R:4:10: warning: [condition_call_linter] Use stop(., call. = FALSE) not to display the call in an error message.
#> if (z>3) stop("ouch")
#> ^~~~~~~~~~~~
That’s why we get warnings about it
- styling: space around, for example, infix operators; implicit integer.
- performance:
anyNA(x)is better thanany(is.na(x)).
Because lintr has been around for a long time, it has an impressive collection of rules, the ‘linters’. Even reading their documentation can teach you a lot, especially because the list grows with time!
How can we improve the code based on these warnings?
Format with Air π»$
Air is software that automatically formats your R code according to a set of rules.
In the terminal:
air format test.R
And this comes back:
lleno <- !any(is.na(x))
ok <- !(x[1] == y[1])
if (ok) {
z <- x + 1
}
if (z > 3) {
stop("ouch")
}Now the distance in the code is normal! The if condition is also formatted on three lines instead of just one. Overall, the code is easier to read because it follows now popular conventions.
Please note that Lintr and Air may give conflicting advice on styling: that’s possible disable lintr styling related rules if you use Air.
Enhance with the new jarl CLI! π»$
The jarl CLI lints and fixes your code, and like lintr identifies potential problems, but unlike lintr jarl also applies fixes!
In the terminal:
jarl check test.R --fix
lleno <- !anyNA(x)
ok <- !(x[1] == y[1])
if (ok) {
z <- x + 1
}
if (z > 3) {
stop("ouch")
}any(is.na(x)) was automatically replaced by anyNA(x)!
The jarl CLI is as fast for controlling and fixing frizz as Air is for styling. Also, because it is a simple binary that does not require R, it is faster to install via continuous integration than an R package (which requires R to be installed, for example).
However, because jarl is newer than lintr, it supports fewer rules for now.
Improve with the {flir} R package π¦
You could supplement the use of lintr, Air and jarl with flir, which you are better at custom rules. For example, what if you’d rather use your codebase? rlang::abort() instead of stop()?
We run first
flir::setup_flir(getwd())
We store the file below flir/rules/custom/stop_abort.yml.
id: stop_abort-1 language: r severity: warning rule: pattern: stop($$$ELEMS) fix: rlang::abort(paste0(~~ELEMS~~)) message: Use `rlang::abort()` instead of `stop()`.
Then we run
flir::fix("test.R", linters = "flir/rules/custom/stop_abort.yml")
#> βΉ Going to check 1 file.
#> β Fixed 1 lint in 1 file.
lleno <- !anyNA(x)
ok <- !(x[1] == y[1])
if (ok) {
z <- x + 1
}
if (z > 3) {
rlang::abort(paste0("ouch"))
}The call for stop() was automatically replaced. Now we may want to delete the useless files manually paste0()but we are already closer to an ideal script.
Integrate these tools into your workflow
Locally you can use these tools if necessary. For example, when I inherit an older project, the first thing I do is renovate the project by applying these tools. A real game changer is using the integration of these tools with your IDE. For example, I have Positron set up like this Air runs on my scripts when I save them. The jarl CLI also provides integrations with IDEs.
You can also use these tools for continuous integration. For example, a useful workflow could be: Suggest formatting changes on Pull Requests. Using suggestions instead of a direct commit means the contributor has a chance to learn more about the improvements.
Another aspect to consider is whether you want to flirt and jarl to make the changes in contrast warning you about them. Which one you choose depends on the context. For example, you can learn more by making the changes yourself. In any case, it is important to review changes carefully before implementing them!
What about artificial intelligence?
Artificial intelligence can be useful in coding applications, but…
- The best LLMs are not local;
- They cost money and are slower;
- They aren’t deterministic, so you won’t necessarily get the same result every time you use one of them;
- Its use may raise a number of ideological and ethical issues that may be worrying or unattractive.
Ultimately, you just don’t need to use LLM for such alerts and solutions… Air, flir, jarl and lintr already work great, are faster and are free (not to mention FOSS)!
Conclusion
You can improve your code effortlessly, even without AI, using:
- {lintr} to identify ‘bad’ patterns, including customizable;
- Airto reformat code efficiently;
- jarlto detect and fix ‘bad’ patterns;
- {flirting}to efficiently refactor code with custom rules.
As with all tools that modify your code, their use is best supplemented by human review.
Related
#code #effort #bloggers


