opt-env-conf 0.15.0.1 → 0.15.0.2
raw patch · 4 files changed
+30/−17 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- CHANGELOG.md +10/−0
- opt-env-conf.cabal +1/−1
- src/OptEnvConf/NonDet.hs +10/−6
- src/OptEnvConf/Run.hs +9/−10
CHANGELOG.md view
@@ -1,5 +1,15 @@ # Changelog +## [0.15.0.2] - 2026-05-15++### Changed++* Fixed an exponential-time blowup when parsing inputs with several `many`-style+ options combined with a `choice` containing positional arguments. Parses that+ previously took minutes now complete in milliseconds. The fix makes the+ internal `ListT` nondet stream lazy in its second argument and threads inner+ branches into the outer parser on demand.+ ## [0.15.0.1] - 2026-03-18 ### Changed
opt-env-conf.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: opt-env-conf-version: 0.15.0.1+version: 0.15.0.2 synopsis: Settings parsing for Haskell: command-line arguments, environment variables, and configuration values. homepage: https://github.com/NorfairKing/opt-env-conf#readme bug-reports: https://github.com/NorfairKing/opt-env-conf/issues
src/OptEnvConf/NonDet.hs view
@@ -56,12 +56,16 @@ joinMMMList :: (Monad m) => m (MList m (m (MList m a))) -> m (MList m a) joinMMMList = (>>= joinMMList) -appendMList :: (Functor m) => MList m a -> MList m a -> MList m a-appendMList MNil ml = ml-appendMList (MCons a ml1) ml2 = MCons a $ (`appendMList` ml2) <$> ml1--appendMMMList :: (Applicative m) => m (MList m a) -> m (MList m a) -> m (MList m a)-appendMMMList ml1 ml2 = appendMList <$> ml1 <*> ml2+-- Lazy in the second argument: do not run its effects until the first list+-- is exhausted. A strict definition (e.g. liftA2 over an eager concat) forces+-- the entire downstream search tree just to produce the head element, which+-- causes exponential blowup through ListT's >>= and <|>.+appendMMMList :: (Monad m) => m (MList m a) -> m (MList m a) -> m (MList m a)+appendMMMList ml1 ml2 = do+ l1 <- ml1+ case l1 of+ MNil -> ml2+ MCons a m -> pure $ MCons a (appendMMMList m ml2) -- This can be directly used as a monad transformer newtype ListT m a = ListT {unListT :: m (MList m a)}
src/OptEnvConf/Run.hs view
@@ -152,8 +152,7 @@ ppIndent $ do e <- ask s <- get- results <- liftIO $ runPP (go p') s e- (result, s') <- ppNonDetList results+ (result, s') <- ppNonDet $ runPPNonDet (go p') s e put s' case result of Success a -> pure a@@ -606,13 +605,15 @@ MonadState PPState ) -runPP ::+-- Keeps nondeterminism lazy: callers consume the resulting branches one at a+-- time, so unreached branches never run.+runPPNonDet :: PP a -> PPState -> PPEnv ->- IO [(Validation ParseError a, PPState)]-runPP (PP p) args envVars =- runNonDetT (runStateT (runValidationT (runReaderT p envVars)) args)+ NonDetT IO (Validation ParseError a, PPState)+runPPNonDet (PP p) args envVars =+ runStateT (runValidationT (runReaderT p envVars)) args runPPLazy :: PP a ->@@ -631,13 +632,11 @@ tryPP pp = do s <- get e <- ask- results <- liftIO $ runPP pp s e- (errOrRes, s') <- ppNonDetList results+ (errOrRes, s') <- ppNonDet $ runPPNonDet pp s e case errOrRes of Failure errs -> if all errorIsForgivable errs- then do- pure Nothing+ then pure Nothing else ppErrors' errs Success a -> do put s' -- Only set state if parsing succeeded.