packages feed

fused-effects-lens (empty) → 0.1.0.0

raw patch · 7 files changed

+270/−0 lines, 7 filesdep +basedep +fused-effectsdep +fused-effects-lenssetup-changed

Dependencies added: base, fused-effects, fused-effects-lens, hspec, lens

Files

+ CHANGES.md view
@@ -0,0 +1,3 @@+# v0.1.0.0++Initial release.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Patrick Thomson (c) 2018++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 Patrick Thomson here nor the names of other+      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+OWNER 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.
+ README.md view
@@ -0,0 +1,40 @@+# fused-effects-lens++This package provides an interface to the [lens](github.com/ekmett/lens) library that is compatible with [fused-effects](github.com/robrix/fused-effects). The standard formulation of `lens` combinators for operating in `MonadState` contexts—`use`, `.=`, et al—rely on `mtl` for `MonadState` and `MonadReader`, which is not applicable to `Reader` and `State` effects.++This package is meant to be used alongside `lens`, like so:++``` haskell+import Control.Lens hiding (view, use, assign)+import Control.Effect.Lens (view, use, assign)+```++## Example++Given a `Context` type that we will use in a `State` effect:++``` haskell+data Context = Context+  { _amount :: Int+  , _disabled :: Bool+  } deriving (Eq, Show)+  +makeLenses ''Context+```++We can can use the `use` combinators to extract a lens target from the current state, and `assign` to write to a field of that state:++``` haskell+stateTest :: (Member (State Context) sig, Carrier sig m, Monad m) => m Int+stateTest = do+  initial <- use amount+  assign amount (initial + 1)+  assign disabled True+  use amount+```++You can find a more complete example, including one that works with multiple `State` constraints in a single computation, in the `test` directory.++## License++BSD3, like `fused-effects`.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ fused-effects-lens.cabal view
@@ -0,0 +1,36 @@+name:                fused-effects-lens+version:             0.1.0.0+synopsis:            Monadic lens combinators for fused-effects.+description:         Provides combinators for the lens-based manipulation of state and context types provided by the fused-effects library, similar to those provided for mtl-based monad transformers.+homepage:            https://github.com/patrickt/fused-effects-lens#readme+license:             BSD3+license-file:        LICENSE+author:              Patrick Thomson+maintainer:          patrickt@github.com+copyright:           2018 Patrick Thomson+category:            Web+build-type:          Simple+cabal-version:       >=1.10+extra-source-files:+  README.md+  CHANGES.md                 ++library+  hs-source-dirs:      src+  exposed-modules:     Control.Effect.Lens+  default-language:    Haskell2010+  build-depends:       base >= 4.7 && < 5+                     , lens >= 4   && < 5+                     , fused-effects >= 0.1.2 && < 1+  ghc-options:         -Wall++test-suite test+  type:                exitcode-stdio-1.0+  main-is:             Main.hs+  hs-source-dirs:      test+  default-language:    Haskell2010+  build-depends:       base >=4.7 && < 5.0+                     , fused-effects-lens+                     , lens >=4   && < 5+                     , fused-effects >= 0.1.2 && < 1+                     , hspec >= 2.4.1
+ src/Control/Effect/Lens.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE FlexibleContexts, RankNTypes #-}++module Control.Effect.Lens+  ( view+  , views+  , use+  , uses+  , assign+  , (.=)+  , modifying+  , (%=)+  ) where++import Control.Effect+import qualified Control.Effect.State as State+import qualified Control.Effect.Reader as Reader+import qualified Control.Lens as Lens+import Control.Lens.Getter (Getting)+import Control.Lens.Setter (ASetter)++-- | View the value pointed to by a 'Control.Lens.Getter.Getter',+-- 'Control.Lens.Iso.Iso' or 'Control.Lens.Lens.Lens' or the result of+-- folding over all the results of a 'Control.Lens.Fold.Fold' or+-- 'Control.Lens.Traversal.Traversal' corresponding to the 'Reader'+-- context of the given monadic carrier.+view :: forall r a sig m . (Member (Reader r) sig, Carrier sig m, Functor m) => Getting a r a -> m a+view l = Reader.asks (Lens.view l)+{-# INLINE view #-}++-- | View a function of the value pointed to by a+-- 'Control.Lens.Getter.Getter', 'Control.Lens.Iso.Iso' or+-- 'Control.Lens.Lens.Lens' or the result of folding over all the+-- results of a 'Control.Lens.Fold.Fold' or+-- 'Control.Lens.Traversal.Traversal' corresponding to the 'Reader'+-- context of the given monadic carrier.+-- +-- This is slightly more general in lens itself, but should suffice for our purposes.+views :: (Member (Reader s) sig, Carrier sig m, Functor m) => Getting a s a -> (a -> b) -> m b+views l f = fmap f (Reader.asks (Lens.view l))+{-# INLINE views #-}++-- | Extract the target of a 'Control.Lens.Lens',+-- 'Control.Lens.Iso.Iso', or 'Control.Lens.Getter.Getter' from the,+-- or use a summary of a 'Control.Lens.Fold.Fold' or+-- 'Control.Lens.Traversal.Traversal' that points to a monoidal value.+use :: forall s a sig m . (Member (State s) sig, Carrier sig m, Monad m) => Getting a s a -> m a+use l = State.gets (Lens.view l)+{-# INLINE use #-}++-- | Use a function of the target of a 'Control.Lens.Lens.Lens',+-- 'Control.Lens.Iso.Iso', or 'Control.Lens.Getter.Getter' in the+-- current state, or use a summary of a 'Control.Lens.Fold.Fold' or+-- 'Control.Lens.Traversal.Traversal' that points to a monoidal value.+uses :: (Carrier sig f, Functor f, Member (State s) sig) => Getting a s a -> (a -> b) -> f b+uses l f = fmap f (State.gets (Lens.view l))+{-# INLINE uses #-}++-- | Replace the target of a 'Control.Lens.Lens.Lens' or all of the+-- targets of a 'Control.Lens.Setter.Setter' or+-- 'Control.Lens.Traversal.Traversal' in our monadic state with a new+-- value, irrespective of the old.+--+-- This is a prefix version of '.='.+assign :: forall s a b sig m . (Member (State s) sig, Carrier sig m, Monad m) => ASetter s s a b -> b -> m ()+assign l b = State.modify (Lens.set l b)+{-# INLINE assign #-}++-- | Replace the target of a 'Control.Lens.Lens.Lens' or all of the+-- targets of a 'Control.Lens.Setter.Setter' or+-- 'Control.Lens.Traversal.Traversal' in our monadic state with a new+-- value, irrespective of the old.+--+-- This is an infix version of 'assign'.+infixr 4 .=+(.=) :: forall s a b sig m . (Member (State s) sig, Carrier sig m, Monad m) => ASetter s s a b -> b -> m ()+(.=) = assign+{-# INLINE (.=) #-}++-- | Map over the target of a 'Control.Lens.Lens.Lens' or all of the+-- targets of a 'Control.Lens.Setter.Setter' or+-- 'Control.Lens.Traversal.Traversal' in our monadic state.+--+-- This is a prefix version of '%='.+modifying :: forall s a b sig m . (Member (State s) sig, Carrier sig m, Monad m) => ASetter s s a b -> (a -> b) -> m ()+modifying l f = State.modify (Lens.over l f)+{-# INLINE modifying #-}++-- | Map over the target of a 'Control.Lens.Lens.Lens' or all of the+-- targets of a 'Control.Lens.Setter.Setter' or+-- 'Control.Lens.Traversal.Traversal' in our monadic state.+--+-- This is an infix version of 'modifying'.+infixr 4 %=+(%=) :: forall s a b sig m . (Member (State s) sig, Carrier sig m, Monad m) => ASetter s s a b -> (a -> b) -> m ()+(%=) = modifying+{-# INLINE (%=) #-}
+ test/Main.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE TypeApplications, FlexibleContexts, MultiParamTypeClasses, TemplateHaskell, TypeFamilies, FlexibleInstances #-}++module Main where++import Control.Effect+import Control.Effect.Reader+import Control.Effect.State+import Control.Lens.Wrapped+import Control.Lens.TH+import Test.Hspec++import Control.Effect.Lens++data Context = Context+  { _amount :: Int+  , _sweatshirt :: Bool+  } deriving (Eq, Show)++initial :: Context+initial = Context 0 False++makeLenses ''Context++stateTest :: (Member (State Context) sig, Carrier sig m, Monad m) => m Int+stateTest = do+  initial <- use amount+  assign amount (initial + 1)+  assign sweatshirt True+  use amount++newtype Foo = Foo { _unFoo :: Int } deriving (Eq, Show)++makeWrapped ''Foo++newtype Bar = Bar { _unBar :: Float } deriving (Eq, Show)++makeWrapped ''Bar++doubleStateTest :: (Member (State Bar) sig, Member (State Foo) sig, Carrier sig m, Monad m) => m Int+doubleStateTest = do+  assign @Foo _Wrapped 5+  assign @Bar _Wrapped 30.5+  pure 50++readerTest :: (Member (Reader Context) sig, Carrier sig m, Monad m) => m Int+readerTest = succ <$> view amount++spec :: Spec+spec = describe "use/assign" $ do+  it "should modify stateful variables" $ do+    let result = run $ runState initial stateTest+    result `shouldBe` (Context 1 True, 1)++  it "works in the presence of polymorphic lenses with -XTypeAnnotations" $ do+    let result = run $ runState (Bar 5) $ runState (Foo 500) doubleStateTest+    result `shouldBe` (Bar 30.5, (Foo 5, 50))++  it "should read from an environment" $ do+    let result = run $ runReader initial readerTest+    result `shouldBe` 1++main :: IO ()+main = hspec $ describe "Control.Effect.Lens" spec