sicp-ex-2.76


Regarding additivity discussed in this chapter:

This is the well-known expression problem.


<< Previous exercise (2.75) | Index | Next exercise (2.77) >>


jirf

System Changes For Adding Types/Operations

Generic Operations with Direct Dispatch

Adding New Types

To add new types one must create the constructor/selectors and update the cond/if blocks which handle dispatch. You have to jump from operation to operation to update the dispatch tables but only one constructor is altered (created).

Adding new Operations

Creating new operations does not require any code changes outside of the creation of the new operation. Might have to jump around to make sure you include each data type in the dispatch table.

Data-Directed Style

Adding New Types

Create and run an "installer" function. Installer should a. implement the constructor, selectors, and each operation b. install with the get procedure the function to the dispatch table.

This style only requires editing/calling the installer which can be done only editing one code location (no jumping :)). Although you might have to jump to your generic function section to remember all of the operations to implement.

Adding New Operations

Each operation dispatch case is written in a separate "installer" function. Adding a new operation involves creating a new table "row" (generic function) and each "column" (operation defined for a specific data type in installer). You have to create the new generic function, and edit each data type installer which requires the new operation. Lot of jumping :(

Message-Passing Style

Adding New Types

Message-passing style internalizes both data and operations completely. Creating a new data type requires only the creation of the constructor. The resulting object must implement internally all selectors, and operations. It is possible to add new types only editing one code location (no jumping).

Adding New Operations.

Because each operation implementation is internal to the object adding new operations requires editing each data type constructor which requires the new operation.

Recommendation: Frequent Type Additions

Message-passing and data-directed styles are equivalent optimal choices. Both internalize their operation implementations. Where operation additions grows slowly relative to type additions on average programmers will have to edit less code locations than direct-dispatch. In my experience this introduces less bugs/mistakes.

Recommendation: Frequent Operation Additions

Direct-dispatch localizes operation logic internal to its definition for all data types. If data types are not constantly being added the code blocks which dispatch on type will not endlessly grow, which can be hard to manage.


Siki

Data-directed style is the most appropriate when new types must often be added.

Message-passing style is the most appropriate when new operations must often be added.


brave one

Actually I don't see the difference: in both cases we have type as a unit, which contains operations. Just dispatch in data-driven variant is external and in message-passing internal. So types are easy to add always, new unit doesn't touch old stuff. And operations touch everything.


torinmr

Hmmm, people seem to be forgetting that there are three strategies here, not just two:

For generic operations with explicit dispatch (which is the same as the "functional programming approach" described in the wiki link above), adding new operations is easy (i.e. can be done without changing existing code).

For message passing, adding new types can be done without changing existing code, but adding new operations requires changing all the old code.

Data directed programming actually "solves" the Expression problem by allowing new types *and* new operations to be added without changing existing code: To add a new type I just fill out a new column in the table of operations, to add a new operation I fill out a new row. (Note that style used in the book of putting all the operations for e.g. the rectangular representation together makes it seem like adding a new operation to all the representations would be hard, but there's nothing stopping me from having my own define block where I register a 'complex-conjuage method under 'rectangular and 'polar.)

Of course, the amount of code that needs to be written to add a new type or operation is about the same under all three approaches - the only difference is how spread out that code needs to be, and how easy it is to locate all the places where code needs to be changed. Data directed programming seems to optimize for the former at the expense of the latter.


krubar

I found torinmr answer to be correct one. What brave one I think is missing is that in data-directed approach one doesn't necessarily need to modify existing install-package declaration to add new operations. It can be done in newly written install-package block or just by registering new operations with series of put calls: either for new type or in the case we want to add operation for all of the types.


rwitak

I think Brian Harvey said it best in his 2011 Berkeley course on the topic, when he stated that "old code should not stop working when you add new code".

In this case: