The Haskell Refactorer, HaRe, and its API Huiqing Li Claus Reinke Simon Thompson Computing Lab, University of Kent
29/06/2015LDTA Outline Refactoring HaRe: The Haskell Refactorer HaRe Demo The HaRe API API Demo Conclusion and Future Work
29/06/2015LDTA Refactoring What? Changing the structure of existing code without changing its meaning. Source-to-source Functionality-preserving Diffuse and bureaucratic Bi-directional Where and why? Development, maintenance, … To make the code easier to understand and modify To improve code reuse, quality and productivity Essential part of the programming process.
29/06/2015LDTA HaRe – The Haskell Refactorer A tool for refactoring Haskell 98 programs. Full Haskell 98 coverage. Driving concerns: usability and extensibility. Implemented in Haskell, using Programatica’s frontends and Strafunski’s generic traversals. Integrated with the two program editors: (X)Emacs and Vim. Preserves both comments and layout style of the source.
29/06/2015LDTA Refactorings Implemented in HaRe Structural Refactorings Generalise a definition e.g. module Test1 where g x = x : (g (x + 1)) g m x = x : ((g m) (x + m)) module Test2 where module Test2 where import Test1 h y = g y h y = g 1 y
29/06/2015LDTA Refactorings Implemented in HaRe Structural Refactorings (cont.) Rename an identifier Promote/demote a definition to widen/narrow its scope Delete an unused function Duplicate a definition Unfold a definition Introduce a definition to name an identified expression Add an argument to a function Remove an unused argument from a function
29/06/2015LDTA Refactorings Implemented in HaRe Module Refactorings Move a definition from one module to another module e.g. module Test1(g) where module Test1 where g x = x : (g (x + 1)) module Test2 where module Test2 where import Test1 h y = g y g x = x : ( g ( x + 1)) h y = g y
29/06/2015LDTA Refactorings Implemented in HaRe Module Refactorings (cont.) Clean the imports Make the used entities explicitly imported Add an item to the export list Remove an item from the export list
29/06/2015LDTA Refactorings Implemented in HaRe Data-oriented Refactorings From concrete to abstract data-type (ADT), which is a composite refactoring built from a sequence of primitive refactorings. Add field labels Add discriminators Add constructors Remove (nested) pattern Create ADT interface.
29/06/2015LDTA Demonstration of HaRe, hosted in Emacs HaRe Demo
29/06/2015LDTA module Tree where data Tree a = Leaf a | Node a (Tree a) (Tree a) flatten :: Tree a -> [a] flatten (Leaf x ) = [x] flatten (Node x l r ) = x : (flatten l ++ flatten r) From concrete data type to ADT
29/06/2015LDTA module Tree where data Tree a = Leaf {leaf1 :: a} | Node {node1 :: a, node2 :: Tree a, node3 :: Tree a} flatten :: Tree a -> [a] flatten (Leaf x ) = [x] flatten (Node x l r ) = x : (flatten l ++ flatten r) From concrete data type to ADT
29/06/2015LDTA module Tree where data Tree a = Leaf {leaf1 :: a} | Node {node1 :: a, node2 :: Tree a, node3 :: Tree a} isLeaf :: (Tree a) -> Bool isLeaf (Leaf _) = True isLeaf _ = False isNode :: (Tree a) -> Bool isNode (Node _ _ _) = True isNode _ = False flatten :: Tree a -> [a] flatten (Leaf x ) = [x] flatten (Node x l r ) = x : (flatten l ++ flatten r) From concrete data type to ADT
29/06/2015LDTA module Tree where data Tree a = Leaf {leaf1 :: a} | Node {node1 :: a, node2 :: Tree a, node3 :: Tree a} mkLeaf :: a -> Tree a mkLeaf = Leaf mkNode :: a -> (Tree a) -> (Tree a) -> Tree a mkNode = Node isLeaf :: (Tree a) -> Bool isLeaf (Leaf _) = True isLeaf _ = False isNode :: (Tree a) -> Bool isNode (Node _ _ _) = True isNode _ = False flatten :: Tree a -> [a] flatten (Leaf x ) = [x] flatten (Node x l r ) = x : (flatten l ++ flatten r) From concrete data type to ADT
29/06/2015LDTA module Tree where data Tree a = Leaf {leaf1 :: a} | Node {node1 :: a, node2 :: Tree a, node3 :: Tree a} mkLeaf :: a -> Tree a mkLeaf = Leaf mkNode :: a -> (Tree a) -> (Tree a) -> Tree a mkNode = Node isLeaf :: (Tree a) -> Bool isLeaf (Leaf _) = True isLeaf _ = False isNode :: (Tree a) -> Bool isNode (Node _ _ _) = True isNode _ = False flatten :: Tree a -> [a] flatten p |isLeaf p = [(leaf1 p)] flatten p |isNode p = (node1 p) : (flatten (node2 p) ++ flatten (node3 p)) From concrete data type to ADT
29/06/2015LDTA module Tree (Tree,flatten,isLeaf,isNode,leaf1,mkLeaf,mkNode,node1,node2,node3) where data Tree a = Leaf {leaf1 :: a} | Node {node1 :: a, node2 :: Tree a, node3 :: Tree a} mkLeaf :: a -> Tree a mkLeaf = Leaf mkNode :: a -> (Tree a) -> (Tree a) -> Tree a mkNode = Node isLeaf :: (Tree a) -> Bool isLeaf (Leaf _) = True isLeaf _ = False isNode :: (Tree a) -> Bool isNode (Node _ _ _) = True isNode _ = False flatten :: Tree a -> [a] flatten p |isLeaf p = [(leaf1 p)] flatten p |isNode p = (node1 p) : (flatten (node2 p) ++ flatten (node3 p)) From concrete data type to ADT
29/06/2015LDTA Test1.hs module Test1(g) where -- a generator g x = x: (g (x +1)) -- Test2.hs module Test2 where import Test1 h y = g y Generalise a definition
29/06/2015LDTA Test1.hs module Test1(g) where -- a generator g m x = x: ((g m) (x +m)) -- Test2.hs module Test2 where import Test1 h y = g 1 y Generalise a definition
29/06/2015LDTA Test1.hs module Test1() where -- Test2.hs module Test2 where import Test1 -- a generator g m x = x: ((g m) (x +m)) h y = g 1 y Move a definition to another module
29/06/2015LDTA Test1.hs module Test1() where -- Test2.hs module Test2 where -- a generator g m x = x: ((g m) (x +m)) h y = g 1 y Clean imports
29/06/2015LDTA The HaRe API A collection of functions for program analysis and transformation extracted from the HaRe. Hidden layout and comment preservation in program transformation functions. Programatica’s abstract syntax for Haskell 98 + Strafunski’s library for AST traversals + HaRe’s API make implementing primitive refactorings or general program transformations much easier.
29/06/2015LDTA The HaRe API Program analysis API Variable analysis Property checking Module interface (import and exports) analysis Module and files Program transformation API Add/remove/update/swap program entities (e.g. declarations, expressions, parameters, identifiers, etc.) Others Parsing/writing Name generators, from textual to AST syntax phrase, …
29/06/2015LDTA API Demo Example refactoring: Swap the first two arguments of a function, say foo. This affects: The formal parameters of foo. The arguments at the use-sites of foo throughout the program (could be a multi-module program). The type signature.
29/06/2015LDTA API Demo -- swap the first two arguments of a function swapArgs fileName row col = do (inscps, exps, mod, ts)<-parseSourceFile fileName -- from text to program let pnt =locToPNT fileName row col mod if isFunPNT pnt mod then -- deal with the current module do r<-applyRefac (doSwap pnt) (Just (mod, ts)) fileName if isExported pnt exps then -- deal with the client modules. do rs <- applyRefacToClientMods (doSwap pnt) fileName writeRefactoredFiles False (r:rs) else writeRefactoredFiles False [r] else error "\nInvalid cursor position!" -- more code to follow
29/06/2015LDTA API Demo --- Inside a module doSwap pnt = applyTP (full_buTP (idTP `adhocTP` inMatch `adhocTP` inExp `adhocTP` inDecl)) where inMatch ((HsMatch loc fun pats rhs ds)::HsMatchP) | fun == pnt = case pats of (p1:p2:ps) -> do pats'<-swap p1 p2 pats return (HsMatch loc fun pats' rhs ds) _ -> error "Insufficient arguments to swap." inMatch m = return m inExp (HsApp (Exp (HsApp e e1)) e2))::HsExpP) | expToPNT e == pnt = swap e1 e2 exp inExp e = return e
29/06/2015LDTA API Demo inDecl (HsTypeSig loc is c tp))::HsDeclP) |isTypeSigOf pnt decl = if length is ==1 then do let ts = tyFunToList tp swap (ts!!0) (ts!!1) ts -- assume no type synonym is used. else error "This type signature defines the type of more than one identifiers." inDecl d = return d tyFunToList (Typ (HsTyFun t1 t2)) = t1:(tyFunToList t2) tyFunToList t = [t]
29/06/2015LDTA Demonstration of the swap refactoring API Demo
29/06/2015LDTA Test3.hs module Test3 where sumLength:: String -> [Int]-> Int sumLength x y = length x + length y -- Test4.hs module Test4 where import Test3 test3 = sumLength "abc" [1,2,3] Swap arguments
29/06/2015LDTA Test3.hs module Test3 where sumLength:: [Int] -> String-> Int sumLength y x = length x + length y -- Test4.hs module Test4 where import Test3 test3 = sumLength [1,2,3] "abc“ Swap arguments
29/06/2015LDTA Conclusion and Future Work The API is relatively low-level, but essential for implementing program transformations. Program appearance preservation hidden in the API. Together with Strafunski, the API allows to write concise source-to-source program transformations and their side- conditions. A framework for exploring your program transformation ideas. In the future: Complete and improve the current API. Look for some higher-level API.
29/06/2015LDTA