• No results found

Refactoring to Introduce Map Operations

The analysis presented in Section 4.3 classifies operations according to obstructiveness, but does not rewrite the fixpoint expression to introduce

4.5. Refactoring to Introduce Map Operations

hvarlisti := var|var ‘,’hvarlisti

harglisti:= hexpressioni| hexpressioni ‘,’ harglisti happlisti := ‘(’ harglisti ‘)’

hlistexpi := ‘nil’ | ‘(’hexpressioni‘::’hexpressioni ‘)’

hifexpi := ‘if’ hexpressioni ‘then’hexpressioni ‘else’hexpressioni ‘end’ hcaseexpi := ‘case’var ‘of’ ‘nil’ ‘->’ hexpressioni ‘,’

var‘::’var‘->’hexpressioni ‘end’

happexpi := varhapplisti | habsexpi happlisti | hfixexpi happlisti

habsexpi := ‘\‘ ‘(‘ <varlist> ‘)’ ‘->’hexpressioni ‘end’ hfixexpi := ‘fix’habsexpi

hexpressioni:= ‘true’ | ‘false’ | int | var | hlistexpi | hifexpi | hcaseexpi | happexpi | habsexpi | hfixexpi

hstatementi := var‘:=’habsexpi

| var ‘:=’ hfixexpi

hprogrami:= hstatementi |hstatementi ‘;’hprogrami

Figure 4.5: Corresponding grammar for E accepted by the prototype Erlang implementation of our analysis.

amap operation. In this section, we describe a refactoring that lifts anun- obstructive operation in a fixpoint expression into amap operation. While

maphas multiple possible implementations, according to the number and type of data structures being traversed, the refactoring defined in this section will use the standard map, defined over a single list.

1 map g [] = []

2 map g (x:xs) = g x : map g xs

Introducing a call to map places additional conditions on the opera- tions we can lift into a mapoperation. We describe these conditions in Section 4.5.1.

The refactoring to lift operations and introduce amapoperation is a

composite refactoring, i.e. a multi-stage refactoring made up of multiple refactorings [81], with 4 stages. Given a fixpoint expression, f, that

traverses a list, xs0, and an operation,g, that is a subexpression off, the refactoring enacts the follow stages:

1. Duplicate the definition of f, perhaps using theDuplicate Function

refactoring. The new, duplicated, definition,f’, becomes the helper function tof. fis rewritten, perhaps using theFold/Unfold Definition

refactoring, as a function expression whose body is a call tof’. For example, a givenf, 1 f xs0@[] = 0 2 f xs0@(x:xs) = g x + (f xs) is refactored 1 f xs0 = f' xs0 2 3 f' xs0@[] = 0 4 f' xs0@(x:xs) = g x + (f' xs)

where f is duplicated to create the helper function, f’, and f

becomes a call tof’.

2. The fixpoint function of themap operation that is to be introduced

4.5. Refactoring to Introduce Map Operations a lambda that takes one argument, i.e. the head of xs0,x. While

gmay take more than one argument, arguments other thanxare guaranteed to be in scope as a consequence of the conditions in Section 4.5.1:

• Literals, i.e. booleans, integers, and nilt, are inherently in

scope.

• Variables come in three possibilities: used but not updated; updated but not used; and updated and used. Variables that are used but not updated have the same value for all successive recursive calls tof’, so can be free variables in the constructed lambda, deriving their initial values in f. Variables that are updated but not used cannot occur as a subexpression to the lifted operation since this would contradict the definition of variable usage. Similarly, variables that are both used and updated cannot occur as a subexpression to the lifted operation since this would contradict the condition thatgis considered

to beunobstructive, and, by extension, contradict the definition ofclean(Definition 4.3.5).

• Complex expressions, i.e. cons-, application-, case-, if-, lambda- , and fixpoint expressions, are in scope if their subexpressions are in scope.

For example, considering the above f’, the lambda constructed by

liftinggis: (\x -> g x).

3. The map operation is now constructed by passing the constructed lambda and xs0 tomap. For example:

1 (map (\x -> g x) xs0)

The constructed map operation is then passed as an additional argument, ys0, to f’in f, and the definition of f’ is updated to accept an additional argument. Pattern matching on ys0 mimics the pattern matching over xs0, and the tail of ys0 is passed in each recursive call. For example, in

1 f xs0 = f' xs0 (map (\x -> g x) xs0)

2

3 f' xs0@[] ys0@[] = 0

4 f' xs0@(x:xs) ys0@(y:ys) = g x + (f' xs ys)

themapoperation is passed as a section argument tof’, andf’has been updated to traverse the new list by pattern matching nil and cons constructors respectively, and the tail ofys0,ys, is passed as the second argument to the recursive call. Where case-expressions are used to discriminate xs0, as in E, a case-expression is introduced to f’ within both branches of the case-expression over xs0. The original body of each case-split is moved into the matching branch of the case-expression overys0, and the other branch is undefined. For example, given a rewrittenf’to use case-expressions instead of pattern matching: 1 f' xs0 ys0 = 2 case xs0 of 3 [] -> case ys0 of 4 [] -> 0 5 (y:ys) -> undefined 6 (x:xs) -> case ys0 of 7 [] -> undefined 8 (y:ys) -> g x + (f' xs ys)

