3

In Java I have following class

public class Instruction {

    public static Instruction label(String name) {
        return new Instruction(Kind.label, name, null, 0);
    }

    public static Instruction literal(int value) {
        return new Instruction(Kind.intLiteral, null, null, value);
    }

    public static Instruction literal(boolean value) {
        return new Instruction(Kind.boolLiteral, null, null, value ? 1 : 0);
    }

    public static Instruction command(String name) {
        return new Instruction(Kind.command, name, null, 0);
    }

    public static Instruction jump(String target) {
        return new Instruction(Kind.jump, target, null, 0);
    }

    public static Instruction branch(String ifTarget, String elseTarget) {
        return new Instruction(Kind.branch, ifTarget, elseTarget, 0);
    }

    private final Kind kind;
    private final String s1;
    private final String s2;
    private final int value;

    private Instruction(Kind kind, String s1, String s2, int value) {
        this.kind = kind;
        this.s1 = s1;
        this.s2 = s2;
        this.value = value;
    }
    ...
}

How this should be done with Kotlin?

Thomas S.
  • 5,804
  • 5
  • 37
  • 72
  • See also https://stackoverflow.com/questions/40352684/what-is-the-equivalent-of-java-static-methods-in-kotlin, for a more general discussion on static functions in Kotlin. – Silvio Mayolo Aug 20 '23 at 15:23
  • 1
    Also, maybe it would be worth using a sealed class instead of a `Kind` enum, and this would even eliminate the need for static factory functions + it would add type safety on what properties are meaningful or not in each case. – Joffrey Aug 20 '23 at 16:42

2 Answers2

4

We do this pretty much the same in Kotlin, but by using a companion object:

fun main() {
    val instruction = Instruction.literal(42)
}

class Instruction private constructor(...) {
    companion object {
        fun label(name: String) = Instruction(Kind.label, name, null, 0)
        fun literal(value: Int) = Instruction(Kind.intLiteral, null, null, value)
    }

    ...
}

Please read companion objects for more info.

broot
  • 21,588
  • 3
  • 30
  • 35
3

@broot's answer is the correct Kotlin equivalent of the Java code. However, in Kotlin we can do better.

You're implicitly thinking of different subtypes of Instruction here (which is clearly shown by the Kind enum) but without really defining those types. For each of those subtypes, the s1, s2, and value properties may or may not mean something. Instead, use a sealed class or interface, and only define properties where they actually make sense:

sealed interface Instruction {
    data class Label(val name: String) : Instruction
    data class IntLiteral(val value: Int) : Instruction
    data class BooleanLiteral(val value: Boolean) : Instruction
    data class Command(val name: String) : Instruction
    data class Jump(val target: String) : Instruction
    data class Branch(val ifTarget: String, val elseTarget: String) : Instruction
}

Then, each time you would have used the kind property, you would instead just use an is check. For instance, if (thing is Interface.Command) { ... } or most likely a when expression with all possible subtypes, which the compiler will check for type safety.

Thanks to Kotlin's flow typing, each type-checked branch (in if or when) will make the relevant properties available (such as value, ifTarget, name, etc.).


Note: alternatively, literals could even be generic:

data class Literal<T>(val value: T) : Instruction
Joffrey
  • 32,348
  • 6
  • 68
  • 100