2

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.

5
  • 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?
    – Laiv
    Jun 19, 2023 at 10:45
  • @Laiv I don't think a (normal) compiler could solve my problem, because I'm not reading strings and translating them to another representation. I'm trying to figure out how I can serialize java objects in multiple per-class-defined ways, and even do that for objects that I (or users of my library) don't own
    – minseong
    Jun 19, 2023 at 14:22
  • Are you serializing values only? Flow control statements too maybe? If you were serializing only values, then you can do it without coupling serialization with the code base (what I was suggesting in my previous comment). For example, the Jackson framework is extensible. We can implement custom modules with custom serializers for any kind of class regardless of the owner. I think you can even enable modules by checking classes within the classpath. But it has limitations of course, you can't go beyond the public interface of an object or class, unless you implement reflection
    – Laiv
    Jun 19, 2023 at 14:37
  • @Laiv I would only be serializing values, because my plan is that flow control statements would be represented by classes that I will write. Of course those (and most expressions/statements altogether, e.g. SetDifference in the question) need to recursively serialize nested parts.
    – minseong
    Jun 19, 2023 at 17:00
  • 2
    It looks like you want to be able to serialize Java objects into a machine readable format. There are JSON libraries for exactly this with very little you need to do . I would suggest looking into that. Jun 20, 2023 at 7:31

1 Answer 1

5

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.

4
  • 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)
    – minseong
    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?
    – minseong
    Jun 18, 2023 at 21:20
  • 2
    1) 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. Jun 18, 2023 at 21:23
  • 4
    To 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 :-) Jun 19, 2023 at 7:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.