Evaluate Nu for Scripting: can it beat F#?

TLDR: No, nushell as a scripting language is not strict enough for a functional programming zealot. It seems I am not the only one waiting for improvements: https://github.com/nushell/nushell/issues/11108.

I am far from the mainstream on my scripting habits. I’ve been using F# as a language of choice with a number of custom-built tools to improve the experience. It’s mostly ok but recently Nushell got my attention. Not really as a shell but as a scripting language. One can sense a strong Rust influence which is no surprise given the shell itself is indeed written in it. I wanted to determine if it could solve my pain points with F# and what the trade-offs would be.

It’s my favourite lang and it’s quite popular for a niche language. I like the statically-typed nature combined with a great type inference. It’s succinct and robust. It has a great REPL. It has a huge ecosystem (dotnet, NuGet packages). So what is not so great? The default scripting experience is at most mediocre. The dotnet fsi runner has quite a startup overhead, (as the script has to be compiled, references resolved etc), that made me develop a custom runner that leverages caching (makes it ok for CI/CD usage). Referencing third-party packages via NuGet is great but it’s too much overhead for own code intended for reuse. Referencing scripts from git only works via a custom package manager - Paket. And yes I made it work in production and it works brilliantly but - could I get a similar experience OOTB?

The nu language is not F# obviously. It is definitely functionally-inspired as is Rust. I could live with it if it provides a similar level of reliability, (in the sense that if it has no syntax errors it won’t surprise me on runtime). Let’s see if it meets all my other criteria.

There are also areas where nushell clearly wins with F#:

  • nushell is, well, a shell, so offers a zero friction experience when interacting with the OS (processes, files, and the like)

  • close to zero startup overhead - scripts execute instantly

  • more portable - just need to install the shell (and not the whole dotnet framework like for F#)

  • fantastic built-ins - nushell has a standard library offering functionality that in F# I’d need to either write code for, or reference third-party packages.

    Examples:

  1. Nu supports records but only the variant called in F# anonymous records. I.e. you can’t declare a record type.
  2. Nu doesn’t have the Option type but does have null. Example: let value = $env.VAR_THAT_MAY_BE_NULL? | default "MY_DEFAULT_VALUE"
  3. Record’s field value may need to be wrapped in parentheses if it’s complex: let r = {value: ($env.VAR_THAT_MAY_BE_NULL? | default "MY_DEFAULT_VALUE")}
  4. The match expression doesn’t seem to be exhaustive and neither it enforces the branches to return values of the same type. This is bad. It’s easy to introduce bugs that way.
  5. There is no built-in formatting tool like cargo fmt. F# has that neither but Fantomas is trivial to install. There is nufmt but it can be only installed via cargo or Nix flakes - I don’t want install Rust just to be able to install a formatting plugin. And no, I don’t use nix.
  6. Couldn’t find a way for referencing scripts from git/http. There is a package manager nupm but I’d say the offered functionality is limited. Also it’s not first-party (yet?).

I could live with most of the limitations but a weak type system is not what I want. This happily get parsed and executed:

let val = null
let my_value: int = match $val {
    null => ["L"]
}
$my_value

I’d want it to turn red in the editor rather than slap me in the face with a runtime error or even worse - silently executing in an unintended way. So until the situation improves I’ll stick to F#. However, I’ll be evaluating nushell for simpler and/or interactive scripting.