From 3809f3ee4381ed30634ba2a072da2d246f6a863c Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 11:30:11 -0400 Subject: [PATCH 01/53] Stub in a Data.Record module. --- semantic-diff.cabal | 1 + src/Data/Record.hs | 1 + 2 files changed, 2 insertions(+) create mode 100644 src/Data/Record.hs diff --git a/semantic-diff.cabal b/semantic-diff.cabal index 56882daf3..c8d8899de 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -19,6 +19,7 @@ library , Data.Bifunctor.Join.Arbitrary , Data.Functor.Both , Data.OrderedMap + , Data.Record , Data.These.Arbitrary , Diff , Diff.Arbitrary diff --git a/src/Data/Record.hs b/src/Data/Record.hs new file mode 100644 index 000000000..c1d146aba --- /dev/null +++ b/src/Data/Record.hs @@ -0,0 +1 @@ +module Data.Record where From d4b63443ab0591cbfd739f062c36e119c743a880 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 11:38:29 -0400 Subject: [PATCH 02/53] Add a simple phantom type tagger. --- src/Data/Record.hs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index c1d146aba..dff32533f 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -1 +1,12 @@ +{-# LANGUAGE GeneralizedNewtypeDeriving, TypeOperators #-} module Data.Record where + +import Prologue + +infix 9 :=> + +newtype a :=> b = (:=>) b + deriving (Eq, Show) + +field :: b -> a :=> b +field = (:=>) From d596c02e740b686d1cb33a0cd91dc9e590e68892 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 11:40:43 -0400 Subject: [PATCH 03/53] :=> and field are shorthand for Tagged. --- semantic-diff.cabal | 1 + src/Data/Record.hs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/semantic-diff.cabal b/semantic-diff.cabal index c8d8899de..87b7bcec0 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -61,6 +61,7 @@ library , QuickCheck >= 2.8.1 , quickcheck-text , semigroups + , tagged , text >= 1.2.1.3 , text-icu , these diff --git a/src/Data/Record.hs b/src/Data/Record.hs index dff32533f..f2c52badc 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -1,12 +1,12 @@ {-# LANGUAGE GeneralizedNewtypeDeriving, TypeOperators #-} module Data.Record where +import Data.Tagged import Prologue infix 9 :=> -newtype a :=> b = (:=>) b - deriving (Eq, Show) +type a :=> b = Tagged a b field :: b -> a :=> b -field = (:=>) +field = Tagged From 811b623652b2cd38e33371978a58b184bc33a0b9 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 11:42:19 -0400 Subject: [PATCH 04/53] Add a Record datatype. --- src/Data/Record.hs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index f2c52badc..332b1e3fc 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE GeneralizedNewtypeDeriving, TypeOperators #-} +{-# LANGUAGE DataKinds, GADTs, KindSignatures, TypeOperators #-} module Data.Record where import Data.Tagged @@ -10,3 +10,7 @@ type a :=> b = Tagged a b field :: b -> a :=> b field = Tagged + +data Record :: [*] -> * where + RNil :: Record '[] + RCons :: h -> Record t -> Record (h ': t) From db035a80094f2494e60a06ef1126f8802c1737e1 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 11:45:53 -0400 Subject: [PATCH 05/53] :memo: --- src/Data/Record.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 332b1e3fc..1f54c318d 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -6,8 +6,12 @@ import Prologue infix 9 :=> +-- | A phantom type tag constructor. type a :=> b = Tagged a b +-- | Smart constructor for type-tagged data fields. +-- | +-- | This has type a :=> b. When you require a to be some concrete type (and you usually will), it should be provided by context, whether using ascription, a type signature for the binding, `asTypeOf`, or some other way to allow the specific type to be inferred. field :: b -> a :=> b field = Tagged From afe112985bcde1d1812104a8c8162034bb592e26 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 11:49:07 -0400 Subject: [PATCH 06/53] Add the HasField typeclass. --- src/Data/Record.hs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 1f54c318d..12c1da99d 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE DataKinds, GADTs, KindSignatures, TypeOperators #-} +{-# LANGUAGE DataKinds, GADTs, KindSignatures, MultiParamTypeClasses, TypeOperators #-} module Data.Record where import Data.Tagged @@ -18,3 +18,6 @@ field = Tagged data Record :: [*] -> * where RNil :: Record '[] RCons :: h -> Record t -> Record (h ': t) + +class HasField (fields :: [*]) (field :: *) where + getField :: Record fields -> field From 4172f838424ad1eeee43eb029e0805d68f0bed9a Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 11:49:42 -0400 Subject: [PATCH 07/53] Move the type tags to the bottom. --- src/Data/Record.hs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 12c1da99d..ba82d18d5 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -4,6 +4,14 @@ module Data.Record where import Data.Tagged import Prologue +data Record :: [*] -> * where + RNil :: Record '[] + RCons :: h -> Record t -> Record (h ': t) + +class HasField (fields :: [*]) (field :: *) where + getField :: Record fields -> field + + infix 9 :=> -- | A phantom type tag constructor. @@ -14,10 +22,3 @@ type a :=> b = Tagged a b -- | This has type a :=> b. When you require a to be some concrete type (and you usually will), it should be provided by context, whether using ascription, a type signature for the binding, `asTypeOf`, or some other way to allow the specific type to be inferred. field :: b -> a :=> b field = Tagged - -data Record :: [*] -> * where - RNil :: Record '[] - RCons :: h -> Record t -> Record (h ': t) - -class HasField (fields :: [*]) (field :: *) where - getField :: Record fields -> field From eb91962cbbc9aeb04072f8e87d54e6bcbce964e1 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 11:50:43 -0400 Subject: [PATCH 08/53] :memo: Record. --- src/Data/Record.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index ba82d18d5..503576c0c 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -4,6 +4,7 @@ module Data.Record where import Data.Tagged import Prologue +-- | A type-safe, extensible record structure. data Record :: [*] -> * where RNil :: Record '[] RCons :: h -> Record t -> Record (h ': t) From 3b8c1546a5d9e2efd352a56764fe64384ac63b5d Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 11:52:14 -0400 Subject: [PATCH 09/53] Add HasField instances for hlists. --- src/Data/Record.hs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 503576c0c..2ebb32126 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -23,3 +23,12 @@ type a :=> b = Tagged a b -- | This has type a :=> b. When you require a to be some concrete type (and you usually will), it should be provided by context, whether using ascription, a type signature for the binding, `asTypeOf`, or some other way to allow the specific type to be inferred. field :: b -> a :=> b field = Tagged + + +-- Instances + +instance {-# OVERLAPPABLE #-} HasField fields field => HasField (notIt ': fields) field where + getField (RCons _ t) = getField t + +instance {-# OVERLAPPABLE #-} HasField (field ': fields) field where + getField (RCons h _) = h From c4eb6241cb00f5e427b51d3c763313f4ac376a40 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 11:52:42 -0400 Subject: [PATCH 10/53] Move the HasField class to a section by itself. --- src/Data/Record.hs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 2ebb32126..d0f2e3b21 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -9,9 +9,6 @@ data Record :: [*] -> * where RNil :: Record '[] RCons :: h -> Record t -> Record (h ': t) -class HasField (fields :: [*]) (field :: *) where - getField :: Record fields -> field - infix 9 :=> @@ -25,6 +22,12 @@ field :: b -> a :=> b field = Tagged +-- Classes + +class HasField (fields :: [*]) (field :: *) where + getField :: Record fields -> field + + -- Instances instance {-# OVERLAPPABLE #-} HasField fields field => HasField (notIt ': fields) field where From fd22620ccdbea699e3111a14e958c60db47c6a21 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 11:53:21 -0400 Subject: [PATCH 11/53] :memo: HasField. --- src/Data/Record.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index d0f2e3b21..0fe74b1b1 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -24,6 +24,7 @@ field = Tagged -- Classes +-- | HasField enables indexing a Record by (phantom) type tags. class HasField (fields :: [*]) (field :: *) where getField :: Record fields -> field From cff9ac00ab315ceef0b141531c943341f458f121 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 11:54:32 -0400 Subject: [PATCH 12/53] :tophat: @aaronlevin. --- src/Data/Record.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 0fe74b1b1..70b2a6df9 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -5,6 +5,8 @@ import Data.Tagged import Prologue -- | A type-safe, extensible record structure. +-- | +-- | This is heavily inspired by Aaron Levin’s [Extensible Effects in the van Laarhoven Free Monad](http://aaronlevin.ca/post/136494428283/extensible-effects-in-the-van-laarhoven-free-monad). data Record :: [*] -> * where RNil :: Record '[] RCons :: h -> Record t -> Record (h ': t) From 63ed03865494887f7da339817de30fc193ee6c79 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 11:58:17 -0400 Subject: [PATCH 13/53] :memo: why OVERLAPPABLE. --- src/Data/Record.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 70b2a6df9..2f62148a0 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -33,6 +33,8 @@ class HasField (fields :: [*]) (field :: *) where -- Instances +-- OVERLAPPABLE is required for the HasField instances so that we can handle the two cases: either the head of the non-empty h-list is the requested field, or it isn’t. The third possible case (the h-list is empty) is rejected at compile-time. + instance {-# OVERLAPPABLE #-} HasField fields field => HasField (notIt ': fields) field where getField (RCons _ t) = getField t From 2475667df932109c1b2f409da0f3b43b99e82df5 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 11:58:29 -0400 Subject: [PATCH 14/53] Define `Show` for RNil. --- src/Data/Record.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 2f62148a0..827ec79ac 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -40,3 +40,7 @@ instance {-# OVERLAPPABLE #-} HasField fields field => HasField (notIt ': fields instance {-# OVERLAPPABLE #-} HasField (field ': fields) field where getField (RCons h _) = h + + +instance Show (Record '[]) where + showsPrec _ RNil = ("'[]"++) From 1bac0faf43dfa29be020bd61bff0b2b715c519fb Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 11:59:42 -0400 Subject: [PATCH 15/53] Add a Show instance for RCons. --- src/Data/Record.hs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 827ec79ac..65e07d5ed 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE DataKinds, GADTs, KindSignatures, MultiParamTypeClasses, TypeOperators #-} +{-# LANGUAGE DataKinds, FlexibleContexts, GADTs, KindSignatures, MultiParamTypeClasses, TypeOperators #-} module Data.Record where import Data.Tagged @@ -42,5 +42,8 @@ instance {-# OVERLAPPABLE #-} HasField (field ': fields) field where getField (RCons h _) = h +instance (Show h, Show (Record t)) => Show (Record (h ': t)) where + showsPrec n (RCons h t) = showsPrec n h . (" : "++) . showsPrec n t + instance Show (Record '[]) where showsPrec _ RNil = ("'[]"++) From 9ebf255da28c09733184f1d652959d17b2992c0c Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 12:00:29 -0400 Subject: [PATCH 16/53] Use `<>` instead of `++`. --- src/Data/Record.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 65e07d5ed..b0e1bfccb 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -43,7 +43,7 @@ instance {-# OVERLAPPABLE #-} HasField (field ': fields) field where instance (Show h, Show (Record t)) => Show (Record (h ': t)) where - showsPrec n (RCons h t) = showsPrec n h . (" : "++) . showsPrec n t + showsPrec n (RCons h t) = showsPrec n h . (" : " <>) . showsPrec n t instance Show (Record '[]) where - showsPrec _ RNil = ("'[]"++) + showsPrec _ RNil = ("'[]" <>) From 39041fc8c34573236c92773aa8598359ffa6a4e5 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 12:02:19 -0400 Subject: [PATCH 17/53] Add an infix synonym for RCons. --- src/Data/Record.hs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index b0e1bfccb..c364071f9 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -11,6 +11,12 @@ data Record :: [*] -> * where RNil :: Record '[] RCons :: h -> Record t -> Record (h ': t) +infixr 0 .:. + +-- | Infix synonym for `RCons`: `a .:. b .:. RNil == RCons a (RCons b RNil)`. +(.:.) :: h -> Record t -> Record (h ': t) +(.:.) = RCons + infix 9 :=> From 675cb59e14181451d7fb9a8eb150a31e21e17dc3 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 12:10:05 -0400 Subject: [PATCH 18/53] :=> constructs a wrapper around Tagged. --- src/Data/Record.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index c364071f9..c48fe3340 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -21,13 +21,13 @@ infixr 0 .:. infix 9 :=> -- | A phantom type tag constructor. -type a :=> b = Tagged a b +newtype a :=> b = (:=>) (Tagged a b) -- | Smart constructor for type-tagged data fields. -- | -- | This has type a :=> b. When you require a to be some concrete type (and you usually will), it should be provided by context, whether using ascription, a type signature for the binding, `asTypeOf`, or some other way to allow the specific type to be inferred. field :: b -> a :=> b -field = Tagged +field = (:=>) . Tagged -- Classes From a08b83432f87ea8487f2d564114e34a401998ee7 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 16:20:16 -0400 Subject: [PATCH 19/53] Use GeneralizedNewtypeDeriving. --- src/Data/Record.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index c48fe3340..138644aec 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE DataKinds, FlexibleContexts, GADTs, KindSignatures, MultiParamTypeClasses, TypeOperators #-} +{-# LANGUAGE DataKinds, FlexibleContexts, GADTs, GeneralizedNewtypeDeriving, KindSignatures, MultiParamTypeClasses, TypeOperators #-} module Data.Record where import Data.Tagged From dcf9cdc6ae6ad60cdc2df06facbab843d612f50a Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 16:23:12 -0400 Subject: [PATCH 20/53] Add an eliminator for :=>. --- src/Data/Record.hs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 138644aec..2383740dd 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -29,6 +29,9 @@ newtype a :=> b = (:=>) (Tagged a b) field :: b -> a :=> b field = (:=>) . Tagged +unField :: a :=> b -> b +unField ((:=>) b) = unTagged b + -- Classes From ba1fc277eb4cd1efdc730d27983eaf291c05f855 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 16:41:43 -0400 Subject: [PATCH 21/53] Rework the constructors and eliminators a bit to not use tagged. --- semantic-diff.cabal | 1 - src/Data/Record.hs | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/semantic-diff.cabal b/semantic-diff.cabal index 87b7bcec0..c8d8899de 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -61,7 +61,6 @@ library , QuickCheck >= 2.8.1 , quickcheck-text , semigroups - , tagged , text >= 1.2.1.3 , text-icu , these diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 2383740dd..56a8f97a4 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -1,7 +1,6 @@ {-# LANGUAGE DataKinds, FlexibleContexts, GADTs, GeneralizedNewtypeDeriving, KindSignatures, MultiParamTypeClasses, TypeOperators #-} module Data.Record where -import Data.Tagged import Prologue -- | A type-safe, extensible record structure. @@ -21,16 +20,13 @@ infixr 0 .:. infix 9 :=> -- | A phantom type tag constructor. -newtype a :=> b = (:=>) (Tagged a b) +newtype a :=> b = Field { unField :: b } -- | Smart constructor for type-tagged data fields. -- | -- | This has type a :=> b. When you require a to be some concrete type (and you usually will), it should be provided by context, whether using ascription, a type signature for the binding, `asTypeOf`, or some other way to allow the specific type to be inferred. field :: b -> a :=> b -field = (:=>) . Tagged - -unField :: a :=> b -> b -unField ((:=>) b) = unTagged b +field = Field -- Classes From b18a30b4419a4147e509efc67c12059248d1d074 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 17:02:08 -0400 Subject: [PATCH 22/53] Add a type family for monotypes. --- src/Data/Record.hs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 56a8f97a4..6c04465c7 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE DataKinds, FlexibleContexts, GADTs, GeneralizedNewtypeDeriving, KindSignatures, MultiParamTypeClasses, TypeOperators #-} +{-# LANGUAGE DataKinds, FlexibleContexts, GADTs, GeneralizedNewtypeDeriving, KindSignatures, MultiParamTypeClasses, TypeFamilies, TypeOperators #-} module Data.Record where import Prologue @@ -29,6 +29,11 @@ field :: b -> a :=> b field = Field +-- Families + +type family ValueOf field + + -- Classes -- | HasField enables indexing a Record by (phantom) type tags. From 2822ec2f51c225ce8ee5fb347e9086e861694c14 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 17:02:17 -0400 Subject: [PATCH 23/53] Add a typeclass for fields. --- src/Data/Record.hs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 6c04465c7..612fcf35d 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -40,6 +40,9 @@ type family ValueOf field class HasField (fields :: [*]) (field :: *) where getField :: Record fields -> field +class IsField field where + getValue :: field -> ValueOf field + -- Instances From 035969c17661fa397da319cfe7f16aa0d51c74ba Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 17:02:26 -0400 Subject: [PATCH 24/53] Add instances for :=> as a field. --- src/Data/Record.hs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 612fcf35d..b326cd5af 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -60,3 +60,9 @@ instance (Show h, Show (Record t)) => Show (Record (h ': t)) where instance Show (Record '[]) where showsPrec _ RNil = ("'[]" <>) + + +type instance ValueOf ((:=>) tag value) = value + +instance IsField (tag :=> value) where + getValue = unField From 856e3ed809fc209d833a379f45facbe0f0ca5f9a Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 17:31:53 -0400 Subject: [PATCH 25/53] Abbreviate the infix RCons syntax. --- src/Data/Record.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index b326cd5af..ebbc76ebd 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -10,11 +10,11 @@ data Record :: [*] -> * where RNil :: Record '[] RCons :: h -> Record t -> Record (h ': t) -infixr 0 .:. +infixr 0 .: --- | Infix synonym for `RCons`: `a .:. b .:. RNil == RCons a (RCons b RNil)`. -(.:.) :: h -> Record t -> Record (h ': t) -(.:.) = RCons +-- | Infix synonym for `RCons`: `a .: b .: RNil == RCons a (RCons b RNil)`. +(.:) :: h -> Record t -> Record (h ': t) +(.:) = RCons infix 9 :=> From 047cc81f4f1448e182244b92848897dcf3ea77fc Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 17:34:36 -0400 Subject: [PATCH 26/53] IsField specifies a setter. --- src/Data/Record.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index ebbc76ebd..f4dff20ce 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -42,6 +42,7 @@ class HasField (fields :: [*]) (field :: *) where class IsField field where getValue :: field -> ValueOf field + setValue :: ValueOf field -> field -- Instances @@ -66,3 +67,4 @@ type instance ValueOf ((:=>) tag value) = value instance IsField (tag :=> value) where getValue = unField + setValue = field From 2f33dd06d18a259eae72b7bd9e14537814ce2541 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 17:37:04 -0400 Subject: [PATCH 27/53] HasField specifies a setter. --- src/Data/Record.hs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index f4dff20ce..5b48fc781 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -39,6 +39,7 @@ type family ValueOf field -- | HasField enables indexing a Record by (phantom) type tags. class HasField (fields :: [*]) (field :: *) where getField :: Record fields -> field + setField :: Record fields -> field -> Record fields class IsField field where getValue :: field -> ValueOf field @@ -51,9 +52,11 @@ class IsField field where instance {-# OVERLAPPABLE #-} HasField fields field => HasField (notIt ': fields) field where getField (RCons _ t) = getField t + setField (RCons h t) f = RCons h (setField t f) instance {-# OVERLAPPABLE #-} HasField (field ': fields) field where getField (RCons h _) = h + setField (RCons _ t) f = RCons f t instance (Show h, Show (Record t)) => Show (Record (h ': t)) where From c5ab897782ead822e591eeeb04c51dbb1f28ad75 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 17:42:22 -0400 Subject: [PATCH 28/53] Split setField into a SetField typeclass. --- src/Data/Record.hs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 5b48fc781..4d229260e 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -39,6 +39,8 @@ type family ValueOf field -- | HasField enables indexing a Record by (phantom) type tags. class HasField (fields :: [*]) (field :: *) where getField :: Record fields -> field + +class SetField (fields :: [*]) (field :: *) where setField :: Record fields -> field -> Record fields class IsField field where @@ -52,10 +54,14 @@ class IsField field where instance {-# OVERLAPPABLE #-} HasField fields field => HasField (notIt ': fields) field where getField (RCons _ t) = getField t + +instance {-# OVERLAPPABLE #-} SetField fields field => SetField (notIt ': fields) field where setField (RCons h t) f = RCons h (setField t f) instance {-# OVERLAPPABLE #-} HasField (field ': fields) field where getField (RCons h _) = h + +instance {-# OVERLAPPABLE #-} SetField (field ': fields) field where setField (RCons _ t) f = RCons f t From 898a0cdbd37a253a1dbae8359cc658804f69c08f Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Thu, 16 Jun 2016 17:44:06 -0400 Subject: [PATCH 29/53] Put the HasField/SetField instances together. --- src/Data/Record.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 4d229260e..f0eb9f775 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -55,12 +55,12 @@ class IsField field where instance {-# OVERLAPPABLE #-} HasField fields field => HasField (notIt ': fields) field where getField (RCons _ t) = getField t -instance {-# OVERLAPPABLE #-} SetField fields field => SetField (notIt ': fields) field where - setField (RCons h t) f = RCons h (setField t f) - instance {-# OVERLAPPABLE #-} HasField (field ': fields) field where getField (RCons h _) = h +instance {-# OVERLAPPABLE #-} SetField fields field => SetField (notIt ': fields) field where + setField (RCons h t) f = RCons h (setField t f) + instance {-# OVERLAPPABLE #-} SetField (field ': fields) field where setField (RCons _ t) f = RCons f t From 9ccd3d2d772b5a7d05106c7502f80542e38edd4b Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 10:26:20 -0400 Subject: [PATCH 30/53] Implement Data.Record-based annotations. --- src/Info.hs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Info.hs b/src/Info.hs index 1d79e0245..ba2d535dd 100644 --- a/src/Info.hs +++ b/src/Info.hs @@ -1,9 +1,32 @@ +{-# LANGUAGE DataKinds, FlexibleContexts, TypeOperators #-} module Info where +import Data.Record import Prologue import Category import Range +newtype RangeA = RangeA { unRangeA :: Range } +newtype CategoryA = CategoryA { unCategoryA :: Category } +newtype SizeA = SizeA { unSizeA :: Integer } +newtype CostA = CostA { unCostA :: Integer } + +type InfoFields = '[ RangeA, CategoryA, SizeA, CostA ] + +type Info' = Record InfoFields + +characterRange' :: HasField fields RangeA => Record fields -> Range +characterRange' = unRangeA . getField + +category' :: HasField fields CategoryA => Record fields -> Category +category' = unCategoryA . getField + +size' :: HasField fields SizeA => Record fields -> Integer +size' = unSizeA . getField + +cost' :: HasField fields CostA => Record fields -> Integer +cost' = unCostA . getField + -- | An annotation for a source file, including the source range and semantic -- | categories. data Info = Info { characterRange :: !Range, category :: !Category, size :: !Integer, cost :: !Integer } From 9e885847264029fd675370172cdbaab93fa0d22f Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 10:29:24 -0400 Subject: [PATCH 31/53] Add setters. --- src/Info.hs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Info.hs b/src/Info.hs index ba2d535dd..eac15416c 100644 --- a/src/Info.hs +++ b/src/Info.hs @@ -18,15 +18,27 @@ type Info' = Record InfoFields characterRange' :: HasField fields RangeA => Record fields -> Range characterRange' = unRangeA . getField +setCharacterRange' :: SetField fields RangeA => Record fields -> Range -> Record fields +setCharacterRange' record = setField record . RangeA + category' :: HasField fields CategoryA => Record fields -> Category category' = unCategoryA . getField +setCategory' :: SetField fields CategoryA => Record fields -> Category -> Record fields +setCategory' record = setField record . CategoryA + size' :: HasField fields SizeA => Record fields -> Integer size' = unSizeA . getField +setSize' :: SetField fields SizeA => Record fields -> Integer -> Record fields +setSize' record = setField record . SizeA + cost' :: HasField fields CostA => Record fields -> Integer cost' = unCostA . getField +setCost' :: SetField fields CostA => Record fields -> Integer -> Record fields +setCost' record = setField record . CostA + -- | An annotation for a source file, including the source range and semantic -- | categories. data Info = Info { characterRange :: !Range, category :: !Category, size :: !Integer, cost :: !Integer } From ed1b0b692dff96bb3ee66e1059aee289c3923b80 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 10:45:41 -0400 Subject: [PATCH 32/53] We no longer require TypeOperators. --- src/Info.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Info.hs b/src/Info.hs index eac15416c..fc594e36f 100644 --- a/src/Info.hs +++ b/src/Info.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE DataKinds, FlexibleContexts, TypeOperators #-} +{-# LANGUAGE DataKinds, FlexibleContexts #-} module Info where import Data.Record From 138f66ae0d443badbfd29f94e92e5cf3740e26ef Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 11:15:22 -0400 Subject: [PATCH 33/53] =?UTF-8?q?Don=E2=80=99t=20wrap=20Range=20&=20Catego?= =?UTF-8?q?ry=20in=20newtypes.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Info.hs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Info.hs b/src/Info.hs index fc594e36f..98500b69a 100644 --- a/src/Info.hs +++ b/src/Info.hs @@ -6,26 +6,24 @@ import Prologue import Category import Range -newtype RangeA = RangeA { unRangeA :: Range } -newtype CategoryA = CategoryA { unCategoryA :: Category } newtype SizeA = SizeA { unSizeA :: Integer } newtype CostA = CostA { unCostA :: Integer } -type InfoFields = '[ RangeA, CategoryA, SizeA, CostA ] +type InfoFields = '[ Range, Category, SizeA, CostA ] type Info' = Record InfoFields -characterRange' :: HasField fields RangeA => Record fields -> Range -characterRange' = unRangeA . getField +characterRange' :: HasField fields Range => Record fields -> Range +characterRange' = getField -setCharacterRange' :: SetField fields RangeA => Record fields -> Range -> Record fields -setCharacterRange' record = setField record . RangeA +setCharacterRange' :: SetField fields Range => Record fields -> Range -> Record fields +setCharacterRange' = setField -category' :: HasField fields CategoryA => Record fields -> Category -category' = unCategoryA . getField +category' :: HasField fields Category => Record fields -> Category +category' = getField -setCategory' :: SetField fields CategoryA => Record fields -> Category -> Record fields -setCategory' record = setField record . CategoryA +setCategory' :: SetField fields Category => Record fields -> Category -> Record fields +setCategory' = setField size' :: HasField fields SizeA => Record fields -> Integer size' = unSizeA . getField From 263c96468c82d6f46fa7990b2d75d25da90cd8b7 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 11:17:20 -0400 Subject: [PATCH 34/53] Remove the obsolete type tag stuff. --- src/Data/Record.hs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index f0eb9f775..742777320 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -17,18 +17,6 @@ infixr 0 .: (.:) = RCons -infix 9 :=> - --- | A phantom type tag constructor. -newtype a :=> b = Field { unField :: b } - --- | Smart constructor for type-tagged data fields. --- | --- | This has type a :=> b. When you require a to be some concrete type (and you usually will), it should be provided by context, whether using ascription, a type signature for the binding, `asTypeOf`, or some other way to allow the specific type to be inferred. -field :: b -> a :=> b -field = Field - - -- Families type family ValueOf field @@ -70,10 +58,3 @@ instance (Show h, Show (Record t)) => Show (Record (h ': t)) where instance Show (Record '[]) where showsPrec _ RNil = ("'[]" <>) - - -type instance ValueOf ((:=>) tag value) = value - -instance IsField (tag :=> value) where - getValue = unField - setValue = field From b8ddaaaaf052cecf073942023ed2932e5adbfdeb Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 11:22:22 -0400 Subject: [PATCH 35/53] Size/Cost are not just wrappers. --- src/Info.hs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Info.hs b/src/Info.hs index 98500b69a..3fa2a06ac 100644 --- a/src/Info.hs +++ b/src/Info.hs @@ -6,10 +6,10 @@ import Prologue import Category import Range -newtype SizeA = SizeA { unSizeA :: Integer } -newtype CostA = CostA { unCostA :: Integer } +newtype Size = Size { unSize :: Integer } +newtype Cost = Cost { unCost :: Integer } -type InfoFields = '[ Range, Category, SizeA, CostA ] +type InfoFields = '[ Range, Category, Size, Cost ] type Info' = Record InfoFields @@ -25,17 +25,17 @@ category' = getField setCategory' :: SetField fields Category => Record fields -> Category -> Record fields setCategory' = setField -size' :: HasField fields SizeA => Record fields -> Integer -size' = unSizeA . getField +size' :: HasField fields Size => Record fields -> Size +size' = getField -setSize' :: SetField fields SizeA => Record fields -> Integer -> Record fields -setSize' record = setField record . SizeA +setSize' :: SetField fields Size => Record fields -> Size -> Record fields +setSize' = setField -cost' :: HasField fields CostA => Record fields -> Integer -cost' = unCostA . getField +cost' :: HasField fields Cost => Record fields -> Cost +cost' = getField -setCost' :: SetField fields CostA => Record fields -> Integer -> Record fields -setCost' record = setField record . CostA +setCost' :: SetField fields Cost => Record fields -> Cost -> Record fields +setCost' = setField -- | An annotation for a source file, including the source range and semantic -- | categories. From f6f067c638de3ebae032c2a34fffd1378e5798cc Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 11:25:45 -0400 Subject: [PATCH 36/53] Derive Eq & Show instances for Size & Cost. --- src/Info.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Info.hs b/src/Info.hs index 3fa2a06ac..9f6471177 100644 --- a/src/Info.hs +++ b/src/Info.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE DataKinds, FlexibleContexts #-} +{-# LANGUAGE DataKinds, FlexibleContexts, GeneralizedNewtypeDeriving #-} module Info where import Data.Record @@ -7,7 +7,9 @@ import Category import Range newtype Size = Size { unSize :: Integer } + deriving (Eq, Show) newtype Cost = Cost { unCost :: Integer } + deriving (Eq, Show) type InfoFields = '[ Range, Category, Size, Cost ] From e8c165fb155b7cc837bf66ce5e1e87e8cf2a98b6 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 11:29:27 -0400 Subject: [PATCH 37/53] Use mappend instead of ++. --- src/Alignment.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index 37cd63982..3533c715a 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -86,7 +86,7 @@ alignBranch getRange children ranges = case intersectingChildren of _ -> case intersectionsWithHeadRanges <$> listToMaybe symmetricalChildren of -- At least one child intersects on both sides, so align symmetrically. Just (True, True) -> let (line, remaining) = lineAndRemaining intersectingChildren (Just headRanges) in - line $ alignBranch getRange (remaining ++ nonIntersectingChildren) (drop 1 <$> ranges) + line $ alignBranch getRange (remaining <> nonIntersectingChildren) (drop 1 <$> ranges) -- A symmetrical child intersects on the right, so align asymmetrically on the left. Just (False, True) -> alignAsymmetrically leftRange first -- A symmetrical child intersects on the left, so align asymmetrically on the right. @@ -101,7 +101,7 @@ alignBranch getRange children ranges = case intersectingChildren of Just headRanges = sequenceL (listToMaybe <$> Join (runBothWith These ranges)) (leftRange, rightRange) = splitThese headRanges alignAsymmetrically range advanceBy = let (line, remaining) = lineAndRemaining asymmetricalChildren range in - line $ alignBranch getRange (remaining ++ symmetricalChildren ++ nonIntersectingChildren) (modifyJoin (advanceBy (drop 1)) ranges) + line $ alignBranch getRange (remaining <> symmetricalChildren <> nonIntersectingChildren) (modifyJoin (advanceBy (drop 1)) ranges) lineAndRemaining _ Nothing = (identity, []) lineAndRemaining children (Just ranges) = let (intersections, remaining) = alignChildren getRange children ranges in ((:) $ (,) <$> ranges `applyToBoth` (sortBy (compare `on` getRange) <$> intersections), remaining) From 01d8c6ed911ed3f5831e08385e97b5ff21104bd0 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 11:33:22 -0400 Subject: [PATCH 38/53] Derive Num instances for Size and Cost. --- src/Info.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Info.hs b/src/Info.hs index 9f6471177..ca41feceb 100644 --- a/src/Info.hs +++ b/src/Info.hs @@ -7,9 +7,9 @@ import Category import Range newtype Size = Size { unSize :: Integer } - deriving (Eq, Show) + deriving (Eq, Num, Show) newtype Cost = Cost { unCost :: Integer } - deriving (Eq, Show) + deriving (Eq, Num, Show) type InfoFields = '[ Range, Category, Size, Cost ] From 844d093616b63a7c3aee30e22f93c7f4b3fc175c Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 11:50:10 -0400 Subject: [PATCH 39/53] =?UTF-8?q?We=20don=E2=80=99t=20use=20GeneralizedNew?= =?UTF-8?q?typeDeriving.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Data/Record.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 742777320..e10ece407 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE DataKinds, FlexibleContexts, GADTs, GeneralizedNewtypeDeriving, KindSignatures, MultiParamTypeClasses, TypeFamilies, TypeOperators #-} +{-# LANGUAGE DataKinds, FlexibleContexts, GADTs, KindSignatures, MultiParamTypeClasses, TypeFamilies, TypeOperators #-} module Data.Record where import Prologue From 3fb69066a57863bee99917eaea7180f47b87b8b7 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 11:50:18 -0400 Subject: [PATCH 40/53] Implement Record equality. --- src/Data/Record.hs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index e10ece407..fd02880f1 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -58,3 +58,10 @@ instance (Show h, Show (Record t)) => Show (Record (h ': t)) where instance Show (Record '[]) where showsPrec _ RNil = ("'[]" <>) + + +instance (Eq h, Eq (Record t)) => Eq (Record (h ': t)) where + RCons h1 t1 == RCons h2 t2 = h1 == h2 && t1 == t2 + +instance Eq (Record '[]) where + _ == _ = True From 10e4aefc2689eb0dd50ef857187002039bc949bf Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 11:50:56 -0400 Subject: [PATCH 41/53] :fire: the redundant IsField typeclass. --- src/Data/Record.hs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index fd02880f1..dca0494ad 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -31,10 +31,6 @@ class HasField (fields :: [*]) (field :: *) where class SetField (fields :: [*]) (field :: *) where setField :: Record fields -> field -> Record fields -class IsField field where - getValue :: field -> ValueOf field - setValue :: ValueOf field -> field - -- Instances From 93f5d53cb44f00393f75afaa16708f3f8b1c47ca Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 11:51:03 -0400 Subject: [PATCH 42/53] :fire: the redundant ValueOf type family. --- src/Data/Record.hs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index dca0494ad..1252e4f6a 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE DataKinds, FlexibleContexts, GADTs, KindSignatures, MultiParamTypeClasses, TypeFamilies, TypeOperators #-} +{-# LANGUAGE DataKinds, FlexibleContexts, GADTs, KindSignatures, MultiParamTypeClasses, TypeOperators #-} module Data.Record where import Prologue @@ -17,11 +17,6 @@ infixr 0 .: (.:) = RCons --- Families - -type family ValueOf field - - -- Classes -- | HasField enables indexing a Record by (phantom) type tags. From fe00a94f52dc9c111a598df9590d4789ce81d727 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 12:19:00 -0400 Subject: [PATCH 43/53] Destructure Info using the field accessors. --- src/Renderer/JSON.hs | 2 +- src/Renderer/Split.hs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Renderer/JSON.hs b/src/Renderer/JSON.hs index e5c72bc44..e17931094 100644 --- a/src/Renderer/JSON.hs +++ b/src/Renderer/JSON.hs @@ -66,7 +66,7 @@ lineFields n term range = [ "number" .= n ] termFields :: (ToJSON recur, KeyValue kv) => Info -> Syntax leaf recur -> [kv] -termFields Info{..} syntax = "range" .= characterRange : "category" .= category : case syntax of +termFields info syntax = "range" .= characterRange info : "category" .= category info : case syntax of Leaf _ -> [] Indexed c -> childrenFields c Fixed c -> childrenFields c diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index 9912d1615..564cb1bbd 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -93,11 +93,11 @@ split diff blobs = TL.toStrict . renderHtml newtype Renderable a = Renderable a instance ToMarkup f => ToMarkup (Renderable (Source Char, Info, Syntax a (f, Range))) where - toMarkup (Renderable (source, Info {..}, syntax)) = (! A.data_ (stringValue (show size))) . classifyMarkup category $ case syntax of - Leaf _ -> span . string . toString $ slice characterRange source - Indexed children -> ul . mconcat $ wrapIn li <$> contentElements source characterRange children - Fixed children -> ul . mconcat $ wrapIn li <$> contentElements source characterRange children - Keyed children -> dl . mconcat $ wrapIn dd <$> contentElements source characterRange children + toMarkup (Renderable (source, info, syntax)) = (! A.data_ (stringValue (show (size info)))) . classifyMarkup (category info) $ case syntax of + Leaf _ -> span . string . toString $ slice (characterRange info) source + Indexed children -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) children + Fixed children -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) children + Keyed children -> dl . mconcat $ wrapIn dd <$> contentElements source (characterRange info) children contentElements :: (Foldable t, ToMarkup f) => Source Char -> Range -> t (f, Range) -> [Markup] contentElements source range children = let (elements, next) = foldr' (markupForContextAndChild source) ([], end range) children in @@ -114,13 +114,13 @@ wrapIn _ l@Blaze.Comment{} = l wrapIn f p = f p instance ToMarkup (Renderable (Source Char, Term a Info)) where - toMarkup (Renderable (source, term)) = Prologue.fst $ cata (\ (info@(Info{..}) :< syntax) -> (toMarkup $ Renderable (source, info, syntax), characterRange)) term + toMarkup (Renderable (source, term)) = Prologue.fst $ cata (\ (info :< syntax) -> (toMarkup $ Renderable (source, info, syntax), characterRange info)) term instance ToMarkup (Renderable (Source Char, SplitDiff a Info)) where - toMarkup (Renderable (source, diff)) = Prologue.fst $ iter (\ (info@(Info{..}) :< syntax) -> (toMarkup $ Renderable (source, info, syntax), characterRange)) $ toMarkupAndRange <$> diff + toMarkup (Renderable (source, diff)) = Prologue.fst $ iter (\ (info :< syntax) -> (toMarkup $ Renderable (source, info, syntax), characterRange info)) $ toMarkupAndRange <$> diff where toMarkupAndRange :: SplitPatch (Term a Info) -> (Markup, Range) - toMarkupAndRange patch = let term@(Info{..} :< _) = runCofree $ getSplitTerm patch in - ((div ! A.class_ (splitPatchToClassName patch) ! A.data_ (stringValue (show size))) . toMarkup $ Renderable (source, cofree term), characterRange) + toMarkupAndRange patch = let term@(info :< _) = runCofree $ getSplitTerm patch in + ((div ! A.class_ (splitPatchToClassName patch) ! A.data_ (stringValue (show (size info)))) . toMarkup $ Renderable (source, cofree term), characterRange info) instance ToMarkup a => ToMarkup (Renderable (Bool, Int, a)) where toMarkup (Renderable (hasChanges, num, line)) = From 5413e1ff430c64cb808c79e7d87a8f19e9579c35 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 13:23:54 -0400 Subject: [PATCH 44/53] =?UTF-8?q?Add=20setter=20functions=20for=20Info?= =?UTF-8?q?=E2=80=99s=20fields.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Info.hs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Info.hs b/src/Info.hs index ca41feceb..c9365853c 100644 --- a/src/Info.hs +++ b/src/Info.hs @@ -43,3 +43,15 @@ setCost' = setField -- | categories. data Info = Info { characterRange :: !Range, category :: !Category, size :: !Integer, cost :: !Integer } deriving (Eq, Show) + +setCharacterRange :: Info -> Range -> Info +setCharacterRange info range = info { characterRange = range } + +setCategory :: Info -> Category -> Info +setCategory info category = info { category = category } + +setSize :: Info -> Integer -> Info +setSize info size = info { size = size } + +setCost :: Info -> Integer -> Info +setCost info cost = info { cost = cost } From 37b59c89d7e197531718734dcfdf504d7daad47c Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 13:28:35 -0400 Subject: [PATCH 45/53] Use the setter functions to update Info. --- src/Alignment.hs | 2 +- src/Diffing.hs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index 3533c715a..49fefc24a 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -69,7 +69,7 @@ alignSyntax toJoinThese toNode getRange sources (infos :< syntax) = case syntax Keyed children -> catMaybes $ wrapInBranch (Keyed . Map.fromList) <$> alignBranch (getRange . Prologue.snd) (Map.toList children >>= pairWithKey) bothRanges where bothRanges = modifyJoin (fromThese [] []) lineRanges lineRanges = toJoinThese $ actualLineRanges <$> (characterRange <$> infos) <*> sources - wrapInBranch constructor = applyThese $ toJoinThese ((\ info (range, children) -> toNode (info { characterRange = range } :< constructor children)) <$> infos) + wrapInBranch constructor = applyThese $ toJoinThese ((\ info (range, children) -> toNode (setCharacterRange info range :< constructor children)) <$> infos) pairWithKey (key, values) = fmap ((,) key) <$> values -- | Given a function to get the range, a list of already-aligned children, and the lists of ranges spanned by a branch, return the aligned lines. diff --git a/src/Diffing.hs b/src/Diffing.hs index c3d2b1a96..2e163f0bb 100644 --- a/src/Diffing.hs +++ b/src/Diffing.hs @@ -55,13 +55,13 @@ breakDownLeavesByWord :: Source Char -> Term T.Text Info -> Term T.Text Info breakDownLeavesByWord source = cata replaceIn where replaceIn :: TermF T.Text Info (Term T.Text Info) -> Term T.Text Info - replaceIn (info :< syntax) = let size' = 1 + sum (size . extract <$> syntax') in cofree $ info { size = size', cost = size' } :< syntax' + replaceIn (info :< syntax) = let size' = 1 + sum (size . extract <$> syntax') in cofree $ setCost (setSize info size') size' :< syntax' where syntax' = case (ranges, syntax) of (_:_:_, Leaf _) -> Indexed (makeLeaf info <$> ranges) _ -> syntax ranges = rangesAndWordsInSource (characterRange info) rangesAndWordsInSource range = rangesAndWordsFrom (start range) (toString $ slice range source) - makeLeaf info (range, substring) = cofree $ info { characterRange = range } :< Leaf (T.pack substring) + makeLeaf info (range, substring) = cofree $ setCharacterRange info range :< Leaf (T.pack substring) -- | Transcode a file to a unicode source. transcode :: B1.ByteString -> IO (Source Char) @@ -95,7 +95,7 @@ diffFiles parser renderer sourceBlobs = do pure $! renderer textDiff sourceBlobs where construct :: CofreeF (Syntax Text) (Both Info) (Diff Text Info) -> Diff Text Info construct (info :< syntax) = free (Free ((setCost <$> info <*> sumCost syntax) :< syntax)) - setCost info cost = info { cost = cost } + setCost info cost = setCost info cost sumCost = fmap getSum . foldMap (fmap Sum . getCost) getCost diff = case runFree diff of Free (info :< _) -> cost <$> info From 7d95cb48dc95a0e1206e51a15fdeb2fcc1936b55 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 13:30:48 -0400 Subject: [PATCH 46/53] Use the Cost & Size types in Info. --- src/Diffing.hs | 6 +++--- src/Info.hs | 6 +++--- src/TreeSitter.hs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Diffing.hs b/src/Diffing.hs index 2e163f0bb..2122891ca 100644 --- a/src/Diffing.hs +++ b/src/Diffing.hs @@ -39,7 +39,7 @@ lineByLineParser input = pure . cofree . root $ case foldl' annotateLeaves ([], where lines = actualLines input root children = let size = 1 + fromIntegral (length children) in - Info (Range 0 $ length input) (Other "program") size size :< Indexed children + Info (Range 0 $ length input) (Other "program") size (Cost (unSize size)) :< Indexed children leaf charIndex line = Info (Range charIndex $ charIndex + T.length line) (Other "program") 1 1 :< Leaf line annotateLeaves (accum, charIndex) line = (accum ++ [ leaf charIndex (toText line) ] @@ -55,7 +55,7 @@ breakDownLeavesByWord :: Source Char -> Term T.Text Info -> Term T.Text Info breakDownLeavesByWord source = cata replaceIn where replaceIn :: TermF T.Text Info (Term T.Text Info) -> Term T.Text Info - replaceIn (info :< syntax) = let size' = 1 + sum (size . extract <$> syntax') in cofree $ setCost (setSize info size') size' :< syntax' + replaceIn (info :< syntax) = let size' = 1 + sum (size . extract <$> syntax') in cofree $ setCost (setSize info size') (Cost (unSize size')) :< syntax' where syntax' = case (ranges, syntax) of (_:_:_, Leaf _) -> Indexed (makeLeaf info <$> ranges) _ -> syntax @@ -104,6 +104,6 @@ diffFiles parser renderer sourceBlobs = do -- | The sum of the node count of the diff’s patches. diffCostWithCachedTermSizes :: Diff a Info -> Integer -diffCostWithCachedTermSizes diff = case runFree diff of +diffCostWithCachedTermSizes diff = unCost $ case runFree diff of Free (info :< _) -> sum (cost <$> info) Pure patch -> sum (cost . extract <$> patch) diff --git a/src/Info.hs b/src/Info.hs index c9365853c..0ce267179 100644 --- a/src/Info.hs +++ b/src/Info.hs @@ -41,7 +41,7 @@ setCost' = setField -- | An annotation for a source file, including the source range and semantic -- | categories. -data Info = Info { characterRange :: !Range, category :: !Category, size :: !Integer, cost :: !Integer } +data Info = Info { characterRange :: !Range, category :: !Category, size :: !Size, cost :: !Cost } deriving (Eq, Show) setCharacterRange :: Info -> Range -> Info @@ -50,8 +50,8 @@ setCharacterRange info range = info { characterRange = range } setCategory :: Info -> Category -> Info setCategory info category = info { category = category } -setSize :: Info -> Integer -> Info +setSize :: Info -> Size -> Info setSize info size = info { size = size } -setCost :: Info -> Integer -> Info +setCost :: Info -> Cost -> Info setCost info cost = info { cost = cost } diff --git a/src/TreeSitter.hs b/src/TreeSitter.hs index b5b40f94b..50ba272ab 100644 --- a/src/TreeSitter.hs +++ b/src/TreeSitter.hs @@ -61,7 +61,7 @@ documentToTerm language document contents = alloca $ \ root -> do range <- pure $! Range { start = fromIntegral $ ts_node_p_start_char node, end = fromIntegral $ ts_node_p_end_char node } let size' = 1 + sum (size . extract <$> children) - let info = Info range (categoriesForLanguage language name) size' size' + let info = Info range (categoriesForLanguage language name) size' (Cost (unSize size')) pure $! termConstructor contents info children getChild node n out = do _ <- ts_node_p_named_child node n out From 1668e94427bff648a40df8d9d0593ff681ce270f Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 13:33:50 -0400 Subject: [PATCH 47/53] Define Info as a synonym over Data.Record. --- src/Diffing.hs | 6 +++-- src/Info.hs | 51 ++++++++++++++--------------------------- src/Parser.hs | 1 + src/TreeSitter.hs | 3 ++- test/AlignmentSpec.hs | 3 ++- test/DiffSummarySpec.hs | 5 ++-- test/InterpreterSpec.hs | 5 ++-- test/PatchOutputSpec.hs | 4 ++-- 8 files changed, 34 insertions(+), 44 deletions(-) diff --git a/src/Diffing.hs b/src/Diffing.hs index 2122891ca..ab38d1211 100644 --- a/src/Diffing.hs +++ b/src/Diffing.hs @@ -1,9 +1,11 @@ +{-# LANGUAGE FlexibleContexts #-} module Diffing where import Prologue hiding (fst, snd) import qualified Data.ByteString.Char8 as B1 import Data.Functor.Both import Data.Functor.Foldable +import Data.Record import qualified Data.Text as T import qualified Data.Text.ICU.Detect as Detect import qualified Data.Text.ICU.Convert as Convert @@ -39,8 +41,8 @@ lineByLineParser input = pure . cofree . root $ case foldl' annotateLeaves ([], where lines = actualLines input root children = let size = 1 + fromIntegral (length children) in - Info (Range 0 $ length input) (Other "program") size (Cost (unSize size)) :< Indexed children - leaf charIndex line = Info (Range charIndex $ charIndex + T.length line) (Other "program") 1 1 :< Leaf line + ((Range 0 $ length input) .: Other "program" .: size .: Cost (unSize size) .: RNil) :< Indexed children + leaf charIndex line = ((Range charIndex $ charIndex + T.length line) .: Other "program" .: 1 .: 1 .: RNil) :< Leaf line annotateLeaves (accum, charIndex) line = (accum ++ [ leaf charIndex (toText line) ] , charIndex + length line) diff --git a/src/Info.hs b/src/Info.hs index 0ce267179..e1ea5b0c5 100644 --- a/src/Info.hs +++ b/src/Info.hs @@ -13,45 +13,28 @@ newtype Cost = Cost { unCost :: Integer } type InfoFields = '[ Range, Category, Size, Cost ] -type Info' = Record InfoFields +type Info = Record InfoFields -characterRange' :: HasField fields Range => Record fields -> Range -characterRange' = getField +characterRange :: HasField fields Range => Record fields -> Range +characterRange = getField -setCharacterRange' :: SetField fields Range => Record fields -> Range -> Record fields -setCharacterRange' = setField +setCharacterRange :: SetField fields Range => Record fields -> Range -> Record fields +setCharacterRange = setField -category' :: HasField fields Category => Record fields -> Category -category' = getField +category :: HasField fields Category => Record fields -> Category +category = getField -setCategory' :: SetField fields Category => Record fields -> Category -> Record fields -setCategory' = setField +setCategory :: SetField fields Category => Record fields -> Category -> Record fields +setCategory = setField -size' :: HasField fields Size => Record fields -> Size -size' = getField +size :: HasField fields Size => Record fields -> Size +size = getField -setSize' :: SetField fields Size => Record fields -> Size -> Record fields -setSize' = setField +setSize :: SetField fields Size => Record fields -> Size -> Record fields +setSize = setField -cost' :: HasField fields Cost => Record fields -> Cost -cost' = getField +cost :: HasField fields Cost => Record fields -> Cost +cost = getField -setCost' :: SetField fields Cost => Record fields -> Cost -> Record fields -setCost' = setField - --- | An annotation for a source file, including the source range and semantic --- | categories. -data Info = Info { characterRange :: !Range, category :: !Category, size :: !Size, cost :: !Cost } - deriving (Eq, Show) - -setCharacterRange :: Info -> Range -> Info -setCharacterRange info range = info { characterRange = range } - -setCategory :: Info -> Category -> Info -setCategory info category = info { category = category } - -setSize :: Info -> Size -> Info -setSize info size = info { size = size } - -setCost :: Info -> Cost -> Info -setCost info cost = info { cost = cost } +setCost :: SetField fields Cost => Record fields -> Cost -> Record fields +setCost = setField diff --git a/src/Parser.hs b/src/Parser.hs index 9e04e07e5..44a9a9114 100644 --- a/src/Parser.hs +++ b/src/Parser.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE FlexibleContexts #-} module Parser where import Prologue hiding (Constructor) diff --git a/src/TreeSitter.hs b/src/TreeSitter.hs index 50ba272ab..6b8b11b87 100644 --- a/src/TreeSitter.hs +++ b/src/TreeSitter.hs @@ -1,6 +1,7 @@ module TreeSitter where import Prologue hiding (Constructor) +import Data.Record import Data.String import Category import Info @@ -61,7 +62,7 @@ documentToTerm language document contents = alloca $ \ root -> do range <- pure $! Range { start = fromIntegral $ ts_node_p_start_char node, end = fromIntegral $ ts_node_p_end_char node } let size' = 1 + sum (size . extract <$> children) - let info = Info range (categoriesForLanguage language name) size' (Cost (unSize size')) + let info = range .: (categoriesForLanguage language name) .: size' .: Cost (unSize size') .: RNil pure $! termConstructor contents info children getChild node n out = do _ <- ts_node_p_named_child node n out diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index 4f51ea2e7..1c8757f0e 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -11,6 +11,7 @@ import Data.Bifunctor.Join.Arbitrary () import Data.Functor.Both as Both import Data.List (nub) import Data.Monoid +import Data.Record import Data.String import Data.Text.Arbitrary () import Data.These @@ -258,7 +259,7 @@ align :: Both (Source.Source Char) -> ConstructibleFree (Patch (Term String Info align sources = PrettyDiff sources . fmap (fmap (getRange &&& identity)) . alignDiff sources . deconstruct info :: Int -> Int -> Info -info start end = Info (Range start end) StringLiteral 0 0 +info start end = Range start end .: StringLiteral .: 0 .: 0 .: RNil prettyDiff :: Both (Source.Source Char) -> [Join These (ConstructibleFree (SplitPatch (Term String Info)) Info)] -> PrettyDiff (SplitDiff String Info) prettyDiff sources = PrettyDiff sources . fmap (fmap ((getRange &&& identity) . deconstruct)) diff --git a/test/DiffSummarySpec.hs b/test/DiffSummarySpec.hs index e441255c2..18cd1410e 100644 --- a/test/DiffSummarySpec.hs +++ b/test/DiffSummarySpec.hs @@ -1,6 +1,7 @@ module DiffSummarySpec where import Prologue +import Data.Record import Data.String import Test.Hspec import Diff @@ -12,10 +13,10 @@ import Category import DiffSummary arrayInfo :: Info -arrayInfo = Info (rangeAt 0) ArrayLiteral 2 0 +arrayInfo = rangeAt 0 .: ArrayLiteral .: 2 .: 0 .: RNil literalInfo :: Info -literalInfo = Info (rangeAt 1) StringLiteral 1 0 +literalInfo = rangeAt 1 .: StringLiteral .: 1 .: 0 .: RNil testDiff :: Diff String Info testDiff = free $ Free (pure arrayInfo :< Indexed [ free $ Pure (Insert (cofree $ literalInfo :< Leaf "a")) ]) diff --git a/test/InterpreterSpec.hs b/test/InterpreterSpec.hs index 054ea511c..fb8422cee 100644 --- a/test/InterpreterSpec.hs +++ b/test/InterpreterSpec.hs @@ -2,6 +2,7 @@ module InterpreterSpec where import Prologue import Diff +import Data.Record import qualified Interpreter as I import Range import Syntax @@ -14,8 +15,8 @@ spec :: Spec spec = parallel $ describe "interpret" $ it "returns a replacement when comparing two unicode equivalent terms" $ - I.diffTerms (free . Free) ((==) `on` extract) diffCost (cofree (Info range StringLiteral 0 0 :< Leaf "t\776")) (cofree (Info range2 StringLiteral 0 0 :< Leaf "\7831")) `shouldBe` - free (Pure (Replace (cofree (Info range StringLiteral 0 0 :< Leaf "t\776")) (cofree (Info range2 StringLiteral 0 0 :< Leaf "\7831")))) + I.diffTerms (free . Free) ((==) `on` extract) diffCost (cofree ((range .: StringLiteral .: 0 .: 0 .: RNil) :< Leaf "t\776")) (cofree ((range2 .: StringLiteral .: 0 .: 0 .: RNil) :< Leaf "\7831")) `shouldBe` + free (Pure (Replace (cofree ((range .: StringLiteral .: 0 .: 0 .: RNil) :< Leaf "t\776")) (cofree ((range2 .: StringLiteral .: 0 .: 0 .: RNil) :< Leaf "\7831")))) where range = Range 0 2 diff --git a/test/PatchOutputSpec.hs b/test/PatchOutputSpec.hs index 612c49bd4..429d18e70 100644 --- a/test/PatchOutputSpec.hs +++ b/test/PatchOutputSpec.hs @@ -2,7 +2,7 @@ module PatchOutputSpec where import Prologue import Data.Functor.Both -import Info +import Data.Record import Range import Renderer.Patch import Source @@ -14,4 +14,4 @@ spec :: Spec spec = parallel $ describe "hunks" $ it "empty diffs have empty hunks" $ - hunks (free . Free $ pure (Info (Range 0 0) StringLiteral 1 0) :< Leaf "") (both (SourceBlob (fromList "") "abcde" "path2.txt" (Just defaultPlainBlob)) (SourceBlob (fromList "") "xyz" "path2.txt" (Just defaultPlainBlob))) `shouldBe` [Hunk {offset = pure 0, changes = [], trailingContext = []}] + hunks (free . Free $ pure (Range 0 0 .: StringLiteral .: 1 .: 0 .: RNil) :< Leaf "") (both (SourceBlob (fromList "") "abcde" "path2.txt" (Just defaultPlainBlob)) (SourceBlob (fromList "") "xyz" "path2.txt" (Just defaultPlainBlob))) `shouldBe` [Hunk {offset = pure 0, changes = [], trailingContext = []}] From 978c91cd59efe237f6332f07ba8ce8692acb6780 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 13:53:33 -0400 Subject: [PATCH 48/53] Fix setCost infinite looping. --- src/Diffing.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Diffing.hs b/src/Diffing.hs index ab38d1211..299dc76d6 100644 --- a/src/Diffing.hs +++ b/src/Diffing.hs @@ -97,7 +97,6 @@ diffFiles parser renderer sourceBlobs = do pure $! renderer textDiff sourceBlobs where construct :: CofreeF (Syntax Text) (Both Info) (Diff Text Info) -> Diff Text Info construct (info :< syntax) = free (Free ((setCost <$> info <*> sumCost syntax) :< syntax)) - setCost info cost = setCost info cost sumCost = fmap getSum . foldMap (fmap Sum . getCost) getCost diff = case runFree diff of Free (info :< _) -> cost <$> info From 03b8076f03a738f81e3edf10c1f19f1ab5ecccf9 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 13:56:45 -0400 Subject: [PATCH 49/53] Show the underlying Integer, not the Size. --- src/Renderer/Split.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index 564cb1bbd..e2abd4095 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -93,7 +93,7 @@ split diff blobs = TL.toStrict . renderHtml newtype Renderable a = Renderable a instance ToMarkup f => ToMarkup (Renderable (Source Char, Info, Syntax a (f, Range))) where - toMarkup (Renderable (source, info, syntax)) = (! A.data_ (stringValue (show (size info)))) . classifyMarkup (category info) $ case syntax of + toMarkup (Renderable (source, info, syntax)) = (! A.data_ (stringValue (show (unSize (size info))))) . classifyMarkup (category info) $ case syntax of Leaf _ -> span . string . toString $ slice (characterRange info) source Indexed children -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) children Fixed children -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) children @@ -120,7 +120,7 @@ instance ToMarkup (Renderable (Source Char, SplitDiff a Info)) where toMarkup (Renderable (source, diff)) = Prologue.fst $ iter (\ (info :< syntax) -> (toMarkup $ Renderable (source, info, syntax), characterRange info)) $ toMarkupAndRange <$> diff where toMarkupAndRange :: SplitPatch (Term a Info) -> (Markup, Range) toMarkupAndRange patch = let term@(info :< _) = runCofree $ getSplitTerm patch in - ((div ! A.class_ (splitPatchToClassName patch) ! A.data_ (stringValue (show (size info)))) . toMarkup $ Renderable (source, cofree term), characterRange info) + ((div ! A.class_ (splitPatchToClassName patch) ! A.data_ (stringValue (show (unSize (size info))))) . toMarkup $ Renderable (source, cofree term), characterRange info) instance ToMarkup a => ToMarkup (Renderable (Bool, Int, a)) where toMarkup (Renderable (hasChanges, num, line)) = From 0969e20f39ace0b5fc1a496fa29f67d213085104 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 14:05:38 -0400 Subject: [PATCH 50/53] Generalize hasChanges to arbitrary annotations. --- src/Alignment.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index 49fefc24a..01a4b3a5b 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -39,7 +39,7 @@ numberedRows = countUp (both 1 1) nextLineNumbers from row = modifyJoin (fromThese identity identity) (succ <$ row) <*> from -- | Determine whether a line contains any patches. -hasChanges :: SplitDiff leaf Info -> Bool +hasChanges :: SplitDiff leaf annotation -> Bool hasChanges = or . (True <$) -- | Align a Diff into a list of Join These SplitDiffs representing the (possibly blank) lines on either side. From 27c17b20fe75df96da50ed353b3c5309f6065d68 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 14:06:55 -0400 Subject: [PATCH 51/53] Generalize getRange to work over any Record with a Range. --- src/SplitDiff.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/SplitDiff.hs b/src/SplitDiff.hs index 2076b883f..30011fde6 100644 --- a/src/SplitDiff.hs +++ b/src/SplitDiff.hs @@ -1,5 +1,7 @@ +{-# LANGUAGE FlexibleContexts #-} module SplitDiff where +import Data.Record import Info import Range import Prologue @@ -17,7 +19,7 @@ getSplitTerm (SplitDelete a) = a getSplitTerm (SplitReplace a) = a -- | Get the range of a SplitDiff. -getRange :: SplitDiff leaf Info -> Range +getRange :: HasField fields Range => SplitDiff leaf (Record fields) -> Range getRange diff = characterRange $ case runFree diff of Free annotated -> headF annotated Pure patch -> extract (getSplitTerm patch) From 8b5dc227973bb227a015201d38e507074a2f4c1d Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 14:07:21 -0400 Subject: [PATCH 52/53] :fire: SetField (roll it into HasField). --- src/Data/Record.hs | 8 +------- src/Info.hs | 8 ++++---- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 1252e4f6a..03127f39a 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -22,8 +22,6 @@ infixr 0 .: -- | HasField enables indexing a Record by (phantom) type tags. class HasField (fields :: [*]) (field :: *) where getField :: Record fields -> field - -class SetField (fields :: [*]) (field :: *) where setField :: Record fields -> field -> Record fields @@ -33,14 +31,10 @@ class SetField (fields :: [*]) (field :: *) where instance {-# OVERLAPPABLE #-} HasField fields field => HasField (notIt ': fields) field where getField (RCons _ t) = getField t + setField (RCons h t) f = RCons h (setField t f) instance {-# OVERLAPPABLE #-} HasField (field ': fields) field where getField (RCons h _) = h - -instance {-# OVERLAPPABLE #-} SetField fields field => SetField (notIt ': fields) field where - setField (RCons h t) f = RCons h (setField t f) - -instance {-# OVERLAPPABLE #-} SetField (field ': fields) field where setField (RCons _ t) f = RCons f t diff --git a/src/Info.hs b/src/Info.hs index e1ea5b0c5..7c459f18a 100644 --- a/src/Info.hs +++ b/src/Info.hs @@ -18,23 +18,23 @@ type Info = Record InfoFields characterRange :: HasField fields Range => Record fields -> Range characterRange = getField -setCharacterRange :: SetField fields Range => Record fields -> Range -> Record fields +setCharacterRange :: HasField fields Range => Record fields -> Range -> Record fields setCharacterRange = setField category :: HasField fields Category => Record fields -> Category category = getField -setCategory :: SetField fields Category => Record fields -> Category -> Record fields +setCategory :: HasField fields Category => Record fields -> Category -> Record fields setCategory = setField size :: HasField fields Size => Record fields -> Size size = getField -setSize :: SetField fields Size => Record fields -> Size -> Record fields +setSize :: HasField fields Size => Record fields -> Size -> Record fields setSize = setField cost :: HasField fields Cost => Record fields -> Cost cost = getField -setCost :: SetField fields Cost => Record fields -> Cost -> Record fields +setCost :: HasField fields Cost => Record fields -> Cost -> Record fields setCost = setField From 87552e0ecb16fe7698b5b98e1b56c9bb1be0ab71 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Fri, 17 Jun 2016 14:07:35 -0400 Subject: [PATCH 53/53] Generalize Alignment over annotations with ranges. --- src/Alignment.hs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index 01a4b3a5b..c6bd1561a 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE RankNTypes, ScopedTypeVariables #-} +{-# LANGUAGE FlexibleContexts, RankNTypes, ScopedTypeVariables #-} module Alignment ( hasChanges , numberedRows @@ -18,6 +18,7 @@ import Data.Functor.Foldable (hylo) import Data.List (partition) import Data.Maybe (fromJust) import qualified Data.OrderedMap as Map +import Data.Record import Data.These import Diff import Info @@ -43,11 +44,11 @@ hasChanges :: SplitDiff leaf annotation -> Bool hasChanges = or . (True <$) -- | Align a Diff into a list of Join These SplitDiffs representing the (possibly blank) lines on either side. -alignDiff :: Show leaf => Both (Source Char) -> Diff leaf Info -> [Join These (SplitDiff leaf Info)] +alignDiff :: (Show leaf, Show (Record fields), HasField fields Range) => Both (Source Char) -> Diff leaf (Record fields) -> [Join These (SplitDiff leaf (Record fields))] alignDiff sources diff = iter (alignSyntax (runBothWith ((Join .) . These)) (free . Free) getRange sources) (alignPatch sources <$> diff) -- | Align the contents of a patch into a list of lines on the corresponding side(s) of the diff. -alignPatch :: forall leaf. Show leaf => Both (Source Char) -> Patch (Term leaf Info) -> [Join These (SplitDiff leaf Info)] +alignPatch :: forall fields leaf. (Show leaf, Show (Record fields), HasField fields Range) => Both (Source Char) -> Patch (Term leaf (Record fields)) -> [Join These (SplitDiff leaf (Record fields))] alignPatch sources patch = case patch of Delete term -> fmap (pure . SplitDelete) <$> alignSyntax' this (fst sources) term Insert term -> fmap (pure . SplitInsert) <$> alignSyntax' that (snd sources) term @@ -55,13 +56,13 @@ alignPatch sources patch = case patch of (alignSyntax' this (fst sources) term1) (alignSyntax' that (snd sources) term2) where getRange = characterRange . extract - alignSyntax' :: (forall a. Identity a -> Join These a) -> Source Char -> Term leaf Info -> [Join These (Term leaf Info)] + alignSyntax' :: (forall a. Identity a -> Join These a) -> Source Char -> Term leaf (Record fields) -> [Join These (Term leaf (Record fields))] alignSyntax' side source term = hylo (alignSyntax side cofree getRange (Identity source)) runCofree (Identity <$> term) this = Join . This . runIdentity that = Join . That . runIdentity -- | The Applicative instance f is either Identity or Both. Identity is for Terms in Patches, Both is for Diffs in unchanged portions of the diff. -alignSyntax :: (Applicative f, Show term) => (forall a. f a -> Join These a) -> (CofreeF (Syntax leaf) Info term -> term) -> (term -> Range) -> f (Source Char) -> CofreeF (Syntax leaf) (f Info) [Join These term] -> [Join These term] +alignSyntax :: (Applicative f, Show term, HasField fields Range) => (forall a. f a -> Join These a) -> (CofreeF (Syntax leaf) (Record fields) term -> term) -> (term -> Range) -> f (Source Char) -> CofreeF (Syntax leaf) (f (Record fields)) [Join These term] -> [Join These term] alignSyntax toJoinThese toNode getRange sources (infos :< syntax) = case syntax of Leaf s -> catMaybes $ wrapInBranch (const (Leaf s)) . fmap (flip (,) []) <$> sequenceL lineRanges Indexed children -> catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (join children) bothRanges