我想将一个函数转换A -> IO B
为IO (A -> B)
,知道只有有限数量的可能值A
。目前我只是做
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'
但是我对这需要的代码量并不满意。
我想将一个函数转换A -> IO B
为IO (A -> B)
,知道只有有限数量的可能值A
。目前我只是做
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'
但是我对这需要的代码量并不满意。
Joachim 答案的略微增强版本,用于Data.Map
更快地执行查找。我也将使用 TupleSections pragma。
{-# LANGUAGE TupleSections #-}
import Data.Map
import Control.Monad
为了更加整洁,假设您的Piece
类型可以给出Ord
,Bounded
和Enum
实例。
data Piece = Knight | Bishop | Rook deriving (Ord,Bounded,Enum,Show)
并定义有用的enumerate
功能
enumerate :: (Bounded a, Enum a) => [a]
enumerate = [minBound..maxBound]
现在你可以做
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!)
如果你有一个 list values :: [A]
,并且A
有一个Eq
-Instance ,这将起作用:
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))
正如其他人所指出的,如果您不介意 的附加类型类要求A
,您可以使用映射或哈希映射来加快查找速度。
此外,从您的用例描述来看,您似乎正在从程序附带的文件中加载静态数据。根据最终程序运行的环境(例如,保证文件存在并且不会更改),这可能是unsafePerformIO
简单地定义A -> B
为顶级函数的有效用途。或者,有一些方法可以在编译源中包含二进制 blob。
为了完整起见,我将提到Hackage 上的可数包通过提供Finite
类型类使这成为可能。你定义了类似的东西
instance Finite Piece where
allValues = [Pawn, Knight, Bishop, Rook, Queen, King]
那么你有
assemble :: (Finite a, Applicative f) => (a -> f b) -> f (a -> b)
它将专门满足您的需求。
看源码,好像是使用了关联列表,所以如果你的类型很大的话会很慢。此外,它还为函数定义了一些孤立的Foldable
和Traversable
和Eq
(!) 实例,有些人可能认为这些实例令人反感。
你有功能f :: A -> IO B
,你有g :: IO A
,你用你的convert
功能Applicative
<*> :: f (a -> b) -> f a -> f b
as
fg :: IO a -> (a ->IO B) -> IO B
fg g f = (convert f) <*> g
但是你可以只使用 monad (>>=) :: m a -> (a -> m b) -> m b
,
fg :: IO a -> (a ->IO B) -> IO B
fg g f = g >>= f
您的函数签名允许a->m b
输入任何函数,但在您内部假设特定范围的值。convert
不像签名似乎声明的那样多态。
您所做的是创建了一个从 a 到 b 的 Map,然后创建了一个纯函数,在该映射中查找纯值。原因如下:
你所要求的类似于为一个类 (C, ⊗, I)实现张量强度- 给定类别 C 中的二元关系 ⊗ 和一个单子 m,将 a ⊗ mb 转换为 m (a ⊗ b)。 strength :: (Monad m) => (a, m b) -> m (a, b)
当这对于满足某些要求的二元关系是可能的时,monad 是强大的。在 Haskell 中,所有单子都是强的,如果张量积 a ⊗ b 被选为一对(a, b)
: strength (a, mb) = mb >>= return . (a,)
. 然而,在这里你试图对二元关系做同样的事情->
。不幸的是,a -> b
不能选择作为张量积,因为它不是双函子 - 它在a
. 因此,您想要的任何功能都无法实现。
您的情况不同的是,本质上您构建了所有 pairs (a,b)
。因此,如果您显式枚举所有可能的a
and对b
,例如通过构建一个m (Map a b)
. 这里的其他人提供了很好的糖,暴露了“类似函数”的接口,但它们只是地图中的查找。