---
title: Bit by Bit
subtitle: Elements of Code and Style
author: Level Up with Shiny for R<br><code>posit::conf(2024)</code>
date: 2024-08-12

format:
  utopia-slides-revealjs:
    html-math-method: plain
    revealjs-url: slides/assets/reveal.js-4.5.0
    slide-level: 2
    # chalkboard: true
    theme:
      - slides.scss
      - assets/code-blocked.scss
    css:
      - ../auto-dark-mode.css

revealjs-plugins:
  - utopia-slides

filters:
  - webr

webr:
  show-startup-message: false

editor:
  render-on-save: true
---

# Coding for humans {.middle}

##

::: {.w-two-thirds .center}
```{.r}
card(card_header("Cost vs Earnings"),
     layout_sidebar(sidebar = sidebar(
                    open = FALSE, position = "right",
                    radioButtons("cost_group_by",
                                 "Group By",
                                 choices = c("Predominant Degree" = "deg_predominant",
                                             "Campus Setting" = "locale_type",
                                             "Testing Requirements" = "adm_req_test"))),
                    plotOutput("plot_cost")), full_screen = TRUE)
```
:::

## {transition="fade"}

::: {.w-two-thirds .center .code-blocked}
```{.r}
card(card_header("Cost vs Earnings"),
     layout_sidebar(sidebar = sidebar(
                    open = FALSE, position = "right",
                    radioButtons("cost_group_by",
                                 "Group By",
                                 choices = c("Predominant Degree" = "deg_predominant",
                                             "Campus Setting" = "locale_type",
                                             "Testing Requirements" = "adm_req_test"))),
                    plotOutput("plot_cost")), full_screen = TRUE)
```
:::

## {transition="fade"}

::: {.w-two-thirds .center .code-blocked}
```{.r}
card(
  card_header("Cost vs Earnings"),
  layout_sidebar(
    sidebar = sidebar(
      open = FALSE,
      position = "right",
      radioButtons(
        "cost_group_by",
        "Group By",
        choices = c(
          "Predominant Degree" = "deg_predominant",
          "Campus Setting" = "locale_type",
          "Testing Requirements" = "adm_req_test"
        ),
      ),
    ),
    plotOutput("plot_cost"),
  ),
  full_screen = TRUE
)
```
:::

## {transition="fade"}

::: {.w-two-thirds .center}
```{.r}
card(
  card_header("Cost vs Earnings"),
  layout_sidebar(
    sidebar = sidebar(
      open = FALSE,
      position = "right",
      radioButtons(
        "cost_group_by",
        "Group By",
        choices = c(
          "Predominant Degree" = "deg_predominant",
          "Campus Setting" = "locale_type",
          "Testing Requirements" = "adm_req_test"
        ),
      ),
    ),
    plotOutput("plot_cost"),
  ),
  full_screen = TRUE
)
```
:::

##

::: {.w-two-thirds .center .code-left-edge}
```{.r}
card(
  card_header("Cost vs Earnings"),
  layout_sidebar(
    sidebar = sidebar(
      open = FALSE,
      position = "right",
      radioButtons(
        "cost_group_by",
        "Group By",
        choices = c(
          "Predominant Degree" = "deg_predominant",
          "Campus Setting" = "locale_type",
          "Testing Requirements" = "adm_req_test"
        ),
      ),
    ),
    plotOutput("plot_cost"),
  ),
  full_screen = TRUE
)
```
:::

## Code Folding

::: {.fragment .absolute}
![](assets/code-folding-wide.png)
:::

::: {.fragment .absolute}
![](assets/code-folding-wide-folded.png)
:::

::: {.fragment .absolute}
![](assets/code-folding.png)
:::

```{=html}
<script>
document.querySelectorAll('.code-blocked').forEach((block) => {
  const atSpans = block.querySelectorAll("span.at")
  atSpans.forEach(span => {
    span.textContent = span.textContent.replace(" =", "")
    const newSpan = document.createElement("span")
    newSpan.textContent = " ="
    span.insertAdjacentElement("afterend", newSpan)
  })
});

document.querySelectorAll('.code-left-edge').forEach((block) => {
  const codeLines = block.querySelectorAll("code span[id]")
  codeLines.forEach(span => {
    if (span.querySelector("span")) return;
    // move text node children into a span
    span.childNodes.forEach(child => {
      if (child.nodeType === Node.TEXT_NODE) {
        // split text at first non-whitespace character
        const text = child.textContent
        const match = text.match(/^\s*/)
        if (match) {
          span.removeChild(child)
          const whitespace = match[0]
          const textNode = document.createTextNode(whitespace)
          span.appendChild(textNode)
          const newSpan = document.createElement('span')
          newSpan.textContent = text.slice(whitespace.length)
          span.appendChild(newSpan)
        }
      }
    })
  })
});
</script>
```

