packages feed

railroad 0.1.0.0 → 0.1.0.1

raw patch · 4 files changed

+140/−2 lines, 4 filesPVP: minor bump suggested

API additions: PVP suggests at least a minor version bump

API changes (from Hackage documentation)

+ Railroad.MonadError: (?!) :: forall m e t a. (MonadError e m, Foldable t) => m (t a) -> (CardinalityError (t a) -> e) -> m a
+ Railroad.MonadError: (?) :: forall m e a. (MonadError e m, Bifurcate a) => m a -> e -> m (CRes a)
+ Railroad.MonadError: (?+) :: forall m e t a. (MonadError e m, Foldable t) => m (t a) -> e -> m (t a)
+ Railroad.MonadError: (?>) :: forall m e a. MonadError e m => m a -> (a -> Bool) -> (a -> e) -> m a
+ Railroad.MonadError: (??) :: forall a m e. (MonadError e m, Bifurcate a) => m a -> (CErr a -> e) -> m (CRes a)
+ Railroad.MonadError: (??~) :: forall m a. (Bifurcate a, Monad m) => m a -> (CErr a -> CRes a) -> m (CRes a)
+ Railroad.MonadError: (?~) :: forall m a. (Bifurcate a, Monad m) => m a -> CRes a -> m (CRes a)
+ Railroad.MonadError: (?∅) :: forall m e t a. (MonadError e m, Foldable t) => m (t a) -> (t a -> e) -> m ()
+ Railroad.MonadError: IsEmpty :: CardinalityError ta
+ Railroad.MonadError: TooMany :: !ta -> CardinalityError ta
+ Railroad.MonadError: bifurcate :: Bifurcate f => f -> Either (CErr f) (CRes f)
+ Railroad.MonadError: cardinalityErr :: e -> (ta -> e) -> CardinalityError ta -> e
+ Railroad.MonadError: class Bifurcate f
+ Railroad.MonadError: collapse :: (MonadError e m, Bifurcate f) => (CErr f -> e) -> f -> m (CRes f)
+ Railroad.MonadError: data CardinalityError ta
+ Railroad.MonadError: infixl 0 ?∅
+ Railroad.MonadError: infixl 1 ?>
+ Railroad.MonadError: type family CRes f

Files

