Fix definition of recursive functions with fn.

Also document explicitly that the function body may refer to the function being
defined.

This fixes #1206.
This commit is contained in:
Qi Xiao 2021-01-03 20:40:12 +00:00
parent 7932f58201
commit d140e98f3c
3 changed files with 16 additions and 2 deletions

View File

@ -171,9 +171,10 @@ func compileFn(cp *compiler, fn *parse.Form) effectOp {
bodyNode := args.nextMustLambda()
args.mustEnd()
index := cp.thisScope().add(name + FnSuffix)
op := cp.lambda(bodyNode)
return fnOp{cp.thisScope().add(name + FnSuffix), op}
return fnOp{index, op}
}
type fnOp struct {

View File

@ -108,6 +108,10 @@ func TestBuiltinSpecial(t *testing.T) {
// fn.
That("fn f [x]{ put x=$x'.' }; f lorem; f ipsum").
Puts("x=lorem.", "x=ipsum."),
// Recursive functions with fn. Regression test for #1206.
That("fn f [n]{ if (== $n 0) { put 1 } else { * $n (f (- $n 1)) } }; f 3").
Puts(6.0),
// return.
That("fn f []{ put a; return; put b }; f").Puts("a"),
)

View File

@ -1587,7 +1587,7 @@ written to a file or a pipe will be discarded. Examples:
- Assuming the file `data` contains a single line `hello`,
`only-values < data` will not do anything.
# Special Commands
# Special commands
**Special commands** obey the same syntax rules as normal commands, but have
evaluation rules that are custom to each command. Consider the following
@ -1868,6 +1868,15 @@ c
**TODO**: Find a better way to describe this. Hopefully the example is
illustrative enough, though.
The lambda may refer to the function being defined. This makes it easy to define
recursive functions:
```elvish-transcript
~> fn f [n]{ if (== $n 0) { put 1 } else { * $n (f (- $n 1)) } }
~> f 3
▶ (float64 6)
```
Under the hood, `fn` defines a variable with the given name plus `~` (see
[variable suffix](#variable-suffix)).