gdiff (empty) → 1.0
raw patch · 4 files changed
+582/−0 lines, 4 filesdep +basesetup-changed
Dependencies added: base
Files
- Data/Generic/Diff.hs +533/−0
- LICENSE +27/−0
- Setup.lhs +3/−0
- gdiff.cabal +19/−0
+ Data/Generic/Diff.hs view
@@ -0,0 +1,533 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE OverlappingInstances #-} -- Only for the Show++{- |++Use this library to get an efficient, optimal, type-safe 'diff' and+'patch' function for your datatypes of choice by defining a simple GADT and+some class instances. The process is explained in the documentation of the+'Family' type class.++The result of 'diff' is an optimal 'EditScript' that contains the operations+that need to be performed to get from the /source/ value to the /destination/+value. The edit script can be used by 'patch', inspected with 'show' and used+for all kinds of interesting stuff /you/ come up with.++The library has been extracted from Eelco Lempsink's Master's Thesis +/Generic type-safe diff and patch for families of datatypes/ (available online:+<http://eelco.lempsink.nl/thesis.pdf>). See Appendix C for a large example.+Note that some types and functions have been renamed for the benefit of the API+(e.g., @diff@ to @diffL@, @diff1@ to @diff@, @Diff@ to @EditScriptL@).++-}+module Data.Generic.Diff (+ -- * Diffing and patchLing+ diff,+ patch, + -- ** For multiple datatypes + diffL,+ patchL, + -- ** Patch compression+ compress,+ -- * Patch datatype+ EditScriptL(..),+ EditScript,+ -- * Type classes+ Family(..),+ Type(..),+ -- ** Supporting datatypes + (:=:)(..),+ Con(..),+ Nil(..), + Cons(..),+) where++-- | Edit script type for two single values.+type EditScript f x y = EditScriptL f (Cons x Nil) (Cons y Nil)++-- | Apply the edit script to value.+patch :: EditScript f x y -> x -> y+patch d x = case patchL d (CCons x CNil) of+ CCons y CNil -> y++-- | Calculate the difference between two values in the form of an edit script.+--+-- See the documentation for 'Family' for how to make your own data types work+-- with this function.+diff :: (Type f x, Type f y) => x -> y -> EditScript f x y+diff x y = diffL (CCons x CNil) (CCons y CNil)++-- | Underlying implementation of 'patch', works with (heterogeneous) lists of+-- values.+patchL :: forall f txs tys . EditScriptL f txs tys -> txs -> tys+patchL (Ins c d) = insert c . patchL d+patchL (Del c d) = patchL d . delete c+patchL (Cpy c d) = insert c . patchL d . delete c+patchL (CpyTree d) = \(CCons x xs) -> CCons x . patchL d $ xs ++-- | Underlying implementation of 'diff', works with (heterogeneous) lists of+-- values.+diffL :: (Family f, List f txs, List f tys) => txs -> tys -> EditScriptL f txs tys+diffL x y = getEditScriptL $ diffLMemo x y+++-- | Datatype for type level lists, corresponding to '[]'. Use when creating+-- your 'Family' instance.+data Nil = CNil+-- | Datatype for type level lists, corresponding to '(:)'. Use when creating+-- your 'Family' instance.+data Cons x xs = CCons x xs++{- | ++To use 'diff' and 'patch' on your datatypes, you must create an instance of+'Family'. ++There are four steps to set up everything for 'diff' and 'patch'.++(1) Define your datatypes. (Presumable, you already have done this.)++(2) Create a family datatype, grouping your datatypes together.++(3) Make the family datatype an instance of 'Family'++(4) Create 'Type' instances for each member of the family.++Steps 1-3 are explained below, step 4 is explained in the documentation for+'Type'.++As a running example, we create a simple family of datatypes (step 1):++> data Expr = Min Expr Term+> data Term = Parens Expr+> | Number Int++The second step is creating the family datatype. Each constructor in the+datatypes above gets a constructor in a family GADT:++> data ExprTermFamily :: * -> * -> * where+> Min' :: ExprTermFamily Expr (Cons Expr (Cons Term Nil)) +> Parens' :: ExprTermFamily Term (Cons Expr Nil)+> Number' :: ExprTermFamily Term (Cons Int Nil)+> Int' :: Int -> ExprTermFamily Int Nil++The first type argument of the datatype must be the resulting type of the+constructor. The second argument captures the types of the arguments the+constructor expects. Note how to use 'Cons' and 'Nil' to create type level+lists.++The @Int'@ constructor is special, in the sense that it captures the 'Int' type+abstractly (listing all 'Int''s constructors would be quite impossible).++/Caveat emptor/: polymorphic datatypes (like lists) are ill-supported and+require family constructors for each instance. It might require another master+thesis project to solve this.++Step 3 is to create the instance for the 'Family' class. For each function you+will have to implement four functions. It's straightforward albeit a bit+boring.++> instance Family ExprTermFamily where+> decEq Min' Min' = Just (Refl, Refl)+> decEq Parens' Parens' = Just (Refl, Refl)+> decEq Number' Number' = Just (Refl, Refl)+> decEq (Int' x) (Int' y) | x == y = Just (Refl, Refl)+> | otherwise = Nothing+> decEq _ _ = Nothing+> +> fields Min' (Min e t) = Just (CCons e (CCons t CNil))+> fields Parens' (Parens e) = Just (CCons e CNil)+> fields Number' (Number i) = Just (CCons i CNil)+> fields (Int' _) _ = Just CNil+> fields _ _ = Nothing+> +> apply Min' (CCons e (CCons t CNil)) = Min e t+> apply Parens' (CCons e CNil) = Parens e+> apply Number' (CCons i CNil) = Number i+> apply (Int' i) CNil = i+> +> string Min' = "Min"+> string Parens' = "Parens"+> string Number' = "Number"+> string (Int' i) = show i++-}++class Family f where+ -- | Return an instance of the equality GADT ('Refl') of the type and+ -- constructor arguments are equal, else return 'Nothing'.+ decEq :: f tx txs -> f ty tys -> Maybe (tx :=: ty, txs :=: tys)+ -- | When the constructor from the family matches the 'real' constructor,+ -- return the arguments in a heterogeneous list, else return 'Nothing'.+ fields :: f t ts -> t -> Maybe ts+ -- | When the constructor from the family and the heterogeneous list of+ -- arguments match, apply the 'real' constructor. For abstract+ -- constructors, the list of arguments should be 'CNil', but you project+ -- out the value saved with the family constructor.+ apply :: f t ts -> ts -> t+ -- | For 'show'ing the constructors from the family.+ string :: f t ts -> String++-- | Equality GADT, see the documentation for 'Family' for an example of its use.+data a :=: b where+ Refl :: a :=: a++{- |++For each type in the family, you need to create an instance of 'Type', listing+all the members of the family GADT which belong to one type.++This is step 4 of the four steps needed to be able to use 'diff' and 'patch'.+Steps 1-3 are explained in the documentation for 'Family'.++Continuing the example from the 'Family' documentation, the instances for+'Type' are:++> instance Type ExprTermFamily Term where+> constructors = [Concr Number', Concr Parens']+> +> instance Type ExprTermFamily Expr where+> constructors = [Concr Min']+> +> instance Type ExprTermFamily Int where+> constructors = [Abstr Int']++-} ++class (Family f) => Type f t where+ -- | List of constructors from the family GADT for one type in your family+ constructors :: [Con f t]++-- | 'Con' wraps both concrete and abstract constructors to a simple type so+-- constructors for a single type can be put together in a list, see 'Type' for+-- more information and an example. +--+-- Use 'Concr' for concrete constructors (e.g., for simple abstract datatypes).+--+-- Use 'Abstr' for abstract constructors (e.g., for build-in types or types with many+-- (or infinite) constructors)+data Con :: (* -> * -> *) -> * -> * where+ Concr :: (List f ts) => f t ts -> Con f t+ Abstr :: (Eq t, List f ts) => (t -> f t ts) -> Con f t++class List f ts where+ list :: IsList f ts++data IsList :: (* -> * -> *) -> * -> * where+ IsNil :: IsList f Nil+ IsCons :: (Type f t) => IsList f ts -> IsList f (Cons t ts)++instance List f Nil where+ list = IsNil++instance (Type f t, List f ts) => List f (Cons t ts) where+ list = IsCons list++{- |++The 'EditScriptL' datatype captures the result of 'diffL' and can be used as the input+to 'patchL' (and 'compress').++The 'diffL' function use a depth-first preorder traversal to traverse the+expression trees. The edit script it outputs contains the operation that must+be applied to the constructor at that point: either keeping it ('Cpy'),+removing it ('Del', which does not remove the complete subtree, but /contracts/+the edge of the removed node) or inserting a new constructor ('Ins', which can+only be done if the available subtrees at that point correspond to the types+the constructor expects). (The 'CpyTree' is only used when running 'compress'+over an existing edit script.)++For more information about this datatype, you're advised to look at Eelco+Lempsink's thesis at <http://eelco.lempsink.nl/thesis.pdf>.++-}+data EditScriptL :: (* -> * -> *) -> * -> * -> * where+ Ins :: (Type f t, List f ts, List f tys) => f t ts ->+ EditScriptL f txs (Append ts tys) ->+ EditScriptL f txs (Cons t tys)+ Del :: (Type f t, List f ts, List f txs) => f t ts ->+ EditScriptL f (Append ts txs) tys ->+ EditScriptL f (Cons t txs) tys+ Cpy :: (Type f t, List f ts, List f txs, List f tys) => f t ts ->+ EditScriptL f (Append ts txs) (Append ts tys) ->+ EditScriptL f (Cons t txs) (Cons t tys)++ CpyTree :: (Type f t, List f txs, List f tys) =>+ EditScriptL f txs tys -> + EditScriptL f (Cons t txs) (Cons t tys)++ End :: EditScriptL f Nil Nil++type family Append txs tys :: *+type instance Append Nil tys = tys+type instance Append (Cons tx txs) tys = Cons tx (Append txs tys)++appendList :: IsList f txs -> IsList f tys -> IsList f (Append txs tys)+appendList IsNil isys = isys+appendList (IsCons isxs) isys = IsCons (appendList isxs isys)++append :: IsList f txs -> IsList f tys -> txs -> tys -> Append txs tys+append IsNil _ CNil ys = ys+append (IsCons isxs) isys (CCons x xs) ys = CCons x (append isxs isys xs ys)++instance Show (EditScriptL f txs tys) where+ show (Ins c d) = "Ins " ++ string c ++ " $ " ++ show d+ show (Del c d) = "Del " ++ string c ++ " $ " ++ show d+ show (Cpy c d) = "Cpy " ++ string c ++ " $ " ++ show d+ show (CpyTree d) = "CpyTree" ++ " $ " ++ show d+ show End = "End"++delete :: (Type f t, List f ts, List f txs) => f t ts -> Cons t txs -> Append ts txs+delete c (CCons x xs) =+ case fields c x of+ Nothing -> error "Patching failed"+ Just ts -> append (isList c) list ts xs++isList :: (Family f, List f ts) => f t ts -> IsList f ts+isList _ = list++insert :: (Type f t, List f ts, List f txs) => f t ts -> Append ts txs -> Cons t txs+insert c xs = CCons (apply c txs) tys+ where (txs, tys) = split (isList c) xs++split :: IsList f txs -> Append txs tys -> (txs, tys)+split IsNil ys = (CNil, ys)+split (IsCons isxs) (CCons x xsys) = let (xs, ys) = split isxs xsys+ in (CCons x xs, ys)++matchConstructor :: (Type f t) => t ->+ (forall ts. f t ts -> IsList f ts -> ts -> r) -> r+matchConstructor = tryEach constructors+ where+ tryEach :: (Type f t) => [Con f t] -> t -> + (forall ts. f t ts -> IsList f ts -> ts -> r) -> r+ tryEach (Concr c : cs) x k = matchOrRetry c cs x k+ tryEach (Abstr c : cs) x k = matchOrRetry (c x) cs x k+ tryEach [] _ _ = error "Incorrect Family or Type instance."++ matchOrRetry :: (List f ts, Type f t) => f t ts -> [Con f t] -> t -> + (forall ts'. f t ts' -> IsList f ts' -> ts' -> r) -> r+ matchOrRetry c cs x k = case fields c x of+ Just ts -> k c (isList c) ts+ Nothing -> tryEach cs x k++best :: EditScriptL f txs tys -> EditScriptL f txs tys -> EditScriptL f txs tys+best dx dy = bestSteps (steps dx) dx (steps dy) dy++data Nat = Zero | Succ Nat+ deriving (Eq, Show)++steps :: EditScriptL f txs tys -> Nat+steps (Ins _ d) = Succ $ steps d+steps (Del _ d) = Succ $ steps d+steps (Cpy _ d) = Succ $ steps d+steps End = Zero++bestSteps :: Nat -> d -> Nat -> d -> d+bestSteps Zero x _ _ = x+bestSteps _ _ Zero y = y+bestSteps (Succ nx) x (Succ ny) y = bestSteps nx x ny y++data RListT :: (* -> * -> *) -> * -> * where+ RList :: List f ts => RListT f ts++reify :: IsList f ts -> RListT f ts+reify IsNil = RList+reify (IsCons ists) = case reify ists of+ RList -> RList++ins :: (Type f t) => IsList f ts -> IsList f tys -> + f t ts -> EditScriptL f txs (Append ts tys) -> EditScriptL f txs (Cons t tys)+ins ists isys =+ case (reify ists, reify isys) of+ (RList, RList) -> Ins++del :: (Type f t) => IsList f ts -> IsList f txs ->+ f t ts -> EditScriptL f (Append ts txs) tys -> EditScriptL f (Cons t txs) tys +del ists isxs =+ case (reify ists, reify isxs) of+ (RList, RList) -> Del++cpy :: (Type f t) => IsList f ts -> IsList f txs -> IsList f tys ->+ f t ts -> EditScriptL f (Append ts txs) (Append ts tys) -> + EditScriptL f (Cons t txs) (Cons t tys)+cpy ists isxs isys =+ case (reify ists, reify isxs, reify isys) of+ (RList, RList, RList) -> Cpy++compress :: (Family f) => EditScriptL f txs tys -> EditScriptL f txs tys+compress End = End +compress (Ins c d) = Ins c (compress d)+compress (Del c d) = Del c (compress d)+compress (CpyTree d) = CpyTree (compress d) +compress (Cpy c d) = let d' = compress d in+ case copied (isList c) d' of+ Just d'' -> CpyTree d''+ Nothing -> Cpy c d'++copied :: (Family f) => IsList f ts -> + EditScriptL f (Append ts txs) (Append ts tys) -> Maybe (EditScriptL f txs tys)+copied IsNil d = Just d+copied (IsCons xs) (CpyTree d) = copied xs d+copied (IsCons _) _ = Nothing ++data EditScriptLMemo :: (* -> * -> *) -> * -> * -> * where+ CC :: (Type f tx, Type f ty, List f txs', List f tys') => + f tx txs' -> f ty tys' ->+ EditScriptL f (Cons tx txs) (Cons ty tys) ->+ EditScriptLMemo f (Cons tx txs) (Append tys' tys) -> + EditScriptLMemo f (Append txs' txs) (Cons ty tys) ->+ EditScriptLMemo f (Append txs' txs) (Append tys' tys) ->+ EditScriptLMemo f (Cons tx txs) (Cons ty tys)+ CN :: (Type f tx, List f txs') => f tx txs' ->+ EditScriptL f (Cons tx txs) Nil ->+ EditScriptLMemo f (Append txs' txs) Nil ->+ EditScriptLMemo f (Cons tx txs) Nil+ NC :: (Type f ty, List f tys') => f ty tys' ->+ EditScriptL f Nil (Cons ty tys) ->+ EditScriptLMemo f Nil (Append tys' tys) ->+ EditScriptLMemo f Nil (Cons ty tys)+ NN :: EditScriptL f Nil Nil ->+ EditScriptLMemo f Nil Nil++diffLMemo :: (Family f, List f txs, List f tys) => txs -> tys -> EditScriptLMemo f txs tys+diffLMemo = diffLMemo' list list++diffLMemo' :: (Family f) => forall txs tys. IsList f txs -> IsList f tys -> + txs -> tys -> EditScriptLMemo f txs tys+diffLMemo' IsNil IsNil CNil CNil = + NN End+diffLMemo' (IsCons isxs) IsNil (CCons x xs) CNil =+ matchConstructor x+ (\ cx isxs' xs' ->+ let d = diffLMemo' (appendList isxs' isxs) IsNil+ (append isxs' isxs xs' xs) CNil+ in cn isxs' isxs cx (del isxs' isxs cx (getEditScriptL d)) d)+diffLMemo' IsNil (IsCons isys) CNil (CCons y ys) =+ matchConstructor y+ (\ cy isys' ys' ->+ let i = diffLMemo' IsNil (appendList isys' isys)+ CNil (append isys' isys ys' ys)+ in nc isys' isys cy (ins isys' isys cy (getEditScriptL i)) i)+diffLMemo' (IsCons isxs) (IsCons isys) (CCons x xs) (CCons y ys) =+ matchConstructor x+ (\ cx isxs' xs' ->+ matchConstructor y+ (\ cy isys' ys' -> + let c = diffLMemo' (appendList isxs' isxs) (appendList isys' isys)+ (append isxs' isxs xs' xs) (append isys' isys ys' ys)+ d = extendd isys' isys cy c + i = extendi isxs' isxs cx c+ in cc isxs' isxs isys' isys cx cy + (bestEditScriptLMemo cx cy isxs' isxs isys' isys i d c) i d c))++getEditScriptL :: EditScriptLMemo f txs tys -> EditScriptL f txs tys+getEditScriptL (CC _ _ d _ _ _) = d+getEditScriptL (CN _ d _) = d+getEditScriptL (NC _ d _) = d+getEditScriptL (NN d) = d++bestEditScriptLMemo :: (Type f tx, Type f ty) => f tx txs' -> f ty tys' ->+ IsList f txs' -> IsList f txs -> IsList f tys' -> IsList f tys ->+ EditScriptLMemo f (Cons tx txs) (Append tys' tys) -> + EditScriptLMemo f (Append txs' txs) (Cons ty tys) ->+ EditScriptLMemo f (Append txs' txs) (Append tys' tys) ->+ EditScriptL f (Cons tx txs) (Cons ty tys)+bestEditScriptLMemo cx cy isxs' isxs isys' isys i d c = case decEq cx cy of+ Just (Refl, Refl) -> cpy isxs' isxs isys cx (getEditScriptL c)+ Nothing -> best (ins isys' isys cy (getEditScriptL i))+ (del isxs' isxs cx (getEditScriptL d))++extendd :: (Type f ty) => IsList f tys' -> IsList f tys -> f ty tys' -> + EditScriptLMemo f txs' (Append tys' tys) ->+ EditScriptLMemo f txs' (Cons ty tys)+extendd isys' isys cy dt@(NN d) = nc isys' isys cy (ins isys' isys cy d) dt+extendd isys' isys cy dt@(NC _ d _) = nc isys' isys cy (ins isys' isys cy d) dt+extendd isys' isys cy dt@(CN _ _ _) = extendd' isys' isys cy dt+extendd isys' isys cy dt@(CC _ _ _ _ _ _) = extendd' isys' isys cy dt++extendd' :: (Type f ty, Type f tx) => IsList f tys' -> IsList f tys -> f ty tys' -> + EditScriptLMemo f (Cons tx txs) (Append tys' tys) ->+ EditScriptLMemo f (Cons tx txs) (Cons ty tys)+extendd' isys' isys cy dt =+ extractd dt (\ isxs' isxs cx dt' -> + let i = dt+ d = extendd isys' isys cy dt'+ c = dt'+ in cc isxs' isxs isys' isys cx cy (bestEditScriptLMemo cx cy isxs' isxs isys' isys i d c) i d c)++extendi :: (Type f tx) => IsList f txs' -> IsList f txs -> f tx txs' ->+ EditScriptLMemo f (Append txs' txs) tys' ->+ EditScriptLMemo f (Cons tx txs) tys'+extendi isxs' isxs cx dt@(NN d) = cn isxs' isxs cx (del isxs' isxs cx d) dt+extendi isxs' isxs cx dt@(CN _ d _) = cn isxs' isxs cx (del isxs' isxs cx d) dt+extendi isxs' isxs cx dt@(NC _ _ _) = extendi' isxs' isxs cx dt+extendi isxs' isxs cx dt@(CC _ _ _ _ _ _) = extendi' isxs' isxs cx dt++extendi' :: (Type f tx, Type f ty) => IsList f txs' -> IsList f txs -> f tx txs' -> + EditScriptLMemo f (Append txs' txs) (Cons ty tys) ->+ EditScriptLMemo f (Cons tx txs) (Cons ty tys)+extendi' isxs' isxs cx dt =+ extracti dt (\ isys' isys cy dt' -> + let i = extendi isxs' isxs cx dt'+ d = dt+ c = dt'+ in cc isxs' isxs isys' isys cx cy + (bestEditScriptLMemo cx cy isxs' isxs isys' isys i d c) + i d c)++extractd :: (Type f tx) => EditScriptLMemo f (Cons tx txs) tys' ->+ (forall txs'. IsList f txs' -> IsList f txs -> f tx txs' ->+ EditScriptLMemo f (Append txs' txs) tys' -> r) -> r+extractd (CC c _ d' _ d _) k = k (isList c) (sourceTail d') c d+extractd (CN c d' d) k = k (isList c) (sourceTail d') c d++sourceTail :: EditScriptL f (Cons tx txs) tys -> IsList f txs+sourceTail (Ins _ d) = sourceTail d+sourceTail (Del _ _) = list+sourceTail (Cpy _ _) = list++extracti :: (Type f ty) => EditScriptLMemo f txs' (Cons ty tys) ->+ (forall tys'. IsList f tys' -> IsList f tys -> f ty tys' ->+ EditScriptLMemo f txs' (Append tys' tys) -> r) -> r+extracti (CC _ c d i _ _) k = k (isList c) (targetTail d) c i +extracti (NC c d i) k = k (isList c) (targetTail d) c i++targetTail :: EditScriptL f txs (Cons ty tys) -> IsList f tys+targetTail (Ins _ _) = list+targetTail (Del _ d) = targetTail d+targetTail (Cpy _ _) = list++nc :: (Type f t) => IsList f ts -> IsList f tys -> + f t ts -> EditScriptL f Nil (Cons t tys) -> + EditScriptLMemo f Nil (Append ts tys) -> EditScriptLMemo f Nil (Cons t tys)+nc ists isys =+ case (reify ists, reify isys) of+ (RList, RList) -> NC++cn :: (Type f t) => IsList f ts -> IsList f txs -> + f t ts -> EditScriptL f (Cons t txs) Nil ->+ EditScriptLMemo f (Append ts txs) Nil -> EditScriptLMemo f (Cons t txs) Nil+cn ists isxs =+ case (reify ists, reify isxs) of+ (RList, RList) -> CN++cc :: (Type f tx, Type f ty) => + IsList f txs' -> IsList f txs -> IsList f tys' -> IsList f tys ->+ f tx txs' -> f ty tys' -> EditScriptL f (Cons tx txs) (Cons ty tys) ->+ EditScriptLMemo f (Cons tx txs) (Append tys' tys) -> + EditScriptLMemo f (Append txs' txs) (Cons ty tys) ->+ EditScriptLMemo f (Append txs' txs) (Append tys' tys) ->+ EditScriptLMemo f (Cons tx txs) (Cons ty tys)+cc isxs' isxs isys' isys =+ case (reify isxs', reify isxs, reify isys', reify isys) of+ (RList, RList, RList, RList) -> CC+
+ LICENSE view
@@ -0,0 +1,27 @@+Copyright (c) 2009-2010, Eelco Lempsink+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ - Redistributions of source code must retain the above copyright notice, this+ list of conditions and the following disclaimer.++ - Redistributions in binary form must reproduce the above copyright notice,+ this list of conditions and the following disclaimer in the documentation+ and/or other materials provided with the distribution.++ - Neither the name of the <ORGANIZATION> nor the names of its contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ gdiff.cabal view
@@ -0,0 +1,19 @@+Name: gdiff+Version: 1.0+Category: Data+License: BSD3+License-File: LICENSE+Author: Eelco Lempsink, Andres Löh+Maintainer: eelco at lempsink nl+Synopsis: Generic diff and patch++Description: + Get an efficient, optimal, type-safe diff and patch function for your+ datatypes of choice by defining a simple GADT and some class instances.+ .+ Extracted from Eelco Lempsink's Thesis (<http://eelco.lempsink.nl/thesis.pdf>).++Build-Depends: base >= 3 && < 5+Build-Type: Simple+Exposed-Modules: Data.Generic.Diff+Tested-With: GHC == 6.10.4, GHC == 6.12.1