Last active
July 3, 2017 16:31
-
-
Save divs1210/85ccc95c454253f8f1c5e4e547f9d5b5 to your computer and use it in GitHub Desktop.
Dynamic Functional Control Flow Structures for Java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.*; | |
import java.util.concurrent.Callable; | |
import java.util.function.Function; | |
class SLIP { | |
/** | |
* Keywords | |
*/ | |
public static final Object | |
_IF = new Object(), | |
_LET = new Object(), | |
_WHILE = new Object(); | |
private static final Object | |
FILLER = new Object(); | |
/** | |
* Y combinator - Creates recursive lambdas | |
* */ | |
public static <T,R> Function<T,R> Y(Hopper<T,R> f) { | |
return SLIP.<T,R> fixer().fix(f); | |
} | |
/** | |
* Functional ternary expression | |
* */ | |
public static <R> R IF(boolean cond, Callable<R> then, Callable<R> _else) { | |
List<Callable> condBody = SLIP.list( | |
(Condition) () -> cond, then, | |
(Condition) () -> true, _else); | |
return COND(condBody); | |
} | |
/** | |
* Linear variant for nested IFs | |
*/ | |
public static <R> R COND(List<Callable> condBodyPairs) { | |
if (condBodyPairs.isEmpty()) | |
return null; | |
Condition cond = (Condition) condBodyPairs.get(0); | |
Callable<R> body = condBodyPairs.get(1); | |
try { | |
if (cond.call()) | |
return body.call(); | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} | |
return COND(drop(2, condBodyPairs)); | |
} | |
private static <R> R LET(List<Object> bindings, Body<R> body, Env env) { | |
if(bindings.isEmpty()) | |
return body.apply(env); | |
String k = (String) bindings.get(0); | |
Object v = bindings.get(1); | |
if(v instanceof DependentBinding) | |
v = ((DependentBinding) v).apply(env); | |
env.put(k, v); | |
return LET(drop(2, bindings), body, env); | |
} | |
/** | |
* Introduce local bindings. | |
* Bindings down the chain can depend on the ones above | |
* them by having a DependentBinding as the value. | |
* | |
* Supports special bindings: _IF, _LET, and _WHILE | |
* that work like their counterparts in Clojure's for | |
* */ | |
public static <R> R LET(List<Object> bindings, Body<R> body) { | |
return LET(bindings, body, new Env()); | |
} | |
private static <T> List<T> FOR(List<Object> bindings, Body<Object> body, Env env) { | |
List<T> res = new ArrayList(1); | |
if(bindings.isEmpty()) { | |
Object tmp = body.apply(env); | |
if (tmp != FILLER) | |
res.add((T) tmp); | |
return res; | |
} | |
Object _k = bindings.get(0); | |
Object v = bindings.get(1); | |
if (_k == SLIP._IF) { | |
boolean cond = (Boolean) ((Body) v).apply(env); | |
Body<Object> newBody = | |
e -> cond? | |
body.apply(e) : | |
FILLER; | |
return FOR(drop(2, bindings), newBody, env); | |
} else if (_k == SLIP._LET){ | |
return LET((List) v, | |
(Body<List<T>>) e -> FOR(drop(2, bindings), body, env), | |
env); | |
} else if (_k == SLIP._WHILE) { | |
if ((Boolean) ((Body) v).apply(env)) | |
return FOR(drop(2, bindings), body, env); | |
else | |
return res; | |
} | |
String k = (String) _k; | |
if(v instanceof Body) | |
v = ((Body) v).apply(env); | |
for(Object o: (Iterable) v){ | |
env.put(k, o); | |
res.addAll(FOR(drop(2, bindings), body, env)); | |
env.remove(k); | |
} | |
return res; | |
} | |
/** | |
* Clojure inspired list comprehension. | |
* Supports DependentBinding like LET. | |
*/ | |
public static <T> List<T> FOR(List<Object> bindings, Body<T> body) { | |
return FOR(bindings, (Body<Object>) body, new Env()); | |
} | |
// Helpers | |
/** | |
* Useful within COND | |
*/ | |
public static final Condition _else = | |
() -> true; | |
/** | |
* @return A list of Objects | |
*/ | |
public static List list(Object... objects) { | |
return Arrays.asList(objects); | |
} | |
/** | |
* @return Numbers from start (inclusive) to | |
* end (exclusive) in steps of 1. | |
*/ | |
public static List<Double> range(double start, double end) { | |
return range(start, end, 1); | |
} | |
/** | |
* @return Numbers from start (inclusive) to | |
* end (exclusive) in steps of step. | |
*/ | |
public static List<Double> range(double start, double end, double step) { | |
List<Double> res = new ArrayList(); | |
try { | |
final Double[] curr = {start}; | |
Condition shouldStop = | |
step > 0 ? | |
() -> curr[0] >= end : | |
() -> curr[0] <= end; | |
while (!shouldStop.call()) { | |
res.add(curr[0]); | |
curr[0] += step; | |
} | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} | |
return res; | |
} | |
/** | |
* @return list without the first n elements | |
*/ | |
public static <T> List<T> drop(int n, List<T> list) { | |
return list.subList(n, list.size()); | |
} | |
// Internal Helpers | |
private static <T,R> SelfApply<Fixer<T,R>> combinator() { | |
return me -> hopper -> input -> hopper.hop(me.self(me).fix(hopper)).apply(input); | |
} | |
private static <T,R> Fixer<T,R> fixer() { | |
final SelfApply<Fixer<T,R>> y = combinator(); | |
return y.self(y); | |
} | |
// Supporting Types | |
public interface Hopper<T,R> { | |
Function<T,R> hop(Function<T,R> inFunc); | |
} | |
private interface Fixer<T,R> { | |
Function<T,R> fix(Hopper<T,R> toFix); | |
} | |
private interface SelfApply<X> { | |
X self(SelfApply<X> me); | |
} | |
// Supporting Type Aliases | |
public static class Env extends HashMap<String,Object> {} | |
public interface Condition extends Callable<Boolean> {} | |
public interface Expression extends Callable<Object> {} | |
public interface Body<R> extends Function<Env,R> {} | |
public interface DependentBinding extends Body<Object> {} | |
} | |
// Tests | |
public class Main { | |
public static void main(String[] args) { | |
// COND is a linear alternative to | |
// nested ternary conditionals. | |
// Bodies can have multiple lines, | |
// unlike the default ternary operator. | |
assert SLIP.<Character> COND(SLIP.list( | |
(SLIP.Condition) () -> 3==0, | |
(SLIP.Expression) () -> 'a', | |
(SLIP.Condition) () -> 2==0, | |
(SLIP.Expression) () -> 'b', | |
(SLIP.Condition) () -> 1==0, | |
(SLIP.Expression) () -> 'c', | |
SLIP._else, | |
(SLIP.Expression) () -> 'd') | |
) == 'd'; | |
// Y combinator (recursive lambda) + IF | |
assert SLIP.<Integer,Integer> Y( | |
fact -> | |
n -> | |
SLIP.IF(n > 1, | |
() -> n * fact.apply(n - 1), | |
() -> 1) | |
).apply(5) == 120; | |
// LET with dependent bindings | |
assert SLIP.LET(SLIP.list( | |
"a", 1, | |
"b", (SLIP.DependentBinding) env -> | |
(Integer) env.get("a") + 1), | |
env -> | |
(Integer) env.get("b") | |
) == 2; | |
// List comprehension | |
List tuples = | |
SLIP.FOR(SLIP.list( | |
"outer", SLIP.range(0, 3), | |
SLIP._LET, SLIP.list("o", (SLIP.DependentBinding) env -> | |
env.get("outer")), | |
SLIP._IF, (SLIP.DependentBinding) env -> | |
(Double) env.get("o") != 1, | |
"inner", SLIP.range(0, 30, 10), | |
SLIP._WHILE, (SLIP.DependentBinding) env -> { | |
double i = (Double) env.get("inner"); | |
return i < 20; | |
}), | |
env -> { | |
int o = ((Double) env.get("o")).intValue(); | |
int i = ((Double) env.get("inner")).intValue(); | |
return SLIP.list(o, i); | |
}); | |
assert tuples.equals( | |
SLIP.list( | |
SLIP.list(0, 0), SLIP.list(0, 10), // (0, 20) filtered | |
// all (1, x) filtered | |
SLIP.list(2, 0), SLIP.list(2, 10) // (2, 20) filtered | |
) | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment