JEP-011 Lexical Scoping and the let()
Function
#24
-
I think a strong consensus exists for accepting the JEP-11 Lexical Scoping proposal that specifies the A preview version of what it would look like on the website is available. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
Lexical Scoping
AbstractThis JEP proposes a new function MotivationAs a JMESPath expression is being evaluated, the current element, which can be Once we’ve drilled down to a specific current element, there is no way, in the For example, suppose we had this data:
Let’s say we wanted to get the list of cities of the state corresponding to our
but it is not possible to base this on a value of SpecificationThere are two components to this JEP, a new function, The let() FunctionThe The let function is defined as follows:
Resolving IdentifiersPrior to this JEP, identifiers are resolved by consulting the current context
will result in the In the case of a sub expression, where the current evaluation context
The identifier This JEP adds an additional step to resolving identifiers. In addition
Parent scopes are created by nested Below are a few examples to make this more clear. First, let’s
In this scenario, we are evaluating the expression Now let’s look at an example where an identifier is resolved from
Here, we’re trying to resolve the
However, we now fall back to looking in the provided scope object Finally, let’s look at an example of parent scopes. Consider the
Here we have nested let calls, and the expression we are trying to
Current Node EvaluationWhile the JMESPath specification defines how the current node is determined,
Given the input data:
When the expression Motivating ExampleWith these changes defined, the expression in the “Motivation” section can be
Which evalutes to RationaleIf we just consider the feature of being able to refer to a parent element,
While this could work, this has a number of downsides, the biggest one being Implementation SurveyC#JMESPath.NET implements this proposal. To this end, the project authors had to introduce a new abstraction to the AST object that implements function calls. /// <summary>
/// The <see cref="IScopeParticipant" /> interface lets
/// implementations participate in a stack of contexts
/// to assist evaluating expressions.
///
/// This supports the <see cref="LetFunction" />.
/// </summary>
public interface IScopeParticipant
{
void PushScope(JToken scope);
void PopScope();
} This abstraction is actually only used for the implementation of the public class LetFunction : JmesPathFunction
{
public LetFunction(IScopeParticipant scopes)
: base("let", 2, scopes)
{
}
public override void Validate(params JmesPathFunctionArgument[] args)
{
System.Diagnostics.Debug.Assert(base.Scopes != null);
EnsureObject(args[0]);
EnsureExpressionType(args[1]);
}
public override JToken Execute(params JmesPathFunctionArgument[] args)
{
scopes_?.PushScope(args[0].Token);
try
{
var expression = args[1].Expression;
var result = expression.Transform(Context);
return result.AsJToken();
}
finally
{
scopes_?.PopScope();
}
}
} This allows to register the function evaluation context to the corresponding lexical scope. Additionally, the following abstraction was introduced: public interface IContextEvaluator
{
JToken Evaluate(string identifier);
} The public sealed class LexicalScopes : IScopeParticipant, IContextEvaluator
{
private readonly Stack<JToken> scopes_
= new Stack<JToken>()
;
public JToken Evaluate(string identifier)
{
if (scopes_.Count == 0)
return JTokens.Null;
foreach (var scope in scopes_)
{
if (scope[identifier] != null)
return scope[identifier];
}
return JTokens.Null;
}
public void PushScope(JToken token)
{
scopes_.Push(token);
}
public void PopScope()
{
scopes_.Pop();
}
} The lexical scope stack contains a series of JSON objects referred to by the public class JmesPathIdentifier : JmesPathExpression
{
private readonly string name_;
internal IContextEvaluator evaluator_;
public JmesPathIdentifier(string name)
{
name_ = name;
}
public string Name => name_;
protected override JmesPathArgument Transform(JToken json)
{
var jsonObject = json as JObject;
return jsonObject?[name_] ?? Evaluate(name_);
}
public override string ToString()
{
return $"JmesPathIdentifier: {name_}";
}
public JToken Evaluate(string identifier)
=> evaluator_?.Evaluate(identifier) ?? JTokens.Null;
} When evaluating an No dependency were required to implement this JEP. PythonA Python implementation also currently exists. When the function Identifier evaluation goes to the scope hierarchy. Other languagesGiven that most object-oriented languages support the concept of abstractions via interfaces (or prototypes) and that
Although I have no experience on other languages, there is no reason to believe it would be any different or even harder than the simple implementation shown here. History
|
Beta Was this translation helpful? Give feedback.
Lexical Scoping
Abstract
This JEP proposes a new function
let()
(originally proposed by MichaelDowling) that allows for evaluating an expression with an explicitly defined
lexical scope. This will require some changes to the lookup semantics in
JMESPath to introduce scoping, but provides useful functionality such as being
able to refer to elements defined outside of the current scope used to evaluate
an expression.
Motivation
As a JMESPath expression is being evaluated, the current element, which can be
explicitly referred to via the
@
token, changes as expressions areevaluated. Giv…