|
@@ -1,54 +1,80 @@
|
|
|
{-# LANGUAGE RankNTypes #-}
|
|
|
{-# LANGUAGE LambdaCase #-}
|
|
|
+{-# LANGUAGE DeriveFunctor #-}
|
|
|
|
|
|
-module Codec.ActivityStream.LensInternal where
|
|
|
+-- Okay, I'm gonna justify this in a comment: I will never, under any
|
|
|
+-- circumstances, build a library that has an explicit `lens` dependency.
|
|
|
+-- I think `lens` is awesome, but it is also a giant package, and I
|
|
|
+-- don't want to inflict it on end-users who might not want it.
|
|
|
+
|
|
|
+-- I am more okay with lens-family-core, but in this case, all I need
|
|
|
+-- is really the `makeLens` function, which (not counting whitespace
|
|
|
+-- and comments) is three lines of code. Three lines! And it doesn't make
|
|
|
+-- sense to drag in a whole extra package when I can just copy this
|
|
|
+-- in.
|
|
|
+
|
|
|
+-- There's also reimplementations of `get` and `set` for possible internal
|
|
|
+-- use---three lines each, for a total of nine. Nine lines of
|
|
|
+-- easily-copyable, verifiable boilerplate. Instead of another dependency
|
|
|
+-- that must be downloaded and installed and managed by Cabal and
|
|
|
+-- addressed in constraint-solving...
|
|
|
+
|
|
|
+-- And that is why this module reimplement a few `lens` functions.
|
|
|
+
|
|
|
+module Codec.ActivityStream.LensInternal
|
|
|
+ ( get
|
|
|
+ , set
|
|
|
+ , Lens'
|
|
|
+ , makeLens
|
|
|
+ , makeAesonLensMb
|
|
|
+ , makeAesonLens
|
|
|
+ ) where
|
|
|
|
|
|
import Data.Aeson as Aeson
|
|
|
import qualified Data.HashMap.Strict as HM
|
|
|
import Data.Maybe (fromJust)
|
|
|
import Data.Text (Text)
|
|
|
|
|
|
-newtype Const a b = Const { fromConst :: a }
|
|
|
-instance Functor (Const a) where fmap f (Const x) = Const x
|
|
|
-
|
|
|
-- We need these to write get and set
|
|
|
-newtype Id a = Id { fromId :: a }
|
|
|
-instance Functor Id where fmap f (Id x) = Id (f x)
|
|
|
+newtype C a b = C { fromC :: a } deriving (Functor)
|
|
|
+newtype I a = I { fromI :: a } deriving (Functor)
|
|
|
|
|
|
+-- This is the same type alias as in @Control.Lens@, and so can be used
|
|
|
+-- anywhere lenses are needed.
|
|
|
type Lens' a b = forall f. Functor f => (b -> f b) -> (a -> f a)
|
|
|
|
|
|
get :: Lens' a b -> a -> b
|
|
|
-get lens a = fromConst (lens Const a)
|
|
|
+get lens a = fromC (lens C a)
|
|
|
|
|
|
set :: Lens' a b -> b -> a -> a
|
|
|
-set lens x a = fromId (lens (const Id x) a)
|
|
|
+set lens x a = fromI (lens (const I x) a)
|
|
|
|
|
|
makeLens :: (a -> b) -> (b -> a -> a) -> Lens' a b
|
|
|
makeLens get set f a = (`set` a) `fmap` f (get a)
|
|
|
|
|
|
+-- This is necessary because of the way we store values as Aeson
|
|
|
+-- values underneath.
|
|
|
fromJSON' :: FromJSON a => Aeson.Value -> Maybe a
|
|
|
fromJSON' v = case fromJSON v of
|
|
|
Success a -> Just a
|
|
|
Error _ -> Nothing
|
|
|
|
|
|
-- Create a lens into an Aeson object wrapper that takes and
|
|
|
+-- returns a Maybe value. When used as a setter, it can either
|
|
|
+-- insert a value in, or delete it from the object (if it is
|
|
|
+-- used with Nothing.)
|
|
|
makeAesonLensMb :: (FromJSON v, ToJSON v)
|
|
|
=> Text -> Lens' c Aeson.Object -> Lens' c (Maybe v)
|
|
|
-makeAesonLensMb key fromObj = fromObj . lens
|
|
|
- where lens = makeLens
|
|
|
- (\ o -> HM.lookup key o >>= fromJSON')
|
|
|
- (\case Just v -> HM.insert key (toJSON v)
|
|
|
- Nothing -> HM.delete key)
|
|
|
+makeAesonLensMb key fromObj = fromObj . makeLens g s
|
|
|
+ where g o = HM.lookup key o >>= fromJSON'
|
|
|
+ s (Just v) o = HM.insert key (toJSON v) o
|
|
|
+ s Nothing o = HM.delete key o
|
|
|
|
|
|
|
|
|
+-- Create a lens into an Aeson object wrapper. This will fail if
|
|
|
+-- the object does not contain the relevant key.
|
|
|
makeAesonLens :: (FromJSON v, ToJSON v)
|
|
|
=> Text -> Lens' c Aeson.Object -> Lens' c v
|
|
|
-makeAesonLens key fromObj = fromObj . lens
|
|
|
- where lens = makeLens
|
|
|
- (\ o -> fromJust (HM.lookup key o >>= fromJSON'))
|
|
|
- (\ v o -> HM.insert key (toJSON v) o)
|
|
|
+makeAesonLens key fromObj = fromObj . makeLens g s
|
|
|
+ where g o = fromJust (HM.lookup key o >>= fromJSON')
|
|
|
+ s v o = HM.insert key (toJSON v) o
|