I want to allow some Java objects to be translated into a string representation which matches Python or JavaScript objects.
I thought that I could tag all compatible classes with compatibilising interfaces:
public interface Pythonable {
public String toPython();
}
public interface JavaScriptable {
public String toJavaScript();
}
If I were manually creating classes that can be compiled to either of the two languages, I would implement those methods directly:
public class Point implements Pythonable, JavaScriptable {
double x;
double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
@Override
public String toPython() {
return String.format("(%f, %f)", x, y);
}
@Override
public String toJavaScript() {
return String.format("[%f, %f]", x, y);
}
}
But I don't own java.util.Set
for example, so how can I implement these translation targets on it?
Mocking up (Rust traits style), something like
implement Pythonable for java.util.Set {
@Override
public String toPython() {
return String.format("{%s}", String.join(",", this));
}
}
I would like to do that so that I can use Java's own objects on the Java side (don't have to manually make Proxy objects or wrapper objects for everything I may want to use myself and end up with disgusting levels of nesting), and the implementations of the compilation targets can still remain member-type-agnostic:
public class SetDifference<T1 extends Pythonable & JavaScriptable, T2 extends Pythonable & JavaScriptable> implements Pythonable, JavaScriptable {
T1 minuend;
T2 subtrahend;
public SetDifference(T1 minuend, T2 subtrahend) {
this.minuend = minuend;
this.subtrahend = subtrahend;
}
@Override
public String toPython() {
return String.format("%s - %s", minuend.toPython(), subtrahend.toPython());
}
@Override
public String toJavaScript() {
return String.format("new Set([...(%s)].filter((x) => !(%s).has(x)))", minuend.toJavaScript(), subtrahend.toJavaScript());
}
}
import java.util.Set;
var setDifference = new SetDifference(Set.of({1, 2, 3}), Set.of({2}));
setDifference.toPython() // produces "{1, 2, 3} - {2}"
And what if a user later wants to add a new compilation target? They make their own
public interface Swiftable {
public String toSwift();
}
How can they add translations to all the existing objects that I've defined?
This doesn't seem very Java-supported.
How can I make this work, or else what's a more Java way to provide many different toString
functions (essentially) for built-in Java types?
I've had an idea to use Transpiler classes that have methods to transpile supported types:
public class PythonTranspiler {
public String toPython(Pythonable pythonable) {
return pythonable.toPython();
}
public String toPython(Set<?> set) {
return String.format(
"set(%s)",
String.join(", ", set.stream().map(e -> this.toPython(e))));
}
}
public class JavaScriptTranspiler {
public String toJavaScript(JavaScriptable javaScriptable) {
return javaScriptable.toJavaScript();
}
public String toJavaScript(Set<?> set) {
return String.format(
"new Set([%s])",
String.join(", ", set.stream().map(e -> this.toJavaScript(e))));
}
}
But I'm not sure how to define a type for those that are translatable altogether (all the <?>
s in the example below), and I guess things like map(e -> this.toJavaScript(e))
won't actually work as-written, because it will select the Set
's generic type parameter method from JavaScriptTranspiler
, not the actual object's type.
1 Answer 1
You can't, at least not without
don't have to manually make Proxy objects or wrapper objects for everything I may want to use myself and end up with disgusting levels of nesting
This is one of the limitations of "classic" Smalltalk-style OOP, in that polymorphism and inheritance are tightly coupled together. As you note, modern "post-OOP" languages, in particular Rust and Go, decouple polymorphism from inheritance (and functional languages have done this for a very long time).
Slight disclaimer there: you could in theory implement something to automatically create proxy objects. I'm not sure that's really any better.
-
Is there a good solution in java for the larger problem of specifying all these custom serializations and group-belongings (based on available serialization options)user224123– user2241232023年06月18日 21:19:11 +00:00Commented Jun 18, 2023 at 21:19
-
If i make wrapper objects for everything, how do users add their own new compilation targets to the wrapper objects?user224123– user2241232023年06月18日 21:20:04 +00:00Commented Jun 18, 2023 at 21:20
-
21) No 2) They write wrapper objects for your wrapper objects. I didn't say it was a good solution. Realistically, if you want to do this, don't use Java.Philip Kendall– Philip Kendall2023年06月18日 21:23:39 +00:00Commented Jun 18, 2023 at 21:23
-
4To somewhat defend Smalltalk-style OOP: in Smalltalk it is entirely normal to extend classes that you don't own, so writing ORM frameworks in Smalltalk is actually pretty easy (having powerful reflection helps, too). C++ and Java are another matter, of course, as those are really non-Smalltalk-style languages :-)Hans-Martin Mosner– Hans-Martin Mosner2023年06月19日 07:48:20 +00:00Commented Jun 19, 2023 at 7:48
How can I make this work
implementing a compiler? I mean, are not you, perhaps, mixing up concerns meant to be unaware of one from another?