简体   繁体   English

如何使用公共变量参数清晰地定义包含函数的Haskell模块?

[英]How to cleanly define a Haskell module containing functions with a common variable parameter?

I'm trying to write a Haskell module that defines functions for a remote XML-RPC API using the library haxr . 我正在尝试编写一个Haskell模块,该模块使用库haxr定义远程XML-RPC API的函数。 Here's how haxr's documentation suggests you define a Haskell function that calls examples.add on the server at url : 以下是haxr的文档建议您如何在url上定义一个调用examples.add的Haskell函数:

add :: String -> Int -> Int -> IO Int
add url = remote url "examples.add"

called like this: 像这样叫:

server = "http://localhost/~bjorn/cgi-bin/simple_server"
add server x y

This seems ok to me if I had one or two XML-RPC methods (I wouldn't need a seperate module then). 如果我有一个或两个XML-RPC方法(我不需要一个单独的模块),这对我来说似乎没问题。 However, the duplication of server in the code is a problem since I have close to 100 functions. 但是,代码中的server重复是一个问题,因为我有近100个函数。 I can't define server in the module, like this: 我无法在模块中定义server ,如下所示:

someRemote :: Remote
someRemote = remote "http://example.com/XMLRPC"

add :: Int -> Int -> IO Int
add = someRemote "examples.add"

since the URL can't be hard-coded if it is to be flexible for the code that uses it. 因为如果URL对于使用它的代码是灵活的,则不能对其进行硬编码。 I also can't define the someRemote as a parameter of the functions, as it has the same duplication issue. 我也无法将someRemote定义为函数的参数,因为它具有相同的重复问题。

Haxr's examples provide no clues on how to solve this. Haxr的例子没有提供如何解决这个问题的线索。

I usually write programs in imperative OOP languages (ie Java, Python). 我通常用命令式OOP语言(即Java,Python)编写程序。 If I were using those languages, I would define a class with an constructor that takes server , and all the functions using the object instances' server variable, rather than asking the calling code for it. 如果我使用这些语言,我会定义一个带有构造函数的类,它使用server ,所有函数都使用对象实例的server变量,而不是询问调用代码。

I've looked for an equivalent to this in Haskell, but I don't seem to know the right keywords to find it. 我在Haskell中找到了相同的东西,但我似乎不知道找到它的正确关键字。 Type classes don't seem to be the answer. 类型类似乎不是答案。 I could write a higher order function that returns the partially applied functions, but unpacking those would be even uglier. 我可以写一个更高阶的函数来返回部分应用的函数,但是解压缩它们会更加丑陋。

I'm not quite sure "duplication of server" is that bad actually. 我不太确定“重复服务器”实际上是那么糟糕。 Of course you should never duplicate a lengthy literal, but for a single variable name that doesn't clutter the code much and is easy to replace this shouldn't be much of an issue. 当然,你永远不应该复制一个冗长的文字,但对于一个不会使代码混乱并且易于替换的单个变量名称,这应该不是什么大问题。

But of course you can avoid such duplication easily, by attaching the shared variable to the monad you're working in, similar to how you'd attach it to an OO class object. 但是当然,通过将共享变量附加到您正在使用的monad中,您可以轻松避免这种重复,类似于将其附加到OO类对象的方式。 That's called a reader . 那叫做读者

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

You could just emulate the OOP approach in Haskell, by wrapping the server in an "object" and passing it to all your "methods" as the first parameter: 您可以通过将服务器包装在“对象”中并将其作为第一个参数传递给所有“方法”来模拟Haskell中的OOP方法:

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"

You still have to pass the Server every time you make a call, but now you can change the representation of Server (to make it a handle to a persistent connection, for example). 您每次拨打电话时仍然必须通过服务器,但现在您可以更改服务器的表示(例如,使其成为持久连接的句柄)。

Additionally, you could use a reader monad to make passing the server implicit: 此外,您可以使用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)

By making MonadServer a typeclass, any reader monad you use can be extended to support the implicit Server argument. 通过使MonadServer成为类型类,可以扩展您使用的任何读者monad以支持隐式Server参数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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