Monday, November 30, 2009

On Values And Options

Further thinking about the relationship between a Value and its related Option interface (what I was calling Ops) lead to the following construction rules:
  • A Value interface contains no methods
  • Storing a reference to an Option is fine, provided you can guarantee the reference is always non-null. If you can't guarantee this for some reason, declare a Value instead.
  • methods which return a reference to the Option interface must always return a non-null Option reference
  • Care should be taken not to store Options in ADT that have methods that can, under some circumstances, return null. For example, a map of type Map< ? , Value.Option> would not be safe since its get() method could return a null Value.Option reference under certain conditions.
  • Option references should never be declared as parameter types in method signatures
  • The methods on the Value interface inherited from java.lang.Object are @deprecated to warn the user they are calling the wrong interface
If an object stores a value object as a property of some container object, then one might expect to see a declaration like this:

class Container
{
    private Value.Option aProperty = Value.UNSPECIFIED;

    public Value.Option getProperty()
    {
        return aProperty;
    }

    public void setProperty(Value aValue)
    { 
        aProperty = Value.WITH.value(aValue);
    }
}

Where Value.WITH.value implements the catamorphism between Value and Value.Option.

The reasons for the asymmetry between the parameter type of the setter and the return type of the method are as follows:

  • As the consumer of a property, you don't want the hassle of applying a catamorphism to a return value before you can use it. It is reasonable for the method implementor to do this for you, and if the Option reference is the reference that is stored, it will, in any case, be trivial to do so
  • Given these construction rules, the only way to obtain a reference to an Option is to receive one from a method return or load one from a variable. If the Value -> Value.Option catamorphism and all other methods are guaranteed to never return null and you never initialize a Value.Option reference with null, there is no way you can receive a null Value.Option.
  • As the implementor of a class, you can guarantee by inspection that you never store or return a null Option. It is not possible for you, as an implementor, to guarantee that you will never receive a null Option-typed parameter from a caller. Type-safety will guarantee that you receive at least a Value. You can guarantee by construction that any Option reference derived from that value is not null. If you accept Option-typed parameters then you either have to assume that a caller will not make a mistake and pass a null or you have to apply a catamorphism to the parameter in order to restablish your certainity that it is not null. It seems simpler to re-use the Value -> Value.Option catamorphism in the setter method than to apply a second catamorphism to guard against the unlikely case that a caller has violated your method's contract.
  • Since the parameter type (Value) is different to the property declaration (Value.Option), type safety ensures that some kind of conversion will be applied to the parameter prior to it being stored. This prevents a lazy implementor of a setter method passing through a null reference to be stored in an Option-typed member variable.
  • Since returns typed with Value.Option are guaranteed by construction to be never null, even if the related value was null, there is no need for null checks in consuming code
  • The rules are simple to follow and unconditional in nature - any violations of these rules is a serious error and should be treated as such. There is no case for a storing or returning a null Option. Any such case is actually a case for passing or storing a null Value, which are the only safe types of reference to pass in this way.

Saturday, November 28, 2009

ephipany: the path to null-safety is the zero-method value object

Or, The Zen Of Zero-Method Value Objects...

I had an ephiphany the other day.

The path to null-safety is the zero-method value object.

Rationale: an interface that defines no methods cannot result in the generation of call-site that causes a NullPointerException for the simple reason that if there is no method, there can be no call. If there is no call, there can be no NullPointerException

Example 1: What you can't call, can't blow up


interface DangerousDateRange
{  
    boolean contains(Date operand);
}
DangerousDateRange dateRange = ...;
...
dateRange.contains(date); // legal, but potentially unsafe

interface SafeDateRange { } SafeDateRange safeDateRange = ...;
...
safeDateRange.contains(date); // illegal, but very safe

Ok, trivial point you say. But what use is a zero method value object? How useful can that really be? After all, how can a date range answer the contains question if it has no contains() method in the first place?

How useful? Very.

A zero-method object can still be the container of state, namely the value.

Operations can be performed on another interface. The interface can be well defined, even for the null reference.

Example 2: Making a zero-method value interface useful


interface SafeDateRange
{
   // here is where the value contract goes ...

   interface Ops
   {
       boolean contains(Date date);  
       SafeDateRange asValue();
   };

   // here is where we store state ...

   private class Impl extends SafeDateRange
   {
        private final Ops ops;

