From c828a48dec1118b4693542d4bcceb064291415d4 Mon Sep 17 00:00:00 2001 From: Jan Sucan Date: Thu, 4 Sep 2025 09:57:11 +0200 Subject: 18_b_1: Add solution --- ch18/18_b_1.hs | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 ch18/18_b_1.hs (limited to 'ch18') diff --git a/ch18/18_b_1.hs b/ch18/18_b_1.hs new file mode 100644 index 0000000..905e730 --- /dev/null +++ b/ch18/18_b_1.hs @@ -0,0 +1,107 @@ +-- Our Parse monad is not a perfect replacement for its earlier +-- counterpart. Because we are using Maybe instead of Either to represent a +-- result, we can't report any useful information if a parse fails. +-- +-- Create an EitherT sometype monad transformer, and use it to implement a more +-- capable Parse monad that can report an error message if parsing fails. +-- +-- Tip: If you like to explore the Haskell libraries for fun, you may have run +-- across an existing Monad instance for the Either type in the +-- Control.Monad.Error module. We suggest that you do not use that as a +-- guide. Its design is too restrictive: it turns Either String into a monad, +-- when you could use a type parameter instead of String. +-- +-- Hint: if you follow this suggestion, you'll probably need to use the +-- FlexibleInstances language extension in your definition. + + +{-# LANGUAGE UndecidableInstances #-} + +import Control.Monad +import Control.Monad.Trans +import Control.Monad.State +import Data.Int (Int64) +import qualified Data.ByteString.Lazy as L + + +{-- From examples/examples/ch18/MaybeTParse.hs and modified --} +data ParseState = ParseState { + string :: L.ByteString + , offset :: Int64 + } deriving (Show) + +newtype Parse a = P { + runP :: EitherT (State ParseState) a + -- Added Functor and Applicative to deriving + } deriving (Functor, Applicative, Monad, MonadState ParseState) + +evalParse :: Parse a -> L.ByteString -> Either String a +evalParse m s = evalState (runEitherT (runP m)) (ParseState s 0) +{-- End of code from examples --} + + +{-- From examples/examples/ch18/MaybeT.hs and modified --} +newtype EitherT m a = EitherT { + runEitherT :: m (Either String a) + } + +bindET :: (Monad m) => EitherT m a -> (a -> EitherT m b) -> EitherT m b +x `bindET` f = EitherT $ do + unwrapped <- runEitherT x + case unwrapped of + Left err -> return $ Left err + Right y -> runEitherT (f y) + +failET :: (Monad m) => String -> EitherT m a +failET err = EitherT $ return $ Left err + +instance (Monad m) => Monad (EitherT m) where + -- Use 'pure' as suggested by the GHC instead of noncanonical 'return' + -- definition (like the 'returnMT') + return = pure + (>>=) = bindET + +instance (Monad m) => MonadFail (EitherT m) where + fail s = failET s + +instance MonadTrans EitherT where + lift m = EitherT (Right `liftM` m) + +instance (Functor m) => Functor (EitherT m) where + fmap f x = EitherT $ fmap (fmap f) . runEitherT $ x + +instance (Applicative m) => Applicative (EitherT m) where + pure :: a -> EitherT m a + pure x = EitherT $ pure $ Right x + + (<*>) :: EitherT m (a -> b) -> EitherT m a -> EitherT m b + ff <*> fa = EitherT $ (fmap x (runEitherT ff)) <*> (runEitherT fa) + where + x :: Either String (a -> b) -> Either String a -> Either String b + x f a = f <*> a + +instance (MonadState s m) => MonadState s (EitherT m) where + get = lift get + put k = lift (put k) +{-- End of code from examples --} + + +identityParse :: a -> Parse a +identityParse x = P $ pure x + +failParse :: a -> Parse a +failParse _ = P $ fail "Test error in fail" + + +-- ghci> :l 18_b_1.hs +-- [1 of 2] Compiling Main ( 18_b_1.hs, interpreted ) +-- Ok, one module loaded. + +-- ghci> evalParse (identityParse 123) L.empty +-- Right 123 + +-- ghci> evalParse (identityParse "Test value") L.empty +-- Right "Test value" + +-- ghci> evalParse (failParse "Test value") L.empty +-- Left "Test error in fail" -- cgit v1.2.3