In Line 3 a case-expression over ys0 is introduced where the ori- ginal expression, (0), is used in the nil branch. By definition the

cons-branch of the ys0 case-split cannot be reached, so it is left undefined in Line 4. Similarly, in Line 5, a case-expression over ys0 is introduced with the original expression,(g x + (f’ xs)), is used in the cons-branch of the ys0case-split, and the unreachable nil branch is left undefined. The recursive call in the cons-branch is updated to passysas a second argument.

4. Finally, substitute (orfold, in the transformational sense [27]) gfor

4.5. Refactoring to Introduce Map Operations

1 f' xs0@[] ys0@[] = 0

2 f' xs0@(x:xs) ys0@(y:ys) = y + (f' xs ys)

(g x)in Line 2 has been substituted fory.

The refactoring can be applied for multiple operations infby construct- ing a map operation for each operation to be lifted. The refactoring should take the outermost unobstructiveoperation; i.e. there should be no

hsuch thathis an unobstructiveoperation andgis a subexpression ofh.

4.5.1 Conditions

The conditions for lifting an operation into a map operation, using the definition of mapabove, is as follows:

• The operation to be lifted is classified asunobstructive, and all subex- pressions that are operations are also classified asunobstructive. • The operation must have exactly one argument with a subexpression

that is a variable y, such that there exists a case-expression inf: caseys0of nilt !enil, consty ys!econs

• fmust traverse exactly one list argument, xs0, andfmust contain exactly one case-expression discriminating on xs0. The tail of the list, ys, must be passed to all recursive calls as the ith argument, where xs0 is declared as the ith argument in f.

The requirement that only one list be traversed for the refactoring to apply may be relaxed if, e.g., amap over two lists is to be introduced. We

conjecture that alternativemapdefinitions will require specific conditions,

according to their definition. For example, themap:

1 map g xs0@[] = []

2 map g xs0@(w:x:xs) = g w x : map g xs

would require that exactly two case-expressions over xs0 occur in f. Alternatively, the map:

1 map g xs0@[] ys0@ = []

2 map g xs0@(x:xs) ys0@(y:ys) = g x y : map g xs ys

more commonly known as zipWith, would require exactly two case- expressions over two list arguments in f.

4.5.2 Additional Cleaning Stages

By lifting unobstructive operations into map operations, some function

arguments may no longer be necessary in the definition off’. Additional refactorings can be used to remove those unnecessary arguments, or at the most extreme, remove an unnecessary helper function.

Oncef is refactored, and allunobstructive operations are lifted into maps and passed to f’, those arguments to f’which are neither used

nor updated in the definition off’ may be eliminated, perhaps by the

Eliminate Function Argumentrefactoring. In the case of list arguments, we extend this to mean neither the head nor the list itself is used, and the tail is only used in all recursive calls as a means of traversing the list. For example, in

1 f xs0 = f' xs0 (map (\x -> g x) xs0)

2

3 f' xs0@[] ys0@[] = 0

4 f' xs0@(x:xs) ys0@(y:ys) = g x + (f' xs ys)

we observe that xand xs0 are not usedin f’, nor are they updated. xs

occurs in the recursive call, but only as a means to traversexs0. f’may therefore be refactored to eliminate xs0:

1 f' ys0@[] = 0

2 f' ys0@(y:ys) = y + (f' ys)

Wheref’contains no other (obstructive) operations as subexpressions,f’

itself may be eliminated, where the call tof’inf’is replaced with the call to the introduced mapoperation. Recall the definition ofsudoku,

1 sudoku ps0@[] = []

4.5. Refactoring to Introduce Map Operations where the operation (solve p)may be lifted into a map:

1 sudoku ps0 = sudoku' ps0 (map (\p -> solve p) ps0)

2

3 sudoku' ps0@[] qs0@[] = []

4 sudoku' ps0@(p:ps) qs0@(q:qs) = q : sudoku' ps qs

Here, no other operations occur within sudoku’, meaning the call to

sudoku’ in sudoku may be wholly replaced by the introduced map

operation.

1 sudoku ps0 = map (\p -> solve p) ps0

Another simplification can be applied when multiple disjoint map operations are introduced. As a fusion step [48], it is possible to combine those map operations that act over the same list, perhaps using the standardTupling transformation. For example, given a refactoredfwith

two disjoint unobstructiveoperations,gand h,

1 f xs0@ =

2 f' (map (\x -> g x) xs0@) (map (\x -> h x) xs0) 3

4 f' ys0@[] zs0@[] = 0

5 f' ys0@(y:ys) zs0@(z:zs) = y + (z + (f ys zs)) the map operations overys0 andzs0 may betupled, orzipped:

1 f xs0@ = f' (map (\x -> ((g x),(h x))) xs0)

2

3 f' ws0@[] = 0

4 f' ws0@((y,z):ws) = y + (z + (f ys zs))

When using the standard definition of map, we note that only one list argument may be traversed as a condition of the refactoring. This means that allintroducedmap operations will, by definition, apply to the same list, and so all map operations may be tupled. This may not hold for