Generalise or specialise a definition


Identity: Generalisation
Category: MultiModule Naming
Classifiers: generalisation specialisation expression definition
Internal cross references:
External cross references:
Language: Haskell

Description:

Generalise a definition by selecting a sub-expression of the right-hand side and making this the value of a new argument added to the definition of the function or constant.

The sub-expression becomes the actual parameter at the call sites.


format :: [String] -> [String]
format []     = []
format [x]    = [x]
format (x:xs) = (x ++ "\n") : format xs

table = concat . format

format :: [a] -> [[a]] -> [[a]]
format sep []     = []
format sep [x]    = [x]
format sep (x:xs) = (x ++ sep) : format sep xs

table = concat . format "\n"

General comment:

The choice of the position where the argument is added is not accidental: putting the argument at the beginning of the argument list means that it can be added correctly to any partial applications of the function.

Note that in the Add Argument refactoring we name the new parameter at the same level as the definition, whereas here we substitute the expression at all call sites.

Left to right comment:

In the example shown, a single expression is selected. It is possible to abstract over a number of occurrences of the (syntactically) identical expression by preceding this refactoring by

  • a transformation to a single equation defined by a case expression;
  • the introduction of a local definition of a name for the common expression.
and by following the refactoring by the appropriate inverse refactorings.

in a multi-module system, some of the free variables in the selected sub-expression might not be accessible to the call sites in some client modules. Instead of explicitly exporting and/or importing these variables, the refactorer creates an auxiliary function (f_gen, say) in the module containing the definition to represent the sub-expression, and makes it accessible to the client modules.

Right to left comment:

The inverse can be seen as a sequence of simpler refactorings.

  • A definition of a special case is introduced:
    fmt = format "\n"
    
    and any uses of format "\n" (outside its definition) are folded to fmt.
  • Using generative folding (i.e. a folding in the style of Burstall and Darlington), the definition of format is specialised to a definition of fmt.
  • If all uses of format take the parameter "\n" then no uses of format remain. Its definition can be removed, and fmt can be renamed to format.

This refactoring has not been implemented yet.

Left to right conditions:

There are two general conditions on the refactoring,

  • Adding the new formal parameter should not capture any existing uses of variables.
  • The abstracted sub expression, e say, becomes the first argument of the new function at every use of it. For every new occurrence of e it is a requirement that the bindings of all free identifiers within e are resolved in the same way that they are in the original occurence.

Right to left conditions:

The successful specialisation depends upon the definition of the function to have a particular form: the particular argument to be removed has to be a constant parameter: that is, it should appear unchanged in every recursive call.

The definition of the original function can only be removed if it is only used in the specialised form.

Analysis required: Static analysis of bindings; call graph; module analysis. If the type declaration is to be modified, then type inference will be needed.