Visitor Variations

<< How can Algebraic DataTypes (ADTs) Be Used? | Comments >>

The Java generated from a jADT definition supports several different variations on the Visitor pattern, each specialized to a particular need. Visitors vary on whether a useful result value is computed vs returning void, and whether all cases must be explicitly handled vs having a default. Thus for the ADT DataType<T1, T2>, the four possible visitors interfaces are DataType.MatchBlock<ResultType, T1, T2>, DataType.MatchBlockWithDefault<ResultType, T1, T2>, DataType.SwitchBlock<T1, T2>, DataType.SwitchBlockWithDefault<T1, T2>.

Visitor Variations
explcitly handle all casesdefault handler
useful resultMatchBlock<ResultType>MatchBlockWithDefault<ResultType>
effects onlySwitchBlockSwitchBlockWithDefault

In example Java is based on visitors generated from this sample enterprisey ADT

TPSReportStatus=
    Pending 
  | Approved(final Manager approver)
  | Denied(final Manager rejector)

MatchBlock<ResultType>

Implementations of the standard MatchBlock<ResultType> interface must have vist* methods for all cases. Use this kind of visitor when you want to return a useful result and when every case requires its own treatment.

For instance, if you want to get a message for a UI or for logging then a standard MatchBlock is probably the right answer.

    public String message(TPSReportStatus status) {
        return status.match(new TPSReportStatus.MatchBlock<String>() {
            @Override
            public String _case(Pending x) {
                return "Pending";
            }

            @Override
            public String _case(Approved x) {
                return "Approved by " + x.approver;
            }

            @Override
            public String _case(Denied x) {
                return "Denied by " + x.rejector;
            }
        });
    }

The non-generic ADT "DataType" will get an interface DataType.MatchBlock<ResultType> where ResultType is the type of the value that the MatchBlock's _case methods will return. The generic ADT "DataType<T1,T2> will get DataType.MatchBlock<ResultType, T1, T2>.

MatchBlockWithDefault<ResultType>

Implementations of the MatchBlockWithDefault<ResultType> base class must at least specify a _default method and may optionally override the various _case* methods to compute something other than the default. Use this kind of visitor when you want to return a useful result and when only a few cases require special treatment.

Often when querying for a property that is true only in a few cases but false in most (or vice versa), MatchBlockWithDefault is the right answer

    public boolean isApproved(TPSReportStatus status) {
        return status.match(new TPSReportStatus.MatchBlockWithDefault<Boolean>() {
            @Override
            public Boolean _case(Approved x) {
                return true;
            }

            @Override
            protected Boolean _default(TPSReportStatus x) {
                return false;
            }
        });
    }

The non-generic ADT "DataType" will get an abstract base class DataType.MatchBlockWithDefault<ResultType> where ResultType is the type of the value that the MatchBlockWithDefault's _case methods will return. The generic ADT "DataType<T1,T2> will get DataType.MatchBlockWithDefault<ResultType, T1, T2>.

SwitchBlock

Implementations of the SwitchBlock interface must have vist* methods for all cases. Use this kind of visitor when you want only effects and when every case requires its own treatment.

For example, if you need to notify another system (e.g. a network service) of a TPSReport, a SwitchBlock will do the job.

public void notify(TPSReportStatus status, final StatusNotifier notifier) {
    status._switch(new TPSReportStatus.SwitchBlock() {
        
        @Override
        public void _case(Denied x) {
            notifier.notifyDenied();
        }
        
        @Override
        public void _case(Approved x) {
            notifier.notifyApproved();                
        }
        
        @Override
        public void _case(Pending x) {
            notifier.notifyPending();
        }
    });
}

The non-generic ADT "DataType" will get an interface DataType.SwitchBlock<ResultType> where the SwitchBlocks's _case methods will return type void. The generic ADT "DataType<T1,T2> will get DataType.SwitchBlock<T1, T2>.

SwitchBlockWithDefault

Implementations of the SwitchBlockWithDefault base class must at least specify a _default method and may optionally override the various _case* methods to perform some action than the default. Use this kind of visitor when you only want effects and when only a few cases require special treatment.

For example, if you need to notify another system (e.g. a network service) only if a TPSReport is denied then SwitchBlockWithDefault will do the job.

    public void notifyDenied(TPSReportStatus status, final StatusNotifier notifier) {
        status._switch(new SwitchBlockWithDefault() {
            
            @Override
            public void _case(Denied x) {
                notifier.notifyDenied();
            }

            @Override
            protected void _default(TPSReportStatus x) {
                // nothing to do if it wasn't denied                
            }
        });
    }

The non-generic ADT "DataType" will get an abstract base class DataType.SwitchBlockWithDefault where the SwitchBlockWithDefault's _case methods will return type void. The generic ADT "DataType<T1,T2> will get DataType.SwitchBlockWithDefault<T1, T2>.

<< How can Algebraic DataTypes (ADTs) Be Used? | Comments >>