4

This is an offshoot of these two questions: 1, 2.

I'd like to implement type-safe data structures in Java that prevent nonsensical operations. For example, if the compiler knows I have an instance of an empty stack, it shouldn't allow me to call pop on the empty stack.

As an example, how would I implement such a (generic) stack in Java?

Community
  • 1
  • 1
Jeff Axelrod
  • 27,676
  • 31
  • 147
  • 246
  • In other words, you want a separate type for a stack that is empty. By extension, you need a separate type for a stack with just one element, since popping that would need to return an `EmptyStack`. And so on... – SLaks Feb 21 '12 at 19:17
  • Corollary: You'd have to know the length of the stack at compile-time, unless you're willing to do casts, which kind of defeats the purpose. – Louis Wasserman Feb 21 '12 at 19:22
  • 1
    @glenviewjeff: from your question(s), I can tell you that you definitely want to read the blog of "Tony Morris" (he was an IBM JVM engineer... He has a strong ego, but he's **very** good and its definitely one of the very best coding blog out there). He blogs about functional programming: mostly Haskell but there are amazing entries about Java. Here's a challenge: http://blog.tmorris.net/understanding-practical-api-design-static-typing-and-functional-programming/ It's about writing a completely safe Tic-Tac-Toe, throwing **compile-time erros** if you try to make an invalid move. – TacticalCoder Feb 21 '12 at 19:47
  • s/erros/errors/ *(damn comments that can only be edited for five minutes ; )* – TacticalCoder Feb 21 '12 at 19:53
  • @TacticalCoder Thanks for the blog--I'll check it out! There's no time limit on deletions, so I usually just delete erroneous comments and create new ones with corrections. – Jeff Axelrod Feb 21 '12 at 19:57
  • @TacticalCoder did you delete your answer? Had you actually implemented the solution or just prototyped interfaces? – Jeff Axelrod Feb 25 '12 at 16:59
  • @glenviewjeff: no I had implemented it, the output was the actual output of the progam. But it was way too fugly to paste. If you're into that kind of stuff I really suggest you look at the whole *"type-safe Tic-Tac-Toe"* thing: it's not "nice" (it's very, very verbose) but it's at least readable. My solution was working, but the code is so ugly I'm not posting it on the Internet ; ) – TacticalCoder Feb 25 '12 at 19:48
  • @TacticalCoder Ha okay, thanks. I did the detective work to find the Tic Tac Toe solution online and started looking at the implementation, but at first glance I saw that he did it with the Functional Java library, which wasn't clear was necessary for the type safety. I read a little bit about Functional Java as a result. When I have time I'll try to look the whole solution in more detail. For now I'm sticking with something like what I posted in the answer. – Jeff Axelrod Feb 26 '12 at 01:04

1 Answers1

3

See below Java implementation based on .net code from stakx's question.

If a client tries to pop too far, the compiler will issue an undefined method error. For example, issuing calls like:

new EmptyStack<Integer>().push(1).pop().getTop()

will result in an undefined method error on the call to getTop().

class GenericStack {
   @Test public void test() {
      final IStack<Integer> stack = new EmptyStack<Integer>();
      assertEquals(new Integer(1), stack.push(1).getTop());
      assertEquals(new Integer(2), stack.push(1).push(2).getTop());
      assertEquals(new Integer(1), stack.push(1).push(2).pop().getTop());
   }

   interface IStack<T> { 
      INonEmptyStack<T, ? extends IStack<T>> push(T x);
   }

   interface IEmptyStack<T> extends IStack<T>
   {
       @Override INonEmptyStack<T, IEmptyStack<T>> push(T x);
   }

   interface INonEmptyStack<T, TStackBeneath extends IStack<T>> 
      extends IStack<T>
   {
       T getTop();
       TStackBeneath pop();
       @Override INonEmptyStack<T, INonEmptyStack<T, TStackBeneath>> 
          push(T x);
   }

   class EmptyStack<T> implements IEmptyStack<T> {
      @Override public INonEmptyStack<T, IEmptyStack<T>> push(T x) {
         return new NonEmptyStack<T, IEmptyStack<T>>(x, this);
      }
   }

   class NonEmptyStack<T, TStackBeneath extends IStack<T>> extends Object 
      implements INonEmptyStack<T, TStackBeneath> {
      private final TStackBeneath stackBeneathTop;
      private final T top;

      NonEmptyStack(T top, TStackBeneath stackBeneathTop) {
         this.top = top;
         this.stackBeneathTop = stackBeneathTop;
      }

      @Override public T getTop() {
         return top;
      }

      @Override public TStackBeneath pop() {
         return stackBeneathTop;
      }

      @Override public INonEmptyStack<T, INonEmptyStack<T, TStackBeneath>> 
         push(T x) {
         return 
            new NonEmptyStack<T, INonEmptyStack<T, TStackBeneath>>(x, this);
      }
   }

   // The following client code at the request of @TacticalCoder demonstrates
   // some of the benefits (and limitations) of this implementation.

   @Test public void testRandomPopper() {
      IStack<?> stack = randomPopper(new EmptyStack<Integer>(), 20);
      // This assertion will fail 1 out of .3^20 runs 
      assertTrue(stack instanceof INonEmptyStack<?,?>); 
      assertFalse(stack instanceof IEmptyStack<?>); 
   }

   public IStack<Integer> randomPopper(IStack<Integer> s, final int N) {
      IStack<Integer> stack;
      if(N<1)
         return s;
      stack = s.Push(1);
      for (int i = 1; i < N; i++) {
         INonEmptyStack<Integer,?> tStack = stack.Push(i+1);
         if(Math.random()<0.3) {
            stack = tStack.Pop();            
         } else {
            stack = tStack;
         }
      }
      return stack;
   }

   @Test public void testDrainStack() {
      IStack<Integer> stack = randomPopper(new EmptyStack<Integer>(), 20);
      IStack<?> maybeEmptyStack = drainStack(stack);
      assertTrue(maybeEmptyStack instanceof IEmptyStack);
      IEmptyStack<?> definitelyEmptyStack = (IEmptyStack<?>) maybeEmptyStack;
      assertTrue(definitelyEmptyStack instanceof IEmptyStack<?>); 
   }

   @Test public void testCastNonEmptyStackToEmptyStack() {
      IStack<Integer> stack = randomPopper(new EmptyStack<Integer>(), 20);
      IStack<?> maybeEmptyStack = stack;
      assertFalse(maybeEmptyStack instanceof IEmptyStack);
      // Below cast should issue warning!  Doesn't and issues runtime error.
      IEmptyStack<?> definitelyEmptyStack = (IEmptyStack<?>) maybeEmptyStack;
      assertFalse(definitelyEmptyStack instanceof IEmptyStack<?>); 
   }

   public IStack<?> drainStack(IStack<?> stack) {
      for (;stack instanceof INonEmptyStack<?,?>;)
         stack = ((INonEmptyStack<?,?>) stack).Pop();
      return stack;
   }
}
Community
  • 1
  • 1
Jeff Axelrod
  • 27,676
  • 31
  • 147
  • 246
  • but that stack isn't practical right? How would you, say, do a *for loop* (say 20 times) that pushes a number on the stack at every pass and that has one chance out of tree to pop something off the stack. Then after you're done looping, pop all the numbers left? Can this be done? (without using any *instanceof* / reflection tricks) – TacticalCoder Feb 22 '12 at 01:26
  • @TacticalCoder How is using instanceof a "trick?" What's wrong with using it? Also, what were you trying to say when you wrote "one chance out of tree to pop something off the stack?" – Jeff Axelrod Feb 22 '12 at 04:55
  • I mean that seen the goal you want to reach, using *instanceOf* wouldn't be clean. You'd first do *instanceOf*, then do casting, which would kinda defeat the entire purpose of what you're trying to do. My point regarding randomly pushing or popping something off the stack is simply that the stack you wrote here doesn't seem practical at all. What you want to do can be done, but I think that perverting generics isn't the way to go. I'll add an answer of my own to show what I have in mind. – TacticalCoder Feb 22 '12 at 12:05
  • You wrote *"If a client tries to pop too far, the compiler will issue an error"*... But zs far as I can see it only works when you're "manually" pushing and popping *from the sourcecode*. I can't see it working if you *programmatically* push/pop to your stack. Which is why, in my own solution/answer, just as a test, I did randomly push/pop from inside a for loop to the stack I created. And in my solution the compiler shall not let you compile a program where you would try to pop of an empty stack (your requirement) **and** I can programmatically push/pop to/from the stack. – TacticalCoder Feb 22 '12 at 12:56
  • @TacticalCoder please see the solution appended to the end of the answer's code. The compiler will warn you if you try to cast an object without testing it using `instanceof`, and this can be configured to be treated as an error (at least with Eclipse's compiler,) so I don't see how this defeats the purpose. – Jeff Axelrod Feb 22 '12 at 16:48