summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvanMiljenovic <>2017-06-30 06:20:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2017-06-30 06:20:00 (GMT)
commit7fa41e2d631ee0e366e31b18819d8e6d8d7b53e4 (patch)
tree4b875421c281b874faa9e2a25cda3464afd763a3
version 0.1.0.00.1.0.0
-rw-r--r--ChangeLog.md5
-rw-r--r--LICENSE20
-rw-r--r--README.md110
-rw-r--r--Setup.hs2
-rw-r--r--src/Streaming/With.hs74
-rw-r--r--src/Streaming/With/Lifted.hs137
-rw-r--r--streaming-with.cabal31
7 files changed, 379 insertions, 0 deletions
diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
index 0000000..9059853
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Revision history for streaming-with
+
+## 0.1.0.0 -- 2017-06-30
+
+* First release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6ae39e1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2017 Ivan Lazar Miljenovic
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0401c6b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,110 @@
+streaming-with
+==============
+
+[![Hackage](https://img.shields.io/hackage/v/streaming-with.svg)](https://hackage.haskell.org/package/streaming-with) [![Build Status](https://travis-ci.org/ivan-m/streaming-with.svg)](https://travis-ci.org/ivan-m/streaming-with)
+
+> with/bracket-style idioms for use with [streaming]
+
+This library provides resource management for the [streaming]
+ecosystem of libraries using bracketed continuations.
+
+[streaming]: http://hackage.haskell.org/package/streaming
+
+Currently, these only contain file-handling utilities; if you can
+think of any more functions that fit in here please let me know!
+
+There are two ways of using this library:
+
+1. Explicitly pass around the continuations using `Streaming.With`.
+
+2. If you have a lot of nested continuations, you may prefer using
+ `Streaming.With.Lifted` with either [`ContT`] or [managed]; these
+ will allow you to pass around the parameters to the continuations.
+
+[`ContT`]: http://hackage.haskell.org/packages/archive/transformers/latest/doc/html/Control-Monad-Trans-Cont.html#v:ContT
+[managed]: http://hackage.haskell.org/package/managed
+
+Motivation
+----------
+
+The streaming library has some usages of `MonadResource` from the
+[resourcet] package to try and perform resource allocation; in
+comparison to `conduit` however, streaming doesn't support prompt
+finalisation. Furthermore, because in may ways `Conduit`s can be
+considered to be a datatype representing monadic functions between two
+types whereas a single `Stream` is more akin to a producing-only
+`Conduit` or `Pipe`, attempts at having something like `readFile` that
+returns a `Stream` does not always lead to the resource being closed
+at the correct point. The consensus in the issue for [promptness for
+streaming] was that perhaps relying upon `MonadResource` was not the
+correct approach.
+
+[resourcet]: http://hackage.haskell.org/package/resourcet
+[promptness for streaming]: https://github.com/michaelt/streaming/issues/23
+
+
+The [Bracket pattern] (also known as the _with..._ idiom, or a subset
+of continuation passing style) allows for a convenient way to obtain
+and then guarantee the release of (possibly scarce) resources. This
+can be further enhanced when dealing with nested usages of these with
+the use of either the [`ContT`] managed transformer or the [managed]
+library (where `Managed` -- or at least its safe variant -- is
+isomorphic to `ContT () IO`).
+
+[Bracket pattern]: https://wiki.haskell.org/Bracket_pattern
+
+The biggest downside of using `bracket` from the standard `base`
+library is that the types are not very convenient in the world of
+monad transformers: it is limited to `IO` computations only, which
+means for our use of streaming that we could only use `Stream f IO r`
+without any state, logging, etc. However, the [exceptions] library
+contains a more general purpose variant that provides us with extra
+flexibility; it doesn't even need to be in `IO` if you have a
+completely pure `Stream`! (A variant is also available in
+[lifted-base], but it is less used and streaming already depends upon
+exceptions).
+
+[exceptions]: http://hackage.haskell.org/package/exceptions
+[lifted-base]: http://hackage.haskell.org/package/lifted-base
+
+Disadvantages
+-------------
+
+Whilst the bracket pattern is powerful, it does have some downsides of
+which you should be aware (specifically compared to [resourcet] which
+is the main alternative).
+
+First of all, independent of its usage with streaming, is that whilst
+`bracket` predates [resourcet], the latter is [more powerful].
+Whether this extra power is of use to you is up to you, but it does
+mean that you in effect have lots of nested resource management rather
+than just one overall resource control.
+
+[more powerful]: http://www.yesodweb.com/blog/2013/03/resourcet-overview
+
+The obvious disadvantage of using the bracket pattern is that it does
+not fit in as nicely in the function composition style that usage of
+streaming enables compared to other stream processing libraries. This
+can be mitigated somewhat with using the lifted variants in this
+package which allows you to operate monadically (which still isn't as
+nice but may be preferable to lots of explicitly nested continuations).
+
+Furthermore, without prompt finalisation the same "long running
+computation" [issue] is relevant. For example, consider something
+that looks like this:
+
+```haskell
+withBinaryFile "myFile" ReadMode $
+ doSomethingWithEachLine
+ . B.lines
+ . B.hGetContents
+```
+
+Ideally, after the last chunk from the file is read, the file handle
+would be closed. However, that is not the case: it's not until the
+_entire computation_ is complete that the handle is closed. Note,
+however, that the same limitation is present when using
+`MonadResource`: this is a limitation of the `Stream` type, not on how
+we choose to allocate and manage resources.
+
+[issue]: http://www.yesodweb.com/blog/2013/10/core-flaw-pipes-conduit
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
index 0000000..9a994af
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Streaming/With.hs b/src/Streaming/With.hs
new file mode 100644
index 0000000..36d16dd
--- /dev/null
+++ b/src/Streaming/With.hs
@@ -0,0 +1,74 @@
+{- |
+ Module : Streaming.With
+ Description : with/bracket-style idioms for use with streaming
+ Copyright : Ivan Lazar Miljenovic
+ License : MIT
+ Maintainer : Ivan.Miljenovic@gmail.com
+
+
+
+ -}
+module Streaming.With
+ ( -- * File-handling
+ withFile
+ , withBinaryFile
+ -- ** Common file-handling cases
+ , writeBinaryFile
+ , appendBinaryFile
+ , withBinaryFileContents
+ -- ** Re-exports
+ -- $reexports
+ , MonadMask
+ , bracket
+ ) where
+
+import Data.ByteString.Streaming (ByteString)
+import qualified Data.ByteString.Streaming as B
+
+import Control.Monad.Catch (MonadMask, bracket)
+import Control.Monad.IO.Class (MonadIO, liftIO)
+import System.IO (Handle, IOMode(..), hClose, openBinaryFile,
+ openFile)
+
+--------------------------------------------------------------------------------
+
+-- | A lifted variant of 'System.IO.withFile'.
+--
+-- You almost definitely don't want to use this; instead, use
+-- 'withBinaryFile' in conjunction with "Data.ByteString.Streaming".
+withFile :: (MonadMask m, MonadIO m) => FilePath -> IOMode -> (Handle -> m r) -> m r
+withFile fp md = bracket (liftIO (openFile fp md)) (liftIO . hClose)
+
+-- | A lifted variant of 'System.IO.withBinaryFile'.
+withBinaryFile :: (MonadMask m, MonadIO m) => FilePath -> IOMode -> (Handle -> m r) -> m r
+withBinaryFile fp md = bracket (liftIO (openBinaryFile fp md)) (liftIO . hClose)
+
+-- | Write to the specified file.
+writeBinaryFile :: (MonadMask m, MonadIO m) => FilePath -> ByteString m r -> m r
+writeBinaryFile fp = withBinaryFile fp WriteMode . flip B.hPut
+
+-- | Append to the specified file.
+appendBinaryFile :: (MonadMask m, MonadIO m) => FilePath -> ByteString m r -> m r
+appendBinaryFile fp = withBinaryFile fp AppendMode . flip B.hPut
+
+-- | Apply a function to the contents of the file.
+--
+-- Note that a different monadic stack is allowed for the
+-- 'ByteString' input, as long as it later gets resolved to the
+-- required output type (e.g. remove transformer).
+withBinaryFileContents :: (MonadMask m, MonadIO m, MonadIO n) => FilePath
+ -> (ByteString n () -> m r) -> m r
+withBinaryFileContents fp f = withBinaryFile fp ReadMode (f . B.hGetContents)
+
+--------------------------------------------------------------------------------
+
+{- $reexports
+
+These may assist in writing your own bracket-style functions.
+
+Note that not everything is re-exported: for example, 'Handle' isn't
+re-exported for use with 'withFile' as it's unlikely that you will
+write another wrapper around it, and furthermore it wouldn't be a
+common enough extension to warrant it.
+
+-}
diff --git a/src/Streaming/With/Lifted.hs b/src/Streaming/With/Lifted.hs
new file mode 100644
index 0000000..b6fc215
--- /dev/null
+++ b/src/Streaming/With/Lifted.hs
@@ -0,0 +1,137 @@
+{-# LANGUAGE FlexibleContexts, RankNTypes, TypeFamilies #-}
+
+{- |
+ Module : Streaming.With.Lifted
+ Description : Lifted with/bracket-style idioms for use with streaming
+ Copyright : Ivan Lazar Miljenovic
+ License : MIT
+ Maintainer : Ivan.Miljenovic@gmail.com
+
+ Both the 'ContT' and 'Managed' (which is a specialised variant of
+ 'ContT') monads can help with writing heavily nested bracketed
+ code, by being able to pass around the argument to each continuation.
+
+ This module - through the use of the 'Withable' class - provides
+ lifted variants of "Streaming.With" to be able to automatically use
+ these functions for resource management in your choice of monad.
+
+ Note that you still need to use the specific monad's running
+ function, as it is not possible to encapsulate those in a generic
+ fashion (unless we wanted to constrain the 'ContT' instance to
+ @ContT ()@).
+
+ To ensure resources don't leak out, it is preferred that if using
+ 'ContT', you keep the final result type to @()@ (which is what
+ 'Managed' recommends with its 'runManaged' function).
+
+ As an example using 'Managed', this function will copy the contents
+ of two files into a third.:
+
+ > copyBoth :: FilePath -> FilePath -> FilePath -> IO ()
+ > copyBoth inF1 inF2 outF = runManaged $ do
+ > bs1 <- withBinaryFileContents inF1
+ > bs2 <- withBinaryFileContents inF2
+ > writeBinaryFile outF bs1
+ > appendBinaryFile outF bs2
+
+ -}
+module Streaming.With.Lifted
+ ( Withable (..)
+ -- * File-handling
+ , withFile
+ , withBinaryFile
+ -- ** Common file-handling cases
+ , writeBinaryFile
+ , appendBinaryFile
+ , withBinaryFileContents
+ -- ** Re-exports
+ -- $reexports
+ , MonadMask
+ , bracket
+ ) where
+
+import Data.ByteString.Streaming (ByteString)
+import qualified Streaming.With as W
+
+import Control.Monad.Catch (MonadMask, bracket)
+import Control.Monad.IO.Class (MonadIO, liftIO)
+import Control.Monad.Managed (Managed, managed)
+import Control.Monad.Trans.Class (lift)
+import Control.Monad.Trans.Cont (ContT(..))
+import System.IO (Handle, IOMode)
+
+--------------------------------------------------------------------------------
+
+-- | How to automatically lift bracket-style expressions into a monad.
+--
+-- The constraints are common ones found throughout this module, and
+-- as such incorporated into this class to avoid repetition in all
+-- the type signatures.
+--
+-- It is highly recommended that you do /not/ try and layer extra
+-- transformers on top of this; the intent of this class is just to
+-- make writing all the underlying continuations in a nicer fashion
+-- without explicit nesting, rather than as the basis of lower-level
+-- code.
+class (MonadMask (WithMonad w), MonadIO (WithMonad w)) => Withable w where
+ type WithMonad w :: * -> *
+
+ liftWith :: (forall r. (a -> WithMonad w r) -> WithMonad w r) -> w a
+
+ liftAction :: WithMonad w a -> w a
+
+instance Withable Managed where
+ type WithMonad Managed = IO
+
+ liftWith = managed
+
+ liftAction = liftIO
+
+instance (MonadMask m, MonadIO m) => Withable (ContT r m) where
+ type WithMonad (ContT r m) = m
+
+ liftWith = ContT
+
+ liftAction = lift
+
+--------------------------------------------------------------------------------
+
+-- | A lifted variant of 'System.IO.withFile'.
+--
+-- You almost definitely don't want to use this; instead, use
+-- 'withBinaryFile' in conjunction with "Data.ByteString.Streaming".
+withFile :: (Withable w) => FilePath -> IOMode -> w Handle
+withFile fp md = liftWith (W.withFile fp md)
+
+-- | A lifted variant of 'System.IO.withBinaryFile'.
+withBinaryFile :: (Withable w) => FilePath -> IOMode -> w Handle
+withBinaryFile fp md = liftWith (W.withBinaryFile fp md)
+
+-- | Write to the specified file.
+writeBinaryFile :: (Withable w) => FilePath -> ByteString (WithMonad w) r -> w r
+writeBinaryFile fp inp = liftAction (W.writeBinaryFile fp inp)
+
+-- | Append to the specified file.
+appendBinaryFile :: (Withable w) => FilePath -> ByteString (WithMonad w) r -> w r
+appendBinaryFile fp inp = liftAction (W.appendBinaryFile fp inp)
+
+-- | Apply a function to the contents of the file.
+--
+-- Note that a different monadic stack is allowed for the
+-- 'ByteString' input, as long as it later gets resolved to the
+-- required output type (e.g. remove transformer).
+withBinaryFileContents :: (Withable w, MonadIO n) => FilePath -> w (ByteString n ())
+withBinaryFileContents fp = liftWith (W.withBinaryFileContents fp)
+
+--------------------------------------------------------------------------------
+
+{- $reexports
+
+These may assist in writing your own bracket-style functions.
+
+Note that not everything is re-exported: for example, 'Handle' isn't
+re-exported for use with 'withFile' as it's unlikely that you will
+write another wrapper around it, and furthermore it wouldn't be a
+common enough extension to warrant it.
+
+-}
diff --git a/streaming-with.cabal b/streaming-with.cabal
new file mode 100644
index 0000000..a7013f8
--- /dev/null
+++ b/streaming-with.cabal
@@ -0,0 +1,31 @@
+name: streaming-with
+version: 0.1.0.0
+synopsis: with/bracket-style idioms for use with streaming
+description:
+ This package provides the foundations for a continuation-based
+ approach for dealing with resources in the streaming ecosystem.
+license: MIT
+license-file: LICENSE
+author: Ivan Lazar Miljenovic
+maintainer: Ivan.Miljenovic@gmail.com
+copyright: Ivan Lazar Miljenovic
+category: Data, Streaming
+build-type: Simple
+extra-source-files: ChangeLog.md, README.md
+cabal-version: >=1.10
+tested-with: GHC == 7.10.2, GHC == 8.0.2, GHC == 8.1.*
+
+source-repository head
+ type: git
+ location: https://github.com/ivan-m/streaming-with.git
+
+library
+ exposed-modules: Streaming.With
+ , Streaming.With.Lifted
+ build-depends: base == 4.*
+ , exceptions >= 0.6 && < 0.9
+ , managed == 1.0.*
+ , streaming-bytestring >= 0.1.0.3 && < 0.2
+ , transformers
+ hs-source-dirs: src
+ default-language: Haskell2010