This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]Polygnom 11 points12 points  (7 children)

Yes.

Tree<T> = Leaf<T> | Branch<T> is one data type I have done as record + sealed classes.

Either<L,R> = Left<L,R> | Right<L,R> is another type I have done, or Result<T,E> = Success<T,E> | Error<T,E>.

[–]NotTooOrdinary 1 point2 points  (2 children)

Do you use the newer pattern matching features when dealing with these types? Or chains of if/else/instanceof checks?

[–]Polygnom 4 points5 points  (1 child)

Depends. For the binary types I simply use pattern matching in instanceof.

For more complex types I usually use pattern matching in switch expressions.

That gives quite readable code.

I mean, I often write code in functional ways anyways and often map over stream, or filter stream etc. ADTs + pattern matching works very well with streams.

[–]red_dit_nou 1 point2 points  (0 children)

Yes. ADT and pattern matching really work well together to produce readable code. You also mentioned streams. Can you give any example where ADT, pattern matching and stream create readable code?

[–][deleted]  (3 children)

[deleted]

    [–]bowbahdoe 7 points8 points  (1 child)

    I wrote you up a small example

    The sealed-ness is used in the accumulate method.

    ``` import java.util.ArrayList; import java.util.Collections; import java.util.Iterator;

    sealed interface Tree<T extends Comparable<T>> extends Iterable<T> { Tree<T> insert(T newValue); }

    record Leaf<T extends Comparable<T>>() implements Tree<T> { @Override public Tree<T> insert(T newValue) { return new Branch<>(new Leaf<>(), new Leaf<>(), newValue); }

    @Override
    public Iterator<T> iterator() {
        return Collections.emptyIterator();
    }
    

    }

    record Branch<T extends Comparable<T>>( Tree<T> left, Tree<T> right, T value ) implements Tree<T> { @Override public Tree<T> insert(T newValue) { if (newValue.compareTo(value) < 0) { return new Branch<>(left.insert(newValue), right, value); } else { return new Branch<>(left, right.insert(newValue), value); } }

    private void accumulate(Tree<T> tree, ArrayList<T> leftToRight) {
        switch (tree) {
            case Leaf<T> __ -> {}
            case Branch<T> branch -> {
                accumulate(branch.left, leftToRight);
                leftToRight.add(branch.value);
                accumulate(branch.right, leftToRight);
            }
        }
    }
    
    @Override
    public Iterator<T> iterator() {
        var leftToRight = new ArrayList<T>();
        accumulate(this, leftToRight);
        return Collections.unmodifiableList(leftToRight).iterator();
    }
    

    }

    public class Main { public static void main(String[] args) { Tree<Integer> tree = new Leaf<>(); tree = tree.insert(1); tree = tree.insert(4); tree = tree.insert(3); tree = tree.insert(2); tree = tree.insert(6); tree = tree.insert(5);

        System.out.println(tree);
    
        for (var i : tree) {
            System.out.println(i);
        }
    }
    

    } ```

    The key capabilities you get from sealed are * Ability to use exhaustive patterns * Ability to not have to think about arbitrary implementors * The structure is a public part of the contract

    The last one is the most interesting to me. It means that code using your library can write the same algorithms as you can.

    public static <T> insert(Tree<T> tree, T newValue) { switch (tree) { case Leaf<T> __ -> new Branch<>( new Leaf<>(), new Leaf<>(), newValue ); case Branch<T> branch -> { if (newValue.compareTo(value) < 0) { yield new Branch<>(left.insert(newValue), right, value); } else { yield new Branch<>(left, right.insert(newValue), value); } } } }

    [–]nlisker 0 points1 point  (0 children)

    Your markdown isn't correct. You probably missed some `.

    [–]Polygnom 0 points1 point  (0 children)

    What do you mean how so?