Is monadic programming in Java 8 slower? Below is my test (a right-biased Either is used that creates new instances for each computation). The imperative version is 1000 times faster. How do I program monadicaly in Java8 while getting comparable performance?
Main.java
public class Main {
public static void main(String args[]){
Main m = new Main();
m.work();
m.work2();
}
public void work(){
final long start = System.nanoTime();
final Either<Throwable,Integer> result =
Try(this::getInput).flatMap((s) ->
Try(this::getInput).flatMap((s2) ->
parseInt(s).flatMap((i) ->
parseInt(s2).map((i2) ->
i + i2
))));
final long end = System.nanoTime();
result.map(this::println).leftMap(this::println);
System.out.println((end-start)/1000+"us to execute");
}
public void work2(){
Object result;
final long start = System.nanoTime();
try {
final String s = getInput();
final String s2 = getInput();
final int i = parzeInt(s);
final int i2 = parzeInt(s2);
result = i + i2;
}catch(Throwable t){
result=t;
}
final long end = System.nanoTime();
println(result);
System.out.println((end-start)/1000+"us to execute");
}
public <A> A println(final A a){
System.out.println(a);
return a;
}
public String getInput(){
final Integer value = new Random().nextInt();
if(value % 2 == 0) return "Surprise!!!";
return value+"";
}
public Either<Throwable,Integer> parseInt(final String s){
try{
return Either.right(Integer.parseInt(s));
}catch(final Throwable t){
return Either.left(t);
}
}
public Integer parzeInt(final String s){
return Integer.parseInt(s);
}
}
Either.java
public abstract class Either<L,R>
{
public static <L,R> Either<L,R> left(final L l){
return new Left(l);
}
public static <L,R> Either<L,R> right(final R r){
return new Right(r);
}
public static<L,R> Either<L,R> toEither(final Optional<R> oR,final L l){
return oR.isPresent() ? right(oR.get()) : left(l);
}
public static <R> Either<Throwable,R> Try(final Supplier<R> sr){
try{
return right(sr.get());
}catch(Throwable t){
return left(t);
}
}
public abstract <R2> Either<L,R2> flatMap(final Function<R,Either<L,R2>> f);
public abstract <R2> Either<L,R2> map(final Function<R,R2> f);
public abstract <L2> Either<L2,R> leftMap(final Function<L,L2> f);
public abstract Either<R,L> swap();
public static class Left<L,R> extends Either<L,R> {
final L l;
private Left(final L l){
this.l=l;
}
public <R2> Either<L,R2> flatMap(final Function<R,Either<L,R2>> f){
return (Either<L,R2>)this;
}
public <R2> Either<L,R2> map(final Function<R,R2> f){
return (Either<L,R2>)this;
}
public <L2> Either<L2,R> leftMap(final Function<L,L2> f){
return new Left(f.apply(l));
}
public Either<R,L> swap(){
return new Right(l);
}
}
public static class Right<L,R> extends Either<L,R> {
final R r;
private Right(final R r){
this.r=r;
}
public <R2> Either<L,R2> flatMap(final Function<R,Either<L,R2>> f){
return f.apply(r);
}
public <R2> Either<L,R2> map(final Function<R,R2> f){
return new Right(f.apply(r));
}
public <L2> Either<L2,R> leftMap(final Function<L,L2> f){
return (Either<L2,R>)this;
}
public Either<R,L> swap(){
return new Left(r);
}
}
}
While I don't quite understand your effort – apparently you are using map
for side effects and you don't really have any alternative to get the result unboxed from the Either type – I have measured your work on JMH as it is. Your usage of Random
was wrong, I corrected that. This is the code I used:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@OperationsPerInvocation(Measure.SIZE)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
@Fork(1)
public class Measure
{
static final int SIZE = 1;
@Benchmark public Either<Throwable, Integer> workMonadically() {
final Either<Throwable,Integer> result =
Try(this::getInput).flatMap((s) ->
Try(this::getInput).flatMap((s2) ->
parseInt(s).flatMap((i) ->
parseInt(s2).map((i2) ->
i + i2
))));
return result;
}
@Benchmark public Object workImperatively() {
Object result;
try {
final String s = getInput();
final String s2 = getInput();
final int i = parzeInt(s);
final int i2 = parzeInt(s2);
result = i + i2;
}catch(Throwable t){
result=t;
}
return result;
}
public String getInput() {
final Integer value = ThreadLocalRandom.current().nextInt();
if (value % 2 == 0) return "Surprise!!!";
return String.valueOf(value);
}
public Either<Throwable,Integer> parseInt(final String s){
try{
return Either.right(Integer.parseInt(s));
}catch(final Throwable t){
return Either.left(t);
}
}
public Integer parzeInt(final String s){
return Integer.parseInt(s);
}
public static abstract class Either<L,R>
{
public static <L,R> Either<L,R> left(final L l){
return new Left<>(l);
}
public static <L,R> Either<L,R> right(final R r){
return new Right<>(r);
}
public static<L,R> Either<L,R> toEither(final Optional<R> oR,final L l){
return oR.isPresent() ? right(oR.get()) : left(l);
}
public static <R> Either<Throwable,R> Try(final Supplier<R> sr){
try{
return right(sr.get());
}catch(Throwable t){
return left(t);
}
}
public abstract <R2> Either<L,R2> flatMap(final Function<R,Either<L,R2>> f);
public abstract <R2> Either<L,R2> map(final Function<R,R2> f);
public abstract <L2> Either<L2,R> leftMap(final Function<L,L2> f);
public abstract Either<R,L> swap();
public static class Left<L,R> extends Either<L,R> {
final L l;
private Left(final L l){
this.l=l;
}
@Override public <R2> Either<L,R2> flatMap(final Function<R,Either<L,R2>> f){
return (Either<L,R2>)this;
}
@Override public <R2> Either<L,R2> map(final Function<R,R2> f){
return (Either<L,R2>)this;
}
@Override public <L2> Either<L2,R> leftMap(final Function<L,L2> f){
return new Left<>(f.apply(l));
}
@Override public Either<R,L> swap(){
return new Right<>(l);
}
}
public static class Right<L,R> extends Either<L,R> {
final R r;
private Right(final R r){
this.r=r;
}
@Override public <R2> Either<L,R2> flatMap(final Function<R,Either<L,R2>> f){
return f.apply(r);
}
@Override public <R2> Either<L,R2> map(final Function<R,R2> f){
return new Right<>(f.apply(r));
}
@Override public <L2> Either<L2,R> leftMap(final Function<L,L2> f){
return (Either<L2,R>)this;
}
@Override public Either<R,L> swap(){
return new Left<>(r);
}
}
}
}
and this is the result:
Benchmark Mode Cnt Score Error Units
Measure.workImperatively avgt 5 1646,874 ± 137,326 ns/op
Measure.workMonadically avgt 5 1990,668 ± 281,646 ns/op
So there's almost no difference at all.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With