我正在尝试编写一个Haskell模块,该模块使用库haxr定义远程XML-RPC API的函数。以下是haxr的文档建议您如何在examples.add
处定义在服务器上调用url
的Haskell函数:
add :: String -> Int -> Int -> IO Int
add url = remote url "examples.add"
这样叫:
server = "http://localhost/~bjorn/cgi-bin/simple_server"
add server x y
如果我有一个或两个XML-RPC方法(我不需要单独的模块),这对我来说似乎没问题。但是,代码中server
的重复是一个问题,因为我有近100个函数。我无法在模块中定义server
,如下所示:
someRemote :: Remote
someRemote = remote "http://example.com/XMLRPC"
add :: Int -> Int -> IO Int
add = someRemote "examples.add"
因为如果URL对于使用它的代码是灵活的,则不能对其进行硬编码。我也无法将someRemote
定义为函数的参数,因为它具有相同的重复问题。
Haxr's examples没有提供如何解决此问题的线索。
我通常用命令式OOP语言(即Java,Python)编写程序。如果我使用这些语言,我会定义一个带有server
的构造函数的类,以及使用对象实例'server
变量的所有函数,而不是询问调用代码。
我在Haskell中找到了相同的东西,但我似乎不知道找到它的正确关键字。类型类似乎不是答案。我可以编写一个更高阶函数来返回部分应用的函数,但是解压缩它们会更加丑陋。
答案 0 :(得分:4)
我不太确定“重复服务器”实际上是那么糟糕。当然,你永远不应该复制一个冗长的文字,但是对于一个不会使代码混乱并且易于替换的单个变量名称,这应该不是一个大问题。
但是当然你可以通过将共享变量附加到你正在使用的monad来轻松避免这种重复,类似于你将它附加到OO类对象的方式。那叫做读者。
import Control.Monad.Trans.Reader
type RemoteIO = ReaderT String IO -- or perhaps `ReaderT Remote IO`
add :: Int -> Int -> RemoteIO Int
add x y = do
url <- ask
lift $ remote url "examples.add" x y
答案 1 :(得分:1)
您可以通过将服务器包装在&#34;对象中来模拟Haskell中的OOP方法。并将其传递给您所有的&#34;方法&#34;作为第一个参数:
module MyServer (
Server, -- don't expose constructor
newServer,
add,
) where
data Server = Server String
newServer :: String -> IO Server
newServer = return . Server
add :: Server -> Int -> Int -> IO Int
add (Server url) = remote url "examples.add"
每次拨打电话时,您仍然必须通过服务器,但现在您可以更改服务器的表示形式(例如,使其成为持久连接的句柄)。
此外,您可以使用reader monad来隐式传递服务器:
class MonadServer m where
withServer :: (Server -> m a) -> m a
instance MonadServer (ReaderT Server m) where
withServer f = ReaderT (\server -> runReaderT (f server) server)
add :: (MonadIO m, MonadServer m) => Int -> Int -> m Int
add x y = withServer (\(Server url) -> liftIO $ remote url "examples.add" x y)
通过使MonadServer成为类型类,可以扩展您使用的任何读者monad以支持隐式Server参数。