0

I have some experience in Java and I am learning Ruby. I encountered a ruby program as below:

class Tree
  attr_accessor :children, :node_name
  def initialize(name, children=[])
    @children = children
    @node_name = name
  end
  def visit_all(&block)
    visit &block
    children.each {|c| c.visit_all &block}
  end
  def visit(&block)
    block.call self
  end
end
ruby_tree = Tree.new( "Ruby" ,
                      [Tree.new("Reia" ),
                       Tree.new("MacRuby" )] )
puts "Visiting a node"
ruby_tree.visit {|node| puts node.node_name}
puts
puts "visiting entire tree"
ruby_tree.visit_all {|node| puts node.node_name}

When I looked at the power of ruby language, I thought to write similar code in Java as below:

public class Tree {

    private String name;
    private Tree[] children;

    Tree(String name, Tree[] children) {
        this.name = name;
        this.children = children;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Tree[] getChildren() {
        return children;
    }

    public void setChildren(Tree[] children) {
        this.children = children;
    }

    public static void main(String[] args) {
        Tree myTree = new Tree("Ruby", new Tree[] {
                new Tree("Reia", new Tree[] {}),
                new Tree("MacRuby", new Tree[] {}) });
        myTree.visit();
        myTree.visit_all();
    }

    public void visit() {
        System.out.println(getName());
    }

    public void visit_all() {
        visit();
        for (Tree tree : children) {
            tree.visit();
        }
    }
}

Question: I know that the java version here is not much flexible as Ruby.Is there anything similar in Java that I can do to achieve the level of flexibility like ruby does provides?

2
  • Did you consider JRuby? Commented Sep 4, 2013 at 8:58
  • I have wrote this program just for my understanding... Commented Sep 4, 2013 at 8:59

2 Answers 2

1

First, a word of caution: that code is absolutely horrible. It provides almost no encapsulation, it leaks implementation details left and right, there's no way that a Tree object can maintain its own invariants or state. Secondly, it doesn't integrate at all with Ruby's collection framework.

As a consequence, my Java translation is also equally horrible, and it also doesn't integrate with Java's collection framework.

The two biggest drawbacks that your Java code has compared to your Ruby are

  • in the Java version, the element type is hard-coded to String, whereas in the Ruby version, it can be any object, and even a mixture of objects within the same tree, and
  • in the Java version, the iterators are hard-coded to printing the name(s), whereas in the Ruby version, the iterators take a block argument with the code to execute.

The first problem cannot be easily solved in Java. You can make the collection generic, so that it can hold elements of any type, but making it heterogeneous (i.e. being able to hold elements of different types in the same collection) is going to be a lot of work. So, I stuck with the partial solution: making the Tree generic.

The second problem can be solved by having the iterators take an object which contains the code. After all, a first-class subroutine is basically the same as an object with only one method. (Java 8 is going to take some of that pain away, I included examples in the code.)

import java.util.Collection;
import java.util.ArrayList;

interface Consumer<T> {
    void accept(T e);
}
// In Java 8, this interface is already part of the JRE.
// Just replace the 3 lines above with this import:
//import java.util.function.Consumer;

class Tree<T> {
    private String nodeName;
    private Collection<Tree<T>> children = new ArrayList<>();

    Tree(String name, Collection<Tree<T>> children) {
        nodeName = name;
        this.children = children;
    }

    Tree(String name) {
        nodeName = name;
    }

    public String getNodeName() { return nodeName; }
    public void setNodeName(String name) { nodeName = name; }

    public Collection<Tree<T>> getChildren() { return children; }
    public void setChildren(Collection<Tree<T>> children) { this.children = children; }

    void visitAll(Consumer<Tree<T>> block) {
        visit(block);
        for (Tree<T> tree : children) tree.visitAll(block);
    }

    void visit(Consumer<Tree<T>> block) {
        block.accept(this);
    }

    public static void main(String... args) {
        ArrayList<Tree<String>> children = new ArrayList<>();
        children.add(new Tree<String>("Reia"));
        children.add(new Tree<String>("MacRuby"));
        Tree<String> rubyTree = new Tree<>("Ruby", children);

        System.out.println("Visiting a node");
        rubyTree.visit(new Consumer<Tree<String>>() {
            public void accept(Tree<String> node) {
                System.out.println(node.getNodeName());
            }
        });
        // In Java 8, you can use a lambda.
        // Just replace the 5 lines above with this line:
        //rubyTree.visit(node -> System.out.println(node.getNodeName()));

        System.out.println();

        System.out.println("Visiting entire tree");
        rubyTree.visitAll(new Consumer<Tree<String>>() {
            public void accept(Tree<String> node) {
                System.out.println(node.getNodeName());
            }
        });
        // In Java 8, you can use a lambda.
        // Just replace the 5 lines above with this line:
        //rubyTree.visitAll(node -> System.out.println(node.getNodeName()));
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much for writing the entire code and also for the caution!
0
def visit(&block)
  block.call self
end

is more nicely written as

def visit
  yield self
end

Also, visit_all and visit would be more idiomatically written as conforming to the Enumerable module:

class Tree
  include Enumerable

  # ...

  def each(&cb)
    cb.call(@element)
    children.each end |child|
      child.each(&cb) if child.respond_to?(:each)
    end
  end
end

This way, you get various other things for free, like e.g. max... and also, everyone knows each applies a block to all elements, while they would have to dig through your API docs or your code to see that the function is called visit_all.


EDIT: a chunk removed because I'm apparently an idiot. Thanks to steenslag for setting me right.

2 Comments

"The default parameter is interpreted at the time def is executed, not at the time the method is invoked." I think this is true in Python, but in Ruby it's not: parameter defaults are evaluated when a method is invoked rather than when it's parsed. def test(a=[]); a<<1; end; test; p test results in [1]
@steenslag: Wow, you are correct! Can't believe I was so misguided for so long! Thankfully, getting it wrong in this direction is way less dangerous than assuming the other way around, so all my code is fine, if a bit redundant. Thanks.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.