failable (empty) → 0.1.0.0
raw patch · 6 files changed
+225/−0 lines, 6 filesdep +basedep +mtldep +transformerssetup-changed
Dependencies added: base, mtl, transformers
Files
- ChangeLog.md +3/−0
- LICENSE +30/−0
- README.md +58/−0
- Setup.hs +2/−0
- failable.cabal +35/−0
- src/Control/Monad/Failable.hs +97/−0
+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for failable++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Author name here (c) 2019++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 Author name 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,58 @@+# failable+#### Yet another "error" handling monad (class)++This library provides a 'Failable' error monad class to unify failure across monads and+transformers most commonly used to implement pipelines that can fail.++*But.. don't we have 'MonadFail', 'MonadThrow', 'MonadError',.. and the true haskeller should be +using 'Alternative' anyway!*++I am sure a lot of ink has been spilled in forums and around water coolers all around the world, +debating the merits and fallacies of one approach or the other. The reason for this package is not+to participate in this discussion but rather to provide a simple nonsense means of signaling a +computation "failure" in those monads that provide the inherent means to do so, and to do it in a+consistent manner++## Usage++```haskell++data FooError = NotImplemented deriving (Typeable, Show)++instance Exception FooError++foo :: (Failable m) => m Int+foo = failure NotImplemented+```++Now, if one called `foo` in a `Maybe monad`:++>>> foo :: Maybe Int+>>> Nothing++the failure is then conveyed by returning `Nothing` as per definition of the `Maybe` monad. Now in the case of the `Either SomeException` monad:++>>> foo :: Either SomeException Int+>>> Left NotImplemented++but what if we are working in the `IO` monad?++>>> foo :: IO Int+>>> * * * Exception: NotImplemented++In this case, the failure can only be conveyed by throwing an IO exception.++Now, the point where `Failable` diverges from say `MonadThrow` for example is when it comes to monad+transformers. For example:++>>> runMaybeT foo :: IO (Maybe Int)++Would throw an `Exception: NotImplemented` if it was implemented in a `MonadThrow` context. Since+the reason d'etre for the runMaybeT is to provide the underlying monad (transformer) with `Maybe` like behaviour, i.e. have `Nothing` be returned in case of aborting the `Maybe` pipeline so to speak, then throwing an exception defeats IMHO the purpose of using `MaybeT` in the first place. So, in the+case of `Failable`:++>>> runMaybeT foo :: IO (Maybe Int)+>>> Nothing++And the same thing applies to `runExceptT` etc. +
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ failable.cabal view
@@ -0,0 +1,35 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.31.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: c71b774e75efdc6c11a1cd835a896e69b5b91d60a4e0a8fb6428c65ca6f5b0a1++name: failable+version: 0.1.0.0+synopsis: A 'Failable' error monad class to unify failure across monads that can fail+description: Please see the README on GitHub at <https://github.com/githubuser/failable#readme>+category: control, exceptions, monad+author: Erick Gonzalez+maintainer: erick@codemonkeylabs.de+copyright: 2019 Erick Gonzalez+license: BSD3+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md+ ChangeLog.md++library+ exposed-modules:+ Control.Monad.Failable+ other-modules:+ Paths_failable+ hs-source-dirs:+ src+ build-depends:+ base >=4.7 && <5+ , mtl+ , transformers+ default-language: Haskell2010
+ src/Control/Monad/Failable.hs view
@@ -0,0 +1,97 @@+{-# LANGUAGE GADTs #-}+{- |+Module: Control.Monad.Failable+Description: Yet another error monad but for people who are not crazy+Copyright: (c) Erick Gonzalez, 2019+License: BSD3+Maintainer: erick@codemonkeylabs.de++This library provides a 'Failable' error monad class to unify errors across monads and+transformers most commonly used to implement pipelines that can fail.++-}++module Control.Monad.Failable (+-- |+-- I am sure a lot of ink has been spilled in forums and around water coolers all around the+-- world, debating the merits and fallacies of one approach or the other. The reason for this+-- package is not to participate in this discussion but rather to provide a simple nonsense+-- means of signaling a computation "failure" in those monads that provide the inherent means+-- to do so, and to do it in a consistent manner.+--+-- When triggering a failure in a monadic context which is an instance of this class, simply+-- define your custom exception type and abort the computation with 'failure'. For example:+--+-- @+-- data MyException = SomeProblem+-- | AnotherProblem+-- deriving (Show, Typeable)+--+-- instance Exception MyException+--+-- foo :: (Failable m) => Int -> m Int+-- foo x = do+-- y <- bar x+-- if y < 0+-- then failure SomeProblem+-- else return y+-- @+--+--+-- if foo is then called in a 'Maybe' Monad, it would return @Nothing@ in case of error+-- or @Just ()@ of course if succesful. In an @Either SomeException@ context, it would+-- return @Left SomeProblem@ in case of error or @Right ()@ upon success, etc.+-- When it comes to monad transformers incorporating the concept of failure, such as 'MaybeT' or+-- 'ExceptT', it preserves the expected semantics upon failure of yielding an @m Nothing@ or+-- @m (Either SomeException a)@ when the transformer is "ran", instead of adopting the strategy+-- of passing the failure to the underlying monad (transformer) which might for example, throw+-- an async exception (as is the case of IO). Since the reason d'etre for something like runMaybeT+-- is to provide the underlying monad (transformer) with `Maybe` like behaviour, i.e. have+-- @Nothing@ be returned in case of aborting the 'Maybe' pipeline so to speak, then throwing an+-- exception defeats IMHO the purpose of using 'MaybeT' in the first place.+-- +-- >>> foo 2 :: Maybe Int+-- >>> Nothing+--+-- >>> foo 2 :: Either SomeException Int+-- >>> Left SomeProblem+--+-- >>> foo 2 :: IO Int+-- >>> * * * Exception: SomeProblem+--+-- >>> runMaybeT $ foo 2 :: IO (Maybe Int)+-- >>> Nothing+-- + Failable(..)) where++import Control.Exception (Exception(..), SomeException, throw)+import Control.Monad.Except (ExceptT, throwError)+import Control.Monad.Trans.Maybe (MaybeT(..))+++-- | The 'Failable' class. A Monad which is an instance of this class can be used as a context+-- in a function running in one with this class constraint, in order to report error conditions++class (Monad m) => Failable m where+ -- | trigger a failure. It takes an exception value as argument and it returns whatever+ -- might be used to abort a monadic computation in the monad instantiating this class.+ failure :: (Exception e) => e -> m a++instance Failable IO where+ failure = throw++instance Failable [] where+ failure _ = []++instance Failable Maybe where+ failure _ = Nothing++instance e ~ SomeException => Failable (Either e) where+ failure = Left . toException++instance (Monad m) => Failable (MaybeT m) where+ failure _ = MaybeT $ pure Nothing++instance (Monad m, e ~ SomeException) => Failable (ExceptT e m) where+ failure = throwError . toException+