Beginner Java Exercise with Data Types

Below is a data type that represents a list that has a maximum length of one. It is often useful in cases where null would otherwise be used.

Consider for example, the getHeaders method on HttpServletRequest which returns a String but might return null if there is no such header. Instead, a new API might return NoneOrOne<String> and do away with the use of null.

The idea of this exercise is to fill out the method bodies (remove the thrown Error) according to the comments without altering the method type. It is not permitted to use null or throw any exception. The tests at the bottom (see main method) should produce the specified results. At the moment, the code compiles, but will not execute successfully.

There are nine methods that need filling out. The tests are not exhaustive (use some intuition). Have fun! Questions are welcome.

// A list that is either empty or has one element.
public abstract class NoneOrOne<A> {
  // The key abstract method (catamorphism).
  public abstract <X> X fold(Thunk<X> none, Func<A, X> one);
 
  // Produces an empty list.
  public static <A> NoneOrOne<A> none() {
    throw new Error("todo");
  }
 
  // Produces a list with the given element.
  public static <A> NoneOrOne<A> one(final A a) {
    throw new Error("todo");
  }
 
  // Returns true if this list is empty.
  public boolean isEmpty() {
    throw new Error("todo");
  }
 
  // Maps the given function on each element of this list.
  public <B> NoneOrOne<B> map(final Func<A, B> f) {
    throw new Error("todo");
  }
 
  // Filters the list on the given predicate.
  // The element is retained if the predicate satisfies.
  public NoneOrOne<A> filter(final Func<A, Boolean> p) {
    throw new Error("todo");
  }
 
  // Applies the possible function on this list.
  public <B> NoneOrOne<B> app(final NoneOrOne<Func<A, B>> f) {
    throw new Error("todo");
  }
 
  // Binds the given function on this list.
  public <B> NoneOrOne<B> bind(final Func<A, NoneOrOne<B>> f) {
    throw new Error("todo");
  }
 
  // Returns the value held in this list.
  // However, if it is empty, return the given default value.
  public A get(final Thunk<A> def) {
    throw new Error("todo");
  }
 
  // If this list is empty, return the given one.
  // Otherwise, return this list.
  public NoneOrOne<A> orElse(final NoneOrOne<A> els) {
    throw new Error("todo");
  }
 
  // For debugging
  public String toString() {
    final StringBuilder s = new StringBuilder();
    s.append('[');
    fold(new Thunk<Object>() {
      public Object value() {
        return null; // Java has no suitable unit type.
      }
    }, new Func<A, Object>() {
      public Object apply(final A a) {
        s.append(a);
        return null; // Java has no suitable unit type.
      }
    });
    return s.append(']').toString();
  }
 
  // TEST
  public static void main(final String[] args) {
    final NoneOrOne<Integer> s = NoneOrOne.one(Integer.parseInt(args[0]));
 
    final NoneOrOne<String> t = s.map(new Func<Integer, String>() {
      public String apply(final Integer i) {
        return new StringBuilder(Integer.valueOf(i * 123).toString()).reverse().toString();
      }
    });
 
    final NoneOrOne<Integer> u = s.filter(new Func<Integer, Boolean>() {
      public Boolean apply(final Integer i) {
        return i < 100;
      }
    });
 
    final NoneOrOne<String> v = s.app(NoneOrOne.<Func<Integer, String>>one(
      new Func<Integer, String>() {
      public String apply(final Integer i) {
        return String.valueOf(Math.pow(i, i));
      }
    }));
 
    final NoneOrOne<String> w = s.bind(new Func<Integer, NoneOrOne<String>>() {
      public NoneOrOne<String> apply(final Integer i) {
        return i % 2 == 0 ?
          one("it's even") :
          i % 3 == 0 ?
            one("it's divisible by 3 but not 6") :
            NoneOrOne.<String>none();
      }
    });
 
    final Integer x = s.get(new Thunk<Integer>() {
      public Integer value() {
        return 42;
      }
    });
 
    final NoneOrOne<Integer> y = NoneOrOne.<Integer>none().orElse(s);
 
    /*
    $ java NoneOrOne 122
    [122]
    [60051]
    []
    [3.4347832971354663E254]
    [it's even]
    122
    [122]
 
    $ java -classpath /tmp/ NoneOrOne 12
    [12]
    [6741]
    [12]
    [8.916100448256E12]
    [it's even]
    12
    [12]
    */
    System.out.println(s);
    System.out.println(t);
    System.out.println(u);
    System.out.println(v);
    System.out.println(w);
    System.out.println(x);
    System.out.println(y);
  }
}
 
// Laziness
interface Thunk<T> {
  T value();
}
 
// Takes an A and produces a B
interface Func<A, B> {
  B apply(A a);
}

9 Responses to “Beginner Java Exercise with Data Types”

  1. Misha Says:

    Thanks! Enjoyed solving this.
    Two questions:
    1/ What is the function of the type in the definition of the filter method?
    2/ In the example output on 122, why should it not output ‘[it's even]‘?

  2. Tony Morris Says:

    Hi Misha,
    1/ I’m not sure I understand this question. The filter function takes a predicate (A -> Boolean) and if the list is non-empty, ensures that the element satisfies this predicate. In any other case, an empty list is returned.

    2/ Woops, yes. Corrected, thanks.

  3. Kristian Domagala Says:

    1/ Perhaps there is confusion caused by the superfluous <B> type declaration in the filter method?

  4. Tony Morris Says:

    Well woopsy doopsy indeed. Thanks Kristian.

  5. czar Says:

    Don’t get wat u guys r trying to do here I am a rookie in java cn u guyz break it down

  6. Andreas Says:

    Hi @all,

    first of all I would like to thank you for this great exercise. I implemented all the methods and wrote same additional tests using TestNG which all works fine.

    1/ What should returned if in method app none() is supplied as parameter?

    2/ Is there a provided solution to this or can you just send it to me via Email?

    Greetings
    Andreas

  7. Andreas Says:

    While I was a bit playing with the code I determined another optimization for the toString() method which in my opinion should not just be for debugging ;)

    Here it is:

    @Override
    public String toString() {
    final StringBuilder s = new StringBuilder();
    s.append("[");
    if (!isEmpty()) {
    s.append(get(defaultThrowingThunk).toString());
    }
    return s.append("]“).toString();
    }

    whereby defaultThrowingThunk is just a Thunk object which throws an exception within methode value().

    Greetings
    Andreas

    P.S.: what is the correct markup value for <code> tag for java code highlighting?

  8. israel Says:

    when I run this program by a eclipse it does not work properly. that means it throws the error.

  9. Tony Morris Says:

    Works fine for me dude.

    $ javac -J-version && md5sum NoneOrOne.java && javac NoneOrOne.java && date
    java version "1.6.0_25"
    Java(TM) SE Runtime Environment (build 1.6.0_25-b06)
    Java HotSpot(TM) 64-Bit Server VM (build 20.0-b11, mixed mode)
    d1bdc0922337ec3075142f2b4549eeaf  NoneOrOne.java
    Wed Jun  1 09:01:03 EST 2011
    

Leave a Reply