How do I correctly define a Haskell module containing functions with a shared variable parameter?

I am trying to write a Haskell module that defines functions for a remote XML-RPC API using the haxr library . Here's how the haxr documentation suggests you define a Haskell function that calls examples.add

on the server in url

:

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

      

It looks like I have one or two XML-RPC methods (then I don't need a separate module). However, duplication server

in the code is a problem since I have about 100 functions. I cannot define server

in a module, for example:

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

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

      

as a url cannot be hardcoded if it needs to be flexible for the code that uses it. I also cannot define someRemote

as a function parameter, as it has the same duplication problem.

The Haxr examples do not provide any guidance on how to fix this problem.

I usually write programs in imperative OOP languages ​​(i.e. Java, Python). If I were using these languages, I would define a class with a constructor that accepts server

all functions using an object instance variable server

, instead of asking for the calling code for it.

I've looked for the equivalent of this in Haskell, but I don't seem to know the correct keywords to find it. Generic classes don't seem to be the answer. I could write a higher-order function that returns partially applied functions, but unpacking them would be even uglier.

+3


source to share


2 answers


I'm not entirely sure if "server duplication" is actually a bad thing. Of course, you should never duplicate a long literal, but for a single variable name that doesn't interfere with the code much and is easy to replace, it shouldn't be a big problem.

But of course, you can easily avoid this duplication by adding a shared variable to the monad you are working in, similar to how you attach it to an OO class object. This is called the 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

      

+3


source


You can simply emulate Haskell's OOP approach by wrapping the server in an "object" and passing it to all your "methods" as the first parameter:

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 Server on every call, but now you can change the server view (for example, make it a handle to a persistent connection).



Alternatively, you can use the reader monad to make the transfer implicit by the server:

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 class, any reader monad you use can be extended to support an implicit server argument.

+1


source







All Articles