API Guidelines

I am trying to create some bindings for an API in Haskell. I noticed that some functions have a huge number of arguments, for example

myApiFunction :: Key -> Account -> Int -> String -> Int -> Int -> IO (MyType)

It is not necessarily bad, in fact, to have many arguments. But as a user, I don't like the long argument functions. However, each of these arguments is absolutely 100% necessary.

Is there another haskell-ish way to abstract from the common parts of these functions? All of the previous credentials here are used to create the url, so I will need it, and what it means is completely function dependent. Some things are consistent, although, for example, Keyand Account, and I wonder what is the best way to ignore these arguments.

Thank!

+4
source share
1 answer

You can combine them into more descriptive data types:

data Config = Config
    { cKey :: Key
    , cAccount :: Account
    }

Then, perhaps, there is typeor newtypesfor other arguments to be more visual:

-- I have no idea what these actually should be, I'm just making up something
type Count = Int
type Name = String
type Position = (Int, Int)

myApiFunction :: Config -> Count -> Name -> Position -> IO MyType
myApiFunction conf count name (x, y) =
    myPreviousApiFunction (cKey conf)
                          (cAccount conf)
                          name
                          name
                          x
                          y

If required Config, I would recommend working in a monad Reader, which you can easily do as

myApiFunction
    :: (MonadReader Config io, MonadIO io)
    => Count -> Name -> Position
    -> io MyType
myApiFunction count name (x, y) = do
    conf <- ask
    liftIO $ myPreviousApiFunction
                (cKey conf)
                (cAccount conf)
                name
                name
                x
                y

It uses a library mtlfor monad transformers. If you do not want to enter this restriction again and again, you can also use the extension ConstraintKindsfor the alias:

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
...

type ApiCtx io = (MonadReader Config io, MonadIO io)

...

myApiFunction
    :: ApiCtx io
    => Count -> Location -> Position
    -> io MyType
myApiFunction ...

Depending on your specific application, you can also break it down into several functions. I saw a lot of APIs before something like

withCount :: ApiCtx io => Count    -> io a -> io a
withName  :: ApiCtx io => Name     -> io a -> io a
withPos   :: ApiCtx io => Position -> io a -> io a

(&) :: a -> (a -> b) -> b

request :: ApiCtx io => io MyType 

> :set +m   -- Multi-line input
> let r = request & withCount 1
|                 & withName "foo"
|                 & withPos (1, 2)
> runReaderT r (Config key acct)

, , . , , , , ( , ConstraintKinds, ).

, , , , , , , , , , . .

+2

All Articles