Reputation: 3771
I want to convert a function A -> IO B
to IO (A -> B)
, knowing that there is only a finite number of possible values of A
. At the moment I just do
convert :: (A -> IO B) -> IO (A -> B)
convert f = do
b1 <- f a1
b2 <- f a2
...
let f' a1 = b1
f' a2 = b2
...
return f'
However I'm not satisfied with the amount of code this requires.
Upvotes: 6
Views: 376
Reputation: 5406
Your function signature permits any function a->m b
on input, yet inside you assume a specific range of values. convert
is not as polymorphic as the signature seems to declare.
What you have done is created a Map from a to b, then made a pure function that looks up a pure value in that map. Here's why:
What you are asking for is similar to implementing tensorial strength strength :: (Monad m) => (a, m b) -> m (a, b)
for a monoidal category (C, ⊗, I) - given a binary relation ⊗ in category C and a monad m, convert a ⊗ m b to m (a ⊗ b). When this is possible for a binary relationship that meets certain requirements, the monad is strong. In Haskell all monads are strong, if tensorial product a ⊗ b is chosen to be a pair (a, b)
: strength (a, mb) = mb >>= return . (a,)
. Yet, here you are attempting to do the same for a binary relationship ->
. Unfortunately, a -> b
cannot be chosen to be a tensor product, because it is not a bi-functor - it is contravariant in a
. So what you want cannot be accomplished for arbitrary functions.
What is different in your case, is that essentially you built all pairs (a,b)
. The amount of code, therefore, can be reduced if you explicitly enumerate all possible pairs of a
and b
, for example by building a m (Map a b)
. The others here offered nice sugars exposing "function-like" interfaces, but they are merely lookups in the map.
Upvotes: 0
Reputation: 7011
For the sake of completeness, I'll mention that the countable package on Hackage makes this possible by providing the Finite
type class. You define something like
instance Finite Piece where
allValues = [Pawn, Knight, Bishop, Rook, Queen, King]
then you have
assemble :: (Finite a, Applicative f) => (a -> f b) -> f (a -> b)
which will specialise to precisely what you need.
Looking at the source, it seems that it uses an association list, so it would be slow if your type was large. Plus, it defines some orphan instances of Foldable
and Traversable
and Eq
(!) for functions, which some may regard as distasteful.
Upvotes: 4
Reputation: 47402
A slightly souped-up version of Joachim's answer, that uses Data.Map
to perform the lookup faster. I'll be using the TupleSections pragma as well.
{-# LANGUAGE TupleSections #-}
import Data.Map
import Control.Monad
For added neatness, assume that your Piece
type can be given Ord
, Bounded
and Enum
instances.
data Piece = Knight | Bishop | Rook deriving (Ord,Bounded,Enum,Show)
and define the useful enumerate
function
enumerate :: (Bounded a, Enum a) => [a]
enumerate = [minBound..maxBound]
Now you can do
convert :: (Monad m, Bounded a, Enum a, Ord a) => (a -> m b) -> m (a -> b)
convert f = do
memo <- sequence [liftM (a,) (f a) | a <- enumerate]
return (fromList memo!)
Upvotes: 9
Reputation: 25782
If you have a list values :: [A]
, and A
has an Eq
-Instance, this would work:
convert :: (A -> IO B) -> IO (A -> B)
convert f = do
lookupTable <- sequence [ (\b -> (a,b)) `fmap` f a | a <- values]
return $ (\a -> fromJust (lookup a lookupTable))
As other have noted, if you don’t mind the additional type class requirements for A
, you can use maps or hashmaps to speed up the lookup.
Also, from your use-case description, it seems that you are loading static data from a file that comes with your program. Depending on the environment where your final program runs (e.g. guaranteed that the files exist and are not changing), this might be a valid use for unsafePerformIO
to simply define A -> B
as a top-level function. Alternatively there are ways to include binary blobs in the compile source.
Upvotes: 6
Reputation: 1803
You have function f :: A -> IO B
and you have g :: IO A
,
you use your convert
function with Applicative
<*> :: f (a -> b) -> f a -> f b
as
fg :: IO a -> (a ->IO B) -> IO B
fg g f = (convert f) <*> g
But you can just use monad (>>=) :: m a -> (a -> m b) -> m b
,
fg :: IO a -> (a ->IO B) -> IO B
fg g f = g >>= f
Upvotes: 0