From 0940bab37823a7e0ffca2e436f563ba28e75d085 Mon Sep 17 00:00:00 2001 From: NotTheDr01ds <32344964+NotTheDr01ds@users.noreply.github.com> Date: Thu, 13 Jun 2024 17:08:25 -0400 Subject: [PATCH] Link from 'TiN -> Immutable Variables' to Variables chapter to reduce redundancy (#1439) * Removed outdated info on immutable variables. Moved current info to 'Variables' chapter * Link 'TiN - Immutable' to Variables chapter to reduce redundancy --- book/thinking_in_nu.md | 34 +---- book/variables.md | 127 +++++++++++++++++- .../chapters/filters/00_filters_overview.md | 41 +----- 3 files changed, 130 insertions(+), 72 deletions(-) diff --git a/book/thinking_in_nu.md b/book/thinking_in_nu.md index aecb7ded355..3619c4f97d5 100644 --- a/book/thinking_in_nu.md +++ b/book/thinking_in_nu.md @@ -63,40 +63,14 @@ would work, since the string can be evaluated at compile-time: For more in-depth explanation, check [How Nushell Code Gets Run](how_nushell_code_gets_run.md). -## Variables are immutable +## Variables are immutable by default -Another common surprise for folks coming from other languages is that Nushell variables are immutable (and indeed some people have started to call them "constants" to reflect this). Coming to Nushell you'll want to spend some time becoming familiar with working in a more functional style, as this tends to help write code that works best with immutable variables. - -You might wonder why Nushell uses immutable variables. Early on in Nushell's development we decided to see how long we could go using a more data-focused, functional style in the language. More recently, we added a key bit of functionality into Nushell that made these early experiments show their value: parallelism. By switching from [`each`](/commands/docs/each.md) to [`par-each`](/commands/docs/par-each.md) in any Nushell script, you're able to run the corresponding block of code in parallel over the input. This is possible because Nushell's design leans heavily on immutability, composition, and pipelining. - -Just because Nushell variables are immutable doesn't mean things don't change. Nushell makes heavy use of the technique of "shadowing". Shadowing means creating a new variable with the same name as a previously declared variable. For example, say you had an `$x` in scope, and you wanted a new `$x` that was one greater: - -```nu -let x = $x + 1 -``` - -This new `x` is visible to any code that follows this line. Careful use of shadowing can make for an easier time working with variables, though it's not required. - -Loop counters are another common pattern for mutable variables and are built into most iterating commands, for example you can get both each item and an index of each item using [`each`](/commands/docs/each.md): - -```nu -> ls | enumerate | each { |it| $"Number ($it.index) is size ($it.item.size)" } -``` - -You can also use the [`reduce`](/commands/docs/reduce.md) command to work in the same way you might mutate a variable in a loop. For example, if you wanted to find the largest string in a list of strings, you might do: - -```nu -> [one, two, three, four, five, six] | reduce {|curr, max| - if ($curr | str length) > ($max | str length) { - $curr - } else { - $max - } -} -``` +Another common surprise for folks coming from other languages is that Nushell variables are immutable by default. Coming to Nushell, you'll want to spend some time becoming familiar with working in a more functional style, as this tends to help write code that works best with immutable variables. **Thinking in Nushell:** If you're used to using mutable variables for different tasks, it will take some time to learn how to do each task in a more functional style. Nushell has a set of built-in capabilities to help with many of these patterns, and learning them will help you write code in a more Nushell-style. The added benefit of speeding up your scripts by running parts of your code in parallel is a nice bonus. +See [Immutable Variables](variables.html#immutable-variables) and [Choosing between mutable and immutable variables](variables.html#choosing-between-mutable-and-immutable-variables) for more information. + ## Nushell's environment is scoped Nushell takes multiple design cues from compiled languages. One such cue is that languages should avoid global mutable state. Shells have commonly used global mutation to update the environment, but Nushell steers clear of this approach. diff --git a/book/variables.md b/book/variables.md index 60df18beed7..9f1c4712a14 100644 --- a/book/variables.md +++ b/book/variables.md @@ -11,11 +11,20 @@ An immutable variable cannot change its value after declaration. They are declar ```nu > let val = 42 -> print $val +> $val 42 +> $val = 100 +Error: nu::shell::assignment_requires_mutable_variable + + × Assignment to an immutable variable. + ╭─[entry #10:1:1] + 1 │ $val = 100 + · ──┬─ + · ╰── needs to be a mutable variable + ╰──── ``` -However, they can be 'shadowed'. Shadowing means that they are redeclared and their initial value cannot be used anymore within the same scope. +However, immutable variables can be 'shadowed'. Shadowing means that they are redeclared and their initial value cannot be used anymore within the same scope. ```nu > let val = 42 # declare a variable @@ -23,6 +32,9 @@ However, they can be 'shadowed'. Shadowing means that they are redeclared and th 101 > $val # in the outer scope the variable remains unchanged 42 +> let val = $val + 1 # now, in the outer scope, shadow the original variable +> $val # in the outer scope, the variable is now shadowed, and +43 # its original value is no longer available. ``` ### Mutable Variables @@ -77,6 +89,117 @@ const script_file = 'path/to/script.nu' source $script_file ``` +## Choosing between mutable and immutable variables + +Try to use immutable variables for most use-cases. + +You might wonder why Nushell uses immutable variables by default. For the first few years of Nushell's development, mutable variables were not a language feature. Early on in Nushell's development, we decided to see how long we could go using a more data-focused, functional style in the language. This experiment showed its value when Nushell introduced parallelism. By switching from [`each`](/commands/docs/each.md) to [`par-each`](/commands/docs/par-each.md) in any Nushell script, you're able to run the corresponding block of code in parallel over the input. This is possible because Nushell's design leans heavily on immutability, composition, and pipelining. + +Many, if not most, use-cases for mutable variables in Nushell have a functional solution that: + +- Only uses immutable variables, and as a result ... +- Has better performance +- Supports streaming +- Can support additional features such as `par-each` as mentioned above + +For instance, loop counters are a common pattern for mutable variables and are built into most iterating commands. For example, you can get both each item and the index of each item using [`each`](/commands/docs/each.md) with [`enumerate`](/commands/docs/enumerate.md): + +```nu +> ls | enumerate | each { |it| $"Item #($it.index) is size ($it.item.size)" } +╭───┬───────────────────────────╮ +│ 0 │ Item #0 is size 812 B │ +│ 1 │ Item #1 is size 3.4 KiB │ +│ 2 │ Item #2 is size 11.0 KiB │ +│ 3 │ ... │ +│ 4 │ Item #18 is size 17.8 KiB │ +│ 5 │ Item #19 is size 482 B │ +│ 6 │ Item #20 is size 4.0 KiB │ +╰───┴───────────────────────────╯ +``` + +You can also use the [`reduce`](/commands/docs/reduce.md) command to work in the same way you might mutate a variable in a loop. For example, if you wanted to find the largest string in a list of strings, you might do: + +```nu +> [one, two, three, four, five, six] | reduce {|current_item, max| + if ($current_item | str length) > ($max | str length) { + $current_item + } else { + $max + } + } + +three +``` + +While `reduce` processes lists, the [`generate`](/commands/docs/generate.md) command can be used with arbitrary sources such as external REST APIs, also without requiring mutable variables. Here's an example that retrieves local weather data every hour and generates a continuous list from that data. The `each` command can be used to consume each new list item as it becomes available. + +```nu +generate khot {|weather_station| + let res = try { + http get -ef $'https://api.weather.gov/stations/($weather_station)/observations/latest' + } catch { + null + } + sleep 1hr + match $res { + null => { + next: $weather_station + } + _ => { + out: ($res.body? | default '' | from json) + next: $weather_station + } + } +} +| each {|weather_report| + { + time: ($weather_report.properties.timestamp | into datetime) + temp: $weather_report.properties.temperature.value + } +} +``` + +### Performance considerations + +Using [filter commands](/commands/categories/filters.html) with immutable variables is often far more performant than mutable variables with traditional flow-control statements such as `for` and `while`. For example: + +- Using a `for` statement to create a list of 50,000 random numbers: + + ```nu + timeit { + mut randoms = [] + for _ in 1..50_000 { + $randoms = ($randoms | append (random int)) + } + } + ``` + + Result: 1min 4sec 191ms 135µs 90ns + +- Using `each` to do the same: + + ```nu + timeit { + let randoms = (1..50_000 | each {random int}) + } + ``` + + Result: 19ms 314µs 205ns + +- Using `each` with 10,000,000 iterations: + + ```nu + timeit { + let randoms = (1..10_000_000 | each {random int}) + } + ``` + + Result: 4sec 233ms 865µs 238ns + + As with many filters, the `each` statement also streams its results, meaning the next stage of the pipeline can continue processing without waiting for the results to be collected into a variable. + + For tasks which can be optimized by parallelization, as mentioned above, `par-each` can have even more drastic performance gains. + ## Variable Names Variable names in Nushell come with a few restrictions as to what characters they can contain. In particular, they cannot contain these characters: diff --git a/lang-guide/chapters/filters/00_filters_overview.md b/lang-guide/chapters/filters/00_filters_overview.md index ac3fe9a86a2..c28cbce8afa 100644 --- a/lang-guide/chapters/filters/00_filters_overview.md +++ b/lang-guide/chapters/filters/00_filters_overview.md @@ -10,43 +10,4 @@ help commands | where category == filters ## Filters vs. Flow Control Statements -While it's certainly possible to modify structured data using "traditional" flow control statements like `for` or `while`, filters are usually more convenient and (often far) more performant. - -For example: - -- Using a `for` statement to create a list of 50,000 random numbers: - - ```nu - timeit { - mut randoms = [] - for _ in 1..50_000 { - $randoms = ($randoms | append (random int)) - } - } - ``` - - Result: 1min 4sec 191ms 135µs 90ns - -- Using `each` to do the same: - - ```nu - timeit { - let randoms = (1..50_000 | each {random int}) - } - ``` - - Result: 19ms 314µs 205ns - -- Using `each` with 10,000,000 iterations: - - ```nu - timeit { - let randoms = (1..10_000_000 | each {random int}) - } - ``` - - Result: 4sec 233ms 865µs 238ns - - As with many filters, the `each` statement also streams its results, meaning the next stage of the pipeline can continue processing without waiting for the results to be collected into a variable. - - For tasks which can be optimized by parallelization, `par-each` can have even more drastic performance gains. +While it's certainly possible to modify structured data using "traditional" flow control statements like `for` or `while`, filters are usually more convenient and (often far) more performant. See the [Variables section of The Book](/book/variables.html#choosing-between-mutable-and-immutable-variables) for more information.