Skip to content

Commit 8f7b6aa

Browse files
authored
Merge pull request #133 from dashjoin/83-partial-arity
Partial application of callables and lambdas without declared arity (#83)
2 parents f1e24f6 + 46d2bcd commit 8f7b6aa

3 files changed

Lines changed: 103 additions & 14 deletions

File tree

‎src/main/java/com/dashjoin/jsonata/Jsonata.java‎

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,8 +1927,8 @@ Object evaluateLambda(Symbol expr, Object input, Frame environment) {
19271927
result = partialApplyProcedure((Symbol)proc, (List)evaluatedArgs);
19281928
} else if (Utils.isFunction(proc)) {
19291929
result = partialApplyNativeFunction((JFunction)proc /*.implementation*/, evaluatedArgs);
1930-
// } else if (typeof proc === "function") {
1931-
// result = partialApplyNativeFunction(proc, evaluatedArgs);
1930+
} else if (proc instanceof JLambda) {
1931+
result = partialApplyNativeFunction(new JFunction((JLambda)proc, null), evaluatedArgs);
19321932
} else {
19331933
throw new JException("T1008",
19341934
//stack: (new Error()).stack,
@@ -2024,6 +2024,12 @@ Object partialApplyNativeFunction(JFunction _native, List args) {
20242024
// create a lambda Object that wraps and invokes the native function
20252025
// get the list of declared arguments from the native function
20262026
// this has to be picked out from the toString() value
2027+
if (_native.getNumberOfArgs() < 0) {
2028+
// Java: parameters unknown (callable without signature), use the number of args supplied
2029+
var fn = new JFunction(_native.function, null);
2030+
fn.numberOfArgs = args.size();
2031+
_native = fn;
2032+
}
20272033
var sigArgs = getNativeFunctionArguments(_native);
20282034
sigArgs = sigArgs.stream().map(sigArg -> {
20292035
return "$" + sigArg;
@@ -2087,34 +2093,34 @@ public static JFunction function(String name, String signature, Class clazz, Obj
20872093
}
20882094

20892095
public static<A,B,R> JFunction function(String name, FnVarArgs<R> func, String signature) {
2090-
return new JFunction(func.getJFunctionCallable(), signature);
2096+
return new JFunction(func, signature);
20912097
}
20922098
public static<A,R> JFunction function(String name, Fn0<R> func, String signature) {
2093-
return new JFunction(func.getJFunctionCallable(), signature);
2099+
return new JFunction(func, signature);
20942100
}
20952101
public static<A,B,R> JFunction function(String name, Fn1<A,R> func, String signature) {
2096-
return new JFunction(func.getJFunctionCallable(), signature);
2102+
return new JFunction(func, signature);
20972103
}
20982104
public static<A,B,R> JFunction function(String name, Fn2<A,B,R> func, String signature) {
2099-
return new JFunction(func.getJFunctionCallable(), signature);
2105+
return new JFunction(func, signature);
21002106
}
21012107
public static<A,B,C,R> JFunction function(String name, Fn3<A,B,C,R> func, String signature) {
2102-
return new JFunction(func.getJFunctionCallable(), signature);
2108+
return new JFunction(func, signature);
21032109
}
21042110
public static<A,B,C,D,R> JFunction function(String name, Fn4<A,B,C,D,R> func, String signature) {
2105-
return new JFunction(func.getJFunctionCallable(), signature);
2111+
return new JFunction(func, signature);
21062112
}
21072113
public static<A,B,C,D,E,R> JFunction function(String name, Fn5<A,B,C,D,E,R> func, String signature) {
2108-
return new JFunction(func.getJFunctionCallable(), signature);
2114+
return new JFunction(func, signature);
21092115
}
21102116
public static<A,B,C,D,E,F,R> JFunction function(String name, Fn6<A,B,C,D,E,F,R> func, String signature) {
2111-
return new JFunction(func.getJFunctionCallable(), signature);
2117+
return new JFunction(func, signature);
21122118
}
21132119
public static<A,B,C,D,E,F,G,R> JFunction function(String name, Fn7<A,B,C,D,E,F,G,R> func, String signature) {
2114-
return new JFunction(func.getJFunctionCallable(), signature);
2120+
return new JFunction(func, signature);
21152121
}
21162122
public static<A,B,C,D,E,F,G,H,R> JFunction function(String name, Fn8<A,B,C,D,E,F,G,H,R> func, String signature) {
2117-
return new JFunction(func.getJFunctionCallable(), signature);
2123+
return new JFunction(func, signature);
21182124
}
21192125

21202126
/**
@@ -2170,6 +2176,14 @@ public Frame createFrame(Frame enclosingEnvironment) {
21702176
}
21712177

21722178
public static interface JLambda {
2179+
JFunctionCallable getJFunctionCallable();
2180+
2181+
/**
2182+
* @return the number of declared parameters, -1 if unknown (varargs)
2183+
*/
2184+
default int getNumberOfArgs() {
2185+
return -1;
2186+
}
21732187
}
21742188

21752189
public static interface FnVarArgs<R> extends JLambda, Function<List<?>, R> {
@@ -2180,42 +2194,49 @@ default JFunctionCallable getJFunctionCallable() {
21802194
}
21812195
}
21822196
public static interface Fn0<R> extends JLambda, Supplier<R> {
2197+
default int getNumberOfArgs() { return 0; }
21832198
default JFunctionCallable getJFunctionCallable() {
21842199
return (input, args) -> get();
21852200
}
21862201
}
21872202
public static interface Fn1<A,R> extends JLambda, Function<A,R> {
2203+
default int getNumberOfArgs() { return 1; }
21882204
default JFunctionCallable getJFunctionCallable() {
21892205
return (input, args) -> apply((A) args.get(0));
21902206
}
21912207
}
21922208
public static interface Fn2<A,B,R> extends JLambda, BiFunction<A,B,R> {
2209+
default int getNumberOfArgs() { return 2; }
21932210
default JFunctionCallable getJFunctionCallable() {
21942211
return (input, args) -> apply((A) args.get(0), (B) args.get(1));
21952212
}
21962213
}
21972214
public static interface Fn3<A,B,C,R> extends JLambda {
2215+
default int getNumberOfArgs() { return 3; }
21982216
R apply(A a, B b, C c);
21992217
default JFunctionCallable getJFunctionCallable() {
22002218
return (input, args) -> apply((A) args.get(0), (B) args.get(1),
22012219
(C) args.get(2));
22022220
}
22032221
}
22042222
public static interface Fn4<A,B,C,D,R> extends JLambda {
2223+
default int getNumberOfArgs() { return 4; }
22052224
R apply(A a, B b, C c, D d);
22062225
default JFunctionCallable getJFunctionCallable() {
22072226
return (input, args) -> apply((A) args.get(0), (B) args.get(1),
22082227
(C) args.get(2), (D) args.get(3));
22092228
}
22102229
}
22112230
public static interface Fn5<A,B,C,D,E,R> extends JLambda {
2231+
default int getNumberOfArgs() { return 5; }
22122232
R apply(A a, B b, C c, D d, E e);
22132233
default JFunctionCallable getJFunctionCallable() {
22142234
return (input, args) -> apply((A) args.get(0), (B) args.get(1),
22152235
(C) args.get(2), (D) args.get(3), (E) args.get(4));
22162236
}
22172237
}
22182238
public static interface Fn6<A,B,C,D,E,F,R> extends JLambda {
2239+
default int getNumberOfArgs() { return 6; }
22192240
R apply(A a, B b, C c, D d, E e, F f);
22202241
default JFunctionCallable getJFunctionCallable() {
22212242
return (input, args) -> apply((A) args.get(0), (B) args.get(1),
@@ -2224,6 +2245,7 @@ default JFunctionCallable getJFunctionCallable() {
22242245
}
22252246
}
22262247
public static interface Fn7<A,B,C,D,E,F,G,R> extends JLambda {
2248+
default int getNumberOfArgs() { return 7; }
22272249
R apply(A a, B b, C c, D d, E e, F f, G g);
22282250
default JFunctionCallable getJFunctionCallable() {
22292251
return (input, args) -> apply((A) args.get(0), (B) args.get(1),
@@ -2232,6 +2254,7 @@ default JFunctionCallable getJFunctionCallable() {
22322254
}
22332255
}
22342256
public static interface Fn8<A,B,C,D,E,F,G,H,R> extends JLambda {
2257+
default int getNumberOfArgs() { return 8; }
22352258
R apply(A a, B b, C c, D d, E e, F f, G g, H h);
22362259
default JFunctionCallable getJFunctionCallable() {
22372260
return (input, args) -> apply((A) args.get(0), (B) args.get(1),
@@ -2260,6 +2283,12 @@ public static class JFunction implements JFunctionCallable, JFunctionSignatureVa
22602283
Signature signature;
22612284
Method method;
22622285
Object methodInstance;
2286+
int numberOfArgs = -1;
2287+
2288+
public JFunction(JLambda lambda, String signature) {
2289+
this(lambda.getJFunctionCallable(), signature);
2290+
this.numberOfArgs = lambda.getNumberOfArgs();
2291+
}
22632292

22642293
public JFunction(JFunctionCallable function, String signature) {
22652294
this.function = function;
@@ -2306,11 +2335,16 @@ public Object validate(Object args, Object context) {
23062335
return args;
23072336
}
23082337

2338+
/**
2339+
* @return the number of declared parameters, -1 if unknown
2340+
*/
23092341
public int getNumberOfArgs() {
23102342
if (method != null)
23112343
return method.getParameterTypes().length;
2344+
if (numberOfArgs >= 0)
2345+
return numberOfArgs;
23122346
// Java: a JFunctionCallable has no declared parameters, use the signature (if any)
2313-
return signature != null ? signature.getNumberOfArgs() : 0;
2347+
return signature != null ? signature.getNumberOfArgs() : -1;
23142348
}
23152349
}
23162350

‎src/test/java/com/dashjoin/jsonata/OldPrsTest.java‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import com.dashjoin.jsonata.Jsonata.JFunction;
1010
import com.dashjoin.jsonata.json.Json;
1111

12-
/** tests from PRs #80, #85, #87, adapted to the existing API (partial application of custom functions without signature: see #83) */
12+
/** tests from PRs #80, #85, #87, adapted to the existing API */
1313
public class OldPrsTest {
1414

1515
static Object res(String name) throws Exception {
@@ -50,6 +50,28 @@ public void testFunctionWithInputData() {
5050
Assertions.assertEquals(9, expression.evaluate(Map.of("factor", "3")));
5151
}
5252

53+
@SuppressWarnings("unchecked")
54+
@Test
55+
public void testFunctionWithInputData2() {
56+
JFunction multiply = new JFunction((input, args) -> {
57+
Object first = args.get(0);
58+
Object second = args.size() > 1 ? args.get(1)
59+
: Integer.parseInt(((Map<String, Object>) input).get("factor").toString());
60+
return (Integer) first * (Integer) second;
61+
}, null);
62+
var expression = Jsonata.jsonata("$multiply(3) ~> $multiply(?, 2)");
63+
expression.registerFunction("multiply", multiply);
64+
Assertions.assertEquals(18, expression.evaluate(Map.of("factor", "3")));
65+
}
66+
67+
@Test
68+
public void testCustomFunctionPipe() {
69+
JFunction add = new JFunction((input, args) -> (Integer) args.get(0) + (Integer) args.get(1), null);
70+
var expression = Jsonata.jsonata("$add(1, 2) ~> $add(?, 3)");
71+
expression.registerFunction("add", add);
72+
Assertions.assertEquals(6, expression.evaluate(null));
73+
}
74+
5375
// #87
5476
@Test
5577
public void testMapWithAppend() {

‎src/test/java/com/dashjoin/jsonata/PartialTest.java‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.junit.jupiter.api.Assertions;
77
import org.junit.jupiter.api.Test;
88
import com.dashjoin.jsonata.Jsonata.JFunction;
9+
import com.dashjoin.jsonata.Jsonata.Fn2;
910
import com.dashjoin.jsonata.Jsonata.JFunctionCallable;
1011

1112
/**
@@ -72,4 +73,36 @@ public void missingDollar() {
7273
var ex = Assertions.assertThrows(JException.class, () -> jsonata("substring(?, 1)").evaluate(null));
7374
Assertions.assertEquals("T1007", ex.getError());
7475
}
76+
77+
// #83: a JFunctionCallable without signature: arity = number of args supplied
78+
@Test
79+
public void customFunctionWithoutSignature() {
80+
var expression = jsonata("$add(1, 2) ~> $add(?, 3)");
81+
expression.registerFunction("add", new JFunction(new JFunctionCallable() {
82+
@SuppressWarnings("rawtypes")
83+
@Override
84+
public Object call(Object input, List args) throws Throwable {
85+
return (Integer) args.get(0) + (Integer) args.get(1);
86+
}
87+
}, null));
88+
Assertions.assertEquals(6, expression.evaluate(null));
89+
}
90+
91+
// Fn* factories without signature: arity of the lambda
92+
@Test
93+
public void fnFactoryWithoutSignature() {
94+
var expression = jsonata("$add(1, 2) ~> $add(?, 3)");
95+
expression.registerFunction("add",
96+
Jsonata.function("add", (Fn2<Integer, Integer, Integer>) (a, b) -> a + b, null));
97+
Assertions.assertEquals(6, expression.evaluate(null));
98+
}
99+
100+
// Fn* lambdas bound via Frame.bind can be partially applied
101+
@Test
102+
public void boundLambda() {
103+
var expression = jsonata("$f(1, 2) ~> $f(?, 3)");
104+
var frame = expression.createFrame();
105+
frame.bind("f", (Fn2<Integer, Integer, Integer>) (a, b) -> a + b);
106+
Assertions.assertEquals(6, expression.evaluate(null, frame));
107+
}
75108
}

0 commit comments

Comments
 (0)