Skip to content

Commit

Permalink
Link from 'TiN -> Immutable Variables' to Variables chapter to reduce…
Browse files Browse the repository at this point in the history
… redundancy (#1439)

* Removed outdated info on immutable variables. Moved current info to 'Variables' chapter

* Link 'TiN - Immutable' to Variables chapter to reduce redundancy
  • Loading branch information
NotTheDr01ds committed Jun 13, 2024
1 parent 19d4049 commit 0940bab
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 72 deletions.
34 changes: 4 additions & 30 deletions book/thinking_in_nu.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
127 changes: 125 additions & 2 deletions book/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,30 @@ 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
> do { let val = 101; $val } # in an inner scope, shadow the variable
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
Expand Down Expand Up @@ -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:
Expand Down
41 changes: 1 addition & 40 deletions lang-guide/chapters/filters/00_filters_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

0 comments on commit 0940bab

Please sign in to comment.