├── java-15 ├── .mvn │ └── jvm.config ├── src │ └── main │ │ └── java │ │ └── examples │ │ ├── Pair.java │ │ ├── Helpers.java │ │ └── SimpleInterpreter.java └── pom.xml ├── java-17 ├── .mvn │ └── jvm.config ├── src │ └── main │ │ └── java │ │ └── examples │ │ ├── Pair.java │ │ ├── Helpers.java │ │ └── SimpleInterpreter.java └── pom.xml ├── java-19 ├── .mvn │ └── jvm.config ├── src │ └── main │ │ └── java │ │ └── examples │ │ ├── Pair.java │ │ ├── Helpers.java │ │ └── SimpleInterpreter.java └── pom.xml ├── scala.png ├── java-8 ├── src │ └── main │ │ └── java │ │ └── examples │ │ ├── Pair.java │ │ ├── Helpers.java │ │ └── SimpleInterpreter.java └── pom.xml ├── run.sh ├── SimpleScala3Interpreter.scala ├── simpleInterpreter.scala └── README.md /java-15/.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | --enable-preview 2 | -------------------------------------------------------------------------------- /java-17/.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | --enable-preview 2 | -------------------------------------------------------------------------------- /java-19/.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | --enable-preview 2 | -------------------------------------------------------------------------------- /scala.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Randgalt/expressive-java/HEAD/scala.png -------------------------------------------------------------------------------- /java-15/src/main/java/examples/Pair.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | public record Pair(A a, B b) { } 4 | -------------------------------------------------------------------------------- /java-17/src/main/java/examples/Pair.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | public record Pair(A a, B b) {} 4 | -------------------------------------------------------------------------------- /java-19/src/main/java/examples/Pair.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | public record Pair(A a, B b) {} 4 | -------------------------------------------------------------------------------- /java-8/src/main/java/examples/Pair.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | public interface Pair { 4 | A a(); 5 | 6 | B b(); 7 | } 8 | -------------------------------------------------------------------------------- /java-15/src/main/java/examples/Helpers.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.soabase.recordbuilder.core.RecordBuilder; 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | @RecordBuilder.Include({ 9 | SimpleInterpreter.M.class, 10 | SimpleInterpreter.Num.class, 11 | SimpleInterpreter.Fun.class, 12 | SimpleInterpreter.App.class, 13 | SimpleInterpreter.Lam.class, 14 | SimpleInterpreter.Add.class, 15 | SimpleInterpreter.Var.class, 16 | SimpleInterpreter.Con.class, 17 | Pair.class 18 | }) 19 | public class Helpers { 20 | public static List cons(T value, List list) { 21 | var worker = new ArrayList(); 22 | worker.add(value); 23 | worker.addAll(list); 24 | return Collections.unmodifiableList(worker); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /java-17/src/main/java/examples/Helpers.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.soabase.recordbuilder.core.RecordBuilder; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | @RecordBuilder.Options( 10 | prefixEnclosingClassNames = false 11 | ) 12 | @RecordBuilder.Include({ 13 | SimpleInterpreter.M.class, 14 | SimpleInterpreter.Num.class, 15 | SimpleInterpreter.Fun.class, 16 | SimpleInterpreter.App.class, 17 | SimpleInterpreter.Lam.class, 18 | SimpleInterpreter.Add.class, 19 | SimpleInterpreter.Var.class, 20 | SimpleInterpreter.Con.class, 21 | Pair.class 22 | }) 23 | public class Helpers { 24 | public static List cons(T value, List list) { 25 | var worker = new ArrayList(); 26 | worker.add(value); 27 | worker.addAll(list); 28 | return Collections.unmodifiableList(worker); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /java-19/src/main/java/examples/Helpers.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import io.soabase.recordbuilder.core.RecordBuilder; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | @RecordBuilder.Options( 10 | prefixEnclosingClassNames = false 11 | ) 12 | @RecordBuilder.Include({ 13 | SimpleInterpreter.M.class, 14 | SimpleInterpreter.Num.class, 15 | SimpleInterpreter.Fun.class, 16 | SimpleInterpreter.App.class, 17 | SimpleInterpreter.Lam.class, 18 | SimpleInterpreter.Add.class, 19 | SimpleInterpreter.Var.class, 20 | SimpleInterpreter.Con.class, 21 | Pair.class 22 | }) 23 | public class Helpers { 24 | public static List cons(T value, List list) { 25 | var worker = new ArrayList(); 26 | worker.add(value); 27 | worker.addAll(list); 28 | return Collections.unmodifiableList(worker); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | HOME=$(PWD) 4 | 5 | buildVersion() { 6 | echo "Building Java $1 version" 7 | cd "$HOME/java-$1" || exit 8 | export JENV_VERSION=$2 9 | export JAVA_HOME=$(jenv javahome) 10 | echo $(java -version) 11 | mvn clean verify 12 | echo "" 13 | } 14 | 15 | runVersion() { 16 | echo "=========================" 17 | echo "Running Java $1 version" 18 | echo "=========================" 19 | cd "$HOME/java-$1" || exit 20 | export JENV_VERSION=$2 21 | export JAVA_HOME=$(jenv javahome) 22 | if [ $1 -eq '8' ]; then 23 | java -cp "./target/classes" examples.SimpleInterpreter 24 | else 25 | java --enable-preview -cp "./target/classes" examples.SimpleInterpreter 26 | fi 27 | echo "" 28 | } 29 | 30 | buildVersion 8 1.8 31 | buildVersion 15 15.0.2 32 | buildVersion 17 17 33 | buildVersion 19 19-ea 34 | 35 | runVersion 8 1.8 36 | runVersion 15 15.0.2 37 | runVersion 17 17 38 | runVersion 19 19-ea 39 | -------------------------------------------------------------------------------- /java-8/src/main/java/examples/Helpers.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import org.immutables.value.Value; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.List; 8 | import java.util.function.Function; 9 | 10 | @Value.Style( 11 | of = "new", 12 | allParameters = true 13 | ) 14 | @Value.Include({ 15 | SimpleInterpreter.M.class, 16 | SimpleInterpreter.Term.class, 17 | SimpleInterpreter.Con.class, 18 | SimpleInterpreter.Add.class, 19 | SimpleInterpreter.Lam.class, 20 | SimpleInterpreter.App.class, 21 | SimpleInterpreter.Num.class, 22 | SimpleInterpreter.Fun.class, 23 | SimpleInterpreter.Var.class, 24 | Pair.class 25 | }) 26 | public class Helpers { 27 | public static List cons(T value, List list) { 28 | ArrayList worker = new ArrayList<>(); 29 | worker.add(value); 30 | worker.addAll(list); 31 | return Collections.unmodifiableList(worker); 32 | } 33 | 34 | public static SimpleInterpreter.M M(A a) { 35 | return new ImmutableM<>(a); 36 | } 37 | 38 | public static SimpleInterpreter.Num Num(int n) { 39 | return new ImmutableNum(n); 40 | } 41 | 42 | public static SimpleInterpreter.Fun Fun(Function> f) { 43 | return new ImmutableFun(f); 44 | } 45 | 46 | public static Pair Pair(A a, B b) { 47 | return new ImmutablePair<>(a, b); 48 | } 49 | 50 | public static SimpleInterpreter.App App(SimpleInterpreter.Term fun, SimpleInterpreter.Term arg) { 51 | return new ImmutableApp(fun, arg); 52 | } 53 | 54 | public static SimpleInterpreter.Lam Lam(String x, SimpleInterpreter.Term body) { 55 | return new ImmutableLam(x, body); 56 | } 57 | 58 | public static SimpleInterpreter.Add Add(SimpleInterpreter.Term l, SimpleInterpreter.Term r) { 59 | return new ImmutableAdd(l, r); 60 | } 61 | 62 | public static SimpleInterpreter.Var Var(String x) { 63 | return new ImmutableVar(x); 64 | } 65 | 66 | public static SimpleInterpreter.Con Con(int n) { 67 | return new ImmutableCon(n); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /SimpleScala3Interpreter.scala: -------------------------------------------------------------------------------- 1 | /* 2 | To run this program: scala run scala3interpreter.scala 3 | */ 4 | 5 | //> using scala 3.6.4 6 | 7 | import scala.annotation.tailrec 8 | 9 | case class M[A](value: A): 10 | def bind[B](k: A => M[B]): M[B] = k(value) 11 | def map[B](f: A => B): M[B] = bind(x => unitM(f(x))) 12 | def flatMap[B](f: A => M[B]): M[B] = bind(f) 13 | 14 | def unitM[A](a: A): M[A] = M(a) 15 | 16 | def showM(m: M[Value]): String = m.value.toString 17 | 18 | type Name = String 19 | 20 | enum Term: 21 | case Var(x: Name) 22 | case Con(n: Int) 23 | case Add(l: Term, r: Term) 24 | case Lam(x: Name, body: Term) 25 | case App(fun: Term, arg: Term) 26 | 27 | enum Value(asString: String): 28 | override def toString: String = asString 29 | 30 | case Wrong extends Value("wrong") 31 | case Num(n: Int) extends Value(n.toString) 32 | case Fun(f: Value => M[Value]) extends Value("") 33 | 34 | type Pair[+T1, +T2] = (T1, T2) 35 | type Environment = List[Pair[Name, Value]] 36 | 37 | import Term.* 38 | import Value.* 39 | 40 | @tailrec 41 | def lookup(x: Name, e: Environment): M[Value] = e match 42 | case List() => unitM(Wrong) 43 | case Tuple2(y, b) :: e1 => if (x == y) unitM(b) else lookup(x, e1) 44 | 45 | def add(a: Value, b: Value): M[Value] = Tuple2(a, b) match 46 | case Tuple2(Num(m), Num(n)) => unitM(Num(m + n)) 47 | case _ => unitM(Wrong) 48 | 49 | def apply(a: Value, b: Value): M[Value] = a match 50 | case Fun(k) => k(b) 51 | case _ => unitM(Wrong) 52 | 53 | def interp(t: Term, e: Environment): M[Value] = t match 54 | case Var(x) => lookup(x, e) 55 | case Con(n) => unitM(Num(n)) 56 | case Add(l, r) => 57 | for (a <- interp(l, e); 58 | b <- interp(r, e); 59 | c <- add(a, b)) 60 | yield c 61 | case Lam(x, t) => unitM(Fun(a => interp(t, (x, a) :: e))) 62 | case App(f, t) => 63 | for (a <- interp(f, e); 64 | b <- interp(t, e); 65 | c <- apply(a, b)) 66 | yield c 67 | 68 | def test(t: Term): String = 69 | showM(interp(t, List())) 70 | 71 | val term0: App = App(Lam("x", Add(Var("x"), Var("x"))), Add(Con(10), Con(11))) 72 | val term1: App = App(Con(1), Con(2)) 73 | 74 | @main 75 | def testInterpreter(): Unit = 76 | println(test(term0)) 77 | println(test(term1)) 78 | -------------------------------------------------------------------------------- /simpleInterpreter.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | object simpleInterpreter { 4 | 5 | case class M[A](value: A) { 6 | def bind[B](k: A => M[B]): M[B] = k(value) 7 | def map[B](f: A => B): M[B] = bind(x => unitM(f(x))) 8 | def flatMap[B](f: A => M[B]): M[B] = bind(f) 9 | } 10 | 11 | def unitM[A](a: A): M[A] = M(a) 12 | 13 | def showM(m: M[Value]): String = m.value.toString(); 14 | 15 | type Name = String 16 | 17 | trait Term; 18 | case class Var(x: Name) extends Term 19 | case class Con(n: int) extends Term 20 | case class Add(l: Term, r: Term) extends Term 21 | case class Lam(x: Name, body: Term) extends Term 22 | case class App(fun: Term, arg: Term) extends Term 23 | 24 | trait Value 25 | case object Wrong extends Value { 26 | override def toString() = "wrong" 27 | } 28 | case class Num(n: int) extends Value { 29 | override def toString() = n.toString() 30 | } 31 | case class Fun(f: Value => M[Value]) extends Value { 32 | override def toString() = "" 33 | } 34 | 35 | type Environment = List[Pair[Name, Value]] 36 | 37 | def lookup(x: Name, e: Environment): M[Value] = e match { 38 | case List() => unitM(Wrong) 39 | case Pair(y, b) :: e1 => if (x == y) unitM(b) else lookup(x, e1) 40 | } 41 | 42 | def add(a: Value, b: Value): M[Value] = Pair(a, b) match { 43 | case Pair(Num(m), Num(n)) => unitM(Num(m + n)) 44 | case _ => unitM(Wrong) 45 | } 46 | 47 | def apply(a: Value, b: Value): M[Value] = a match { 48 | case Fun(k) => k(b) 49 | case _ => unitM(Wrong) 50 | } 51 | 52 | def interp(t: Term, e: Environment): M[Value] = t match { 53 | case Var(x) => lookup(x, e) 54 | case Con(n) => unitM(Num(n)) 55 | case Add(l, r) => for (val a <- interp(l, e); 56 | val b <- interp(r, e); 57 | val c <- add(a, b)) 58 | yield c 59 | case Lam(x, t) => unitM(Fun(a => interp(t, Pair(x, a) :: e))) 60 | case App(f, t) => for (val a <- interp(f, e); 61 | val b <- interp(t, e); 62 | val c <- apply(a, b)) 63 | yield c 64 | } 65 | 66 | def test(t: Term): String = 67 | showM(interp(t, List())) 68 | 69 | val term0 = App(Lam("x", Add(Var("x"), Var("x"))), Add(Con(10), Con(11))) 70 | val term1 = App(Con(1), Con(2)) 71 | 72 | def main(args: Array[String]) { 73 | println(test(term0)) 74 | println(test(term1)) 75 | } 76 | } -------------------------------------------------------------------------------- /java-8/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | java-8 7 | 1.0-SNAPSHOT 8 | 9 | 10 | UTF-8 11 | UTF-8 12 | UTF-8 13 | 14 | 8 15 | 2.8.3 16 | 3.8.1 17 | 18 | 19 | 20 | 21 | org.immutables 22 | value 23 | ${immutables-version} 24 | 25 | 26 | 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-compiler-plugin 32 | ${maven-compiler-plugin-version} 33 | 34 | ${jdk-version} 35 | ${jdk-version} 36 | 37 | 38 | 39 | 40 | org.codehaus.mojo 41 | exec-maven-plugin 42 | 3.0.0 43 | 44 | 45 | verify 46 | 47 | java 48 | 49 | 50 | examples.SimpleInterpreter 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /java-19/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | java-19 8 | org.example 9 | 1.0-SNAPSHOT 10 | 11 | 12 | UTF-8 13 | UTF-8 14 | UTF-8 15 | 16 | 19 17 | 29 18 | 3.8.1 19 | 20 | 21 | 22 | 23 | io.soabase.record-builder 24 | record-builder-core 25 | ${record-builder-version} 26 | 27 | 28 | 29 | 30 | 31 | 32 | org.apache.maven.plugins 33 | maven-compiler-plugin 34 | ${maven-compiler-plugin-version} 35 | 36 | 37 | 38 | io.soabase.record-builder 39 | record-builder-processor 40 | ${record-builder-version} 41 | 42 | 43 | 44 | io.soabase.recordbuilder.processor.RecordBuilderProcessor 45 | 46 | 47 | ${jdk-version} 48 | 49 | --enable-preview 50 | 51 | 52 | 53 | 54 | 55 | org.codehaus.mojo 56 | exec-maven-plugin 57 | 3.0.0 58 | 59 | 60 | 61 | java 62 | 63 | 64 | 65 | 66 | examples.SimpleInterpreter 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /java-17/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | java-17 8 | org.example 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | UTF-8 14 | UTF-8 15 | UTF-8 16 | 17 | 17 18 | 29 19 | 3.8.1 20 | 21 | 22 | 23 | 24 | io.soabase.record-builder 25 | record-builder-core 26 | ${record-builder-version} 27 | 28 | 29 | 30 | 31 | 32 | 33 | org.apache.maven.plugins 34 | maven-compiler-plugin 35 | ${maven-compiler-plugin-version} 36 | 37 | 38 | 39 | io.soabase.record-builder 40 | record-builder-processor 41 | ${record-builder-version} 42 | 43 | 44 | 45 | io.soabase.recordbuilder.processor.RecordBuilderProcessor 46 | 47 | 48 | ${jdk-version} 49 | 50 | --enable-preview 51 | 52 | 53 | 54 | 55 | 56 | org.codehaus.mojo 57 | exec-maven-plugin 58 | 3.0.0 59 | 60 | 61 | verify 62 | 63 | java 64 | 65 | 66 | examples.SimpleInterpreter 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /java-15/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.example 6 | java-15 7 | 1.0-SNAPSHOT 8 | 9 | 10 | UTF-8 11 | UTF-8 12 | UTF-8 13 | 14 | 15 15 | 27-java15 16 | 3.8.1 17 | 18 | 19 | 20 | 21 | io.soabase.record-builder 22 | record-builder-core 23 | ${record-builder-version} 24 | 25 | 26 | 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-compiler-plugin 32 | ${maven-compiler-plugin-version} 33 | 34 | 35 | 36 | io.soabase.record-builder 37 | record-builder-processor 38 | ${record-builder-version} 39 | 40 | 41 | 42 | io.soabase.recordbuilder.processor.RecordBuilderProcessor 43 | 44 | 45 | ${jdk-version} 46 | 47 | --enable-preview 48 | -AprefixEnclosingClassNames=false 49 | 50 | 51 | 52 | 53 | 54 | org.codehaus.mojo 55 | exec-maven-plugin 56 | 3.0.0 57 | 58 | 59 | verify 60 | 61 | java 62 | 63 | 64 | examples.SimpleInterpreter 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /java-15/src/main/java/examples/SimpleInterpreter.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import java.util.List; 4 | import java.util.function.Function; 5 | 6 | import static examples.AddBuilder.Add; 7 | import static examples.AppBuilder.App; 8 | import static examples.ConBuilder.Con; 9 | import static examples.FunBuilder.Fun; 10 | import static examples.Helpers.cons; 11 | import static examples.LamBuilder.Lam; 12 | import static examples.MBuilder.M; 13 | import static examples.NumBuilder.Num; 14 | import static examples.PairBuilder.Pair; 15 | import static examples.VarBuilder.Var; 16 | 17 | public interface SimpleInterpreter { 18 | record M(A value) { 19 | M bind(Function> k) { 20 | return k.apply(value); 21 | } 22 | 23 | M map(Function f) { 24 | return bind(x -> unitM(f.apply(x))); 25 | } 26 | 27 | M flatMap(Function> f) { 28 | return bind(f); 29 | } 30 | } 31 | 32 | static M unitM(A a) { 33 | return M(a); 34 | } 35 | 36 | static String showM(M m) { 37 | return m.value.toString(); 38 | } 39 | 40 | interface Term {} 41 | record Var(String x) implements Term {} 42 | record Con(int n) implements Term {} 43 | record Add(Term l, Term r) implements Term {} 44 | record Lam(String x, Term body) implements Term {} 45 | record App(Term fun, Term arg) implements Term {} 46 | 47 | interface Value {} 48 | Value Wrong = new Value() { 49 | public String toString() { 50 | return "wrong"; 51 | } 52 | }; 53 | record Num(int n) implements Value { 54 | public String toString() { 55 | return Integer.toString(n); 56 | } 57 | } 58 | record Fun(Function> f) implements Value { 59 | public String toString() { 60 | return ""; 61 | } 62 | } 63 | 64 | static M lookup(String x, List> e) { 65 | if (e.isEmpty()) { 66 | return unitM(Wrong); 67 | } 68 | var first = e.get(0); 69 | return first.a().equals(x) ? unitM(first.b()) : lookup(x, e.subList(1, e.size())); 70 | } 71 | 72 | static M add(Value a, Value b) { 73 | if ((a instanceof Num aNum) && (b instanceof Num bNum)) { 74 | return unitM(Num(aNum.n + bNum.n)); 75 | } 76 | return unitM(Wrong); 77 | } 78 | 79 | static M apply(Value a, Value b) { 80 | if (a instanceof Fun fun) { 81 | return fun.f.apply(b); 82 | } 83 | return unitM(Wrong); 84 | } 85 | 86 | static M interp(Term t, List> e) { 87 | if (t instanceof Var var) { 88 | return lookup(var.x, e); 89 | } 90 | if (t instanceof Con con) { 91 | return unitM(Num(con.n)); 92 | } 93 | if (t instanceof Add add) { 94 | var a = interp(add.l, e); 95 | var b = interp(add.r, e); 96 | return add(a.value, b.value); 97 | } 98 | if (t instanceof Lam lam) { 99 | return unitM(Fun(a -> interp(lam.body, cons(Pair(lam.x, a), e)))); 100 | } 101 | if (t instanceof App app) { 102 | var a = interp(app.fun, e); 103 | var b = interp(app.arg, e); 104 | return apply(a.value, b.value); 105 | } 106 | return unitM(Wrong); 107 | } 108 | 109 | static String test(Term t) { 110 | return showM(interp(t, List.of())); 111 | } 112 | 113 | static void main(String[] args) { 114 | var term0 = App(Lam("x", Add(Var("x"), Var("x"))), Add(Con(10), Con(11))); 115 | var term1 = App(Con(1), Con(2)); 116 | 117 | System.out.println(test(term0)); 118 | System.out.println(test(term1)); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /java-8/src/main/java/examples/SimpleInterpreter.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.function.Function; 6 | 7 | import static examples.Helpers.*; 8 | 9 | public interface SimpleInterpreter { 10 | interface M { 11 | A value(); 12 | 13 | default M bind(Function> k) { 14 | return k.apply(value()); 15 | } 16 | 17 | default M map(Function f) { 18 | return bind(x -> unitM(f.apply(x))); 19 | } 20 | 21 | default M flatMap(Function> f) { 22 | return bind(f); 23 | } 24 | } 25 | 26 | static M unitM(A s) { 27 | return M(s); 28 | } 29 | 30 | static String showM(M m) { 31 | return m.value().toStr(); 32 | } 33 | 34 | interface Term { } 35 | interface Var extends Term { 36 | String x(); 37 | } 38 | interface Con extends Term { 39 | int n(); 40 | } 41 | interface Add extends Term { 42 | Term l(); 43 | 44 | Term r(); 45 | } 46 | interface Lam extends Term { 47 | String x(); 48 | 49 | Term body(); 50 | } 51 | interface App extends Term { 52 | Term fun(); 53 | 54 | Term arg(); 55 | } 56 | 57 | interface Value { 58 | String toStr(); // can't override/implement toString() in an interface 59 | } 60 | Value Wrong = () -> "wrong"; 61 | interface Num extends Value { 62 | int n(); 63 | 64 | @Override 65 | default String toStr() { 66 | return Integer.toString(n()); 67 | } 68 | } 69 | interface Fun extends Value { 70 | Function> f(); 71 | 72 | @Override 73 | default String toStr() { 74 | return ""; 75 | } 76 | } 77 | 78 | static M lookup(String x, List> e) { 79 | if (e.isEmpty()) { 80 | return unitM(Wrong); 81 | } 82 | Pair first = e.get(0); 83 | return first.a().equals(x) ? unitM(first.b()) : lookup(x, e.subList(1, e.size())); 84 | } 85 | 86 | static M add(Value a, Value b) { 87 | if ((a instanceof Num) && (b instanceof Num)) { 88 | return unitM(Num(((Num) a).n() + ((Num) b).n())); 89 | } 90 | return unitM(Wrong); 91 | } 92 | 93 | static M apply(Value a, Value b) { 94 | if (a instanceof Fun) { 95 | return ((Fun) a).f().apply(b); 96 | } 97 | return unitM(Wrong); 98 | } 99 | 100 | static M interp(Term t, List> e) { 101 | if (t instanceof Var) { 102 | return lookup(((Var) t).x(), e); 103 | } 104 | if (t instanceof Con) { 105 | return unitM(Num(((Con) t).n())); 106 | } 107 | if (t instanceof Add) { 108 | M a = interp(((Add) t).l(), e); 109 | M b = interp(((Add) t).r(), e); 110 | return add(a.value(), b.value()); 111 | } 112 | if (t instanceof Lam) { 113 | return unitM(Fun(a -> interp(((Lam) t).body(), cons(Pair(((Lam) t).x(), a), e)))); 114 | } 115 | if (t instanceof App) { 116 | M a = interp(((App) t).fun(), e); 117 | M b = interp(((App) t).arg(), e); 118 | return apply(a.value(), b.value()); 119 | } 120 | return unitM(Wrong); 121 | } 122 | 123 | static String test(Term t) { 124 | return showM(interp(t, Collections.emptyList())); 125 | } 126 | 127 | static void main(String[] args) { 128 | App term0 = App(Lam("x", Add(Var("x"), Var("x"))), Add(Con(10), Con(11))); 129 | App term1 = App(Con(1), Con(2)); 130 | 131 | System.out.println(test(term0)); 132 | System.out.println(test(term1)); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /java-19/src/main/java/examples/SimpleInterpreter.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import java.util.List; 4 | import java.util.function.Function; 5 | 6 | import static examples.AddBuilder.Add; 7 | import static examples.AppBuilder.App; 8 | import static examples.ConBuilder.Con; 9 | import static examples.FunBuilder.Fun; 10 | import static examples.Helpers.cons; 11 | import static examples.LamBuilder.Lam; 12 | import static examples.MBuilder.M; 13 | import static examples.NumBuilder.Num; 14 | import static examples.PairBuilder.Pair; 15 | import static examples.VarBuilder.Var; 16 | 17 | public interface SimpleInterpreter { 18 | record M (A value) { 19 | M bind(Function> k) { 20 | return k.apply(value); 21 | } 22 | 23 | M map(Function f) { 24 | return bind(x -> unitM(f.apply(x))); 25 | } 26 | 27 | M flatMap(Function> f) { 28 | return bind(f); 29 | } 30 | } 31 | 32 | static M unitM(A a) { 33 | return M(a); 34 | } 35 | 36 | static String showM(M m) { 37 | return m.value.toString(); 38 | } 39 | 40 | sealed interface Term permits Var,Con,Add,Lam,App {} 41 | record Var(String x) implements Term {} 42 | record Con(int n) implements Term {} 43 | record Add(Term l, Term r) implements Term {} 44 | record Lam(String x, Term body) implements Term {} 45 | record App(Term fun, Term arg) implements Term {} 46 | 47 | sealed interface Value permits Num,Fun,Wrong {} 48 | record Wrong() implements Value { 49 | public static final Wrong INSTANCE = new Wrong(); 50 | public String toString() { 51 | return "wrong"; 52 | } 53 | } 54 | record Num(int n) implements Value { 55 | public String toString() { 56 | return Integer.toString(n); 57 | } 58 | } 59 | record Fun(Function> f) implements Value { 60 | public String toString() { 61 | return ""; 62 | } 63 | } 64 | 65 | static M lookup(String x, List> e) { 66 | return switch (e) { 67 | case List __ when e.isEmpty() -> unitM(Wrong.INSTANCE); 68 | default -> e.get(0).a().equals(x) ? unitM(e.get(0).b()) : lookup(x, e.subList(1, e.size())); 69 | }; 70 | } 71 | 72 | static M add(Value a, Value b) { 73 | return switch (Pair(a, b)) { 74 | case Pair(Num(var m), Num(var n)) -> unitM(Num(m + n)); 75 | default -> unitM(Wrong.INSTANCE); 76 | }; 77 | } 78 | 79 | static M apply(Value a, Value b) { 80 | return switch (a) { 81 | case Fun(var k) -> k.apply(b); 82 | default -> unitM(Wrong.INSTANCE); 83 | }; 84 | } 85 | 86 | static M interp(Term t, List> e) { 87 | return switch (t) { 88 | case Var(var x) -> lookup(x, e); 89 | case Con(var n) -> unitM(Num(n)); 90 | case Add(var l, var r) -> { 91 | var a = interp(l, e); 92 | var b = interp(r, e); 93 | yield add(a.value, b.value); 94 | } 95 | case Lam(var x, var term) -> unitM(Fun(a -> interp(term, cons(Pair(x, a), e)))); 96 | case App(var f, var arg) -> { 97 | var a = interp(f, e); 98 | var b = interp(arg, e); 99 | yield apply(a.value, b.value); 100 | } 101 | }; 102 | } 103 | 104 | static String test(Term t) { 105 | return showM(interp(t, List.of())); 106 | } 107 | 108 | static void main(String[] args) { 109 | var term0 = App(Lam("x", Add(Var("x"), Var("x"))), Add(Con(10), Con(11))); 110 | var term1 = App(Con(1), Con(2)); 111 | 112 | System.out.println(test(term0)); 113 | System.out.println(test(term1)); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /java-17/src/main/java/examples/SimpleInterpreter.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import java.util.List; 4 | import java.util.function.Function; 5 | 6 | import static examples.AddBuilder.Add; 7 | import static examples.AppBuilder.App; 8 | import static examples.ConBuilder.Con; 9 | import static examples.FunBuilder.Fun; 10 | import static examples.Helpers.cons; 11 | import static examples.LamBuilder.Lam; 12 | import static examples.MBuilder.M; 13 | import static examples.NumBuilder.Num; 14 | import static examples.PairBuilder.Pair; 15 | import static examples.VarBuilder.Var; 16 | 17 | public interface SimpleInterpreter { 18 | record M (A value) { 19 | M bind(Function> k) { 20 | return k.apply(value); 21 | } 22 | 23 | M map(Function f) { 24 | return bind(x -> unitM(f.apply(x))); 25 | } 26 | 27 | M flatMap(Function> f) { 28 | return bind(f); 29 | } 30 | } 31 | 32 | static M unitM(A a) { 33 | return M(a); 34 | } 35 | 36 | static String showM(M m) { 37 | return m.value.toString(); 38 | } 39 | 40 | sealed interface Term permits Var,Con,Add,Lam,App {} 41 | record Var(String x) implements Term {} 42 | record Con(int n) implements Term {} 43 | record Add(Term l, Term r) implements Term {} 44 | record Lam(String x, Term body) implements Term {} 45 | record App(Term fun, Term arg) implements Term {} 46 | 47 | sealed interface Value permits Num,Fun,Wrong {} 48 | record Wrong() implements Value { 49 | public static final Wrong INSTANCE = new Wrong(); 50 | public String toString() { 51 | return "wrong"; 52 | } 53 | } 54 | record Num(int n) implements Value { 55 | public String toString() { 56 | return Integer.toString(n); 57 | } 58 | } 59 | record Fun(Function> f) implements Value { 60 | public String toString() { 61 | return ""; 62 | } 63 | } 64 | 65 | static M lookup(String x, List> e) { 66 | return switch (e) { 67 | case List __ && e.isEmpty() -> unitM(Wrong.INSTANCE); 68 | case List __ && e.get(0).a().equals(x) -> unitM(e.get(0).b()); 69 | default -> lookup(x, e.subList(1, e.size())); 70 | }; 71 | } 72 | 73 | static M add(Value a, Value b) { 74 | return switch (Pair(a, b)) { 75 | case Pair p && p.a() instanceof Num m && p.b() instanceof Num n -> unitM(Num(m.n() + n.n())); 76 | default -> unitM(Wrong.INSTANCE); 77 | }; 78 | } 79 | 80 | static M apply(Value a, Value b) { 81 | return switch (a) { 82 | case Fun fun -> fun.f.apply(b); 83 | default -> unitM(Wrong.INSTANCE); 84 | }; 85 | } 86 | 87 | static M interp(Term t, List> e) { 88 | return switch (t) { 89 | case Var var -> lookup(var.x, e); 90 | case Con con -> unitM(Num(con.n)); 91 | case Add add -> { 92 | var a = interp(add.l, e); 93 | var b = interp(add.r, e); 94 | yield add(a.value, b.value); 95 | } 96 | case Lam lam -> unitM(Fun(a -> interp(lam.body, cons(Pair(lam.x, a), e)))); 97 | case App app -> { 98 | var a = interp(app.fun, e); 99 | var b = interp(app.arg, e); 100 | yield apply(a.value, b.value); 101 | } 102 | }; 103 | } 104 | 105 | static String test(Term t) { 106 | return showM(interp(t, List.of())); 107 | } 108 | 109 | static void main(String[] args) { 110 | var term0 = App(Lam("x", Add(Var("x"), Var("x"))), Add(Con(10), Con(11))); 111 | var term1 = App(Con(1), Con(2)); 112 | 113 | System.out.println(test(term0)); 114 | System.out.println(test(term1)); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Getting Closer to Scala’s Expressiveness 2 | 3 | Java will never be as expressive as Scala. Scala’s feature set allows for writing complete DSLs as well as concise, 4 | terse code. That said, Java is getting closer. Since JDK 14 Java has had preview support for “records” which are similar 5 | to Scala’s case classes. In future JDKs records will have near feature parity with Scala’s case classes. 6 | 7 | ## The Example 8 | 9 | For me, the most expressive feature of Scala is its case classes with pattern matching. Once you’ve coded with this 10 | feature you’ll never want to return to the alternatives. This repo shows Java’s progress towards equalling Scala’s 11 | case class feature. It’s unlikely that Java will ever completely match Scala but it will get close. 12 | 13 | Shown here is a very old interpreter from the [Scala examples website](https://www.scala-lang.org/old/node/56.html). It’s 14 | not meant to represent current idiomatic Scala nor is it meant to be a definitive example. However, it’s simple, fits on one 15 | page and shows the expressiveness of case classes. 16 | 17 | Multiple attempts are shown. Versions in Java 15, 17 and 19 take advantage of records and Java's initial support for pattern matching. The other in Java 8. 18 | 19 | ![comparison](https://user-images.githubusercontent.com/264818/171995071-e1a08b21-5816-48d0-b838-6f09ca4d33dc.png) 20 | 21 | ## Java 19 Attempt 22 | 23 | Record patterns are now available and the Java version is nearly identical to the Scala version. Record patterns 24 | are limited to type comparison and extraction and so can't match on, for example, empty-list but that's about 25 | the only feature missing. Java is still more verbose (the need for `var` for example) but it's very impressive 26 | how close it now is. Java, of course, is still missing a built-in `Pair` syntax for list head/tail, etc. 27 | 28 | ## Java 17 Attempt 29 | 30 | Java records are now part of the language. My [RecordBuilder library](https://github.com/Randgalt/record-builder) is still used for some builder sugar. Huge 31 | kudos to [Gavin Ray](https://github.com/GavinRay97) for contributing the Java 17 32 | version using Sealed Classes as well as Pattern Matching for Switch. This is 33 | getting very, very close to the Scala version now. Once deconstruction is added 34 | we'll have a nearly exact version. 35 | 36 | Java 17's enhanced pattern matching for switch is missing reification and deconstruction. So, for now, 37 | a combination of the current pattern-matching-for `switch` with an extra pattern match for `instanceof` is used. 38 | 39 | See the Java 15 attempt for what will be permanent issues with lack of a `Pair` 40 | and type aliasing. 41 | 42 | ## Java 15 Attempt 43 | 44 | [Java 15’s Records](https://openjdk.java.net/jeps/359) are an interim feature towards full pattern matching and 45 | deconstruction. Java’s records are very close to Scala’s case classes (sans pattern matching/deconstruction which 46 | will come later). The only missing feature is a static constructor/builder method which makes allocating case 47 | classes a bit cleaner (no need for `new`). Fortunately, my [RecordBuilder library](https://github.com/Randgalt/record-builder) 48 | can auto-generate these so I’ve used it here to match Scala. 49 | 50 | The major thing missing in this Java 15 attempt is, of course, Scala’s case-match. Future versions of Java will 51 | add support for something similar but for now I’ve had to use Java 15’s [new pattern matching for instanceof](https://openjdk.java.net/jeps/375). 52 | 53 | Java 15 is also missing a built-in Pair class. With records, however, this is very easy to create. Java also does 54 | not support type aliases. It’s unlikely Java will add support for this. Thus, the Scala example’s `Environment` and `Name` 55 | cannot be duplicated. 56 | 57 | These caveats aside, the Java 15 attempt is not too bad. Once we get Java’s version of pattern matching it will be possible 58 | to write a near perfect duplication of this Scala example. 59 | 60 | ## Java 8 Attempt 61 | 62 | Java 8, of course, does not support anything like records or case classes. However, we can use 63 | generators such as [Immutables](https://immutables.github.io) to generate something close to a record. 64 | Having to use Java interfaces as a spec for the Immutables builder is not as satisfying and makes the 65 | code much more verbose. Also, the Immutables builder does not generate static constructors/builders so these 66 | have to be added manually. 67 | 68 | Like in the Java 15 example, there is no built-in Pair class and in this Java 8 instance it has to be 69 | specified as an interface so that Immutables can generate the implementation. 70 | 71 | Lastly, Java 8 does not have Java 15’s pattern matching for instanceof so extra work in the form of 72 | casts must be done in the parts of the code that match on operation types. 73 | 74 | ## Links 75 | 76 | - Original Scala Example: [Scala SimpleInterpreter](https://www.scala-lang.org/old/node/56.html) 77 | - Java 17 Attempt: [Java 17 SimpleInterpreter](https://github.com/Randgalt/expressive-java/blob/master/java-17/src/main/java/examples/SimpleInterpreter.java#L17) 78 | - Java 15 Attempt: [Java 15 SimpleInterpreter](https://github.com/Randgalt/expressive-java/blob/master/java-15/src/main/java/examples/SimpleInterpreter.java#L17) 79 | - Java 8 Attempt: [Java 8 SimpleInterpreter](https://github.com/Randgalt/expressive-java/blob/master/java-8/src/main/java/examples/SimpleInterpreter.java#L9) 80 | - Helpers: 81 | - Java 19: 82 | - [Helpers.java](https://github.com/Randgalt/expressive-java/blob/master/java-19/src/main/java/examples/Helpers.java) 83 | - [Pair.java](https://github.com/Randgalt/expressive-java/blob/master/java-19/src/main/java/examples/Pair.java) 84 | - Java 17: 85 | - [Helpers.java](https://github.com/Randgalt/expressive-java/blob/master/java-17/src/main/java/examples/Helpers.java) 86 | - [Pair.java](https://github.com/Randgalt/expressive-java/blob/master/java-17/src/main/java/examples/Pair.java) 87 | - Java 15: 88 | - [Helpers.java](https://github.com/Randgalt/expressive-java/blob/master/java-15/src/main/java/examples/Helpers.java) 89 | - [Pair.java](https://github.com/Randgalt/expressive-java/blob/master/java-15/src/main/java/examples/Pair.java) 90 | - Java 8: 91 | - [Helpers.java](https://github.com/Randgalt/expressive-java/blob/master/java-8/src/main/java/examples/Helpers.java) 92 | - [Pair.java](https://github.com/Randgalt/expressive-java/blob/master/java-8/src/main/java/examples/Pair.java) 93 | - [Scala 3 Version of the interpreter](https://github.com/Randgalt/expressive-java/blob/master/SimpleScala3Interpreter.scala) 94 | 95 | ## Building/Running 96 | 97 | - Make sure Javas 8, 15, 17 and 19 are installed as well as [jenv](https://www.jenv.be) 98 | - Make sure a recent version of [Maven](https://maven.apache.org) is installed 99 | - From the root directory run `./run.sh` 100 | 101 | --------------------------------------------------------------------------------