Open GavinRay97 opened 2 years ago
A note about the Javadoc on the class that mentions caching statements to results:
I tried to implement this but it turned out to be a bit trickier than I thought
Had started writing a class, EvaluationContext
, which holds an Environment
+ Map<String, ForeignValue>
+ AstNode
So I thought that a Cache<EvaluationContext, CompiledStatement>
could be implemented.
But the hashing doesn't seem to work, I always get cache misses =/
Implementing .equals()
and .hashCode()
on AstNode
and MapEnvironment
don't seem to change this 🤔
public class ProgrammaticShell implements Session.Shell {
// ...
private boolean enableCache = true;
private Cache<EvaluationContext, CompiledStatement> compiledStatementCache =
CacheBuilder.newBuilder().recordStats().build();
// ....
private void command(AstNode statement, Consumer<String> outLines) {
try {
final Map<String, Binding> outBindings = new LinkedHashMap<>();
final Environment env = env0.bindAll(outBindings.values());
final List<Binding> bindings = new ArrayList<>();
CompiledStatement compiled;
// If cache is enabled
if (enableCache) {
EvaluationContext context = new EvaluationContext(env0, foreignValueMap, statement);
CompiledStatement compiledStatement = compiledStatementCache.getIfPresent(context);
// If not in cache, compile and put in cache
if (compiledStatement == null) {
compiled = Compiles.prepareStatement(typeSystem, session, env,
statement, null, e -> appendToOutput(e, outLines));
compiledStatementCache.put(context, compiled);
} else {
// If in cache, use cached compiled statement
compiled = compiledStatement;
}
} else {
// If cache is disabled, compile and don't put in cache
compiled = Compiles.prepareStatement(typeSystem, session, env,
statement, null, e -> appendToOutput(e, outLines));
}
compiled.eval(session, env, outLines, bindings::add);
bindings.forEach(b -> outBindings.put(b.id.name, b));
} catch (Codes.MorelRuntimeException e) {
appendToOutput(e, outLines);
}
}
}
final class EvaluationContext {
private final Session session;
private final Environment env;
private final Map<String, ForeignValue> foreignValueMap;
EvaluationContext(
Session session,
Environment env,
Map<String, ForeignValue> foreignValueMap) {
this.session = session;
this.env = env;
this.foreignValueMap = foreignValueMap;
}
public Session session() {
return session;
}
public Environment env() {
return env;
}
public Map<String, ForeignValue> foreignValueMap() {
return foreignValueMap;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (EvaluationContext) obj;
return Objects.equals(this.session, that.session) &&
Objects.equals(this.env, that.env) &&
Objects.equals(this.foreignValueMap, that.foreignValueMap);
}
@Override
public int hashCode() {
return Objects.hash(session, env, foreignValueMap);
}
@Override
public String toString() {
return "EvaluationContext[" +
"session=" + session + ", " +
"env=" + env + ", " +
"foreignValueMap=" + foreignValueMap + ']';
}
}
Your EvaluationContext
looks fairly similar to the bindingMap
field in Main.Shell
.
I wouldn't use a Cache
for these purposes, because we don't want bindings to disappear, there's no reliable way to re-create a value that has been dropped from the cache (if Morel ever becomes even a little bit impure).
In ML and Morel bindings in the shell become inaccessible if another binding has been created with the same name. Then the garbage collector will take its course. The values, of course, may still be referenced via closures:
val x = 1;
val addX = fn y => y + x;
addX 2; (* returns 3 *)
val x = 5; (* makes the previous binding of x invisible *)
addX 2; (* still returns 3 *)
This is currently failing in the test, and I've tried to debug but unsure why (attempted to mirror the existing Shell code):EDIT: Removing
static
from the variable fixed it.