-
Notifications
You must be signed in to change notification settings - Fork 149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP add a couple of types for working with patches #357
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
{-# LANGUAGE FlexibleContexts #-} | ||
{-# LANGUAGE FlexibleInstances #-} | ||
{-# LANGUAGE LambdaCase #-} | ||
{-# LANGUAGE MultiParamTypeClasses #-} | ||
{-# LANGUAGE PolyKinds #-} | ||
{-# LANGUAGE Rank2Types #-} | ||
{-# LANGUAGE ScopedTypeVariables #-} | ||
{-# LANGUAGE StandaloneDeriving #-} | ||
{-# LANGUAGE TypeApplications #-} | ||
{-# LANGUAGE TypeFamilies #-} | ||
{-# LANGUAGE UndecidableInstances #-} | ||
|
||
{-# OPTIONS_GHC -Wall #-} | ||
|
||
-- | 'Patch'es on 'DMap' that consist only of insertions (or overwrites) and deletions. | ||
module Reflex.Patch.DMapWithReset where | ||
|
||
import Reflex.Patch.Class | ||
|
||
import Data.Dependent.Map (DMap, GCompare (..)) | ||
import qualified Data.Dependent.Map as DMap | ||
import Data.Semigroup (Semigroup (..)) | ||
import Data.Constraint.Extras | ||
|
||
-- | A set of changes to a 'DMap'. Any element may be inserted/updated or deleted. | ||
-- Insertions are represented as @'ComposeMaybe' (Just value)@, | ||
-- while deletions are represented as @'ComposeMaybe' Nothing@. | ||
newtype PatchDMapWithReset k p = PatchDMapWithReset { unPatchDMapWithReset :: DMap k (By p) } | ||
|
||
-- | Holds the information about each key: where its new value should come from, | ||
-- and where its old value should go to | ||
data By p a | ||
= By_Insert (PatchTarget (p a)) -- ^ Insert the given value here | ||
| By_Delete -- ^ Delete the existing value, if any, from here | ||
| By_Patch (p a) -- ^ Patch the value here with the given patch | ||
|
||
instance (Semigroup (p a), Patch (p a)) => Semigroup (By p a) where | ||
x@(By_Insert _) <> _ = x | ||
By_Delete <> _ = By_Delete | ||
By_Patch x <> By_Insert y = By_Insert (applyAlways x y) | ||
By_Patch x <> By_Patch y = By_Patch (x <> y) | ||
By_Patch _ <> By_Delete = By_Delete | ||
|
||
instance (Monoid (p a), Patch (p a)) => Monoid (By p a) where | ||
mappend = (<>) | ||
mempty = By_Patch mempty | ||
|
||
instance | ||
( GCompare k | ||
, Has' Semigroup k p | ||
, Has' Patch k p | ||
) | ||
=> Semigroup (PatchDMapWithReset k p) where | ||
PatchDMapWithReset xs <> PatchDMapWithReset ys = PatchDMapWithReset $ DMap.unionWithKey | ||
(\k -> has' @Patch @p k | ||
$ has' @Semigroup @p k | ||
$ (<>)) xs ys | ||
|
||
instance | ||
( GCompare k | ||
, Has' Semigroup k p | ||
, Has' Patch k p | ||
) | ||
=> Monoid (PatchDMapWithReset k p) where | ||
mappend = (<>) | ||
mempty = PatchDMapWithReset DMap.empty | ||
|
||
class (Patch (p a), PatchTarget (p a) ~ Patches1LocallyTarget p a) => Patches1Locally p a where | ||
type Patches1LocallyTarget p :: k -> * | ||
|
||
data These1 f g x | ||
= This1 (f x) | ||
| That1 (g x) | ||
| These1 (f x) (g x) | ||
|
||
mergeWithKey | ||
:: forall k v1 v2 v. | ||
(GCompare k) | ||
=> (forall x. k x -> v1 x -> Maybe (v x)) | ||
-> (forall x. k x -> v2 x -> Maybe (v x)) | ||
-> (forall x. k x -> v1 x -> v2 x -> Maybe (v x)) | ||
-> DMap k v1 -> DMap k v2 -> DMap k v | ||
mergeWithKey f g fg = \xs ys -> DMap.mapMaybeWithKey onlyThat $ DMap.unionWithKey doIt (DMap.map This1 xs) (DMap.map That1 ys) | ||
where | ||
doIt _ (This1 xs) (That1 ys) = These1 xs ys | ||
doIt _ _ _ = error "mergeWithKey misalligned keys" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
onlyThat :: forall x. k x -> These1 v1 v2 x -> Maybe (v x) | ||
onlyThat k = \case | ||
This1 xs -> f k xs | ||
That1 ys -> g k ys | ||
These1 xs ys -> fg k xs ys | ||
{-# INLINE mergeWithKey #-} | ||
|
||
-- | Apply the insertions or deletions to a given 'DMap'. | ||
instance (GCompare k, Has (Patches1Locally p) k) => Patch (PatchDMapWithReset k p) where | ||
|
||
type PatchTarget (PatchDMapWithReset k p) = DMap k (Patches1LocallyTarget p) | ||
|
||
apply = go | ||
where | ||
go :: PatchDMapWithReset k p -> DMap k (Patches1LocallyTarget p) -> Maybe (DMap k (Patches1LocallyTarget p)) | ||
go (PatchDMapWithReset diff) old = Just $! mergeWithKey (\_ -> Just) inserts updates old diff | ||
where | ||
updates :: forall x. k x -> Patches1LocallyTarget p x -> By p x -> Maybe (Patches1LocallyTarget p x) | ||
updates k ys = has @(Patches1Locally p) k $ \case | ||
By_Insert x -> Just x | ||
By_Delete -> Nothing | ||
By_Patch x -> Just $ applyAlways x ys | ||
|
||
inserts :: forall x. k x -> By p x -> Maybe (Patches1LocallyTarget p x) | ||
inserts k = has @(Patches1Locally p) k $ \case | ||
By_Insert x -> Just x | ||
By_Delete -> Nothing | ||
By_Patch _ -> Nothing | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What to do when the patch is invalid? Is it better to call error (which seems kind of ugly... )? The use of Maybe as documented seems to be more about Patches which don't cause any updates, rather than for error handling - I guess this is the only sensible thing to do though? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when is a patch invalid? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For example when applying a patch "By_Patch" to a key which doesn't exist... |
||
|
||
deriving instance (Patch (p a), Eq (p a), Eq (PatchTarget (p a))) => Eq (By p a) | ||
deriving instance (Patch (p a), Show (p a), Show (PatchTarget (p a))) => Show (By p a) | ||
deriving instance (Eq (DMap k (By p))) => Eq (PatchDMapWithReset k p) | ||
deriving instance (Show (DMap k (By p))) => Show (PatchDMapWithReset k p) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{-# LANGUAGE DeriveGeneric #-} | ||
{-# LANGUAGE FlexibleContexts #-} | ||
{-# LANGUAGE LambdaCase #-} | ||
{-# LANGUAGE StandaloneDeriving #-} | ||
{-# LANGUAGE TypeFamilies #-} | ||
|
||
-- The derived instances are undecidable in the case of a pathological instance like | ||
-- instance Patch x where | ||
-- type PatchTarget x = Patchable x | ||
{-# LANGUAGE UndecidableInstances #-} | ||
|
||
module Reflex.Patch.Patchable where | ||
|
||
-- import Data.Aeson | ||
import GHC.Generics | ||
import Reflex.Patch | ||
|
||
-- | Like SemiMap/PartialMap but for anything patchable | ||
data Patchable p | ||
= Patchable_Patch p | ||
| Patchable_Complete (PatchTarget p) | ||
deriving (Generic) | ||
|
||
completePatchable :: Patchable p -> Maybe (PatchTarget p) | ||
completePatchable = \case | ||
Patchable_Complete t -> Just t | ||
Patchable_Patch _ -> Nothing | ||
|
||
deriving instance (Eq p, Eq (PatchTarget p)) => Eq (Patchable p) | ||
deriving instance (Ord p, Ord (PatchTarget p)) => Ord (Patchable p) | ||
deriving instance (Show p, Show (PatchTarget p)) => Show (Patchable p) | ||
deriving instance (Read p, Read (PatchTarget p)) => Read (Patchable p) | ||
-- instance (ToJSON p, ToJSON (PatchTarget p)) => ToJSON (Patchable p) | ||
-- instance (FromJSON p, FromJSON (PatchTarget p)) => FromJSON (Patchable p) | ||
|
||
instance (Monoid p, Patch p) => Monoid (Patchable p) where | ||
mempty = Patchable_Patch mempty | ||
mappend = (<>) | ||
|
||
instance (Semigroup p, Patch p) => Semigroup (Patchable p) where | ||
(<>) = curry $ \case | ||
(Patchable_Patch a, Patchable_Patch b) -> Patchable_Patch $ a <> b | ||
(Patchable_Patch a, Patchable_Complete b) -> Patchable_Complete $ applyAlways a b | ||
(Patchable_Complete a, _) -> Patchable_Complete a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the connection with ComposeMaybe? Leftover from vanilla PatchDMap?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name "WithReset" - what is "Reset" in this case?
I think there's a need for this type (also for normal Map), I've seen a few instances of this type in different places...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also didn't know what
Reset
meant. Even if it has some precedence somewhere out there, the fact that none of us understand it means we should probably change it.