module Darcs.Patch.Ident ( Ident(..) , SignedIdent , PatchId , (=\^/=) , (=/^\=) , SignedId(..) , StorableId(..) , fastRemoveFL , fastRemoveRL , fastRemoveSubsequenceRL , findCommonFL , findCommonRL , findCommonWithThemFL , findCommonWithThemRL , commuteToPrefix -- * Properties , prop_identInvariantUnderCommute , prop_sameIdentityImpliesCommutable , prop_equalImpliesSameIdentity , prop_sameIdentityImpliesEqual ) where import qualified Data.Set as S import Darcs.Prelude import Darcs.Patch.Commute ( Commute, commute, commuteFL, commuteRL ) import Darcs.Patch.Permutations ( partitionFL', partitionRL' ) import Darcs.Patch.Show ( ShowPatchFor ) import Darcs.Patch.Witnesses.Eq ( Eq2(..), EqCheck(..), isIsEq ) import Darcs.Patch.Witnesses.Ordered ( (:>)(..) , (:\/:)(..) , FL(..) , RL(..) , Fork(..) , (+<<+) , (+>>+) , mapFL , mapRL , reverseFL , reverseRL ) import Darcs.Patch.Witnesses.Unsafe ( unsafeCoercePEnd, unsafeCoercePStart ) import Darcs.Util.Parser ( Parser ) import Darcs.Util.Printer ( Doc ) -- | The reason this is not associated to class 'Ident' is that for technical -- reasons we want to be able to define type instances for patches that don't -- have an identity and therefore cannot be lawful members of class 'Ident'. type family PatchId (p :: * -> * -> *) {- | Class of patches that have an identity/name. Patches with an identity give rise to the notion of /nominal equality/, expressed by the operators '=\^/=' and '=/^\='. Laws: [/ident-commute/] Patch identity must be invariant under commutation: prop> 'commute' (p :> _) == 'Just' (_ :> p') => 'ident' p == 'ident' p' and thus (via symmetry of 'commute'): prop> 'commute' (_ :> q) == 'Just' (q' :> _) => 'ident' q == 'ident' q' Conversely, patches with the same identity result from a series of 'commute's: prop> 'ident' p == 'ident' p' => exists qs, qs' :: FL p. 'commuteFL' (p :> qs) == 'Just' (qs' :> p') [/ident-compare/] In general, comparing patches via their identity is weaker than (semantic) equality: prop> 'unsafeCompare' p q => 'ident' p == 'ident' q However, if the patches have a common context, then semantic and nominal equality should coincide, up to internal re-ordering: prop> p '=\~/=' q <=> p '=\^/=' q prop> p '=/~\=' q <=> p '=/^\=' q (Technical note: equality up to internal re-ordering is currently only defined for 'FL's, but it should be obvious how to generalize it.) Taken together, these laws express the assumption that recording a patch gives it a universally unique identity. Note that violations of this universal property are currently not detected in a reliable way. Fixing this is possible but far from easy. -} class Ord (PatchId p) => Ident p where ident :: p wX wY -> PatchId p type instance PatchId (FL p) = S.Set (PatchId p) type instance PatchId (RL p) = S.Set (PatchId p) type instance PatchId (p :> p) = S.Set (PatchId p) instance Ident p => Ident (FL p) where ident = S.fromList . mapFL ident instance Ident p => Ident (RL p) where ident = S.fromList . mapRL ident instance Ident p => Ident (p :> p) where ident (p :> q) = S.fromList [ident p, ident q] -- | Nominal equality for patches with an identity in the same context. Usually -- quite a bit faster than structural equality. (=\^/=) :: Ident p => p wA wB -> p wA wC -> EqCheck wB wC p =\^/= q = if ident p == ident q then unsafeCoercePEnd IsEq else NotEq (=/^\=) :: Ident p => p wA wC -> p wB wC -> EqCheck wA wB p =/^\= q = if ident p == ident q then unsafeCoercePStart IsEq else NotEq {- | Signed identities. Like for class 'Invert', we require that 'invertId' is self-inverse: prop> 'invertId' . 'invertId' = 'id' We also require that inverting changes the sign: prop> 'positiveId' . 'invertId' = 'not' . 'positiveId' Side remark: in mathematical terms, these properties can be expressed by stating that 'invertId' is an involution and that 'positiveId' is a "homomorphism of sets with an involution" (there is no official term for this) from @a@ to the simplest non-trivial set with involution, namely 'Bool' with the involution 'not'. -} class Ord a => SignedId a where positiveId :: a -> Bool invertId :: a -> a {- | Constraint for patches that have an identity that is signed, i.e. can be positive (uninverted) or negative (inverted). Provided that an instance 'Invert' exists, inverting a patch inverts its identity: prop> 'ident' ('invert' p) = 'invertId' ('ident' p) -} type SignedIdent p = (Ident p, SignedId (PatchId p)) {- | Storable identities. The methods here can be used to help implement ReadPatch and ShowPatch for a patch type containing the identity. As with all Read/Show pairs, We expect that the output of @showId ForStorage x@ can be parsed by 'readId' to produce @x@: prop> 'parse' 'readId' . 'renderPS' . 'showId' 'ForStorage' == 'id' -} class StorableId a where readId :: Parser a showId :: ShowPatchFor -> a -> Doc {-# INLINABLE fastRemoveFL #-} -- | Remove a patch from an FL of patches with an identity. The result is -- 'Just' whenever the patch has been found and removed and 'Nothing' -- otherwise. If the patch is not found at the head of the sequence we must -- first commute it to the head before we can remove it. -- -- We assume that this commute always succeeds. This is justified because -- patches are created with a (universally) unique identity, implying that if -- two patches have the same identity, then they have originally been the same -- patch; thus being at a different position must be due to commutation, -- meaning we can commute it back. -- -- For patch types that define semantic equality via nominal equality, this is -- only faster than 'removeFL' if the patch does not occur in the sequence, -- otherwise we have to perform the same number of commutations. fastRemoveFL :: forall p wX wY wZ. (Commute p, Ident p) => p wX wY -> FL p wX wZ -> Maybe (FL p wY wZ) fastRemoveFL a bs | i `notElem` mapFL ident bs = Nothing | otherwise = do _ :> bs' <- pullout NilRL bs Just (unsafeCoercePStart bs') where i = ident a pullout :: RL p wA wB -> FL p wB wC -> Maybe ((p :> FL p) wA wC) pullout _ NilFL = Nothing pullout acc (x :>: xs) | ident x == i = do x' :> acc' <- commuteRL (acc :> x) Just (x' :> acc' +>>+ xs) | otherwise = pullout (acc :<: x) xs -- | Same as 'fastRemoveFL' only for 'RL'. fastRemoveRL :: forall p wX wY wZ. (Commute p, Ident p) => p wY wZ -> RL p wX wZ -> Maybe (RL p wX wY) fastRemoveRL a bs | i `notElem` mapRL ident bs = Nothing | otherwise = do bs' :> _ <- pullout bs NilFL Just (unsafeCoercePEnd bs') where i = ident a pullout :: RL p wA wB -> FL p wB wC -> Maybe ((RL p :> p) wA wC) pullout NilRL _ = Nothing pullout (xs :<: x) acc | ident x == i = do acc' :> x' <- commuteFL (x :> acc) Just (xs +<<+ acc' :> x') | otherwise = pullout xs (x :>: acc) fastRemoveSubsequenceRL :: (Commute p, Ident p) => RL p wY wZ -> RL p wX wZ -> Maybe (RL p wX wY) fastRemoveSubsequenceRL NilRL ys = Just ys fastRemoveSubsequenceRL (xs :<: x) ys = fastRemoveRL x ys >>= fastRemoveSubsequenceRL xs -- | Find the common and uncommon parts of two lists that start in a common -- context, using patch identity for comparison. Of the common patches, only -- one is retained, the other is discarded. findCommonFL :: (Commute p, Ident p) => FL p wX wY -> FL p wX wZ -> Fork (FL p) (FL p) (FL p) wX wY wZ findCommonFL xs ys = case findCommonWithThemFL xs ys of cxs :> xs' -> case findCommonWithThemFL ys xs of cys :> ys' -> case cxs =\^/= cys of NotEq -> error "common patches aren't equal" IsEq -> Fork cxs xs' ys' findCommonWithThemFL :: (Commute p, Ident p) => FL p wX wY -> FL p wX wZ -> (FL p :> FL p) wX wY findCommonWithThemFL xs ys = case partitionFL' ((`S.member` yids) . ident) NilRL NilRL xs of cxs :> NilRL :> xs' -> cxs :> reverseRL xs' _ -> error "failed to commute common patches" where yids = S.fromList (mapFL ident ys) findCommonRL :: (Commute p, Ident p) => RL p wX wY -> RL p wX wZ -> Fork (RL p) (RL p) (RL p) wX wY wZ findCommonRL xs ys = case findCommonWithThemRL xs ys of cxs :> xs' -> case findCommonWithThemRL ys xs of cys :> ys' -> case cxs =\^/= cys of NotEq -> error "common patches aren't equal" IsEq -> Fork cxs xs' ys' findCommonWithThemRL :: (Commute p, Ident p) => RL p wX wY -> RL p wX wZ -> (RL p :> RL p) wX wY findCommonWithThemRL xs ys = case partitionRL' (not . (`S.member` yids) . ident) xs of cxs :> NilFL :> xs' -> reverseFL cxs :> xs' _ -> error "failed to commute common patches" where yids = S.fromList (mapRL ident ys) -- | Try to commute all patches matching any of the 'PatchId's in the set to the -- head of an 'FL', i.e. backwards in history. commuteToPrefix :: (Commute p, Ident p) => S.Set (PatchId p) -> FL p wX wY -> Maybe ((FL p :> RL p) wX wY) commuteToPrefix is ps | prefix :> NilRL :> rest <- partitionFL' ((`S.member` is) . ident) NilRL NilRL ps = Just (prefix :> rest) | otherwise = Nothing prop_identInvariantUnderCommute :: (Commute p, Ident p) => (p :> p) wX wY -> Maybe Bool prop_identInvariantUnderCommute (p :> q) = case commute (p :> q) of Just (q' :> p') -> Just $ ident p == ident p' && ident q == ident q' Nothing -> Nothing prop_sameIdentityImpliesCommutable :: (Commute p, Eq2 p, Ident p) => (p :\/: (RL p :> p)) wX wY -> Maybe Bool prop_sameIdentityImpliesCommutable (p :\/: (ps :> q)) | ident p == ident q = case commuteRL (ps :> q) of Just (p' :> _) -> Just $ isIsEq (p =\/= p') Nothing -> Just False | otherwise = Nothing prop_equalImpliesSameIdentity :: (Eq2 p, Ident p) => p wA wB -> p wC wD -> Maybe Bool prop_equalImpliesSameIdentity p q | p `unsafeCompare` q = Just $ ident p == ident q | otherwise = Nothing -- Note the assumption of coinciding start states here! prop_sameIdentityImpliesEqual :: (Eq2 p, Ident p) => (p :\/: p) wX wY -> Maybe Bool prop_sameIdentityImpliesEqual (p :\/: q) | ident p == ident q = Just $ isIsEq $ p =\/= q | otherwise = Nothing