4

I have several data types in an IO context like:

a :: IO String
b :: IO FilePath
c :: String -> IO String

I want to put them all together in one data object like:

data Configdata = Configdata String FilePath (String -> String)

So I don't have to get each value for its own out of the IO context, but just out of IO Configdata.

The critical point where I don't have a solution is how I can transform String -> IO String to IO (String -> String). Hoogle doesn't give me any functions which are capable of doing this.

I am not sure if it's maybe even not possible, since the input of the function is possibly infinite.

Does someone have a solution or an explanation why it's not possible? I know that using a list instead of a function is an option, but I would prefer using a function if possible.

F. Böller
  • 4,194
  • 2
  • 20
  • 32

1 Answers1

10

Indeed this is not possible. Consider the function:

import Acme.Missiles

boo :: String -> IO String
boo "cute" = return "Who's a nice kitty?"
boo "evil" = launchMissiles >> return "HTML tags lea͠ki̧n͘g fr̶ǫm ̡yo​͟ur eye͢s̸ ̛l̕ik͏e liq​uid pain"

Now, if it were possible to transform this to IO (String -> String), it would have to execute all possible IO actions for any input before returning the pure String -> String function. IOW, even if you only planned to use the function for kitten-watching purposes, it would entail nuclear holocaust.

Nevertheless, it may well be possible to do this for your specific application. In particular, if you know the function will only ever be called for a predetermined set of strings, you can pre-query them from IO and store the results in a map, which can then be indexed purely.

import qualified Data.Map as Map

puh :: IO (String -> String)
puh = fmap ((Map.!) . Map.fromList) . forM ["cute"] $ \q -> do
       res <- boo q
       return (q, res)

Of course, this may not be feasible performance-wise.

Community
  • 1
  • 1
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • 2
    If there is a predetermined set of strings, it probably makes sense to make a small custom type with one value per string in the set. Then the universe package can give you [`sequenceA :: (Foo -> IO String) -> IO (Foo -> String)`](http://hackage.haskell.org/package/universe-1.0/docs/Data-Universe-Instances-Reverse.html#t:Traversable), which has the behavior you describe of creating a `Map` and indexing into it. – Daniel Wagner Aug 18 '16 at 16:31