capability 0.1.0.0 → 0.2.0.0
raw patch · 8 files changed
+87/−45 lines, 8 filesdep ~generic-lensPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: generic-lens
API changes (from Hackage documentation)
- Capability.Stream: instance forall k (tag :: k) a (m :: * -> *). Capability.Writer.HasWriter tag (Data.DList.DList a) m => Capability.Stream.HasStream tag a (Capability.Stream.StreamDList m)
- Capability.Writer: WriterLog :: m a -> WriterLog m a
- Capability.Writer: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Capability.Writer.WriterLog m)
- Capability.Writer: instance Control.Monad.Primitive.PrimMonad m => Control.Monad.Primitive.PrimMonad (Capability.Writer.WriterLog m)
- Capability.Writer: instance GHC.Base.Applicative m => GHC.Base.Applicative (Capability.Writer.WriterLog m)
- Capability.Writer: instance GHC.Base.Functor m => GHC.Base.Functor (Capability.Writer.WriterLog m)
- Capability.Writer: instance GHC.Base.Monad m => GHC.Base.Monad (Capability.Writer.WriterLog m)
- Capability.Writer: newtype WriterLog m a
- Capability.Writer: tell_ :: HasWriter tag w m => Proxy# tag -> w -> m ()
+ Capability.Stream: StreamLog :: m a -> StreamLog m
+ Capability.Stream: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Capability.Stream.StreamLog m)
+ Capability.Stream: instance Control.Monad.Primitive.PrimMonad m => Control.Monad.Primitive.PrimMonad (Capability.Stream.StreamLog m)
+ Capability.Stream: instance GHC.Base.Applicative m => GHC.Base.Applicative (Capability.Stream.StreamLog m)
+ Capability.Stream: instance GHC.Base.Functor m => GHC.Base.Functor (Capability.Stream.StreamLog m)
+ Capability.Stream: instance GHC.Base.Monad m => GHC.Base.Monad (Capability.Stream.StreamLog m)
+ Capability.Stream: instance forall k (tag :: k) a (m :: * -> *). Capability.Stream.HasStream tag (Data.DList.DList a) m => Capability.Stream.HasStream tag a (Capability.Stream.StreamDList m)
+ Capability.Stream: instance forall k w (tag :: k) (m :: * -> *). (GHC.Base.Monoid w, Capability.State.Internal.Class.HasState tag w m) => Capability.Stream.HasStream tag w (Capability.Stream.StreamLog m)
+ Capability.Stream: newtype StreamLog m (a :: *)
+ Capability.Writer: StreamLog :: m a -> StreamLog m
+ Capability.Writer: newtype StreamLog m (a :: *)
+ Capability.Writer: type WriterLog = StreamLog
- Capability.Writer: class (Monoid w, Monad m) => HasWriter (tag :: k) (w :: *) (m :: * -> *) | tag m -> w
+ Capability.Writer: class (Monoid w, Monad m, HasStream tag w m) => HasWriter (tag :: k) (w :: *) (m :: * -> *) | tag m -> w
Files
- ChangeLog.md +7/−0
- README.md +2/−5
- capability.cabal +2/−2
- examples/WordCount.hs +13/−13
- examples/Writer.hs +7/−5
- src/Capability/Stream.hs +34/−3
- src/Capability/Writer.hs +22/−15
- src/Capability/Writer/Discouraged.hs +0/−2
ChangeLog.md view
@@ -1,5 +1,12 @@ # Revision history for capability +## 0.2.0.0 -- 2019-03-22++* Make HasStream a superclass of HasWriter.+ See [#64](https://github.com/tweag/capability/pull/64)+* Bumped version bounds on generic-lens.+ See [#67](https://github.com/tweag/capability/pull/67)+ ## 0.1.0.0 -- 2018-10-04 * Initial release.
README.md view
@@ -90,11 +90,8 @@ For more complex examples, see the [Examples section](#examples) and the [`examples` subtree](./examples). -This package is not available on Hackage yet, as some of its-dependencies have not been updated to GHC 8.6, yet.--API documentation can be found in the artifacts tab of any successful-build in the [CircleCI project][circleci].+API documentation can be found on+[Hackage](http://hackage.haskell.org/package/capability). [circleci]: https://circleci.com/gh/tweag/capabilities-via/tree/master [mtl]: http://hackage.haskell.org/package/mtl
capability.cabal view
@@ -1,5 +1,5 @@ name: capability-version: 0.1.0.0+version: 0.2.0.0 homepage: https://github.com/tweag/capability license: BSD3 license-file: LICENSE@@ -47,7 +47,7 @@ base >= 4.12 && < 5.0 , dlist >= 0.8 && < 0.9 , exceptions >= 0.6 && < 0.11- , generic-lens >= 1.0 && < 1.1+ , generic-lens >= 1.0 && < 1.2 , lens >= 4.16 && < 5.0 , monad-control >= 1.0 && < 1.1 , monad-unlift >= 0.2 && < 0.3
examples/WordCount.hs view
@@ -14,7 +14,7 @@ import Capability.Reader import Capability.State-import Capability.Writer+import Capability.Stream import Control.Lens (ifor_) import Data.Coerce (coerce) import Data.Map.Strict (Map)@@ -56,21 +56,21 @@ -- | Count the occurrence of a single letter. countLetter ::- HasWriter "letterCount" (Occurrences Char) m+ HasStream "letterCount" (Occurrences Char) m => Char -> m ()-countLetter letter = tell @"letterCount" (oneOccurrence letter)+countLetter letter = yield @"letterCount" (oneOccurrence letter) -- | Count the occurrence of a single word. countWord ::- HasWriter "wordCount" (Occurrences Text) m+ HasStream "wordCount" (Occurrences Text) m => Text -> m ()-countWord word = tell @"wordCount" (oneOccurrence word)+countWord word = yield @"wordCount" (oneOccurrence word) -- | Count the occurrence of a single word and all the letters in it. countWordAndLetters ::- ( HasWriter "letterCount" (Occurrences Char) m- , HasWriter "wordCount" (Occurrences Text) m )+ ( HasStream "letterCount" (Occurrences Char) m+ , HasStream "wordCount" (Occurrences Text) m ) => Text -> m () countWordAndLetters word = do countWord word@@ -80,8 +80,8 @@ -- | Count the occurrences of words and letters in a text, -- excluding white space. countWordsAndLettersInText ::- ( HasWriter "letterCount" (Occurrences Char) m- , HasWriter "wordCount" (Occurrences Text) m )+ ( HasStream "letterCount" (Occurrences Char) m+ , HasStream "wordCount" (Occurrences Text) m ) => Text -> m () countWordsAndLettersInText text = mapM_ countWordAndLetters (Text.words text)@@ -98,14 +98,14 @@ -- | Counter application monad. newtype Counter a = Counter { runCounter :: CounterCtx -> IO a } deriving (Functor, Applicative, Monad) via (ReaderT CounterCtx IO)- deriving (HasWriter "letterCount" (Occurrences Char)) via- (WriterLog -- Generate HasWriter using HasState of Monoid+ deriving (HasStream "letterCount" (Occurrences Char)) via+ (StreamLog -- Generate HasStream using HasState of Monoid (ReaderIORef -- Generate HasState from HasReader of IORef (Field "letterCount" "ctx" -- Focus on the field letterCount (MonadReader -- Generate HasReader using mtl MonadReader (ReaderT CounterCtx IO))))) -- Use mtl ReaderT newtype- deriving (HasWriter "wordCount" (Occurrences Text)) via- WriterLog (ReaderIORef+ deriving (HasStream "wordCount" (Occurrences Text)) via+ StreamLog (ReaderIORef (Field "wordCount" "ctx" (MonadReader (ReaderT CounterCtx IO))))
examples/Writer.hs view
@@ -9,6 +9,7 @@ module Writer where import Capability.State+import Capability.Stream import Capability.Writer import Control.Monad.State.Strict (State, StateT (..), runState) import Data.Monoid (Sum (..))@@ -20,9 +21,10 @@ -- | Increase a counter using a writer monad. useWriter :: HasWriter "count" (Sum Int) m => m () useWriter = do- tell @"count" 1- tell @"count" 2- tell @"count" 3+ -- Add 3 and retrieve result+ ((), count) <- listen @"count" (tell @"count" 3)+ -- Duplicate+ tell @"count" count -- | Mix writer and state monad operations on the same tag.@@ -49,7 +51,7 @@ -- via clause. newtype WriterM a = WriterM (State Int a) deriving (Functor, Applicative, Monad)- deriving (HasWriter "count" (Sum Int))+ deriving (HasStream "count" (Sum Int), HasWriter "count" (Sum Int)) via WriterLog (Coerce (Sum Int) (MonadState (State Int))) runWriterM :: WriterM a -> (a, Int)@@ -63,7 +65,7 @@ -- See caveat on 'mixWriterState'. newtype BadWriterM a = BadWriterM (State Int a) deriving (Functor, Applicative, Monad)- deriving (HasWriter "count" (Sum Int))+ deriving (HasStream "count" (Sum Int), HasWriter "count" (Sum Int)) via WriterLog (Coerce (Sum Int) (MonadState (State Int))) deriving (HasState "count" Int) via MonadState (State Int)
src/Capability/Stream.hs view
@@ -40,13 +40,13 @@ -- * Strategies , StreamStack(..) , StreamDList(..)+ , StreamLog(..) -- ** Modifiers , module Capability.Accessors ) where import Capability.Accessors import Capability.State-import Capability.Writer import Control.Monad.IO.Class (MonadIO) import Control.Monad.Primitive (PrimMonad) import Control.Monad.Trans.Class (MonadTrans, lift)@@ -86,15 +86,46 @@ -- | Accumulate streamed values in forward order in a difference list. newtype StreamDList m (a :: *) = StreamDList (m a) deriving (Functor, Applicative, Monad, MonadIO, PrimMonad)-instance HasWriter tag (DList a) m => HasStream tag a (StreamDList m) where- yield_ _ = coerce @(a -> m ()) $ tell @tag . DList.singleton+-- | This instance may seem a bit odd at first. All it does is wrap each+-- 'yield'ed value in a single element difference list. How does re-yielding+-- something else constitute a strategy for implementing 'HasStream' in the+-- first place? The answer is that difference lists form a monoid, which allows+-- a second stragegy to be used which accumulates all 'yield's in a single+-- value, actually eliminating the 'HasStream' constraint this time.+--+-- 'StreamLog' below in fact does this, so the easiest way to fully eliminate+-- the 'HasStream' constraint as described above is:+--+-- > deriving (HasStream tag w) via+-- > StreamDList (StreamLog (MonadState SomeStateMonad))+instance HasStream tag (DList a) m => HasStream tag a (StreamDList m) where+ yield_ _ = coerce @(a -> m ()) $ yield @tag . DList.singleton {-# INLINE yield_ #-} +-- | Accumulate streamed values with their own monoid.+newtype StreamLog m (a :: *) = StreamLog (m a)+ deriving (Functor, Applicative, Monad, MonadIO, PrimMonad)+instance (Monoid w, HasState tag w m) => HasStream tag w (StreamLog m) where+ yield_ _ w = coerce @(m ()) $ modify' @tag (<> w)+ {-# INLINE yield_ #-}+ instance Monad m => HasStream tag a (S.Stream (Of a) m) where yield_ _ = S.yield {-# INLINE yield_ #-} -- | Lift one layer in a monad transformer stack.+--+-- Note, that if the 'HasStream' instance is based on 'HasState', then it is+-- more efficient to apply 'Lift' to the underlying state capability. E.g.+-- you should favour+--+-- > deriving (HasStream tag w) via+-- > StreamLog (Lift (SomeTrans (MonadState SomeStateMonad)))+--+-- over+--+-- > deriving (HasStream tag w) via+-- > Lift (SomeTrans (StreamLog (MonadState SomeStateMonad))) instance (HasStream tag a m, MonadTrans t, Monad (t m)) => HasStream tag a (Lift (t m)) where
src/Capability/Writer.hs view
@@ -46,15 +46,15 @@ , listen , pass -- * Strategies- , WriterLog(..)+ , WriterLog+ , StreamLog (..) -- ** Modifiers , module Capability.Accessors ) where import Capability.Accessors import Capability.State-import Control.Monad.IO.Class (MonadIO)-import Control.Monad.Primitive (PrimMonad)+import Capability.Stream import Data.Coerce (Coercible, coerce) import GHC.Exts (Proxy#, proxy#) @@ -69,7 +69,22 @@ -- prop> listen @t (m >>= k) = listen @t m >>= \(a, w1) -> listen @t (k a) >>= \(b, w2) -> pure (b, w1 `mappend` w2) -- prop> pass @t (tell @t w >> pure (a, f)) = tell @t (f w) >> pure a -- prop> writer @t (a, w) = tell @t w >> pure a-class (Monoid w, Monad m)+--+-- = A note on the 'HasStream' super class.+--+-- 'HasStream' offers one 'yield' method with the same signature as 'tell'.+-- Many people's intuition, however, wouldn't connect the two: 'yield'ing+-- tosses the value down some black-box chute, while 'tell'ing grows and+-- accumulation via the monoid. The connection is since the 'chute' is opaque,+-- the tosser cannot rule out there being such an accumulation at the chutes+-- other end.+--+-- Formally, we reach the same conclusion. 'HasStream' has no laws,+-- indicating the user can make no assumptions beyond the signature of 'yield'.+-- 'HasWriter', with 'tell' defined as 'yield', is thus always compatable+-- regardless of whatever additional methods it provides and laws by which it+-- abides.+class (Monoid w, Monad m, HasStream tag w m) => HasWriter (tag :: k) (w :: *) (m :: * -> *) | tag m -> w where -- | For technical reasons, this method needs an extra proxy argument.@@ -79,11 +94,6 @@ writer_ :: Proxy# tag -> (a, w) -> m a -- | For technical reasons, this method needs an extra proxy argument. -- You only need it if you are defining new instances of 'HasReader'.- -- Otherwise, you will want to use 'tell'.- -- See 'tell' for more documentation.- tell_ :: Proxy# tag -> w -> m ()- -- | For technical reasons, this method needs an extra proxy argument.- -- You only need it if you are defining new instances of 'HasReader'. -- Otherwise, you will want to use 'listen'. -- See 'listen' for more documentation. listen_ :: Proxy# tag -> m a -> m (a, w)@@ -106,7 +116,7 @@ -- | @tell \@tag w@ -- appends @w@ to the output of the writer capability @tag@. tell :: forall tag w m. HasWriter tag w m => w -> m ()-tell = tell_ (proxy# @_ @tag)+tell = yield_ (proxy# @_ @tag) {-# INLINE tell #-} -- | @listen \@tag m@@@ -132,16 +142,13 @@ , Monad m, HasWriter tag w (t2 (t1 m)) ) => HasWriter tag w ((t2 :.: t1) m) -newtype WriterLog m a = WriterLog (m a)- deriving (Functor, Applicative, Monad, MonadIO, PrimMonad)+type WriterLog = StreamLog instance (Monoid w, HasState tag w m) => HasWriter tag w (WriterLog m) where- writer_ tag (a, w) = tell_ tag w >> pure a+ writer_ tag (a, w) = yield_ tag w >> pure a {-# INLINE writer_ #-}- tell_ _ w = coerce @(m ()) $ modify' @tag (<> w)- {-# INLINE tell_ #-} listen_ :: forall a. Proxy# tag -> WriterLog m a -> WriterLog m (a, w) listen_ _ m = coerce @(m (a, w)) $ do w0 <- get @tag
src/Capability/Writer/Discouraged.hs view
@@ -43,8 +43,6 @@ writer_ :: forall a. Proxy# tag -> (a, w) -> Lift (t m) a writer_ _ = coerce @((a, w) -> t m a) $ lift . writer @tag {-# INLINE writer_ #-}- tell_ _ = coerce @(w -> t m ()) $ lift . tell @tag- {-# INLINE tell_ #-} listen_ :: forall a. Proxy# tag -> Lift (t m) a -> Lift (t m) (a, w) listen_ _ = coerce @(t m a -> t m (a, w)) $ \m -> do u <- askUnlift