1
1
mirror of https://github.com/github/semantic.git synced 2024-12-25 07:55:12 +03:00

Curry diffing operations.

This commit is contained in:
Rob Rix 2017-09-13 19:34:27 -04:00
parent 8816a1b705
commit bf2de994ac
7 changed files with 34 additions and 33 deletions

View File

@ -9,7 +9,6 @@ module Interpreter
import Algorithm import Algorithm
import Control.Monad.Free.Freer import Control.Monad.Free.Freer
import Data.Align.Generic import Data.Align.Generic
import Data.Functor.Both
import Data.Functor.Foldable (cata) import Data.Functor.Foldable (cata)
import Data.Functor.Classes (Eq1) import Data.Functor.Classes (Eq1)
import Data.Hashable (Hashable) import Data.Hashable (Hashable)
@ -26,25 +25,28 @@ import Term
-- | Diff two terms recursively, given functions characterizing the diffing. -- | Diff two terms recursively, given functions characterizing the diffing.
diffTerms :: HasField fields Category diffTerms :: HasField fields Category
=> Both (Term Syntax (Record fields)) -- ^ A pair of terms representing the old and new state, respectively. => Term Syntax (Record fields) -- ^ A term representing the old state.
-> Term Syntax (Record fields) -- ^ A term representing the new state.
-> Diff Syntax (Record fields) -> Diff Syntax (Record fields)
diffTerms = decoratingWith getLabel (diffTermsWith algorithmWithTerms comparableByCategory) diffTerms = decoratingWith getLabel (diffTermsWith algorithmWithTerms comparableByCategory)
-- | Diff two terms by decorating with feature vectors computed using the supplied labelling algebra, and stripping the feature vectors from the resulting diff. -- | Diff two terms by decorating with feature vectors computed using the supplied labelling algebra, and stripping the feature vectors from the resulting diff.
decoratingWith :: (Hashable label, Traversable f) decoratingWith :: (Hashable label, Traversable f)
=> (forall a. TermF f (Record fields) a -> label) => (forall a. TermF f (Record fields) a -> label)
-> (Both (Term f (Record (FeatureVector ': fields))) -> Diff f (Record (FeatureVector ': fields))) -> (Term f (Record (FeatureVector ': fields)) -> Term f (Record (FeatureVector ': fields)) -> Diff f (Record (FeatureVector ': fields)))
-> Both (Term f (Record fields)) -> Term f (Record fields)
-> Term f (Record fields)
-> Diff f (Record fields) -> Diff f (Record fields)
decoratingWith getLabel differ = stripDiff . differ . fmap (defaultFeatureVectorDecorator getLabel) decoratingWith getLabel differ t1 t2 = stripDiff (differ (defaultFeatureVectorDecorator getLabel t1) (defaultFeatureVectorDecorator getLabel t2))
-- | Diff a pair of terms recurisvely, using the supplied continuation and 'ComparabilityRelation'. -- | Diff a pair of terms recurisvely, using the supplied continuation and 'ComparabilityRelation'.
diffTermsWith :: forall f fields . (Traversable f, GAlign f, Eq1 f, HasField fields FeatureVector) diffTermsWith :: forall f fields . (Traversable f, GAlign f, Eq1 f, HasField fields FeatureVector)
=> (Term f (Record fields) -> Term f (Record fields) -> Algorithm (Term f) (Diff f) (Record fields) (Diff f (Record fields))) -- ^ A function producing syntax-directed continuations of the algorithm. => (Term f (Record fields) -> Term f (Record fields) -> Algorithm (Term f) (Diff f) (Record fields) (Diff f (Record fields))) -- ^ A function producing syntax-directed continuations of the algorithm.
-> ComparabilityRelation f fields -- ^ A relation on terms used to determine comparability and equality. -> ComparabilityRelation f fields -- ^ A relation on terms used to determine comparability and equality.
-> Both (Term f (Record fields)) -- ^ A pair of terms. -> Term f (Record fields) -- ^ A term representing the old state.
-> Term f (Record fields) -- ^ A term representing the new state.
-> Diff f (Record fields) -- ^ The resulting diff. -> Diff f (Record fields) -- ^ The resulting diff.
diffTermsWith refine comparable (Join (a, b)) = runFreer decompose (diff a b) diffTermsWith refine comparable t1 t2 = runFreer decompose (diff t1 t2)
where decompose :: AlgorithmF (Term f) (Diff f) (Record fields) result -> Algorithm (Term f) (Diff f) (Record fields) result where decompose :: AlgorithmF (Term f) (Diff f) (Record fields) result -> Algorithm (Term f) (Diff f) (Record fields) result
decompose step = case step of decompose step = case step of
Algorithm.Diff t1 t2 -> refine t1 t2 Algorithm.Diff t1 t2 -> refine t1 t2

View File

@ -88,18 +88,18 @@ diffBlobPair renderer blobs = case (renderer, effectiveLanguage) of
where effectiveLanguage = runBothWith (<|>) (blobLanguage <$> blobs) where effectiveLanguage = runBothWith (<|>) (blobLanguage <$> blobs)
syntaxParser = parserForLanguage effectiveLanguage syntaxParser = parserForLanguage effectiveLanguage
run :: Functor f => (Blob -> Task (Term f a)) -> (Both (Term f a) -> Diff f a) -> (Diff f a -> output) -> Task output run :: Functor f => (Blob -> Task (Term f a)) -> (Term f a -> Term f a -> Diff f a) -> (Diff f a -> output) -> Task output
run parse diff renderer = distributeFor blobs parse >>= diffTermPair blobs diff >>= render renderer run parse diff renderer = distributeFor blobs parse >>= runBothWith (diffTermPair blobs diff) >>= render renderer
diffRecursively :: (Eq1 f, GAlign f, Show1 f, Traversable f, Diffable f) => Both (Term f (Record fields)) -> Diff f (Record fields) diffRecursively :: (Eq1 f, GAlign f, Show1 f, Traversable f, Diffable f) => Term f (Record fields) -> Term f (Record fields) -> Diff f (Record fields)
diffRecursively = decoratingWith constructorNameAndConstantFields (diffTermsWith algorithmForTerms comparableByConstructor) diffRecursively = decoratingWith constructorNameAndConstantFields (diffTermsWith algorithmForTerms comparableByConstructor)
-- | A task to diff a pair of 'Term's, producing insertion/deletion 'Patch'es for non-existent 'Blob's. -- | A task to diff a pair of 'Term's, producing insertion/deletion 'Patch'es for non-existent 'Blob's.
diffTermPair :: Functor f => Both Blob -> Differ f a -> Both (Term f a) -> Task (Diff f a) diffTermPair :: Functor f => Both Blob -> Differ f a -> Term f a -> Term f a -> Task (Diff f a)
diffTermPair blobs differ terms = case runJoin (blobExists <$> blobs) of diffTermPair blobs differ t1 t2 = case runJoin (blobExists <$> blobs) of
(True, False) -> pure (deleting (Both.fst terms)) (True, False) -> pure (deleting t1)
(False, True) -> pure (inserting (Both.snd terms)) (False, True) -> pure (inserting t2)
_ -> time "diff" logInfo $ diff differ terms _ -> time "diff" logInfo $ diff differ t1 t2
where where
logInfo = let (a, b) = runJoin blobs in logInfo = let (a, b) = runJoin blobs in
[ ("before_path", blobPath a) [ ("before_path", blobPath a)

View File

@ -67,7 +67,7 @@ data TaskF output where
Time :: String -> [(String, String)] -> Task output -> TaskF output Time :: String -> [(String, String)] -> Task output -> TaskF output
Parse :: Parser term -> Blob -> TaskF term Parse :: Parser term -> Blob -> TaskF term
Decorate :: Functor f => RAlgebra (TermF f (Record fields)) (Term f (Record fields)) field -> Term f (Record fields) -> TaskF (Term f (Record (field ': fields))) Decorate :: Functor f => RAlgebra (TermF f (Record fields)) (Term f (Record fields)) field -> Term f (Record fields) -> TaskF (Term f (Record (field ': fields)))
Diff :: Differ f a -> Both (Term f a) -> TaskF (Diff f a) Diff :: Differ f a -> Term f a -> Term f a -> TaskF (Diff f a)
Render :: Renderer input output -> input -> TaskF output Render :: Renderer input output -> input -> TaskF output
Distribute :: Traversable t => t (Task output) -> TaskF (t output) Distribute :: Traversable t => t (Task output) -> TaskF (t output)
@ -82,7 +82,7 @@ data TaskF output where
type Task = Freer TaskF type Task = Freer TaskF
-- | A function to compute the 'Diff' for a pair of 'Term's with arbitrary syntax functor & annotation types. -- | A function to compute the 'Diff' for a pair of 'Term's with arbitrary syntax functor & annotation types.
type Differ f a = Both (Term f a) -> Diff f a type Differ f a = Term f a -> Term f a -> Diff f a
-- | A function to render terms or diffs. -- | A function to render terms or diffs.
type Renderer i o = i -> o type Renderer i o = i -> o
@ -117,8 +117,8 @@ decorate :: Functor f => RAlgebra (TermF f (Record fields)) (Term f (Record fiel
decorate algebra term = Decorate algebra term `Then` return decorate algebra term = Decorate algebra term `Then` return
-- | A 'Task' which diffs a pair of terms using the supplied 'Differ' function. -- | A 'Task' which diffs a pair of terms using the supplied 'Differ' function.
diff :: Differ f a -> Both (Term f a) -> Task (Diff f a) diff :: Differ f a -> Term f a -> Term f a -> Task (Diff f a)
diff differ terms = Semantic.Task.Diff differ terms `Then` return diff differ term1 term2 = Semantic.Task.Diff differ term1 term2 `Then` return
-- | A 'Task' which renders some input using the supplied 'Renderer' function. -- | A 'Task' which renders some input using the supplied 'Renderer' function.
render :: Renderer input output -> input -> Task output render :: Renderer input output -> input -> Task output
@ -182,7 +182,7 @@ runTaskWithOptions options task = do
either (pure . Left) yield res either (pure . Left) yield res
Parse parser blob -> go (runParser options blob parser) >>= either (pure . Left) yield Parse parser blob -> go (runParser options blob parser) >>= either (pure . Left) yield
Decorate algebra term -> pure (decoratorWithAlgebra algebra term) >>= yield Decorate algebra term -> pure (decoratorWithAlgebra algebra term) >>= yield
Semantic.Task.Diff differ terms -> pure (differ terms) >>= yield Semantic.Task.Diff differ term1 term2 -> pure (differ term1 term2) >>= yield
Render renderer input -> pure (renderer input) >>= yield Render renderer input -> pure (renderer input) >>= yield
Distribute tasks -> Async.mapConcurrently go tasks >>= either (pure . Left) yield . sequenceA . withStrategy (parTraversable (parTraversable rseq)) Distribute tasks -> Async.mapConcurrently go tasks >>= either (pure . Left) yield . sequenceA . withStrategy (parTraversable (parTraversable rseq))
LiftIO action -> action >>= yield LiftIO action -> action >>= yield

View File

@ -23,14 +23,14 @@ spec = parallel $ do
prop "equal terms produce identity diffs" $ prop "equal terms produce identity diffs" $
\ a -> let term = decorate (a :: Term Syntax (Record '[Category])) in \ a -> let term = decorate (a :: Term Syntax (Record '[Category])) in
diffCost (diffTerms (pure term)) `shouldBe` 0 diffCost (diffTerms term term) `shouldBe` 0
describe "beforeTerm" $ do describe "beforeTerm" $ do
prop "recovers the before term" $ prop "recovers the before term" $
\ a b -> let diff = diffTerms (both a b :: Both (Term Syntax (Record '[Category]))) in \ a b -> let diff = diffTerms a b :: Diff Syntax (Record '[Category]) in
beforeTerm diff `shouldBe` Just a beforeTerm diff `shouldBe` Just a
describe "afterTerm" $ do describe "afterTerm" $ do
prop "recovers the after term" $ prop "recovers the after term" $
\ a b -> let diff = diffTerms (both a b :: Both (Term Syntax (Record '[Category]))) in \ a b -> let diff = diffTerms a b :: Diff Syntax (Record '[Category]) in
afterTerm diff `shouldBe` Just b afterTerm diff `shouldBe` Just b

View File

@ -20,18 +20,17 @@ spec = parallel $ do
it "returns a replacement when comparing two unicode equivalent terms" $ it "returns a replacement when comparing two unicode equivalent terms" $
let termA = Term $ (StringLiteral :. Nil) `In` Leaf "t\776" let termA = Term $ (StringLiteral :. Nil) `In` Leaf "t\776"
termB = Term $ (StringLiteral :. Nil) `In` Leaf "\7831" in termB = Term $ (StringLiteral :. Nil) `In` Leaf "\7831" in
diffTerms (both termA termB) `shouldBe` replacing termA termB diffTerms termA termB `shouldBe` replacing termA termB
prop "produces correct diffs" $ prop "produces correct diffs" $
\ a b -> let diff = diffTerms (unListableF <$> both a b :: Both (Term Syntax (Record '[Category]))) in \ a b -> let diff = diffTerms a b :: Diff Syntax (Record '[Category]) in
(beforeTerm diff, afterTerm diff) `shouldBe` (Just (unListableF a), Just (unListableF b)) (beforeTerm diff, afterTerm diff) `shouldBe` (Just a, Just b)
prop "constructs zero-cost diffs of equal terms" $ prop "constructs zero-cost diffs of equal terms" $
\ a -> let term = (unListableF a :: Term Syntax (Record '[Category])) \ a -> let diff = diffTerms a a :: Diff Syntax (Record '[Category]) in
diff = diffTerms (pure term) in
diffCost diff `shouldBe` 0 diffCost diff `shouldBe` 0
it "produces unbiased insertions within branches" $ it "produces unbiased insertions within branches" $
let term s = Term ((StringLiteral :. Nil) `In` Indexed [ Term ((StringLiteral :. Nil) `In` Leaf s) ]) :: Term Syntax (Record '[Category]) let term s = Term ((StringLiteral :. Nil) `In` Indexed [ Term ((StringLiteral :. Nil) `In` Leaf s) ]) :: Term Syntax (Record '[Category])
root = termIn (Program :. Nil) . Indexed in root = termIn (Program :. Nil) . Indexed in
diffTerms (both (root [ term "b" ]) (root [ term "a", term "b" ])) `shouldBe` merge ((Program :. Nil, Program :. Nil)) (Indexed [ inserting (term "a"), cata (\ (In a r) -> merge (a, a) r) (term "b") ]) diffTerms (root [ term "b" ]) (root [ term "a", term "b" ]) `shouldBe` merge ((Program :. Nil, Program :. Nil)) (Indexed [ inserting (term "a"), cata (\ (In a r) -> merge (a, a) r) (term "b") ])

View File

@ -31,11 +31,11 @@ spec = parallel $ do
describe "diffTermPair" $ do describe "diffTermPair" $ do
it "produces an Insert when the first blob is missing" $ do it "produces an Insert when the first blob is missing" $ do
result <- runTask (diffTermPair (both (emptyBlob "/foo") (sourceBlob "/foo" Nothing "")) (runBothWith replacing) (pure (termIn () []))) result <- runTask (diffTermPair (both (emptyBlob "/foo") (sourceBlob "/foo" Nothing "")) replacing (termIn () []) (termIn () []))
result `shouldBe` Diff (Patch (Insert (In () []))) result `shouldBe` Diff (Patch (Insert (In () [])))
it "produces a Delete when the second blob is missing" $ do it "produces a Delete when the second blob is missing" $ do
result <- runTask (diffTermPair (both (sourceBlob "/foo" Nothing "") (emptyBlob "/foo")) (runBothWith replacing) (pure (termIn () []))) result <- runTask (diffTermPair (both (sourceBlob "/foo" Nothing "") (emptyBlob "/foo")) replacing (termIn () []) (termIn () []))
result `shouldBe` Diff (Patch (Delete (In () []))) result `shouldBe` Diff (Patch (Delete (In () [])))
where where

View File

@ -47,7 +47,7 @@ spec = parallel $ do
\ diff -> let diff' = (diff :: Diff Syntax ()) in entryPayload <$> tableOfContentsBy (const (Just ())) diff' `shouldBe` replicate (diffSize diff') () \ diff -> let diff' = (diff :: Diff Syntax ()) in entryPayload <$> tableOfContentsBy (const (Just ())) diff' `shouldBe` replicate (diffSize diff') ()
prop "produces an unchanged entry for identity diffs" $ prop "produces an unchanged entry for identity diffs" $
\ term -> let term' = (term :: Term Syntax (Record '[Category])) in tableOfContentsBy (Just . termAnnotation) (diffTerms (pure term')) `shouldBe` [Unchanged (lastValue term')] \ term -> tableOfContentsBy (Just . termAnnotation) (diffTerms term term) `shouldBe` [Unchanged (lastValue (term :: Term Syntax (Record '[Category])))]
prop "produces inserted/deleted/replaced entries for relevant nodes within patches" $ prop "produces inserted/deleted/replaced entries for relevant nodes within patches" $
\ patch -> let patch' = (patch :: Patch (Term Syntax Int)) in tableOfContentsBy (Just . termAnnotation) (these deleting inserting replacing (unPatch patch')) `shouldBe` these (fmap Deleted) (fmap Inserted) (const (fmap Replaced)) (unPatch (foldMap pure <$> patch')) \ patch -> let patch' = (patch :: Patch (Term Syntax Int)) in tableOfContentsBy (Just . termAnnotation) (these deleting inserting replacing (unPatch patch')) `shouldBe` these (fmap Deleted) (fmap Inserted) (const (fmap Replaced)) (unPatch (foldMap pure <$> patch'))
@ -132,7 +132,7 @@ spec = parallel $ do
prop "equal terms produce identity diffs" $ prop "equal terms produce identity diffs" $
\a -> let term = defaultFeatureVectorDecorator (Info.category . termAnnotation) (a :: Term') in \a -> let term = defaultFeatureVectorDecorator (Info.category . termAnnotation) (a :: Term') in
diffTOC (diffTerms (pure term)) `shouldBe` [] diffTOC (diffTerms term term) `shouldBe` []
describe "JSONSummary" $ do describe "JSONSummary" $ do
it "encodes modified summaries to JSON" $ do it "encodes modified summaries to JSON" $ do