2017-09-18 07:23:14 +03:00
{- # LANGUAGE DataKinds, DefaultSignatures, GADTs, RankNTypes, TypeOperators, UndecidableInstances # -}
2015-11-18 03:20:52 +03:00
module Algorithm where
2015-11-18 03:24:01 +03:00
2017-09-19 17:48:26 +03:00
import Control.Applicative ( Alternative ( .. ) )
import Control.Monad ( guard )
2017-09-09 13:23:57 +03:00
import Control.Monad.Free.Freer
2017-09-27 19:41:41 +03:00
import Data.Diff
2017-06-13 21:49:22 +03:00
import Data.Functor.Classes
2017-08-14 23:42:13 +03:00
import Data.List.NonEmpty ( NonEmpty ( .. ) )
2017-07-28 21:37:02 +03:00
import Data.Maybe
2017-08-05 01:49:21 +03:00
import Data.Proxy
2017-09-27 19:37:37 +03:00
import Data.Term
2017-02-23 20:25:45 +03:00
import Data.These
2017-07-21 00:15:01 +03:00
import Data.Union
import GHC.Generics
2016-08-04 01:12:31 +03:00
2017-02-21 18:26:27 +03:00
-- | A single step in a diffing algorithm, parameterized by the types of terms, diffs, and the result of the applicable algorithm.
2017-09-23 05:24:50 +03:00
data AlgorithmF term1 term2 result partial where
2017-02-23 20:24:31 +03:00
-- | Diff two terms with the choice of algorithm left to the interpreter’ s discretion.
2017-09-23 05:24:50 +03:00
Diff :: term1 -> term2 -> AlgorithmF term1 term2 result result
2017-02-21 18:38:10 +03:00
-- | Diff two terms recursively in O(n) time, resulting in a single diff node.
2017-09-23 06:07:41 +03:00
Linear :: term1 -> term2 -> AlgorithmF term1 term2 result result
2017-02-21 18:38:10 +03:00
-- | Diff two lists of terms by each element’ s similarity in O(n³ log n), resulting in a list of diffs.
2017-09-23 05:24:50 +03:00
RWS :: [ term1 ] -> [ term2 ] -> AlgorithmF term1 term2 result [ result ]
2017-09-19 17:02:26 +03:00
-- | Delete a term.
2017-09-23 05:24:50 +03:00
Delete :: term1 -> AlgorithmF term1 term2 result result
2017-02-21 19:40:13 +03:00
-- | Insert a term.
2017-09-23 05:24:50 +03:00
Insert :: term2 -> AlgorithmF term1 term2 result result
2017-02-21 19:40:13 +03:00
-- | Replace one term with another.
2017-09-23 05:24:50 +03:00
Replace :: term1 -> term2 -> AlgorithmF term1 term2 result result
2017-09-19 14:21:31 +03:00
-- | An 'Algorithm' that always fails.
2017-09-23 05:24:50 +03:00
Empty :: AlgorithmF term1 term2 result a
2017-09-19 14:21:31 +03:00
-- | An 'Algorithm' to try one of two alternatives.
2017-09-23 05:24:50 +03:00
Alt :: a -> a -> AlgorithmF term1 term2 result a
2017-09-19 14:20:54 +03:00
2017-09-19 17:52:47 +03:00
-- | The free(r) monad for 'AlgorithmF'. This enables us to construct algorithms to diff using '<$>', '<*>', '>>=', and do-notation.
2017-09-23 05:24:50 +03:00
type Algorithm term1 term2 result = Freer ( AlgorithmF term1 term2 result )
2017-02-21 18:44:48 +03:00
2016-08-04 19:08:34 +03:00
-- DSL
2017-02-23 20:24:31 +03:00
-- | Diff two terms without specifying the algorithm to be used.
2017-09-23 05:24:50 +03:00
diff :: term1 -> term2 -> Algorithm term1 term2 result result
2017-09-09 13:23:57 +03:00
diff = ( liftF . ) . Algorithm . Diff
2017-02-23 20:24:31 +03:00
2017-02-23 20:30:26 +03:00
-- | Diff a These of terms without specifying the algorithm to be used.
2017-09-23 05:24:50 +03:00
diffThese :: These term1 term2 -> Algorithm term1 term2 result result
2017-02-23 20:25:45 +03:00
diffThese = these byDeleting byInserting diff
2017-05-03 23:56:15 +03:00
-- | Diff a pair of optional terms without specifying the algorithm to be used.
2017-09-23 05:24:50 +03:00
diffMaybe :: Maybe term1 -> Maybe term2 -> Algorithm term1 term2 result ( Maybe result )
2017-09-19 19:51:25 +03:00
diffMaybe ( Just a ) ( Just b ) = Just <$> diff a b
diffMaybe ( Just a ) _ = Just <$> byDeleting a
diffMaybe _ ( Just b ) = Just <$> byInserting b
diffMaybe _ _ = pure Nothing
2017-05-03 23:56:15 +03:00
2017-02-21 19:39:41 +03:00
-- | Diff two terms linearly.
2017-09-23 06:07:41 +03:00
linearly :: term1 -> term2 -> Algorithm term1 term2 result result
2017-09-23 05:24:50 +03:00
linearly f1 f2 = liftF ( Linear f1 f2 )
2016-08-04 01:38:35 +03:00
2017-02-21 19:39:41 +03:00
-- | Diff two terms using RWS.
2017-09-23 05:24:50 +03:00
byRWS :: [ term1 ] -> [ term2 ] -> Algorithm term1 term2 result [ result ]
2017-02-24 21:28:44 +03:00
byRWS a b = liftF ( RWS a b )
2017-02-21 19:40:13 +03:00
-- | Delete a term.
2017-09-23 05:24:50 +03:00
byDeleting :: term1 -> Algorithm term1 term2 result result
2017-02-24 21:28:44 +03:00
byDeleting = liftF . Delete
2017-02-21 19:40:13 +03:00
-- | Insert a term.
2017-09-23 05:24:50 +03:00
byInserting :: term2 -> Algorithm term1 term2 result result
2017-02-24 21:28:44 +03:00
byInserting = liftF . Insert
2017-02-21 19:40:13 +03:00
-- | Replace one term with another.
2017-09-23 05:24:50 +03:00
byReplacing :: term1 -> term2 -> Algorithm term1 term2 result result
2017-02-24 21:28:44 +03:00
byReplacing = ( liftF . ) . Replace
2017-06-13 21:49:22 +03:00
2017-09-23 05:24:50 +03:00
instance ( Show term1 , Show term2 ) => Show1 ( AlgorithmF term1 term2 result ) where
2017-09-19 14:20:54 +03:00
liftShowsPrec sp _ d algorithm = case algorithm of
2017-09-23 05:24:50 +03:00
Algorithm . Diff t1 t2 -> showsBinaryWith showsPrec showsPrec " Diff " d t1 t2
2017-09-23 06:07:41 +03:00
Linear t1 t2 -> showsBinaryWith showsPrec showsPrec " Linear " d t1 t2
2017-09-23 05:24:50 +03:00
RWS as bs -> showsBinaryWith ( liftShowsPrec showsPrec showList ) ( liftShowsPrec showsPrec showList ) " RWS " d as bs
Delete t1 -> showsUnaryWith showsPrec " Delete " d t1
Insert t2 -> showsUnaryWith showsPrec " Insert " d t2
Replace t1 t2 -> showsBinaryWith showsPrec showsPrec " Replace " d t1 t2
2017-09-19 14:20:54 +03:00
Empty -> showString " Empty "
Alt a b -> showsBinaryWith sp sp " Alt " d a b
2017-07-21 00:15:01 +03:00
2017-09-23 05:24:50 +03:00
instance Alternative ( Algorithm term1 term2 result ) where
2017-09-19 14:20:54 +03:00
empty = Empty ` Then ` return
2017-09-19 15:23:33 +03:00
( Empty ` Then ` _ ) <|> b = b
a <|> ( Empty ` Then ` _ ) = a
2017-09-19 14:20:54 +03:00
a <|> b = Alt a b ` Then ` id
2017-07-21 03:21:20 +03:00
-- | Diff two terms based on their generic Diffable instances. If the terms are not diffable
-- (represented by a Nothing diff returned from algorithmFor) replace one term with another.
2017-09-14 22:47:37 +03:00
algorithmForTerms :: Diffable syntax
2017-09-14 16:41:52 +03:00
=> Term syntax ann1
-> Term syntax ann2
2017-09-23 05:24:50 +03:00
-> Algorithm ( Term syntax ann1 ) ( Term syntax ann2 ) ( Diff syntax ann1 ann2 ) ( Diff syntax ann1 ann2 )
2017-09-19 21:21:37 +03:00
algorithmForTerms t1 @ ( Term ( In ann1 f1 ) ) t2 @ ( Term ( In ann2 f2 ) )
2017-09-19 23:13:23 +03:00
= mergeFor t1 t2
2017-09-23 16:12:58 +03:00
<|> deleteF . In ann1 <$> subalgorithmFor byDeleting ( flip mergeFor t2 ) f1
<|> insertF . In ann2 <$> subalgorithmFor byInserting ( mergeFor t1 ) f2
2017-09-23 16:12:19 +03:00
where mergeFor ( Term ( In ann1 f1 ) ) ( Term ( In ann2 f2 ) ) = merge ( ann1 , ann2 ) <$> algorithmFor f1 f2
2017-07-21 03:21:20 +03:00
-- | A type class for determining what algorithm to use for diffing two terms.
2017-07-21 00:15:01 +03:00
class Diffable f where
2017-09-26 20:03:44 +03:00
-- | Construct an algorithm to diff a pair of @f@s populated with disjoint terms.
2017-09-23 05:24:50 +03:00
algorithmFor :: f term1
-> f term2
-> Algorithm term1 term2 result ( f result )
2017-09-15 01:04:54 +03:00
default
algorithmFor :: ( Generic1 f , GDiffable ( Rep1 f ) )
2017-09-23 05:24:50 +03:00
=> f term1
-> f term2
-> Algorithm term1 term2 result ( f result )
2017-09-13 18:48:51 +03:00
algorithmFor = genericAlgorithmFor
2017-09-26 20:03:49 +03:00
-- | Construct an algorithm to diff against positions inside an @f@.
--
-- This is very like 'traverse', with two key differences:
--
-- 1. The traversal distributes through an 'Alternative' functor, not just an 'Applicative'.
-- 2. The traversal is mediated by two different functions, one for positions which should be ignored for substructural diffing, the other for positions which should be diffed substructurally.
--
-- These two functions allow us to say e.g. that comparisons against 'Data.Syntax.Context' should also be made against its subject, but not against any of the comments, resulting in the insertion of both comments and context when documenting an existing function.
--
-- By default, 'subalgorithmFor' produces 'empty', rejecting substructural comparisons. This is important for performance, as alternations with 'empty' are eliminated at construction time.
subalgorithmFor :: Alternative g -- ^ The 'Alternative' instance will in general be 'Algorithm', but left opaque to make it harder to shoot oneself in the foot.
=> ( a -> g b ) -- ^ A “blur” function to traverse positions which should not be diffed against.
-> ( a -> g b ) -- ^ A “focus” function to traverse positions which should be diffed against.
-> f a -- ^ The syntax to diff inside of.
-> g ( f b ) -- ^ The resulting algorithm (or other 'Alternative' context), producing the traversed syntax.
2017-09-19 21:02:08 +03:00
subalgorithmFor _ _ _ = empty
2017-09-25 19:24:42 +03:00
genericAlgorithmFor :: ( Generic1 f , GDiffable ( Rep1 f ) )
=> f term1
-> f term2
-> Algorithm term1 term2 result ( f result )
2017-09-19 17:48:26 +03:00
genericAlgorithmFor a b = to1 <$> galgorithmFor ( from1 a ) ( from1 b )
2017-09-13 18:48:51 +03:00
2017-07-21 00:15:01 +03:00
2017-07-21 03:25:27 +03:00
-- | Diff a Union of Syntax terms. Left is the "rest" of the Syntax terms in the Union,
2017-07-21 20:50:53 +03:00
-- Right is the "head" of the Union. 'weaken' relaxes the Union to allow the possible
-- diff terms from the "rest" of the Union, and 'inj' adds the diff terms into the Union.
2017-07-21 03:21:20 +03:00
-- NB: If Left or Right Syntax terms in our Union don't match, we fail fast by returning Nothing.
2017-09-18 07:23:14 +03:00
instance Apply Diffable fs => Diffable ( Union fs ) where
2017-09-19 17:48:26 +03:00
algorithmFor u1 u2 = fromMaybe empty ( apply2' ( Proxy :: Proxy Diffable ) ( \ inj f1 f2 -> inj <$> algorithmFor f1 f2 ) u1 u2 )
2017-07-21 00:15:01 +03:00
2017-09-19 21:02:08 +03:00
subalgorithmFor blur focus = apply' ( Proxy :: Proxy Diffable ) ( \ inj f1 -> inj <$> subalgorithmFor blur focus f1 )
2017-09-19 19:49:34 +03:00
-- | Diff two 'Maybe's.
instance Diffable Maybe where
2017-09-19 19:51:25 +03:00
algorithmFor = diffMaybe
2017-09-19 19:49:34 +03:00
2017-09-19 19:46:15 +03:00
-- | Diff two lists using RWS.
2017-07-21 20:50:24 +03:00
instance Diffable [] where
2017-09-25 16:10:44 +03:00
algorithmFor = byRWS
2017-07-21 20:50:24 +03:00
2017-09-19 19:46:24 +03:00
-- | Diff two non-empty lists using RWS.
instance Diffable NonEmpty where
algorithmFor ( a :| as ) ( b :| bs ) = ( \ ( d : ds ) -> d :| ds ) <$> byRWS ( a : as ) ( b : bs )
2017-07-21 03:21:20 +03:00
-- | A generic type class for diffing two terms defined by the Generic1 interface.
2017-09-14 22:09:30 +03:00
class GDiffable f where
2017-09-23 05:24:50 +03:00
galgorithmFor :: f term1 -> f term2 -> Algorithm term1 term2 result ( f result )
2017-07-21 00:15:01 +03:00
2017-07-21 20:50:53 +03:00
-- | Diff two constructors (M1 is the Generic1 newtype for meta-information (possibly related to type constructors, record selectors, and data types))
2017-09-14 22:09:30 +03:00
instance GDiffable f => GDiffable ( M1 i c f ) where
2017-09-19 17:48:26 +03:00
galgorithmFor ( M1 a ) ( M1 b ) = M1 <$> galgorithmFor a b
2017-07-21 00:15:01 +03:00
2017-07-21 20:50:53 +03:00
-- | Diff the fields of a product type.
-- i.e. data Foo a b = Foo a b (the 'Foo a b' is captured by 'a :*: b').
2017-09-14 22:09:30 +03:00
instance ( GDiffable f , GDiffable g ) => GDiffable ( f :*: g ) where
2017-09-19 17:48:26 +03:00
galgorithmFor ( a1 :*: b1 ) ( a2 :*: b2 ) = ( :*: ) <$> galgorithmFor a1 a2 <*> galgorithmFor b1 b2
2017-07-21 00:15:01 +03:00
2017-07-21 20:50:53 +03:00
-- | Diff the constructors of a sum type.
-- i.e. data Foo a = Foo a | Bar a (the 'Foo a' is captured by L1 and 'Bar a' is R1).
2017-09-14 22:09:30 +03:00
instance ( GDiffable f , GDiffable g ) => GDiffable ( f :+: g ) where
2017-09-19 17:48:26 +03:00
galgorithmFor ( L1 a ) ( L1 b ) = L1 <$> galgorithmFor a b
galgorithmFor ( R1 a ) ( R1 b ) = R1 <$> galgorithmFor a b
galgorithmFor _ _ = empty
2017-07-21 00:15:01 +03:00
2017-07-21 03:21:20 +03:00
-- | Diff two parameters (Par1 is the Generic1 newtype representing a type parameter).
2017-07-21 20:50:53 +03:00
-- i.e. data Foo a = Foo a (the 'a' is captured by Par1).
2017-09-14 22:09:30 +03:00
instance GDiffable Par1 where
2017-09-23 05:24:50 +03:00
galgorithmFor ( Par1 a ) ( Par1 b ) = Par1 <$> diff a b
2017-07-21 00:15:01 +03:00
2017-07-21 03:21:20 +03:00
-- | Diff two constant parameters (K1 is the Generic1 newtype representing type parameter constants).
2017-07-21 20:50:53 +03:00
-- i.e. data Foo = Foo Int (the 'Int' is a constant parameter).
2017-09-14 22:09:30 +03:00
instance Eq c => GDiffable ( K1 i c ) where
2017-09-19 17:48:26 +03:00
galgorithmFor ( K1 a ) ( K1 b ) = guard ( a == b ) *> pure ( K1 a )
2017-07-21 00:15:01 +03:00
2017-07-21 03:21:20 +03:00
-- | Diff two terms whose constructors contain 0 type parameters.
-- i.e. data Foo = Foo.
2017-09-14 22:09:30 +03:00
instance GDiffable U1 where
2017-09-19 17:48:26 +03:00
galgorithmFor _ _ = pure U1
2017-07-21 00:15:01 +03:00
2017-09-19 19:46:40 +03:00
-- | Diff two 'Diffable' containers of parameters.
instance Diffable f => GDiffable ( Rec1 f ) where
galgorithmFor a b = Rec1 <$> algorithmFor ( unRec1 a ) ( unRec1 b )