Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/facebookincubator/retrie
Retrie is a powerful, easy-to-use codemodding tool for Haskell.
https://github.com/facebookincubator/retrie
Last synced: 3 months ago
JSON representation
Retrie is a powerful, easy-to-use codemodding tool for Haskell.
- Host: GitHub
- URL: https://github.com/facebookincubator/retrie
- Owner: facebookincubator
- License: mit
- Created: 2019-02-26T02:29:05.000Z (almost 6 years ago)
- Default Branch: main
- Last Pushed: 2024-06-27T18:58:26.000Z (7 months ago)
- Last Synced: 2024-10-02T08:24:10.741Z (4 months ago)
- Language: Haskell
- Homepage:
- Size: 243 KB
- Stars: 501
- Watchers: 25
- Forks: 30
- Open Issues: 22
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
- my-awesome - facebookincubator/retrie - 06 star:0.5k fork:0.0k Retrie is a powerful, easy-to-use codemodding tool for Haskell. (Haskell)
README
Retrie is a powerful, easy-to-use codemodding tool for Haskell.
# Install
```
cabal update
cabal install retrie
```# Example
Assume you have some code, including functions like `foo`:
```haskell
module MyModule wherefoo :: [Int] -> [Int]
foo ints = map bar (map baz ints)
```Someone points out that traversing the list `ints` twice is slower than doing it once. You could fix the code by hand, or you could rewrite it with retrie:
```bash
retrie --adhoc "forall f g xs. map f (map g xs) = map (f . g) xs"
```Retrie applies the equation as a rewrite to all the Haskell modules it finds in the current directory:
```diff
module MyModule wherefoo :: [Int] -> [Int]
-foo ints = map bar (map baz ints)
+foo ints = map (bar . baz) ints
```Of course, now you might find this code more difficult to understand. You also learn that GHC will do this sort of optimization automatically, so you decide to undo your rewrite:
```bash
retrie --adhoc "forall f g xs. map (f . g) xs = map f (map g xs)"
```Now you have your original code back.
# Other Sources Of Equations
* The `--adhoc` flag, above, admits anything you can specify in a `RULES` pragma.
* You can apply actual `RULES` pragmas, in either direction, with `--rule-forward` and `--rule-backward`.
* Since definitions in Haskell are themselves equations, you can unfold (or inline) function definitions with `--unfold`. You can also fold a function definition with `--fold`, replacing an instance of the function's body with a call to that function.
* Type synonyms are also equations. You can apply type synonyms in either direction using `--type-forward` and `--type-backward`.To try some examples, put the following into `MyModule2.hs`:
```haskell
module MyModule2 wheremaybe :: b -> (a -> b) -> Maybe a -> b
maybe d f mb = case mb of
Nothing -> d
Just x -> f xtype MyMaybe = Maybe Int
{-# RULES "myRule" forall x. maybe Nothing Just x = x #-}
foo :: Maybe Int
foo = maybe Nothing Just (Just 5)
```Then try the following rewrites and check the contents of the module after each step:
```bash
retrie --type-backward MyModule2.MyMaybe
``````diff
module MyModule2 wheremaybe :: b -> (a -> b) -> Maybe a -> b
maybe d f mb = case mb of
Nothing -> d
Just x -> f xtype MyMaybe = Maybe Int
{-# RULES "myRule" forall x. maybe Nothing Just x = x #-}
-foo :: Maybe Int
+foo :: MyMaybe
foo = maybe Nothing Just (Just 5)
``````bash
retrie --unfold MyModule2.maybe
``````diff
module MyModule2 wheremaybe :: b -> (a -> b) -> Maybe a -> b
maybe d f mb = case mb of
Nothing -> d
Just x -> f xtype MyMaybe = Maybe Int
-{-# RULES "myRule" forall x. maybe Nothing Just x = x #-}
+{-# RULES "myRule" forall x. case x of
+ Nothing -> Nothing
+ Just x1 -> Just x1 = x #-}foo :: MyMaybe
-foo = maybe Nothing Just (Just 5)
+foo = case Just 5 of
+ Nothing -> Nothing
+ Just x -> Just x
``````bash
retrie --fold MyModule2.maybe
``````diff
module MyModule2 wheremaybe :: b -> (a -> b) -> Maybe a -> b
maybe d f mb = case mb of
Nothing -> d
Just x -> f xtype MyMaybe = Maybe Int
-{-# RULES "myRule" forall x. case x of
- Nothing -> Nothing
- Just x1 -> Just x1 = x #-}
+{-# RULES "myRule" forall x. maybe Nothing Just x = x #-}foo :: MyMaybe
-foo = case Just 5 of
- Nothing -> Nothing
- Just x -> Just x
+foo = maybe Nothing Just (Just 5)
``````bash
retrie --rule-forward MyModule2.myRule
``````diff
module MyModule2 wheremaybe :: b -> (a -> b) -> Maybe a -> b
maybe d f mb = case mb of
Nothing -> d
Just x -> f xtype MyMaybe = Maybe Int
{-# RULES "myRule" forall x. maybe Nothing Just x = x #-}
foo :: MyMaybe
-foo = maybe Nothing Just (Just 5)
+foo = Just 5
``````bash
retrie --type-forward MyModule2.MyMaybe
``````diff
module MyModule2 wheremaybe :: b -> (a -> b) -> Maybe a -> b
maybe d f mb = case mb of
Nothing -> d
Just x -> f xtype MyMaybe = Maybe Int
{-# RULES "myRule" forall x. maybe Nothing Just x = x #-}
-foo :: MyMaybe
+foo :: Maybe Int
foo = Just 5
```# Motivation
Refactoring tools fall on a spectrum. At one end is simple string replacement (`grep` and `sed`). At the other is parsing an abstract-syntax tree (AST) and directly manipulating it. Broadly, the tradeoffs are:
* String manipulation
* Hard to write: Essentially need to hand-roll a parser using a regular expression.
* Limited power: Find and replace.
* Fast.* AST manipulation
* Hard to write: Requires extensive domain knowledge about language/parser.
* Very powerful.
* Slow: Parsing and traversing large codebases is expensive.Retrie finds a middle ground:
* Easy to write: Equations are defined using syntax of target language.
* Powerful:
* Equations are more powerful than regular expressions.
* Rewrites can be scripted and enforce side-conditions (see below).
* Fast: Search space is narrowed using `grep` before parsing. Time is (morally) proportional to the number of matches, not the number of target files.# Features
* Power
* Can rewrite expressions, types, and patterns.
* Matching is up to alpha-equivalence.
* Rewrites are equational: a quantifier that appears twice in the left-hand side must match the same expression (up to alpha-equivalence).
* Inserts imports. (As specified by the user, and automatically in some cases.)
* Rewrites can be scripted and have side conditions.
* Uses GHC's parser, so supports all of the *de facto* Haskell language.
* Correctness
* Local scoping is respected. (Will not introduce shadowing/capture bugs.)
* Impossible to match/rewrite an incomplete expression fragment.
* Parentheses are automatically removed/inserted as needed.
* Whitespace
* Whitespace is ignored when matching. No fiddling with `\s`.
* Whitespace is preserved in resulting expression.
* Will not rewrite in comments. Existing comments are preserved.
* Respects git/hg ignore files.See `retrie --help` for a complete list of options.
# Scripting and Side Conditions
Retrie can be used as a library to tackle more complex rewrites.
Consider the task of changing the argument type of a function from `String` to an enumeration:
```haskell
fooOld :: String -> IO ()data FooArg = Foo | Bar
fooNew :: FooArg -> IO ()
```Retrie provides a small monadic DSL for scripting the application of rewrites. It also allows you to intercept and manipulate the result of matching the left-hand side of an equation. Putting those two together, you could implement the following refactoring:
```haskell
{-# LANGUAGE OverloadedStrings #-}
module Main whereimport qualified GHC.Paths as GHC.Paths
import Retrie
main :: IO ()
main = runScript GHC.Paths.libdir $ \opts -> do
[rewrite] <- parseRewrites GHC.Paths.libdir opts [Adhoc "forall arg. fooOld arg = fooNew arg"]
return $ apply [setRewriteTransformer stringToFooArg rewrite]
argMapping :: [(FastString, String)]
argMapping = [("foo", "Foo"), ("bar", "Bar")]
stringToFooArg :: MatchResultTransformer
stringToFooArg _ctxt match
| MatchResult substitution template <- match
, Just (HoleExpr expr) <- lookupSubst "arg" substitution
, L _ (HsLit _ (HsString _ str)) <- astA expr = do
newExpr <- case lookup str argMapping of
Nothing ->
parseExpr GHC.Paths.libdir $ "error \"invalid argument: " ++ unpackFS str ++ "\""
Just constructor -> parseExpr GHC.Paths.libdir constructor
return $
MatchResult (extendSubst substitution "arg" (HoleExpr newExpr)) template
| otherwise = return NoMatch
```Running this program would create the following diff:
```diff
module MyModule3 where
baz, bar, quux :: IO ()
-baz = fooOld "foo"
+baz = fooNew Foo
-bar = fooOld "bar"
+bar = fooNew Bar-quux = fooOld "quux"
+quux = fooNew (error "invalid argument: quux")
```Defining the `stringToFooArg` function requires knowledge of both the Retrie library and GHC's internal AST types. You'll find haddock/hoogle invaluable for both.
# Reporting Bugs/Submitting Patches
To report a bug in the result of a rewrite, please create a test case ([example](tests/inputs/Adhoc.test)) and submit it as an issue or merge request.
To report other bugs, please create a GitHub issue.
[![Build Status](https://travis-ci.com/facebookincubator/retrie.svg?branch=master)](https://travis-ci.com/facebookincubator/retrie)
# License
Retrie is MIT licensed, as found in the [LICENSE](LICENSE) file.