Chris
Chris

Reputation: 963

How to interact with pure algorithm in IO code

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

Answers (2)

Alec
Alec

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 coerceing) 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

leftaroundabout
leftaroundabout

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

Related Questions