Is Function Ambivalence a Good Thing?
Vector 6:4, page 96
Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 96
I am amazed by the extent of the feedback generated by my brief letter concerning an ‘ambivalent operator’. The concept was originally simply an exercise for my own intellectual interest and was not (as I hope my response to Claude Henriod in Vector[6;3;93] made clear) a serious attempt at a real solution. Clearly the subject is of interest to many APL users. The suggestion of a potential solution (however great its shortcomings) to the problems of making use of ambivalence begs more serious questions. The most fundamental must be:
"is function ambivalence a good thing?"
To be a ‘good thing’ we presumably mean a facility must provide useful functionality without sacrificing anything (or at most sacrificing less than we gain). What facilities does functional ambivalence provide? - quite simply the ability to elide the left argument of a defined dyadic function. We use this capability to either:
- supply a default value for the elided argument (as with the subtract vs minus or divide vs reciprocal primitive functions)
- undertake some different but possibly related action (as with the multiply vs signum or add vs conjugate primitive functions).
What, then, is the cost of using function ambivalence? If used to supply default values for the elided argument, the effects are usually not too serious. Criticism can be made about defining data (i.e. the default value) procedurally rather than as a parameter, but this is rather theoretical. The real weakness can be seen if we examine what happens when the default has to change. The code making use of the default either stops working, or, worse still, continues to work but provides different (presumably invalid) results. Of course, we could always create a brand new function for each different value of default.
When used to implement two separate functions the cost of ambivalence is greater. By implementing two separate functions in a single unit of code we increase the complexity of the code written (back to the start of the discussion; we have to write code to handle the ambivalence). We also reduce the coherence (strength) of the function.
If the two functions are well separated, we may be able to classify the function as having "informational cohesion" (Myers, Reliable Software through Composite Design 1975). This is only slightly inferior to the best case "functional" cohesion and no significant maintenance or quality problems will be introduced. On the other hand, if the functions are not well isolated, the best classification we can assign is "logical" cohesion (Page-Jones, The Practical Guide to Structured Systems Design 1988) with the maintenance and quality problems such a poorly designed function normally introduces.
Note that for both types of use of ambivalence, the coupling between the function and its superior calling functions is increased. It is likely that the level of coupling will be reduced to that of "control" coupling (see Page-Jones). The argument is not only being used to pass data to the sub-function, information is also being passed by the presence or elision of the argument. Since data and control information are passed with the same piece of data, the coupling is more likely to be classed as "hybrid" Page-Jones makes a good job of explaining the problems associated with this type of coupling.
So, then, we are left to consider cases where it may be valid to use function ambivalence. In "open" systems or utilities, ambivalence can be used to enrich the syntax of the language defined for the user. Even here, it should be used with great care, especially if ambivalence is used to set default parameters.
With regard to the contribution from Phil Last (Vector[6;2;117]), I certainly would not countenance the use of ⎕FX to fix functions on the fly. Using this technique, we manage to descend right to the bottom of the quality ladder, to the depths of "pathological" (or content) coupling - one function is being used to modify the contents of another!
The quality impact of using execute as advocated by Claude Henriod and Norman Thomson (Vector[6;3;94 97]) is harder to quantify but still smacks of pathological coupling; my general reaction to it (and all uses of execute) is YUCK!! I guess, on those occasions when I have (or want) to use ambivalence, I shall have to resort to the test and branch syndrome. At least in APL*PLUS I can use the diamond separator to get the test and the branch on a single function line.
Whether the costs in terms of design quality, discussed above, are greater than the benefits derived from the use of function ambivalence is largely a matter of personal judgement. My own opinion is that except for "open" systems cases, the potential costs far outweigh any short term benefits. I hope this provides some helpful (recursive) feedback and clarifies my viewpoint.
D B Piper