oops-examples (empty) → 0.2.0.0
raw patch · 3 files changed
+395/−0 lines, 3 filesdep +basedep +exceptionsdep +mtl
Dependencies added: base, exceptions, mtl, oops, text, transformers
Files
- LICENSE +21/−0
- oops-examples.cabal +45/−0
- src/Examples.hs +329/−0
+ LICENSE view
@@ -0,0 +1,21 @@+Copyright (c) 2023 John Ky+Copyright (c) 2019 Tom Harding++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.
+ oops-examples.cabal view
@@ -0,0 +1,45 @@+cabal-version: 3.0++name: oops-examples+version: 0.2.0.0+synopsis: Oops examples+description: Oops examples.+homepage: https://www.github.com/haskell-works/oops+license: MIT+license-file: LICENSE+author: John Ky+maintainer: newhoggy@gmail.com+copyright: 2023 John Ky+ 2019 Tom Harding+category: Data+ Control+tested-with: GHC == 9.4.4, GHC == 9.2.5, GHC == 9.0.2, GHC == 8.10.7++source-repository head+ type: git+ location: https://github.com/haskell-works/oops++common base { build-depends: base >= 4.12 && < 5 }++common exceptions { build-depends: exceptions }+common mtl { build-depends: mtl }+common oops { build-depends: oops }+common text { build-depends: text }+common transformers { build-depends: transformers }++common project-config+ default-language: Haskell2010+ default-extensions: BlockArguments+ RankNTypes+ ScopedTypeVariables+ ghc-options: -Wall++library+ import: base, project-config,+ exceptions,+ mtl,+ oops,+ text,+ transformers,+ exposed-modules: Examples+ hs-source-dirs: src
+ src/Examples.hs view
@@ -0,0 +1,329 @@+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE MonoLocalBinds #-}+{-# LANGUAGE DataKinds #-}++module Examples where++import Control.Monad.Except (MonadError, ExceptT, runExceptT)+import Control.Monad.Oops ( CouldBe, Variant )+import Data.Text (Text)+import Text.Read (readMaybe)+import Data.Functor.Identity (Identity)+import Control.Monad.Trans (MonadIO)+import Data.Function ((&))++import qualified Control.Monad.Oops as OO+import qualified System.IO as IO+import Control.Monad.IO.Class (MonadIO(..))+import Control.Exception (IOException)+import Control.Monad.Catch (MonadCatch)++-- | A simple function that throws an error.+--+-- The type is the one that is inferred by GHC.+readIntV1 :: ()+ => MonadError (Variant e) m+ => OO.CouldBeF e Text+ => String+ -> m Int+readIntV1 s = case readMaybe @Int s of+ Just i -> return i+ Nothing -> OO.throw @Text "Not an integer"++-- | A simple function that throws an error.+--+-- This is the same as before, but we can rewrite constraint on 'e' differently.+readIntV2 :: ()+ => MonadError (Variant e) m+ => e `CouldBe` Text+ => String+ -> m Int+readIntV2 s = case readMaybe @Int s of+ Just i -> return i+ Nothing -> OO.throw @Text "Not an integer"++-- | A simple function that throws an error.+--+-- We can also use 'ExceptT'+readIntV3 :: ()+ => e `CouldBe` Text+ => String+ -> ExceptT (Variant e) Identity Int+readIntV3 s = case readMaybe @Int s of+ Just i -> return i+ Nothing -> OO.throw @Text "Not an integer"++-- | A simple IO function that throws an error.+--+-- We can also use 'ExceptT' of 'IO'.+readIntV4 :: ()+ => e `CouldBe` Text+ => String+ -> ExceptT (Variant e) IO Int+readIntV4 s = case readMaybe @Int s of+ Just i -> return i+ Nothing -> OO.throw @Text "Not an integer"++-- | A simple function that throws an error.+--+-- Or use MonadIO instead of IO directly.+readIntV5 :: ()+ => MonadError (Variant e) m+ => MonadIO m+ => e `CouldBe` Text+ => String+ -> m Int+readIntV5 s = case readMaybe @Int s of+ Just i -> return i+ Nothing -> OO.throw @Text "Not an integer"++-- | A simple function that throws an error.+--+-- Or use 'MonadIO' with 'ExceptT'.+readIntV6 :: ()+ => MonadIO m+ => e `CouldBe` Text+ => String+ -> ExceptT (Variant e) m Int+readIntV6 s = case readMaybe @Int s of+ Just i -> return i+ Nothing -> OO.throw @Text "Not an integer"++-- We can represent each error as a separate type.++data NotAnInteger = NotAnInteger++data NotPositive = NotPositive++-- | A simple function can throw two errors+readPositiveInt1 :: ()+ => MonadIO m+ => e `CouldBe` NotAnInteger+ => e `CouldBe` NotPositive+ => String+ -> ExceptT (Variant e) m Int+readPositiveInt1 s =+ case readMaybe @Int s of+ Just i ->+ if i > 0+ then return i+ else OO.throw NotPositive+ Nothing -> OO.throw NotAnInteger++-- We can call a function that throws an error of type 'Text' and allow the+-- error to propagate by declaring we also throw an error of type 'Text'.+example1 :: ()+ => e `CouldBe` Text+ => String+ -> ExceptT (Variant e) IO Int+example1 s = do+ i <- readInt s+ liftIO $ IO.print i+ return i+ where+ readInt :: ()+ => e `CouldBe` Text+ => String+ -> ExceptT (Variant e) IO Int+ readInt = error "unimplemented"++-- Or alternatively, we can catch the error of type 'Text' and handle it.+-- in which case the error doesn't propagate. Notice the 'e CouldBe Text'+-- constraint is not needed in this case.+example2 :: ()+ => String+ -> ExceptT (Variant e) IO Int+example2 s = do+ i <- readInt s+ & OO.catch @Text (\_ -> pure 0)+ liftIO $ IO.print i+ return i+ where+ readInt :: ()+ => e `CouldBe` Text+ => String+ -> ExceptT (Variant e) IO Int+ readInt = error "unimplemented"++-- When we don't throw any errors, we can use the 'runOops' function to+-- convert the 'ExceptT' to an 'IO' action.+example3 :: ()+ => String+ -> IO Int+example3 s = OO.runOops $ do+ i <- readInt s+ & OO.catch @Text (\_ -> pure 0)+ liftIO $ IO.print i+ return i+ where+ readInt :: ()+ => e `CouldBe` Text+ => String+ -> ExceptT (Variant e) IO Int+ readInt = error "unimplemented"++data FileNotFound = FileNotFound++data FileNotReadable = FileNotReadable++data Errors1+ = Errors1NotPositive NotPositive+ | Errors1NotAnInteger NotAnInteger++-- We can call a function that throws multiple errors into a function+-- that only throws one by just catching and rethrowing the one+-- error.+example4 :: ()+ => MonadIO m+ => e `CouldBe` Errors1+ => String+ -> ExceptT (Variant e) m Int+example4 s = do+ i <- readPositiveInt1 s+ & OO.catch @NotPositive (OO.throw . Errors1NotPositive)+ & OO.catch @NotAnInteger (OO.throw . Errors1NotAnInteger)+ liftIO $ IO.print i+ return i++--------------------------------------------------------------------------------+-- Embedding 'Oops' into vanilla 'ExceptT' and 'Either' code.++-- We we have a function that only throws one error, we can use 'runOopsInExceptT'+-- to remove the 'Variant' wrapper leaving only the 'ExceptT'.+example5 :: ()+ => MonadIO m+ => String+ -> ExceptT Errors1 m Int+example5 s = OO.runOopsInExceptT $ do+ i <- readPositiveInt1 s+ & OO.catch @NotPositive (OO.throw . Errors1NotPositive)+ & OO.catch @NotAnInteger (OO.throw . Errors1NotAnInteger)+ liftIO $ IO.print i+ return i++-- We we have a function that only throws one error, we can use 'runOopsInEither'+-- to remove the 'Variant' wrapper and the 'Except' leaving only the 'Either'.+example6 :: ()+ => MonadIO m+ => String+ -> m (Either Errors1 Int)+example6 s = OO.runOopsInEither $ do+ i <- readPositiveInt1 s+ & OO.catch @NotPositive (OO.throw . Errors1NotPositive)+ & OO.catch @NotAnInteger (OO.throw . Errors1NotAnInteger)+ liftIO $ IO.print i+ return i++--------------------------------------------------------------------------------+-- Embedding vanilla 'ExceptT' and 'Either' code into 'Oops' code.+-- We can call a function that throws multiple errors into a function+-- that only throws one by just catching and rethrowing the one+-- error.++-- We can call a function that throws vanilla 'ExceptT' errors and use 'onLeft'+-- to catch and rethrow the errors as oops errors.+example7 :: ()+ => MonadIO m+ => e `CouldBe` NotPositive+ => e `CouldBe` NotAnInteger+ => String+ -> ExceptT (Variant e) m Int+example7 s = do+ i <- readInt s+ & OO.onLeft OO.throw+ pos <- requirePositive i+ & OO.onLeft OO.throw+ liftIO $ IO.print pos+ return pos+ where+ readInt :: MonadIO m => String -> m (Either NotAnInteger Int)+ readInt = error "unimplemented"+ requirePositive :: MonadIO m => Int -> m (Either NotPositive Int)+ requirePositive = error "unimplemented"++-- 'onLeftThrow' is shorthand for 'onLeft throw'.+example8 :: ()+ => MonadIO m+ => e `CouldBe` NotPositive+ => e `CouldBe` NotAnInteger+ => String+ -> ExceptT (Variant e) m Int+example8 s = do+ i <- readInt s+ & OO.onLeftThrow+ pos <- requirePositive i+ & OO.onLeftThrow+ liftIO $ IO.print pos+ return pos+ where+ readInt :: MonadIO m => String -> m (Either NotAnInteger Int)+ readInt = error "unimplemented"+ requirePositive :: MonadIO m => Int -> m (Either NotPositive Int)+ requirePositive = error "unimplemented"++-- We can similarly call a function that throws via vanilla 'ExceptT'+example9 :: ()+ => MonadIO m+ => e `CouldBe` NotPositive+ => e `CouldBe` NotAnInteger+ => String+ -> ExceptT (Variant e) m Int+example9 s = do+ i <- liftIO (runExceptT (readInt s))+ & OO.onLeftThrow+ pos <- liftIO (runExceptT (requirePositive i))+ & OO.onLeftThrow+ liftIO $ IO.print pos+ return pos+ where+ readInt :: String -> ExceptT NotAnInteger IO Int+ readInt = error "unimplemented"+ requirePositive :: Int -> ExceptT NotPositive IO Int+ requirePositive = error "unimplemented"++-- We can similarly call a function that throws via vanilla 'ExceptT'+example10 :: ()+ => MonadIO m+ => e `CouldBe` NotPositive+ => e `CouldBe` NotAnInteger+ => String+ -> ExceptT (Variant e) m Int+example10 s = do+ i <- liftIO (runExceptT (readInt s))+ & OO.onLeftThrow+ pos <- liftIO (runExceptT (requirePositive i))+ & OO.onLeftThrow+ liftIO $ IO.print pos+ return pos+ where+ readInt :: String -> ExceptT NotAnInteger IO Int+ readInt = error "unimplemented"+ requirePositive :: Int -> ExceptT NotPositive IO Int+ requirePositive = error "unimplemented"++-- We can also catch runtime exceptions and rethrow them as checked exceptions.+example11 :: ()+ => MonadIO m+ => MonadCatch m+ => e `CouldBe` IOException+ => ExceptT (Variant e) m String+example11 = do+ i <- liftIO (IO.readFile "moo")+ & OO.onException @IOException OO.throw+ liftIO $ IO.print i+ return i++-- The 'onExceptionThrow' is shorthand for 'onException throw'.+example12 :: ()+ => MonadIO m+ => MonadCatch m+ => e `CouldBe` IOException+ => ExceptT (Variant e) m String+example12 = do+ i <- liftIO (IO.readFile "moo")+ & OO.onExceptionThrow @IOException+ liftIO $ IO.print i+ return i