diff --git a/core/nyan-interpolation-core.cabal b/core/nyan-interpolation-core.cabal index 833a1d9..4a57190 100644 --- a/core/nyan-interpolation-core.cabal +++ b/core/nyan-interpolation-core.cabal @@ -32,9 +32,7 @@ library Text.Interpolation.Nyan.Core.Internal.Processor Text.Interpolation.Nyan.Core.Internal.RMode Text.Interpolation.Nyan.Core.Internal.Splice - Text.Interpolation.Nyan.Lens Text.Interpolation.Nyan.Lens.TH - Text.Interpolation.Nyan.Lens.Type Text.Interpolation.Nyan.RModes.Buildable Text.Interpolation.Nyan.RModes.CommonExtra Text.Interpolation.Nyan.RModes.Show @@ -142,6 +140,7 @@ test-suite nyan-interpolation-core-tests , base <4.17 , fmt , megaparsec + , microlens , mtl , nyan-interpolation-core , tasty diff --git a/core/package.yaml b/core/package.yaml index d8ddd09..99f2d15 100644 --- a/core/package.yaml +++ b/core/package.yaml @@ -37,6 +37,7 @@ tests: <<: *test-common dependencies: - HUnit + - microlens - nyan-interpolation-core - tasty - tasty-hunit-compat diff --git a/core/src/Text/Interpolation/Nyan/Core/Internal/Base.hs b/core/src/Text/Interpolation/Nyan/Core/Internal/Base.hs index a84ee47..709f745 100644 --- a/core/src/Text/Interpolation/Nyan/Core/Internal/Base.hs +++ b/core/src/Text/Interpolation/Nyan/Core/Internal/Base.hs @@ -10,7 +10,7 @@ import Data.Monoid (Endo (..)) import Data.Text (Text) import Language.Haskell.TH (ExpQ) -import Text.Interpolation.Nyan.Lens (makeLenses) +import Text.Interpolation.Nyan.Lens.TH (makeLenses) {- $setup diff --git a/core/src/Text/Interpolation/Nyan/Lens.hs b/core/src/Text/Interpolation/Nyan/Lens.hs deleted file mode 100644 index f0c8b8a..0000000 --- a/core/src/Text/Interpolation/Nyan/Lens.hs +++ /dev/null @@ -1,52 +0,0 @@ --- SPDX-FileCopyrightText: 2022 Serokell --- --- SPDX-License-Identifier: MPL-2.0 - -module Text.Interpolation.Nyan.Lens - ( module Text.Interpolation.Nyan.Lens.TH - , module Text.Interpolation.Nyan.Lens.Type - , (^.) - - , (%~) - , (%=) - - , (.~) - , (.=) - - , (?~) - , (?=) - - , (&~) - ) - where - -import Control.Monad.State (MonadState, State, execState, modify) -import Control.Applicative (Const(..)) -import Data.Functor.Identity (Identity(..)) - -import Text.Interpolation.Nyan.Lens.TH -import Text.Interpolation.Nyan.Lens.Type - -(^.) :: s -> Getting a s a -> a -s ^. l = getConst $ l Const s - -(%~) :: ASetter s t a b -> (a -> b) -> s -> t -l %~ f = runIdentity . l (Identity . f) - -(%=) :: MonadState s m => ASetter s s a b -> (a -> b) -> m () -l %= f = modify (l %~ f) - -(.~) :: ASetter s t a b -> b -> s -> t -l .~ b = runIdentity . l (const $ Identity b) - -(.=) :: MonadState s m => ASetter s s a b -> b -> m () -l .= b = modify (l .~ b) - -(?~) :: ASetter s t a (Maybe b) -> b -> s -> t -l ?~ b = l .~ (Just b) - -(?=) :: MonadState s m => ASetter s s a (Maybe b) -> b -> m () -l ?= b = modify (l ?~ b) - -(&~) :: s -> State s a -> s -s &~ l = execState l s diff --git a/core/src/Text/Interpolation/Nyan/Lens/TH.hs b/core/src/Text/Interpolation/Nyan/Lens/TH.hs index b361a43..dc35ca2 100644 --- a/core/src/Text/Interpolation/Nyan/Lens/TH.hs +++ b/core/src/Text/Interpolation/Nyan/Lens/TH.hs @@ -7,7 +7,7 @@ module Text.Interpolation.Nyan.Lens.TH ) where -import Control.Monad ((<=<)) +import Control.Monad (forM) import Language.Haskell.TH -- | Information about the record field the lenses will operate on. @@ -15,7 +15,12 @@ type RecordFieldInfo = (Name, Strict, Type) -- | Given a record datatype, derives lenses for all of its fields. makeLenses :: Name -> Q [Dec] -makeLenses = mapM deriveLens <=< extractRecordFields +makeLenses datatype = do + fields <- extractRecordFields datatype + fmap concat $ forM fields \field -> do + sig <- deriveLensSignature datatype field + body <- deriveLensBody field + return [sig, body] extractRecordFields :: Name -> Q [RecordFieldInfo] extractRecordFields datatype = do @@ -25,29 +30,41 @@ extractRecordFields datatype = do TyConI (DataD _ _ _ _ [RecC _ fs] _) -> fs TyConI (NewtypeD _ _ _ _ (RecC _ fs) _) -> fs TyConI (DataD _ _ _ _ [_] _) -> - error $ "Can't derive lenses without record selectors: " ++ datatypeStr + fail $ "Can't derive lenses without record selectors: " ++ datatypeStr TyConI NewtypeD{} -> - error $ "Can't derive lenses without record selectors: " ++ datatypeStr + fail $ "Can't derive lenses without record selectors: " ++ datatypeStr TyConI TySynD{} -> - error $ "Can't derive lenses for type synonym: " ++ datatypeStr + fail $ "Can't derive lenses for type synonym: " ++ datatypeStr TyConI DataD{} -> - error $ "Can't derive lenses for a sum type: " ++ datatypeStr + fail $ "Can't derive lenses for a sum type: " ++ datatypeStr _ -> - error $ "Can't derive lenses for: " ++ datatypeStr + fail $ "Can't derive lenses for: " ++ datatypeStr ++ ", type name required." +mkLensName :: Name -> Name +mkLensName = mkName . (<> "L") . nameBase + +deriveLensSignature :: Name -> RecordFieldInfo -> Q Dec +deriveLensSignature datatype (fieldName, _, fieldType) = + sigD (mkLensName fieldName) + [t|forall f. Functor f => ($field -> f $field) + -> $record -> f $record + |] + where + field = return fieldType + record = conT datatype + -- | Given a record field name, -- produces a single function declaration: -- @lensName f a = (\x -> a { field = x }) `fmap` f (field a)@ -deriveLens :: RecordFieldInfo -> Q Dec -deriveLens (fieldName, _, _) = funD lensName [defLine] +deriveLensBody :: RecordFieldInfo -> Q Dec +deriveLensBody (fieldName, _, _) = funD (mkLensName fieldName) [defLine] where - lensName = mkName $ (nameBase fieldName) <> "L" a = mkName "a" f = mkName "f" defLine = clause pats (normalB body) [] pats = [varP f, varP a] body = [| (\x -> $(record a fieldName [|x|])) - `fmap` $(appE (varE f) (appE (varE fieldName) (varE a))) + `fmap` $(varE f `appE` (varE fieldName `appE` varE a)) |] record rec fld val = val >>= \v -> recUpdE (varE rec) [return (fld, v)] diff --git a/core/src/Text/Interpolation/Nyan/Lens/Type.hs b/core/src/Text/Interpolation/Nyan/Lens/Type.hs deleted file mode 100644 index d5c28f9..0000000 --- a/core/src/Text/Interpolation/Nyan/Lens/Type.hs +++ /dev/null @@ -1,17 +0,0 @@ --- SPDX-FileCopyrightText: 2022 Serokell --- --- SPDX-License-Identifier: MPL-2.0 - -module Text.Interpolation.Nyan.Lens.Type where - -import Control.Applicative (Const) -import Data.Functor.Identity (Identity) - -type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t -type Lens' s a = Lens s s a a - -type ASetter s t a b = (a -> Identity b) -> s -> Identity t -type ASetter' s a = ASetter s s a a - -type Getting r s a = (a -> Const r a) -> s -> Const r s -type SimpleGetter s a = forall r. Getting r s a diff --git a/core/tests/Test/Lens.hs b/core/tests/Test/Lens.hs index c54286d..b282877 100644 --- a/core/tests/Test/Lens.hs +++ b/core/tests/Test/Lens.hs @@ -4,10 +4,11 @@ module Test.Lens where +import Lens.Micro import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) -import Text.Interpolation.Nyan.Lens +import Text.Interpolation.Nyan.Lens.TH (makeLenses) data Pair = Pair { first :: Int, second :: String } deriving stock (Show, Eq) @@ -38,20 +39,6 @@ test_makeLenses = testGroup "Lenses produced by 'makeLenses' work as expected" single & (valueL ?~ "Some value") @?= Single (Just "Some value") ] - - , testGroup "Operators leveraging 'MonadState', 'State' work as expected" - [ testCase "(&~) and (.=) work" do - (pair &~ do firstL .= 102) - @?= Pair 102 "Hundred" - - , testCase "(&~) and (?=) work" do - (single &~ do valueL ?= "Some value") - @?= Single (Just "Some value") - - , testCase "(&~) and (%=) work" do - (single &~ do valueL %= (const $ Just "Some value")) - @?= Single (Just "Some value") - ] ] where a & f = f a