Tuesday, August 25, 2009

A favorite functional idiom in Java

Ok, two favorite functional idioms in Java:

public abstract class Option<T>
        extends Cursorable<T> {
    public static <T> Option<T> some(final T value) {
        return new Option<T>() {
            @Override
            public int size() {
                return 1;
            }

            @Override
            public T get() {
                return value;
            }
        };
    }

    public static <T> Option<T> none() {
        return new Option<T>() {
            @Override
            public int size() {
                return 0;
            }

            @Override
            public T get() {
                throw new IllegalStateException();
            }
        };
    }

    public abstract int size();

    public abstract T get();

    public final boolean isEmpty() {
        return 0 == size();
    }

    @Override
    public final Iterator<T> iterator() {
        return new Iterator<T>() {
            private boolean empty = isEmpty();

            @Override
            public boolean hasNext() {
                return !empty;
            }

            @Override
            public T next() {
                if (empty)
                    throw new NoSuchElementException();
                empty = true;
                return get();
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }
}

public abstract class Cursorable<T>
        implements Iterable<T> {
    public static <T> Cursorable<T> over(
            final Iterable<T> it) {
        return new Cursorable<T>() {
            @Override
            public Iterator<T> iterator() {
                return it.iterator();
            }
        };
    }

    public final Cursorable<T> select(
            final Select<T> select) {
        return new Cursorable<T>() {
            @Override
            public Iterator<T> iterator() {
                return new Iterator<T>() {
                    private final Iterator<T> it
                            = Cursorable.this.iterator();

                    private Option<T> current = none();

                    @Override
                    public boolean hasNext() {
                        while (it.hasNext()) {
                            final T next = it.next();
                            if (!select.accept(next))
                                continue;
                            current = some(next);
                            return true;
                        }
                        current = none();
                        return false;
                    }

                    @Override
                    public T next() {
                        if (current.isEmpty())
                            throw new NoSuchElementException();
                        return current.get();
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }
        };
    }

    public final <U> Cursorable<U> project(
            final Project<T, U> project) {
        return new Cursorable<U>() {
            @Override
            public Iterator<U> iterator() {
                return new Iterator<U>() {
                    private final Iterator<T> it
                            = Cursorable.this.iterator();

                    @Override
                    public boolean hasNext() {
                        return it.hasNext();
                    }

                    @Override
                    public U next() {
                        return project.on(it.next());
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }
        };
    }

    public interface Select<T> {
        boolean accept(final T input);
    }

    public interface Project<T, U> {
        U on(final T input);
    }
}

And a demo:

public class SelectAndProjectMain {
    public static void main(final String... args) {
        for (final Integer number : over(asList(1, 2, 3, 4))
                .select(new Cursorable.Select() {
                    @Override
                    public boolean accept(final Integer input) {
                        return 0 == input % 2;
                    }
                })) {
            System.out.println("Even: " + number);
        }

        for (final String number : over(asList(1, 2, 3, 4))
                .project(new Cursorable.Project() {
                    @Override
                    public String on(final Integer input) {
                        return Integer.toBinaryString(input);
                    }
                })) {
            System.out.println("Binary: " + number);
        }
    }
}

Produces:

Even: 2
Even: 4
Binary: 1
Binary: 10
Binary: 11
Binary: 100

Although I am afraid to add this to my project at work. I worry for the maintainer after me.

UPDATE: A coworker called me to the mat for extreme terseness. Demonstrating a more fluent style, I refactored the demo code:

public class SelectAndProjectMain {
    public static void main(final String... args) {
        final List<Integer> numbers = asList(1, 2, 3, 4);

        for (final Integer number : over(numbers).select(evens()))
            System.out.println("Even: " + number);

        for (final String number : over(numbers).project(binaries()))
            System.out.println("Binary: " + number);
    }

    static class Evens
            implements Cursorable.Select<Integer> {
        static Evens evens() {
            return new Evens();
        }

        @Override
        public boolean accept(final Integer input) {
            return 0 == input % 2;
        }
    }

    static class Binaries
            implements Cursorable.Project<Integer, String> {
        static Binaries binaries() {
            return new Binaries();
        }

        @Override
        public String on(final Integer input) {
            return Integer.toBinaryString(input);
        }
    }
}

UPDATE: And lastly, more on Option from the land of Scala.

No comments: