Skip to content

Instantly share code, notes, and snippets.

@juangamnik
Last active August 3, 2017 07:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save juangamnik/c913f7a29ed5aca901f0e806cb2132d6 to your computer and use it in GitHub Desktop.
Save juangamnik/c913f7a29ed5aca901f0e806cb2132d6 to your computer and use it in GitHub Desktop.
package memoizable
import java.util.HashMap
import java.util.List
import java.util.stream.Stream
import org.eclipse.xtend.lib.macro.Active
import org.eclipse.xtend.lib.macro.TransformationContext
import org.eclipse.xtend.lib.macro.TransformationParticipant
import org.eclipse.xtend.lib.macro.declaration.CompilationStrategy.CompilationContext
import org.eclipse.xtend.lib.macro.declaration.MutableMethodDeclaration
import org.eclipse.xtend.lib.macro.declaration.TypeReference
import org.eclipse.xtend.lib.macro.declaration.Visibility
@Active(MemoizeProcessor)
annotation Memoizable {
int expectedSize = 666;
}
class MemoizeProcessor implements TransformationParticipant<MutableMethodDeclaration> {
override doTransform(List<? extends MutableMethodDeclaration> methods, TransformationContext context) {
methods.forEach [ it, index |
switch (parameters.size) {
case 0: new ParameterlessMethodMemoizer(it, context, index).generate
case 1: new SingleParameterMethodMemoizer(it, context, index).generate
default: new MultipleParameterMethodMemoizer(it, context, index).generate
}
]
}
}
class ParameterlessMethodMemoizer extends MethodMemoizer {
new(MutableMethodDeclaration method, TransformationContext context, int index) {
super(method, context, index)
}
override cacheFieldType() {
wrappedReturnType
}
override cacheFieldInit(CompilationContext context) '''null'''
override cacheCall(extension CompilationContext context) '''
if («cacheFieldName» == null) {
// do the heavy work.
final «wrappedReturnType.toJavaCode» cacheValue = «initMethodName»();
«cacheFieldName» = cacheValue;
}
«IF !Stream.newTypeReference.isAssignableFrom(wrappedReturnType)»
// for non-lazy return types, return the cached value.
return «cacheFieldName»;
«ELSE»
«val typeArgument = wrappedReturnType.actualTypeArguments.head»
// for lazy iterations use the duplicate feature of Jooq.
final org.jooq.lambda.tuple.Tuple2<Seq<«typeArgument.toJavaCode»>, Seq<«typeArgument.toJavaCode»>> cacheValueDupe =
org.jooq.lambda.Seq.seq(«cacheFieldName»).duplicate();
«cacheFieldName» = cacheValueDupe.v1;
return cacheValueDupe.v2;
«ENDIF»
'''
}
class MultipleParameterMethodMemoizer extends ParametrizedMethodMemoizer {
new(MutableMethodDeclaration method, TransformationContext context, int index) {
super(method, context, index)
}
override parametersToCacheKey() '''
java.util.Arrays.asList(«method.parameters.join("", ",", "")[ simpleName ]»);
'''
}
class SingleParameterMethodMemoizer extends ParametrizedMethodMemoizer {
new(MutableMethodDeclaration method, TransformationContext context, int index) {
super(method, context, index)
}
override parametersToCacheKey() {
method.parameters.head.simpleName
}
}
/**
* Parent type of all method memoizers with parameters
*/
abstract class ParametrizedMethodMemoizer extends MethodMemoizer {
new(MutableMethodDeclaration method, TransformationContext context, int index) {
super(method, context, index)
}
final override cacheFieldType() {
HashMap.newTypeReference(
Object.newTypeReference,
wrappedReturnType
)
}
final override cacheFieldInit(extension CompilationContext context) '''
new «cacheFieldType.toJavaCode»((int)(«expectedSize»*1.5))
'''
def getExpectedSize() {
method.findAnnotation(Memoizable.findTypeGlobally).getIntValue("expectedSize")
}
final override cacheCall(extension CompilationContext context) '''
try {
final Object cacheKey = «parametersToCacheKey»;
if (!«cacheFieldName».containsKey(cacheKey)) {
// do the heavy work.
final «wrappedReturnType.toJavaCode» cacheValue = «initMethodName»(«initMethodParameters»);
// we have a cache miss. hence we call the initializer method
// and store the result for future calls.
«cacheFieldName».put(cacheKey, cacheValue);
}
«IF !Stream.newTypeReference.isAssignableFrom(wrappedReturnType)»
// for non-lazy return types, return the cached value.
return «cacheFieldName».get(cacheKey);
«ELSE»
«val typeArgument = wrappedReturnType.actualTypeArguments.head»
// for lazy iterations use the duplicate feature of Jooq.
final org.jooq.lambda.tuple.Tuple2<
org.jooq.lambda.Seq<«typeArgument.toJavaCode»>,
org.jooq.lambda.Seq<«typeArgument.toJavaCode»>
> cacheValueDupe =
org.jooq.lambda.Seq.seq(«cacheFieldName».get(cacheKey)).duplicate();
«cacheFieldName».put(cacheKey, cacheValueDupe.v1);
return cacheValueDupe.v2;
«ENDIF»
}
catch (Throwable e) {
throw «Exceptions.newTypeReference.toJavaCode».sneakyThrow(e.getCause());
}
'''
/**
* Defines how a cache key object is created from method parameters.
*/
protected def CharSequence parametersToCacheKey()
private def CharSequence initMethodParameters() {
(method.parameters).join("", ",", "") [ simpleName ]
}
}
/**
* Parent type of all method memoizers.
*/
abstract class MethodMemoizer {
protected val extension TransformationContext context
protected val MutableMethodDeclaration method
val int index
new(MutableMethodDeclaration method, TransformationContext context, int index) {
this.method = method
this.context = context
this.index = index
}
protected def TypeReference cacheFieldType()
protected def CharSequence cacheFieldInit(CompilationContext context)
protected def CharSequence cacheCall(CompilationContext context)
def final generate() {
// add a cache field and an initializer method.
method.declaringType => [
// cache field for storing memoized function return values.
addField(cacheFieldName) [
static = method.static
type = cacheFieldType
initializer = [cacheFieldInit]
]
// initializer method executes original method body for creating a cache value.
addMethod(initMethodName) [ init |
init.static = method.static
init.visibility = Visibility.PRIVATE
init.returnType = wrappedReturnType
method.parameters.forEach[init.addParameter(simpleName, type)]
init.exceptions = method.exceptions
init.body = method.body
]
]
// replace original method with one that tries to get the value
// from cache first and if not existent executes the initializer method.
method => [
body = [cacheCall]
returnType = wrappedReturnType
]
}
final protected def wrappedReturnType() {
method.returnType.wrapperIfPrimitive
}
final protected def initMethodName() {
method.simpleName + "_init"
}
final protected def String cacheFieldName() '''cache«index»_«method.simpleName»'''
}
package memoizable;
import java.math.BigInteger;
import java.util.HashMap;
import memoizable.Memoizable;
import org.eclipse.xtend2.lib.StringConcatenation;
import org.eclipse.xtext.xbase.lib.Exceptions;
import org.eclipse.xtext.xbase.lib.InputOutput;
@SuppressWarnings("all")
public class MemoizableExample {
public BigInteger fibonacciNormal(final int n) {
BigInteger _switchResult = null;
switch (n) {
case 0:
_switchResult = BigInteger.ZERO;
break;
case 1:
_switchResult = BigInteger.ONE;
break;
default:
BigInteger _fibonacciNormal = this.fibonacciNormal((n - 1));
BigInteger _fibonacciNormal_1 = this.fibonacciNormal((n - 2));
_switchResult = _fibonacciNormal.add(_fibonacciNormal_1);
break;
}
return _switchResult;
}
@Memoizable
public BigInteger fibonacci(final int n) {
try {
final Object cacheKey = n;
if (!cache0_fibonacci.containsKey(cacheKey)) {
// do the heavy work.
final BigInteger cacheValue = fibonacci_init(n);
// we have a cache miss. hence we call the initializer method
// and store the result for future calls.
cache0_fibonacci.put(cacheKey, cacheValue);
}
// for non-lazy return types, return the cached value.
return cache0_fibonacci.get(cacheKey);
}
catch (Throwable e) {
throw Exceptions.sneakyThrow(e.getCause());
}
}
public static void main(final String[] args) {
final MemoizableExample mem = new MemoizableExample();
long startTime = System.currentTimeMillis();
StringConcatenation _builder = new StringConcatenation();
_builder.append("fib: ");
BigInteger _fibonacci = mem.fibonacci(50);
_builder.append(_fibonacci, "");
InputOutput.<String>println(_builder.toString());
StringConcatenation _builder_1 = new StringConcatenation();
_builder_1.append("time: ");
long _currentTimeMillis = System.currentTimeMillis();
long _minus = (_currentTimeMillis - startTime);
_builder_1.append(_minus, "");
InputOutput.<String>println(_builder_1.toString());
InputOutput.println();
long _currentTimeMillis_1 = System.currentTimeMillis();
startTime = _currentTimeMillis_1;
StringConcatenation _builder_2 = new StringConcatenation();
_builder_2.append("fib: ");
BigInteger _fibonacciNormal = mem.fibonacciNormal(50);
_builder_2.append(_fibonacciNormal, "");
InputOutput.<String>println(_builder_2.toString());
StringConcatenation _builder_3 = new StringConcatenation();
_builder_3.append("time: ");
long _currentTimeMillis_2 = System.currentTimeMillis();
long _minus_1 = (_currentTimeMillis_2 - startTime);
_builder_3.append(_minus_1, "");
InputOutput.<String>println(_builder_3.toString());
}
private HashMap<Object, BigInteger> cache0_fibonacci = new HashMap<Object, BigInteger>((int)(666*1.5))
;
private BigInteger fibonacci_init(final int n) {
BigInteger _switchResult = null;
switch (n) {
case 0:
_switchResult = BigInteger.ZERO;
break;
case 1:
_switchResult = BigInteger.ONE;
break;
default:
BigInteger _fibonacci = this.fibonacci((n - 1));
BigInteger _fibonacci_1 = this.fibonacci((n - 2));
_switchResult = _fibonacci.add(_fibonacci_1);
break;
}
return _switchResult;
}
}
package memoizable
import java.math.BigInteger
class MemoizableExample {
def BigInteger fibonacciNormal(int n) {
switch n {
case 0: 0bi
case 1: 1bi
default: fibonacciNormal(n - 1) + fibonacciNormal(n - 2)
}
}
@Memoizable def BigInteger fibonacci(int n) {
switch n {
case 0: 0bi
case 1: 1bi
default: fibonacci(n - 1) + fibonacci(n - 2)
}
}
def static void main(String[] args) {
val mem = new MemoizableExample
var startTime = System.currentTimeMillis
println('''fib: «mem.fibonacci(50)»''')
println('''time: «System.currentTimeMillis - startTime»''')
println
startTime = System.currentTimeMillis
println('''fib: «mem.fibonacciNormal(50)»''')
println('''time: «System.currentTimeMillis - startTime»''')
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment