github.com/xiaq/elvish@v0.12.0/website/src/learn/effective-elvish.md (about)

     1  <!-- toc -->
     2  
     3  Elvish is not an entirely new language. Its programming techniques have two
     4  primary sources: traditional Unix shells and functional programming languages,
     5  both dating back to many decades ago. However, the way Elvish combines those
     6  two paradigms is unique in many ways, which enables new ways to write code.
     7  
     8  This document is an advanced tutorial focusing on how to write idiomatic
     9  Elvish code, code that is concise and clear, and takes full advantage of
    10  Elvish's features.
    11  
    12  An appropriate adjective for idiomatic Elvish code, like *Pythonic* for Python
    13  or *Rubyesque* for Ruby, is **Elven**. In [Roguelike
    14  games](https://en.wikipedia.org/wiki/Roguelike), Elven items are known to be
    15  high-quality, artful and resilient. So is Elven code.
    16  
    17  
    18  # Style
    19  
    20  ## Naming
    21  
    22  Use `dash-delimited-words` for names of variables and functions. Underscores
    23  are allowed in variable and function names, but their use should be limited to
    24  environment variables (e.g. `$E:LC_ALL`) and external commands (e.g. `pkg_add`).
    25  
    26  When building a module, use a leading dash to communicate that a variable or
    27  function is subject to change in future and cannot be relied upon, either
    28  because it is an experimental feature or implementation detail.
    29  
    30  Elvish's core libraries follow the naming convention above.
    31  
    32  ## Indentation
    33  
    34  Indent by two spaces.
    35  
    36  ## Code Blocks
    37  
    38  In Elvish, code blocks in control structures are delimited by curly braces.
    39  This is perhaps the most visible difference of Elvish from most other shells
    40  like bash, zsh or fish. The following bash code:
    41  
    42  ```bash
    43  if true; then
    44    echo true
    45  fi
    46  ```
    47  
    48  Is written like this in Elvish:
    49  
    50  ```elvish
    51  if $true {
    52    echo true
    53  }
    54  ```
    55  
    56  If you have used lambdas in Elvish, you will notice that code blocks are
    57  syntactically just parameter-list-less lambdas.
    58  
    59  In Elvish, you cannot put opening braces of code blocks on the next line. This
    60  won't work:
    61  
    62  ```elvish
    63  if $true
    64  { # wrong!
    65    echo true
    66  }
    67  ```
    68  
    69  Instead, you must write:
    70  
    71  ```elvish
    72  if $true {
    73    echo true
    74  }
    75  ```
    76  
    77  This is because in Elvish, control structures like `if` follow the same syntax
    78  as normal commands, hence newlines terminate them. To make the code block part
    79  of the `if` command, it must appear on the same line.
    80  
    81  
    82  # Using the Pipeline
    83  
    84  Elvish is equipped with a powerful tool for passing data: the pipeline. Like
    85  in traditional shells, it is an intuitive notation for data processing: data
    86  flows from left to right, undergoing one transformation after another. Unlike
    87  in traditional shells, it is not restricted to unstructured bytes: all Elvish
    88  values, including lists, maps and even closures, can flow in the pipeline.
    89  This section documents how to make the most use of pipelines.
    90  
    91  ## Returning Values with Structured Output
    92  
    93  Unlike functions in most other programming languages, Elvish commands do not
    94  have return values. Instead, they can write to *structured output*, which is
    95  similar to the traditional byte-based stdout, but preserves all internal
    96  structures of aribitrary Elvish values. The most fundamental command that does
    97  this is `put`:
    98  
    99  ```elvish-transcript
   100  ~> put foo
   101  ▶ foo
   102  ~> x = (put foo)
   103  ~> put $x
   104  ▶ foo
   105  ```
   106  
   107  This is hardly impressive - you can output and recover simple strings using
   108  good old byte-based output as well. But let's try this:
   109  
   110  ```elvish-transcript
   111  ~> put "a\nb" [foo bar]
   112  ▶ "a\nb"
   113  ▶ [foo bar]
   114  ~> s li = (put "a\nb" [foo bar])
   115  ~> put $s
   116  ▶ "a\nb"
   117  ~> put $li[0]
   118  ▶ foo
   119  ```
   120  
   121  Here, two things are worth mentioning: the first value we `put` contains a
   122  newline, and the second value is a list. When we capture the output, we get
   123  those exact values back. Passing structured data is difficult with byte-based
   124  output, but trivial with value output.
   125  
   126  Besides `put`, many other builtin commands also write to structured output,
   127  like `splits`:
   128  
   129  ```elvish-transcript
   130  ~> splits , foo,bar
   131  ▶ foo
   132  ▶ bar
   133  ~> words = [(splits , foo,bar)]
   134  ~> put $words
   135  ▶ [foo bar]
   136  ```
   137  
   138  User-defined functions behave in the same way: they "return" values by writing
   139  to structured stdout. Without realizing that "return values" are just outputs
   140  in Elvish, it is easy to think of `put` as **the** command to "return" values
   141  and write code like this:
   142  
   143  ```elvish-transcript
   144  ~> fn split-by-comma [s]{ put (splits , $s) }
   145  ~> split-by-comma foo,bar
   146  ▶ foo
   147  ▶ bar
   148  ```
   149  
   150  The `split-by-comma` function works, but it can be written more concisely
   151  as:
   152  
   153  ```elvish-transcript
   154  ~> fn split-by-comma [s]{ splits , $s }
   155  ~> split-by-comma foo,bar
   156  ▶ foo
   157  ▶ bar
   158  ```
   159  
   160  In fact, the pattern `put (some-cmd)` is almost always redundant and
   161  equivalent to just `some-command`.
   162  
   163  Similarly, it is seldom necessary to write `echo (some-cmd)`: it is almost
   164  always equivalent to just `some-cmd`. As an exercise, try simplifying the
   165  following function:
   166  
   167  ```elvish
   168  fn git-describe { echo (git describe --tags --always) }
   169  ```
   170  
   171  ## Mixing Bytes and Values
   172  
   173  Each pipe in Elvish comprises two components: one traditional byte pipe that
   174  carries unstructured bytes, and one value pipe that carries Elvish values. You
   175  can write to both, and output capture will capture both:
   176  
   177  ```elvish-transcript
   178  ~> fn f { echo bytes; put value }
   179  ~> f
   180  bytes
   181  ▶ value
   182  ~> outs = [(f)]
   183  ~> put $outs
   184  ▶ [bytes value]
   185  ```
   186  
   187  This also illustrates that the output capture operator `(...)` works with both
   188  byte and value outputs, and it can recover the output sent to `echo`. When
   189  byte output contains multiple lines, each line becomes one value:
   190  
   191  ```elvish-transcript
   192  ~> x = [(echo "lorem\nipsum")]
   193  ~> put $x
   194  ▶ [lorem ipsum]
   195  ```
   196  
   197  Most Elvish builtin functions also work with with both byte and value inputs.
   198  Similarly to output capture, they split their byte intput by newlines. For
   199  example:
   200  
   201  ```elvish-transcript
   202  ~> use str
   203  ~> put lorem ipsum | each $str:to-upper~
   204  ▶ LOREM
   205  ▶ IPSUM
   206  ~> echo "lorem\nipsum" | each $str:to-upper~
   207  ▶ LOREM
   208  ▶ IPSUM
   209  ```
   210  
   211  This line-oriented processing of byte input is consistent with traditional
   212  Unix tools like `grep`, `sed` and `awk`. In fact, it is easy to write your own
   213  `grep` in Elvish:
   214  
   215  ```elvish-transcript
   216  ~> use re
   217  ~> fn mygrep [p]{ each [line]{ if (re:match $p $line) { echo $line } } }
   218  ~> cat in.txt
   219  abc
   220  123
   221  lorem
   222  456
   223  ~> cat in.txt | mygrep '[0-9]'
   224  123
   225  456
   226  ```
   227  
   228  (Note that it is more concise to write `mygrep ... < in.txt`, but due to [a
   229  bug](https://github.com/elves/elvish/issues/600) this does not work.)
   230  
   231  However, this line-oriented behavior is not always desirable: not all Unix
   232  commands output newline-separated data. When you want to get the output as is,
   233  as a single string, you can use the `slurp` command:
   234  
   235  ```elvish-transcript
   236  ~> echo "a\nb\nc" | slurp
   237  ▶ "a\nb\nc\n"
   238  ```
   239  
   240  One immediate use of `slurp` is to read a whole file into a string:
   241  
   242  ```elvish-transcript
   243  ~> cat hello.go
   244  package main
   245  
   246  import "fmt"
   247  
   248  func main() {
   249              fmt.Println("vim-go")
   250  }
   251  ~> hello-go = (slurp < hello.go)
   252  ~> put $hello-go
   253  ▶ "package main\n\nimport \"fmt\"\n\nfunc main()
   254  {\n\tfmt.Println(\"vim-go\")\n}\n"
   255  ```
   256  
   257  It is also useful, for example, when working with NUL-separated output:
   258  
   259  ```elvish-transcript
   260  ~> touch "a\nb.go"
   261  ~> mkdir d
   262  ~> touch d/f.go
   263  ~> find . -name '*.go' -print0 | splits "\000" (slurp)
   264  ▶ "./a\nb.go"
   265  ▶ ./d/f.go
   266  ▶ ''
   267  ```
   268  
   269  In the above command, `slurp` turns the input into one string, which is then
   270  used as an argument to `splits`. The `splits` command then splits the whole
   271  input by NUL bytes.
   272  
   273  Note that in Elvish, strings can contain NUL bytes; in fact, they can contain
   274  any byte; this makes Elvish suitable for working with binary data. (Also, note
   275  that the `find` command terminates its output with a NUL byte, hence we see a
   276  trailing empty string in the output.)
   277  
   278  One side note: In the first example, we saw that `bytes` appeared before `value`.
   279  This is not guaranteed: byte output and value output are separate, it is
   280  possible to get `value` before `bytes` in more complex cases. Writes to one
   281  component, however, always have their orders preserved, so in `put x; put y`,
   282  `x` will always appear before `y`.
   283  
   284  ## Prefer Pipes Over Parentheses
   285  
   286  If you have experience with Lisp, you will discover that you can write Elvish
   287  code very similar to Lisp. For instance, to split a string containing
   288  comma-separated value, reduplicate each value (using commas as separators),
   289  and rejoin them with semicolons, you can write:
   290  
   291  ```elvish-transcript
   292  ~> csv = a,b,foo,bar
   293  ~> joins ';' [(each [x]{ put $x,$x } [(splits , $csv)])]
   294  ▶ 'a,a;b,b;foo,foo;bar,bar'
   295  ```
   296  
   297  This code works, but it is a bit unreadable. In particular, since `splits`
   298  outputs multiple values but `each` wants a list argument, you have to wrap the
   299  output of `splits` in a list with `[(splits ...)]`. Then you have to do this
   300  again in order to pass the output of `each` to `joins`. You might wonder why
   301  commands like `splits` and `each` do not simply output a list to make this
   302  easier.
   303  
   304  The answer to that particular question is in the next subsection, but for the
   305  program at hand, there is a much better way to write it:
   306  
   307  ```elvish-transcript
   308  ~> csv = a,b,foo,bar
   309  ~> splits , $csv | each [x]{ put $x,$x } | joins ';'
   310  ▶ 'a,a;b,b;foo,foo;bar,bar'
   311  ```
   312  
   313  Besides having fewer pairs of parentheses (and brackets), this program is also
   314  more readable, because the data flows from left to right, and there is no
   315  nesting. You can see that `$csv` is first split by commas, then each value
   316  gets reduplicated, and then finally everything is joined by semicolons. It
   317  matches exactly how you would describe the algorithm in spoken English -- or
   318  for that matter, any spoken language!
   319  
   320  Both versions work, because commands like `each` and `joins` that work with
   321  multiple inputs can take their inputs in two ways: they can take the inputs as
   322  one list argument, like in the first version; or from the pipeline, like the
   323  second version. Whenever possible, you should prefer the input-from-pipeline
   324  form: it makes for programs that have little nesting, read naturally.
   325  
   326  One exception to the recommendation is when the input is a small set of things
   327  known beforehand. For example:
   328  
   329  ```elvish-transcript
   330  ~> each $str:to-upper~ [lorem ipsum]
   331  ▶ LOREM
   332  ▶ IPSUM
   333  ```
   334  
   335  Here, using the input-from-argument is completely fine: if you want to use the
   336  input-from-input form, you have to supply the input using `put`, which is also
   337  OK but a bit more wordy:
   338  
   339  ```elvish-transcript
   340  ~> put lorem ipsum | each $str:to-upper~
   341  ▶ LOREM
   342  ▶ IPSUM
   343  ```
   344  
   345  However, not all commands support taking input from the pipeline. For example,
   346  if we want to first join some values with space and then split at commas, this
   347  won't work:
   348  
   349  ```elvish-transcript
   350  ~> joins ' ' [a,b c,d] | splits ,
   351  Exception: want 2 arguments, got 1
   352  [tty], line 1: joins ' ' [a,b c,d] | splits ,
   353  ```
   354  
   355  This is because the `splits` command only ever works with one input (one
   356  string to split), and was not implemented to support taking input from
   357  pipeline; hence it always takes 2 arguments and we got an exception.
   358  
   359  It is easy to remedy this situation however. The `all` command passes its
   360  input to its output, and by capturing its output, we can turn the input into
   361  an argument:
   362  
   363  ```elvish-transcript
   364  ~> joins ' ' [a,b c,d] | splits , (all)
   365  ▶ a
   366  ▶ 'b c'
   367  ▶ d
   368  ```
   369  
   370  ## Streaming Multiple Outputs
   371  
   372  In the previous subsection, we remarked that commands like `splits` and `each`
   373  write multiple output values instead of one list. Why?
   374  
   375  This has to do with another advantage of passing data through the pipeline: in
   376  a pipeline, all commands are executed in parallel. A command in a pipeline
   377  does not need to wait for its previous command to finish running before it can
   378  start processing data. Try this in your terminal:
   379  
   380  ```elvish-transcript
   381  ~> each $str:to-upper~ | each [x]{ put $x$x }
   382  (Start typing)
   383  abc
   384  ▶ ABCABC
   385  xyz
   386  ▶ XYZXYZ
   387  (Press ^D)
   388  ```
   389  
   390  You will notice that as soon as you press Enter after typing `abc`, the output
   391  `ABCABC` is shown. As soon as one input is available, it goes through the
   392  entire pipeline, each command doing its work. This gives you immediate
   393  feedback, and makes good use of multi-core CPUs on modern computers. Pipelines
   394  are like assembly lines in the manufacturing industry.
   395  
   396  If instead of passing multiple values, we pass a list through the pipeline:
   397  that means that each command will now be waiting for its previous command to
   398  do all the processing and pack the results in a list before it can start doing
   399  anything. Now, although the commands themselves are run in parallel, they all
   400  need to be waiting for their previous commands to finish before they can start
   401  doing real work.
   402  
   403  This is why commands like `each` and `splits` produce multiple values instead
   404  of one list. When writing your functions, try to make them produce multiple
   405  values as well: they will cooperate better with builtin commands, and they can
   406  benefit from the efficiency of parallel computations.
   407  
   408  
   409  # Working with Multiple Values
   410  
   411  In Elvish, many constructs can evaluate to multiple values. This can be
   412  surprising if you are not familiar with it.
   413  
   414  To start with, output captures evaluate to all the captured values, instead of
   415  a list:
   416  
   417  ```elvish-transcript
   418  ~> splits , a,b,c
   419  ▶ a
   420  ▶ b
   421  ▶ c
   422  ~> li = (splits , a,b,c)
   423  Exception: arity mismatch
   424  [tty], line 1: li = (splits , a,b,c)
   425  ```
   426  
   427  The assignment fails with "arity mismatch" because the right hand side
   428  evaluates to 3 values, but you are attempting to assign them to just one
   429  variable. If you want to capture the results into a list, you have to
   430  explicitly do so, either by constructing a list or using rest variables:
   431  
   432  ```elvish-transcript
   433  ~> li = [(splits , a,b,c)]
   434  ~> put $li
   435  ▶ [a b c]
   436  ~> @li = (splits , a,b,c) # equivalent and slightly shorter
   437  ```
   438  
   439  ## Assigning Multiple Variables
   440  
   441  
   442  # To Be Continued...
   443  
   444  As of writing, Elvish is neither stable nor complete. The builtin libraries
   445  still have missing pieces, the package manager is in its early days, and
   446  things like a type system and macros have been proposed and considered, but
   447  not yet worked on. Deciding best practices for using feature *x* can be a bit
   448  tricky when that feature *x* doesn't yet exist!
   449  
   450  The current version of the document is what the lead developer of Elvish
   451  (@xiaq) has collected as best practices for writing Elvish code in early 2018,
   452  between the release of Elvish 0.11 and 0.12. They apply to aspects of the
   453  Elvish language that are relatively complete and stable; but as Elvish
   454  evolves, the document will co-evolve. You are invited to revisit this document
   455  once in a while!