Say I have code where I want to do the following:
- Input: a list of strings
[String]
- Operation (
checkSat
andcheckResult
)- Obtains a boolean from an input string.
- Output:
- If everything in the input is parseable into a boolean, only return those that resulted in "unsat".
- If at least one of them has an error, return an error.
data Err = Err String deriving (Show)
-- Detail omitted for this example
checkSat :: String -> Either Err String
checkSat = Right . id
checkResult :: String -> Either Err Bool
checkResult "sat" = Right True
checkResult "unsat" = Right False
checkResult err = Left $ Err err
onlyUnsat :: [String] -> Either Err [String]
onlyUnsat xs = filter (traverse notSat xs) xs
where notSat x = fmap not $ checkSat x >>= checkResult
onlyUnsat = filter (traverse notSat xs) xs
The code above doesn't quite work. What's the right way to write onlyUnsat
with the correct typing?
CodePudding user response:
Try this:
onlyUnsat :: [String] -> Either Err [String]
onlyUnsat = traverse checkSat >=> filterM (fmap not . checkResult)
where
filterM :: Applicative m => (a -> m Bool) -> [a] -> m [a]
filterM p = fmap (map fst . filter snd) . traverse (\x -> withFst x <$> p x)
where withFst x = \y -> (x, y)
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
f >=> g = \x -> f x >>= g
Note that both filterM
and >=>
can be imported from Control.Monad
. I've redefined them here for the sake of clarity :-)
The basic idea is to split the operation into two stages: (1) apply checkSat
on every item in the list; and (2) filter every item in the list. The first stage is given by traverse checkSat
, which has type [String] -> Either Err [String]
. The second stage is given by filterM (fmap not . checkResult)
, which also has type [String] -> Either Err String
. We then use >=>
to chain these two computations together into one of the same type.