Reputation: 963
To illustrate the point with a trivial example, say I have implemented filter
:
filter :: (a -> Bool) -> [a] -> [a]
And I have a predicate p
that interacts with the real world:
p :: a -> IO Bool
How do it make it work with filter
without writing a separate implementation:
filterIO :: (a -> IO Bool) -> [a] -> IO [a]
Presumably if I can turn p
into p'
:
p': IO (a -> Bool)
Then I can do
main :: IO ()
main = do
p'' <- p'
print $ filter p'' [1..100]
But I haven't been able to find the conversion.
Edited: As people have pointed out in the comment, such a conversion doesn't make sense as it would break the encapsulation of the IO Monad.
Now the question is, can I structure my code so that the pure and IO versions don't completely duplicate the core logic?
Upvotes: 2
Views: 202
Reputation: 32309
How do it make it work with filter without writing a separate implementation
That isn't possible and the fact this sort of thing isn't possible is by design - Haskell places firm limits on its types and you have to obey them. You cannot sprinkle IO
all over the place willy-nilly.
Now the question is, can I structure my code so that the pure and IO versions don't completely duplicate the core logic?
You'll be interested in filterM
. Then, you can get both the functionality of filterIO
by using the IO
monad and the pure functionality using the Identity
monad. Of course, for the pure case, you now have to pay the extra price of wrapping/unwrapping (or coerce
ing) the Identity
wrapper. (Side remark: since Identity
is a newtype
this is only a code readability cost, not a runtime one.)
ghci> data Color = Red | Green | Blue deriving (Read, Show, Eq)
Here is a monadic example (note that the lines containing only Red
, Blue
, and Blue
are user-entered at the prompt):
ghci> filterM (\x -> do y<-readLn; pure (x==y)) [Red,Green,Blue]
Red
Blue
Blue
[Red,Blue] :: IO [Color]
Here is a pure example:
ghci> filterM (\x -> Identity (x /= Green)) [Red,Green,Blue]
Identity [Red,Blue] :: Identity [Color]
Upvotes: 5
Reputation: 120711
As already said, you can use filterM
for this specific task. However, it is usually better to keep with Haskell's characteristic strict seperation of IO and calculations. In your case, you can just tick off all necessary IO
in one go and then do the interesting filtering in nice, reliable, easily testable pure code (i.e. here, simply with the normal filter
):
type A = Int
type Annotated = (A, Bool)
p' :: Annotated -> Bool
p' = snd
main :: IO ()
main = do
candidates <- forM [1..100] $ \n -> do
permitted <- p n
return (n, permitted)
print $ fst <$> filter p' candidates
Here, we first annotate each number with a flag indicating what the environment says. This flag can then simply be read out in the actual filtering step, without requiring any further IO.
In short, this would be written:
main :: IO ()
main = do
candidates <- forM [1..100] $ \n -> (n,) <$> p n
print $ fst <$> filter snd candidates
While it is not feasible for this specific task, I'd also add that you can in principle achieve the IO
seperation with something like your p'
. This requires that the type A
is “small enough” that you can evaluate the predicate with all values that are possible at all. For instance,
import qualified Data.Map as Map
type A = Char
p' :: IO (A -> Bool)
p' = (Map.!) . Map.fromList <$> mapM (\c -> (c,) <$> p c) ['\0'..]
This evaluates the predicate once for all of the 1114112 chars there are and stores the results in a lookup table.
Upvotes: 1