├── 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 | 
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 |
--------------------------------------------------------------------------------