        private Impl(final Date lower, final Date upper)
        {
            ops = 
                new Ops()
                {
                    boolean contains(Date date)
                    {
                       // ... whatever
                    };

                    SafeDateRange asValue()
                    {
                         return Impl.this;
                    }       
                };
            }

        }            
        Ops asOps()
        {
                return ops;
        }
   }

   // this is how we generate new values

   BinaryOp<Date, Date, Ops> NEW = new BinaryOp<Date, Date,  Ops>()
   {
         Ops apply(final Date lower, final Date upper) { return new Impl(lower, upper); }
   }

   // and this is how we provide access to the behaviour
   // in a null-safe manner

   UnaryOp<SafeDateRange, Ops> OPS = new UnaryOp<SafeDateRange,  Ops>()
   {
        Ops apply(final SafeDateRange ops) 
        {
             if (ops instantof Impl)
             {
                 return ((Impl)ops).asOps();
             }
             else 
             {
                 // and this is how we make it null-safe 

                 return new SafeDateRange.Ops()
                 {
                     boolean contains(Date date)
                     {
                         return true;
                     }

                     SafeDateRange asValue()
                     {
                         return null;
                     }
                 }
             }
         }
   }

}

Example 3: Using A Null Safe Date Range



SafeDateRange safeDateRange = SafeDateRange.NEW.apply(lower, upper).asValue();
SafeDateRange.Impl impl = null; //illegal - can't declare impl

SafeDateRange.OPS.apply(safeDateRange).contains(test); // safe, legal, well-defined
SafeDateRange.OPS.apply(null).contains(test); // safe, legal, well-defined

impl.contains(test);    // illegal: impl can't have been declared in the first place

Example 4: Caveat: There will always be idiots...


class Foo
{
    SafeDateRange     safe;    // always do this
    SafeDateRange.Ops unsafe;  // almost never do this 
}

Foo foolish = new Foo();

...
SafeDateRange.OPS.apply(foolish.safe).contains(test); // legal, safe
foolish.unsafe.contains(test);                        // legal, unsafe


Yep, there will always be idiots. But hopefully you can teach idiots that declaring long-lived references (e.g. member variables) to SafeDateRange.Ops is almost always a bad thing. Exception: if you can guarantee, by construction, that a reference to SafeDateRange.Ops is never null, you can relax this rule. Update: Thinking about this further - declaring a Ops reference is fine and (for performance reasons probably preferable), but you have to ensure that it is never null to use it safely. See my later post for a discussion of this.

Credit Where Credit Is Due

I am almost certain that I am not the first to discover the zen of zero-method value objects so if you have seen this or a similar idea written up before, please let me know and I'll will include a reference to that place.

It would not surprise me, for example, if this is very similar to one of Tony Morris's ideas.

Update:

As suspected, this is something addressed by Tony Morris' functional java, as per this response from him


Hi Jon,
What you are discovering has this catamorphism:
λa.λx. (a -> x) -> x -> x

To translate that to Java:

interface Function { Y apply(X x); }
interface Thunk { X apply(); }
interface Nullable<A> {
  X cata(Function s, Thunk n);
}

It's available in Functional Java

Many (non-total) languages do not have null.
And this:
Haskell calls it Maybe, ML calls it option, Scala calls it Option,
Functional Java calls it Option. Note that it (the type constructor)
is a covariant functor and a monad. The catamorphism is the important
part. You can use languages that simply don't have null, or assume
language subsets without it (many Java users do). You can also write
in languages that guarantee termination i.e. all total functions (with
some sacrifices of course) e.g. Agda, Coq, Isabelle.

Saturday, November 07, 2009

Lions. It's all about food.

Concepts:

  • lions (real)
  • death (real)
  • food (real)

Lions cause death, because to them, it's all about food.

Concepts, about real things, linked together with plausible explanations to make a statement that has some correspondence to reality.

Compare and contrast

  • son of god (imaginary)
  • eternal life (imaginary)

Two imaginary concepts linked together haphazardly in an attempt to make a nonsensical statement about an imaginary state of reality.

Found: MP3 player

I found an MP3 player which I believe belongs to a woman called Claire who was travelling from McMahon's Point ferry wharf at around 11:30am today (Saturday 7th November, 2009).

Contact me to arrange return of the player.