packages feed

railroad 0.1.2.0 → 0.1.2.1

raw patch · 5 files changed

+54/−5 lines, 5 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -1,5 +1,11 @@ # Revision history for railroad +## 0.1.2.1 -- 2026-10-11+* Fix overlapping instances: `Either e (Maybe a)`, `Maybe (Maybe a)`, `Either e Bool` and the like+  did not compile when used with `?`, so nested layers could not be peeled (the README's first+  example did not compile). The outermost layer is now peeled first, as documented; lists and other+  traversables inside a layer behave as before. Tests added, including the README example.+ ## 0.1.2.0 -- 2026-10-11 * Add the fallback operator `(?|)`: runs the left action, and only if it fails runs the right one.   The two sides can be different structures (`Maybe`, `Either`, ...) with the same success type, the
railroad.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               railroad-version:            0.1.2.0+version:            0.1.2.1 license:            BSD-3-Clause license-file:       LICENSE author:             Frederik Kallstrup Mastratisi
src/Railroad.hs view
@@ -53,18 +53,20 @@     Success a -> Right a   -- validation :: (e -> b) -> (a -> b) -> Validation e a -> b -instance (Traversable t, CErr (t Bool) ~ (), CRes (t Bool) ~ t ())+-- The traversable instances overlap the base ones for e.g. @Either e (Maybe a)@. They are+-- INCOHERENT so the base instance wins and the outer layer is peeled first (@m ? e1 ? e2@).+instance {-# INCOHERENT #-} (Traversable t, CErr (t Bool) ~ (), CRes (t Bool) ~ t ())     => Bifurcate (t Bool) where   bifurcate = bifurcate . sequenceA . fmap bifurcate   -- Bool is not Applicative, so we need to map to Either first-instance (Traversable t, CErr (t (Maybe a)) ~ (), CRes (t (Maybe a)) ~ t a)+instance {-# INCOHERENT #-} (Traversable t, CErr (t (Maybe a)) ~ (), CRes (t (Maybe a)) ~ t a)     => Bifurcate (t (Maybe a)) where   bifurcate = bifurcate . sequenceA-instance (Traversable t, CErr (t (Either e a)) ~ e, CRes (t (Either e a)) ~ t a)+instance {-# INCOHERENT #-} (Traversable t, CErr (t (Either e a)) ~ e, CRes (t (Either e a)) ~ t a)     => Bifurcate (t (Either e a)) where   bifurcate = sequenceA   -- pedagogically: bifurcate = bifurcate . sequenceA-instance (Traversable t, Semigroup e, CErr (t (Validation e a)) ~ e, CRes (t (Validation e a)) ~ t a)+instance {-# INCOHERENT #-} (Traversable t, Semigroup e, CErr (t (Validation e a)) ~ e, CRes (t (Validation e a)) ~ t a)     => Bifurcate (t (Validation e a)) where   bifurcate = bifurcate . sequenceA 
test/Railroad/MonadErrorSpec.hs view
@@ -102,3 +102,21 @@       it "needs nothing special for Maybe sources" $ do         runCount (pure (Nothing :: Maybe Int) ?|<> pure Nothing ? "none")           `shouldBe` (Left "none", 0)++    describe "Layered structures (the outermost layer is peeled first)" $ do+      it "peels Either of Maybe one layer at a time" $ do+        runMonadError (pure (Right (Just 1) :: Either String (Maybe Int)) ? "outer" ? "inner") `shouldBe` Right 1+        runMonadError (pure (Right Nothing :: Either String (Maybe Int)) ? "outer" ? "inner") `shouldBe` Left "inner"+        runMonadError (pure (Left "x" :: Either String (Maybe Int)) ? "outer" ? "inner") `shouldBe` Left "outer"+      it "peels Maybe of Maybe and Either of Bool" $ do+        runMonadError (pure (Just Nothing :: Maybe (Maybe Int)) ? "o" ? "i") `shouldBe` Left "i"+        runMonadError (pure (Right False :: Either String Bool) ? "db" ? "denied") `shouldBe` Left "denied"+      it "runs the README example" $ do+        let readmeExample :: Either String Int+            readmeExample = runExcept $ do+              x <- pure (Just 2) ? "Value missing"+              y <- pure (Right $ Just 1) ? "Outer fail" ? "Inner fail"+              z <- pure [Just 4] ? "List failed" ?! const "Not a single element"+              q <- pure Nothing ?~ 1+              pure (x + y + z + q)+        readmeExample `shouldBe` Right 8
test/RailroadSpec.hs view
@@ -150,3 +150,26 @@     it "needs nothing special for Maybe sources" $ do       runCount (pure (Nothing :: Maybe Int) ?|<> pure Nothing ? "none")         `shouldBe` (Left "none", 0)++  describe "Layered structures (the outermost layer is peeled first)" $ do+    it "peels Either of Maybe one layer at a time" $ do+      let ok = pure (Right (Just 1)) :: Eff '[Error String] (Either String (Maybe Int))+      runRail (ok ? "outer" ? "inner") `shouldBe` Right 1+      runRail (pure (Right Nothing :: Either String (Maybe Int)) ? "outer" ? "inner") `shouldBe` Left "inner"+      runRail (pure (Left "x" :: Either String (Maybe Int)) ? "outer" ? "inner") `shouldBe` Left "outer"++    it "peels Maybe of Maybe" $ do+      runRail (pure (Just (Just 1) :: Maybe (Maybe Int)) ? "o" ? "i") `shouldBe` Right 1+      runRail (pure (Just Nothing :: Maybe (Maybe Int)) ? "o" ? "i") `shouldBe` Left "i"+      runRail (pure (Nothing :: Maybe (Maybe Int)) ? "o" ? "i") `shouldBe` Left "o"++    it "peels Either of Bool" $ do+      runRail (pure (Right True :: Either String Bool) ? "db" ? "denied") `shouldBe` Right ()+      runRail (pure (Right False :: Either String Bool) ? "db" ? "denied") `shouldBe` Left "denied"+      runRail (pure (Left "x" :: Either String Bool) ? "db" ? "denied") `shouldBe` Left "db"++    it "peels Either of Either" $ do+      runRail (pure (Right (Left "in") :: Either String (Either String Int)) ? "out" ?? id) `shouldBe` Left "in"++    it "still traverses a list inside a layer" $ do+      runRail (pure (Just [Just 1, Just 2 :: Maybe Int]) ? "o" ? "i") `shouldBe` Right [1, 2]