## Automating code styling

### `{styler}`: tidyverse style in a package

::: text-center
![](assets/styler-active-file.png)
:::

## Automating code styling

### `{grkstyle}`: MY style in a package

```r
# In ~/.Rprofile or Console
grkstyle::use_grk_style()

# Then use {styler} addins as normal

# Easily switch between tabs and spaces
grkstyle::grk_reindent_tabs_dir()
grkstyle::grk_reindent_spaces_dir()
```

##

```{r}
#| echo: true
card_example <- r"(
card(card_header("Cost vs Earnings"),
     layout_sidebar(sidebar = sidebar(
                    open = FALSE, position = "right",
                    radioButtons("cost_group_by",
                                 "Group By",
                                 choices = c("Predominant Degree" = "deg_predominant",
                                             "Campus Setting" = "locale_type",
                                             "Testing Requirements" = "adm_req_test"))),
                    plotOutput("plot_cost")), full_screen = TRUE)
)"
```

##

::: {.easy-columns}
::: {.col}
```{r}
#| echo: true
styler::style_text(card_example)
```
:::

::: {.col .fragment}
```{r}
#| echo: true
#| attr.out:
#|   height: auto
grkstyle::grk_style_text(card_example)
```
:::
:::

## Garrick's Golden Guideline

::: incremental
* A function call fits on a single line

* Or it has one argument per line

* Oh, and closing parentheses are on their own line
:::

# Comments {.w-100 .middle .left}

## Code comments {.text-center}

::: {.w-50-l .center}
![](assets/code-comments-2.png)
:::

## Comments provide

::: {.fs-step-4}
```
# Information and context
```

::: {.fragment .m-block-start-m}
```
# Structure ----
```
:::

::: {.fragment .m-block-start-m}
```
#' Documentation
#'
#' @param output The output file path
```
:::
:::

## Structural Comments

```{.r}
# Setup -----------------------------------------------------------------------

# UI --------------------------------------------------------------------------
  # | Sidebar ----
  # | - Accordion ----
  # | - Other Inputs ----
  # | Main ----
  # | - Value Boxes ----
  # | - Cards ----

# Server ----------------------------------------------------------------------
server <- function(input, output, session) {
  # Value Boxes ----
  # Plot ----
  # Map ----
}
```

Open `_examples/02-bslib/99_app.R` in the IDE as an example.

## Informational Comments?

```{.r}
{{< include ../../_examples/04-style/shiny-with-comments.R >}}
```

## How to write better comments

::: incremental
1. Some say: _Comments should explain why, not what (or the how)_<br>
   [NOPE! What, why and how are all allowed]{.fragment .purple}

1. Comments should help make the code **easier to read and understand**.

1. Explain the code to someone else:
    * What won't they know unless they talk to you?
    * What might they get wrong if weren't there?
:::

## Case Study: Joe Cheng

```{.r filename="shiny/R/middleware.R"}
{{< include ../../_examples/04-style/middleware.R >}}
```

## Case Study: Davis Vaughn

```{.r filename="dplyr/R/across.R" ln-start-from="293"}
# Loop in such an order that all functions are applied
# to a single column before moving on to the next column
for (i in seq_n_cols) {
  var <- vars[[i]]
  col <- data[[i]]

  context_poke("column", var)

  for (j in seq_fns) {
    fn <- fns[[j]]
    out[[k]] <- fn(col, ...)
    k <- k + 1L
  }
}
```

::: aside
📘 🤓 <https://github.com/tidyverse/dplyr/blob/main/R/across.R#L283-L313>
:::

## Case Study: Davis Vaughn

```{.r filename="dplyr/R/across.R" ln-start-from="293"}
# Loop in such an order that all functions are applied
# to a single column before moving on to the next column
for (i in seq_n_cols) {
  var <- vars[[i]]
  col <- data[[i]]
  context_poke("column", var)
  for (j in seq_fns) {
    fn <- fns[[j]]
    out[[k]] <- fn(col, ...)
    k <- k + 1L
  }
}
```

##

```{.r filename="shiny/R/knit.R" code-line-numbers="|7-8|1-3"}
# If there's an R Markdown runtime option set but it isn't set to Shiny, then
# return a warning indicating the runtime is inappropriate for this object.
# Returns NULL in all other cases.
shiny_rmd_warning <- function() {
  runtime <- knitr::opts_knit$get("rmarkdown.runtime")
  if (!is.null(runtime) && runtime != "shiny") {
    # note that the RStudio IDE checks for this specific string to detect Shiny
    # applications in static document
    list(structure(
      "Shiny application in a static R Markdown document",
      class = "rmd_warning"
    ))
  } else {
    NULL
  }
}
```

