jmespath / jmespath/jmespath.jep

[Initial feedback] Potential proposal for user-defined function

Open
#25 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
10
Forks
3
PR merge metrics
No merged PRs in 30d

Description

I’m investigating user-defined _lambda-like_ functions as [discussed here](https://github.com/jmespath/jmespath.jep/pull/18#issuecomment-1483085482).

Is this something of interest to discuss here?

## User-defined functions

JMESPath already supports `expression-type` which are, conceptually, anonymous functions without arguments.

I have toyed with the idea of extending exprefs to support named arguments.
An updated grammar that works looks like this:

```abnf
expression-type = "&" expression / "<" arguments ">" "=>" expression
arguments = variable-ref *( "," variable-ref
```

It requires a [new token](https://github.com/jmespath-community/typescript-jmespath/blob/d1fecf6c1d7aa535a736ff669aa59a6c1135090f/src/Lexer.type.ts#L7) `=>` which is even not absolutely necessary. Parsing involves a [new entry](https://github.com/jmespath-community/typescript-jmespath/blob/d1fecf6c1d7aa535a736ff669aa59a6c1135090f/src/Parser.ts#L180-L210) in the `nud()` function to match on token `<` (less-than-sign).

## Reduce function

As `expression-type` are only supported as function arguments, a `reduce()`
function [could work](https://github.com/jmespath-community/typescript-jmespath/blob/d1fecf6c1d7aa535a736ff669aa59a6c1135090f/src/Runtime.ts#L481-L501) like this:

`` reduce(array $array, any $seed, expr [any, any]->any) ``

Example:

```json
[1, 3, 5, 7]
```

```jmespath
reduce(@, `1`, <$acc, $cur> => $acc × $cur)
```

The `reduce()` function _knows_ how to iterate over its first array argument
and repeatedly create bindings for both function arguments `$acc` and `$cur`.

Funnily enough, the lambda function does not support natively the `@` current node.
An argument can be made that it could be similar to the `$cur` argument. In that case,
a choice must be made as to which "context" is specified when evaluating the expref.

## Reusing user-defined functions

I then investigated what reusing user-defined functions could look like.
This requires a mechanism to store or hold functions, possibly by name,
as well as a way to call those functions with appropriate parameters.

By taking advantage of the hopefully upcoming `let-expression` design, one
could bind the anonymous function to a variable reference. Then, syntax for
calling the function using this variable must be extended and supported in
the grammar.

In the following expression:

```jmespath
let $concat = <$lhs, $rhs> => join('-', [$lhs, $rhs])
in $concat('4', '2')
```

`$concat` is bound to the `` <$lhs, $rhs> => join('-', [$lhs, $rhs]) `` lambda `expression-type`. Then, `$concat('4', '2')` invokes the function indirectly using the `$concat` variable reference.

The following grammar changes is required:

```abnf
function-expression = ( unquoted-string / variable-ref ) no-args / one-or-more-args )
```

Again, [implementation is quite easy](https://github.com/jmespath-community/typescript-jmespath/blob/d1fecf6c1d7aa535a736ff669aa59a6c1135090f/src/TreeInterpreter.ts#L285-L307).

Funnily enough, having also implemented `arithmetic-expression` grammar rules, I was then able to [implement the recursive Fibonacci sequence](https://github.com/jmespath-community/typescript-jmespath/blob/d1fecf6c1d7aa535a736ff669aa59a6c1135090f/test/jmespath-interpreter.spec.ts#L17) using the following expression:

```jmespath
let $fib = <$n> => (
($n == `0` && `0`) ||
(($n == `1` && `1`) ||
( $fib($n - `1`) + $fib($n - `2`) )
)
) in
$fib(@)
```

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.