简体   繁体   中英

A -> IO B to IO (A -> B)

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.

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

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 (eg 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.

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.

You have function f :: A -> IO B and you have g :: IO A , you use your convert function with Applicative <*> :: f (a -> b) -> fa -> fb as

fg :: IO a -> (a ->IO B) -> IO B
fg g f = (convert f) <*>  g

But you can just use monad (>>=) :: ma -> (a -> mb) -> mb ,

fg :: IO a -> (a ->IO B) -> IO B
fg g f = g >>= f

Your function signature permits any function a->mb 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, mb) -> m (a, b) for a monoidal category (C, ⊗, I) - given a binary relation ⊗ in category C and a monad m, convert a ⊗ mb 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,) 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 ab) . The others here offered nice sugars exposing "function-like" interfaces, but they are merely lookups in the map.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM