将(a -> IO b)的转换为IO(a -> b)

4

我在IO上下文中有几种数据类型,例如:

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

我希望将它们全部放在一个数据对象中,例如:
data Configdata = Configdata String FilePath (String -> String)

所以我不必从IO上下文中获取每个值,而只需从IO Configdata中获取即可。
我没有解决的关键点是如何将String -> IO String转换为IO (String -> String)。Hoogle没有给我任何能够实现这个功能的函数。
我不确定是否可能,因为函数的输入可能是无限的。
有人有解决方案或者解释为什么不可能吗? 我知道使用列表代替函数是一种选择,但如果可能的话,我更喜欢使用函数。

14
这并不是完全兼容的。在“String -> IO String”中,IO计算可以依赖于参数,而在“IO (String -> String)”中则不能。 - Bergi
1
@Bergi说得非常准确。这是Applicative和Monad之间的关键区别。 - user2297560
可能是 https://dev59.com/k3fZa4cB1Zd3GeqPMQTL 的重复问题。 - atravers
1个回答

10

确实,这是不可能的。考虑函数:

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"

现在,如果可以将此转换为IO(String -> String),则必须执行所有可能的IO操作以获得任何输入的纯 String -> String 函数。 换句话说,即使您只计划将该函数用于观看小猫咪的目的,也将导致核灾难。

尽管如此,对于您特定的应用程序,这可能是可能的。 特别地,如果您知道该函数仅会针对预定的一组字符串进行调用,则可以从IO中预查询它们并将结果存储在一个映射中,然后可以纯粹地对其进行索引。

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)

当然,从性能的角度来看可能不可行。


2
如果有一组预定的字符串,那么创建一个小型自定义类型,每个字符串在集合中对应一个值可能是有意义的。然后,universe包可以为您提供sequenceA :: (Foo -> IO String) -> IO (Foo -> String),它具有您所描述的行为,即创建一个Map并进行索引。 - Daniel Wagner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接