Last active
August 3, 2017 07:54
-
-
Save juangamnik/c913f7a29ed5aca901f0e806cb2132d6 to your computer and use it in GitHub Desktop.
This file contains 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
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»''' | |
} |
This file contains 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
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; | |
} | |
} |
This file contains 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
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