## What does this code do?

```{webr-r}
card_image <- function(file = NULL, ..., src = NULL) {
  if (is.null(src)) {
    if (grepl("^([[:alnum:]]+:)?//|data:", file)) {
      src <- file
    } else {
      if (!file.exists(file)) {
        rlang::abort(sprintf("`file` does not exist: %s", file))
      }
      src <- base64enc::dataURI(
        file = file,
        mime = mime_type %||% mime::guess_type(file)
      )
    }
  }

  htmltools::tags$img(src = src, ...)
}
```

::: notes
You _could use comments_ here to explain how `file` becomes `src` and the regular expression.

Better yet, use an explanatory variable for the `grepl`
and an explanatory function for `encode_file_as_data_uri()`.
:::

## Hot tip: Use early returns

Use this one weird trick to make your code easier to read!

```{webr-r}
shiny_rmd_warning <- function() {
  runtime <- knitr::opts_knit$get("rmarkdown.runtime")
  if (!is.null(runtime) && runtime != "shiny") {
    # note that the RStudio IDE checks for this specific string to detect Shiny
    # applications in static document
    list(structure(
      "Shiny application in a static R Markdown document",
      class = "rmd_warning"
    ))
  } else {
    NULL
  }
}
```

# Naming things is hard {.middle}

## What will this function do?

```{r avg-sat-scores}
library(collegeScorecard)
library(dplyr)
library(ggplot2)

avg_sat_scores <- function(scorecard, year_start = "2020-21") {
  scorecard |>
    filter(
      !is.na(score_sat_avg),
      academic_year >= year_start
    ) |>
    mutate(
      admissions_rate = case_when(
        rate_admissions < 0.5  ~ "Highly Selective",
        rate_admissions >= 0.5 ~ "Wide Acceptance",
        .default = "Unknown"
      )
    ) |>
    ggplot() +
    aes(score_sat_avg, fill = academic_year) +
    geom_density(alpha = 0.5, show.legend = FALSE) +
    facet_grid(academic_year ~ admissions_rate, switch = "y") +
    labs(
      x = "Average SAT Score",
      y = NULL,
    ) +
    theme_minimal(18) +
    theme(
      strip.text.y.left = element_text(angle = 0),
      axis.text.y = element_blank(),
    )
}
```

```{r}
#| echo: true
#| output-location: fragment
#| fig.asp: 0.5
avg_sat_scores(scorecard, "2020-21")
```

::: fragment
**What's a better name?**
:::

## Feitelson's three-step plan

1. Select the concepts to include in the name.
1. Choose the words to represent each concept.
1. Construct a name using these words.

::: aside
[How Developers Choose Names](https://www.cs.huji.ac.il/~feit/papers/Names20TSE.pdf), Dror G. Fietelson \
[The Programmer's Brain](https://www.manning.com/books/the-programmers-brain), Felienne Hermans \
[Explaining variable](https://blog.thepete.net/blog/2021/06/24/explaining-variable/), Pete Hodgson
:::

## Give this function a better name

```{r}
#| echo: true
#| ref.label: avg-sat-scores

```

## Naming shiny things (outputs)

::: {.text-center .fs-step-4}
```
plot_cost
plot_cost_avg
plot_cost_avg_by_month
```
:::

::: {.text-center .fs-step-4 .fragment}
```
text_n_undergrads
ui_n_undergrads
vb_n_undergrads
```
:::

::: {.callout-caution title="Did you know" .fragment}
All IDs should be unique in an application. See [`?shiny::NS()`](https://shiny.posit.co/r/reference/shiny/latest/ns).
:::

## Naming shiny things (inputs)

Follow the data and how it's supposed to be used.

::: {.text-center .fs-step-4}
```
input$n_undergrads
input$cost_avg_range
input$cost_avg_min
```
:::

## Naming shiny things (other things)

What are some other useful prefixes in Shiny?

::: {.text-center .fs-step-5}
{{< partial ../partials/content-editable-code.html text="* example_" >}}
:::

# Switch away from if {.middle}

##

::: {.fs-step-4}
```{webr-r}
input <- list(type = "cat")

if (input$type == "types") {
  inspectdf::inspect_types(df)
} else if (input$type == "cat") {
  inspectdf::inspect_cat(df)
} else if (input$type == "num") {
  inspectdf::inspect_num(df)
} else if (input$type == "na") {
  inspectdf::inspect_na(df)
}
```
:::

##

```{.r}
switch(
  input$type,
  "types" = inspectdf::inspect_types(df),
  "cat" = inspectdf::inspect_cat(df),
  "num" = inspectdf::inspect_num(df),
  "na" = inspectdf::inspect_na(df)
)
```
