I'd love to hear your thoughts on this.
I did so recently, when I was working on a Java implementation
of state monads.
I wanted to defined the "bind" operation for a state monad.
First, I used interfaces to define what a state, value,
product-and-state-pair, operation and parameterized operation
is:
interface StateType {};
interface ValueType {};
interface ProductAndState<ValueType,StateType>
{ ValueType getProduct();
StateType getState(); }
interface Operation<ValueType,StateType>
{ ProductAndState<ValueType,StateType> execute( StateType state ); }
interface ParameterizedOperation<ValueType,StateType,ParameterType>
{ Operation<ValueType,StateType> asDeterminedBy( final ParameterType parameter ); }
Now, I can define the bind-operation using only these interfaces:
class Bind<ValueType,StateType,ProductType>
{ Operation<ValueType,StateType> firstOperation;
ParameterizedOperation<ProductType,StateType,ValueType> secondOperation;
Bind
( final Operation<ValueType,StateType> firstOperation,
final ParameterizedOperation<ProductType,StateType,ValueType> secondOperation )
{ this.firstOperation = firstOperation;
this.secondOperation = secondOperation; }
Operation<ProductType,StateType> composition()
{ return new Operation<ProductType,StateType>()
{ public ProductAndState<ProductType,StateType> execute( final StateType initialState )
{ ProductAndState<ValueType,StateType> intermediateProductAndState =
firstOperation.execute( initialState );
return secondOperation.asDeterminedBy( intermediateProductAndState.getProduct() )
.execute( intermediateProductAndState.getState() ); }}; }}
Finally, I can implement the interfaces and use the bind operation
on the implementations:
class MemoryState implements StateType
{ MemoryState( final int value ){ this.value = value; }
int value;
public int getValue(){ return this.value; }}
class IntValue implements ValueType
{ IntValue( final int value ){ this.value = value; }
public int asInt(){ return this.value; }
int value; }
class IntProductAndMemoryState implements ProductAndState<IntValue,MemoryState>
{ final IntValue product;
final MemoryState state;
IntProductAndMemoryState( final IntValue product, final MemoryState state )
{ this.product = product; this.state = state; }
public IntValue getProduct(){ return product; }
public MemoryState getState(){ return state; }}
class IntValueOperation implements Operation<IntValue,MemoryState>
{ final IntValue value;
IntValueOperation( final IntValue value ){ this.value = value; }
public ProductAndState<IntValue,MemoryState>
execute( final MemoryState state )
{ return new IntProductAndMemoryState( value, state ); }}
class ReaderOperation implements Operation<IntValue,MemoryState>
{ ReaderOperation(){}
public ProductAndState<IntValue,MemoryState>
execute( final MemoryState state )
{ return new IntProductAndMemoryState
( new IntValue( state.getValue() ), state ); }}
class WriterOperation implements Operation<IntValue,MemoryState>
{ IntValue value; WriterOperation( final IntValue value )
{ this.value = value; }
public ProductAndState<IntValue,MemoryState>
execute( final MemoryState state )
{ return new IntProductAndMemoryState
(( IntValue )null, new MemoryState( value.asInt() )); }}
class ReadValue
implements ParameterizedOperation<IntValue,MemoryState,IntValue>
{ public Operation<IntValue,MemoryState> asDeterminedBy( IntValue value )
{ return new ReaderOperation(); /* ignores the value */ }}
class WriteValue
implements ParameterizedOperation<IntValue,MemoryState,IntValue>
{ public Operation<IntValue,MemoryState> asDeterminedBy( IntValue value )
{ return new WriterOperation( value ); /* writes the value */ }}
class Monade
{
static Operation<IntValue,MemoryState> bind
( final Operation<IntValue,MemoryState> first,
final ParameterizedOperation<IntValue,MemoryState,IntValue> second )
{ return new Bind<IntValue,MemoryState,IntValue>( first, second ).composition(); }
static Operation<IntValue,MemoryState> value( final int i )
{ return new IntValueOperation( new IntValue( i )); }
static ParameterizedOperation<IntValue,MemoryState,IntValue> writeValue()
{ return new WriteValue(); }
static ParameterizedOperation<IntValue,MemoryState,IntValue> readValue()
{ return new ReadValue(); }
public static void main( final String[] _ )
{ final MemoryState initState = new MemoryState( 0 );
final int initValue = 2;
ProductAndState<IntValue,MemoryState> productAndState =
bind( bind( value( initValue ), writeValue() ), readValue() ).
execute( initState );
java.lang.System.out.println( productAndState.getProduct().asInt() ); }}
So, this technique would be:
- first, define the types (interfaces)
- then, define all algorithms on these types (interfaces)
- finally, define "thin" implementations using the types and
algorithms defined in the previous steps
However, the same result can be achieved using refactoring by
factoring out algorithms and interfaces from a thick
implementation.
(An additional nice idea would be to use a multimethod
framework for the algorithms. So that the implementations
might use a multi-argument run-time dispatch in cases,
when this is helpful.)