Skip to content

Align partial function application with jsonata-js (#95) - #130

Closed
aeberhart wants to merge 5 commits into
mainfrom
95-partial-functions
Closed

aeberhart wants to merge 5 commits into
mainfrom
95-partial-functions

Conversation

@aeberhart

@aeberhart aeberhart commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #95, #79, #83, #86

Root cause

jsonata-js partially applies a native function by building function($a, $b){ _ } and replacing the body with the native function (bodyAST.body = native). When invoked, applyProcedure calls applyNativeFunction, which does not validate the signature. The Java port instead generated a trampoline that called the builtin by name ($each($a, $b)), so the signature was checked again and the function had to be looked up by name.

Changes (following the JS code)

  • partialApplyNativeFunction sets bodyAST.body = _native (Symbol.body is now Object); implemented the commented-out applyNativeFunction / getNativeFunctionArguments and the native branch in applyProcedure
  • partialApplyProcedure: only ? leaves a parameter unbound; missing args are bound to null like JS binds undefined (incorrect result when $map($, $append(?)) #86)
  • ? comparison: the token value is a Character, so arg.value.equals("?") was always false
  • no lookup by name, so custom / unnamed JFunctions work (Custom function pipe error #83)
  • $each / $merge iterate arrays like JS for..in (string index keys), since the signature is no longer enforced for partials (Incorrect each function signature validation #79)
  • T1007 check was inverted (proc != null vs JS typeof proc === 'undefined')
  • implemented the JS typeof proc === 'function' branch: Fn* lambdas bound via Frame.bind(...) can be partially applied

Arity of Java functions

JS takes the parameters of a native function from func.toString(). In Java, JFunction.getNumberOfArgs() now uses, in order:

  1. the parameters of the implementing Method
  2. the declared arity of a JLambda (Fn0–Fn8 → 0–8), kept by the Jsonata.function(...) factories
  3. the number of parameters in the signature
  4. unknown (raw JFunctionCallable / FnVarArgs without signature): the number of args supplied at the partial application, e.g. $add(?, 3) → 2. This is the only deviation from JS (no dropping of extra args / binding of missing ones)

API note

JLambda was an empty marker interface and now declares getJFunctionCallable() and a default getNumberOfArgs(). All Fn* types implement them; a class implementing JLambda directly would need to add getJFunctionCallable().

Testing

New PartialTest (11 cases); full suite: 1747 tests pass, also with -Dmaven.compiler.release=11. Compared 31 partial-application expressions against jsonata-js 2.1.0.

🤖 Generated with Claude Code

Partially applying a native function now wraps the function itself as the
lambda body (bodyAST.body = native) instead of generating a trampoline
expression that calls the builtin by name. As in JS, the signature of the
native function is not validated when the partial is invoked.

- implement applyNativeFunction / getNativeFunctionArguments
- only '?' leaves a parameter unbound, missing args are bound to null (#86)
- fix '?' comparison (token value is a Character)
- no lookup by name, so unnamed / custom JFunctions work (#83)
- arity of a JFunctionCallable is taken from its signature
- $each / $merge iterate arrays like JS for..in (#79)
- fix inverted T1007 check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Test Results

1 825 tests  +19   1 823 ✅ +19   8s ⏱️ +2s
  124 suites + 2       2 💤 ± 0 
  124 files   + 2       0 ❌ ± 0 

Results for commit 64ec3c7. ± Comparison against base commit 6850f8d.

♻️ This comment has been updated with latest results.

aeberhart and others added 2 commits November 4, 2026 14:09
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- JLambda (Fn0..Fn8) reports its number of declared parameters, which
  JFunctions created via Jsonata.function(...) keep
- arity unknown (callable / FnVarArgs without signature): use the number
  of args supplied at the partial application
- implement the JS "typeof proc === 'function'" branch: lambdas bound via
  Frame.bind can be partially applied

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@aeberhart
aeberhart requested a review from uw4 November 4, 2026 13:30
Tests contributed by @sergeevik in the earlier PRs, adapted to the
existing API. All pass with the partial application changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@aeberhart

Copy link
Copy Markdown
Contributor Author

might be better to split this large PR into the following smaller parts:

var evaluatedArgs = new ArrayList<>();
for(var ii = 0; ii < ((Symbol)result).body.arguments.size(); ii++) {
evaluatedArgs.add(/* await */ evaluate(((Symbol)result).body.arguments.get(ii), ((Symbol)result).input, ((Symbol)result).environment));
for(var ii = 0; ii < ((Symbol)((Symbol)result).body).arguments.size(); ii++) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe extract (Symbol)result to var?
so many cast. so many code. and now it's twice cast

if (next instanceof Symbol) // Java: not if JFunction
((Symbol)next).token = ((Symbol)result).body.procedure.value;
((Symbol)next).token = ((Symbol)((Symbol)result).body).procedure.value;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bad formatting
closing parenthesis looks like for "if (next instanceof Symbol)" but not

@aeberhart

Copy link
Copy Markdown
Contributor Author

closing this PR. will be done in 3 smaller steps

@aeberhart aeberhart closed this Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incomplete implementation of partial functions

2 participants