CHANGELOG.md view
@@ -1,5 +1,11 @@ # Revision history for railroad +## 0.1.0.1 -- 2026-02-26++* Fix that `Railroad.MonadError` is not exposed in the cabal file+* Fix ambiguous occurrence of `(??~)` in `Railroad.MonadError` by hiding it in import.+* Add tests for the MonadError version of the operators such that issues like the above will be caught + ## 0.1.0.0 -- 2026-02-25  * First version. Released on an unsuspecting world.
railroad.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               railroad-version:            0.1.0.0+version:            0.1.0.1 license:            BSD-3-Clause license-file:       LICENSE author:             Frederik Kallstrup Mastratisi@@ -29,7 +29,7 @@ library     import:           stuff     exposed-modules:  Railroad-                      +                      Railroad.MonadError     hs-source-dirs:   src     default-language: GHC2021     @@ -41,6 +41,7 @@     hs-source-dirs:   test     main-is:          Main.hs     other-modules:    RailroadSpec+                      Railroad.MonadErrorSpec     build-depends:                   base ^>=4.20.2.0,                   hspec  >= 2.11.14 && < 2.12,
+ src/Railroad/MonadError.hs view
@@ -0,0 +1,71 @@+-- |+-- Re-implementation of Railroad operators ('??', '?', '?>', etc.)+-- using 'MonadError' / 'throwError' instead of the Effectful 'Error' effect.+--+-- Use this module in classic @mtl@ / @transformers@ / @ExceptT@ code.+-- For Effectful prefer the versions from "Railroad".+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE FlexibleContexts    #-}++module Railroad.MonadError+  ( module Railroad          -- re-exports CErr, CRes, Bifurcate, CardinalityError, etc.+  , collapse+  , (??), (?), (?>), (??~), (?~), (?+), (?!), (?∅)+  ) where++import           Railroad             hiding (collapse, (?!), (?), (?+), (?>),+                                       (??), (??~), (?~), (?∅))++import           Control.Monad.Except (MonadError (..))+import           Data.Foldable        (toList)++-- | Collapses a structure into its inner type or a `MonadError` error.+collapse :: (MonadError e m, Bifurcate f)+         => (CErr f -> e) -> f -> m (CRes f)+collapse toErr = either (throwError . toErr) pure . bifurcate++(??) :: forall a m e. (MonadError e m, Bifurcate a)+     => m a -> (CErr a -> e) -> m (CRes a)+action ?? toErr = action >>= collapse toErr++(?) :: forall m e a. (MonadError e m, Bifurcate a)+    => m a -> e -> m (CRes a)+action ? err = action ?? const err++(?>) :: forall m e a. (MonadError e m)+     => m a -> (a -> Bool) -> (a -> e) -> m a+(?>) action predicate toErr = do+  val <- action+  if predicate val then pure val else throwError $ toErr val++-- | Collapses a structure and recovers to a value dependent on the error+(??~) :: forall m a. (Bifurcate a, Monad m) => m a -> (CErr a -> CRes a) -> m (CRes a)+action ??~ defaultFunc = action >>= either (pure . defaultFunc) pure . bifurcate++-- | Collapses a structure, and recovers to a constant default value in the error case+(?~) :: forall m a. (Bifurcate a, Monad m) => m a -> CRes a -> m (CRes a)+action ?~ defaultVal = action ??~ (const defaultVal)++(?+) :: forall m e t a. (MonadError e m, Foldable t)+     => m (t a) -> e -> m (t a)+(?+) action err = do+  xs <- action+  if null xs then throwError err else pure xs++(?!) :: forall m e t a. (MonadError e m, Foldable t)+     => m (t a) -> (CardinalityError (t a) -> e) -> m a+(?!) action toErr = do+  xs <- action+  case toList xs of+    []  -> throwError $ toErr IsEmpty+    [x] -> pure x+    _   -> throwError $ toErr $ TooMany xs++(?∅) :: forall m e t a. (MonadError e m, Foldable t)+     => m (t a) -> (t a -> e) -> m ()+(?∅) action toErr = do+  xs <- action+  if null xs then pure () else throwError (toErr xs)++infixl 0 ??, ?, ?~, ?!, ?+, ?∅+infixl 1 ?>
+ test/Railroad/MonadErrorSpec.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE DataKinds        #-}+{-# LANGUAGE TypeApplications #-}++module Railroad.MonadErrorSpec where++import           Control.Monad.Except+import           Data.Functor.Identity+import           Data.Validation+import           Railroad.MonadError+import           Test.Hspec++-- Helper to run MonadError computations in pure Either+runMonadError :: ExceptT String Identity a -> Either String a+runMonadError = runIdentity . runExceptT++spec :: Spec+spec = do+  describe "MonadError version of Operators" $ do+    describe "Basic Operators (? and ??)" $ do+      it "unwraps success values with (?)" $ do+        runMonadError (pure (Just 10 :: Maybe Int) ? "missing") `shouldBe` Right 10+      it "throws constant error on failure with (?)" $ do+        runMonadError (pure (Nothing :: Maybe ()) ? "missing") `shouldBe` Left "missing"+      it "maps internal errors with (??)" $ do+        let action = pure (Left "original" :: Either String String)+        runMonadError (action ?? reverse) `shouldBe` Left "lanigiro"++    describe "Predicate Operator (?>)" $ do+      it "passes when predicate is met" $ do+        runMonadError (pure 10 ?> (> 5) $ const "too small") `shouldBe` Right 10+      it "fails when predicate is not met" $ do+        runMonadError (pure 4 ?> (> 5) $ const "too small") `shouldBe` Left "too small"++    describe "Recovery Operators (?~ and ??~)" $ do+      it "recovers to a constant value with (?~)" $ do+        runMonadError (pure Nothing ?~ 0) `shouldBe` Right (0 :: Int)+      it "recovers using a function with (??~)" $ do+        runMonadError (pure (Left "err") ??~ length) `shouldBe` Right 3++    describe "Cardinality Operators" $ do+      describe "(?+)" $ do+        it "succeeds on non-empty list" $ do+          runMonadError (pure [1, 2, 3] ?+ "empty") `shouldBe` Right [1, 2, 3]+        it "fails on empty list" $ do+          runMonadError (pure ([] :: [Int]) ?+ "empty") `shouldBe` Left "empty"++      describe "(?!)" $ do+        let toErr = cardinalityErr "none" (const "too many")+        it "extracts the single element" $ do+          runMonadError (pure [42] ?! toErr) `shouldBe` Right 42+        it "fails on empty" $ do+          runMonadError (pure ([] :: [Int]) ?! toErr) `shouldBe` Left "none"+        it "fails on multiple elements" $ do+          runMonadError (pure [1, 2] ?! toErr) `shouldBe` Left "too many"++      describe "(?∅)" $ do+        it "succeeds on empty" $ do+          runMonadError (pure [] ?∅ const "not empty") `shouldBe` Right ()+        it "fails on non-empty" $ do+          runMonadError (pure [1] ?∅ const "not empty") `shouldBe` Left "not empty"