2015-05-29 2 views
4

Я пытаюсь создать некоторые привязки для API в Haskell. Я заметил, что некоторые функции имеют огромное количество аргументов, например.Рекомендации по работе с API

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

Это не обязательно плохо, само по себе, чтобы иметь много аргументов. Но как пользователь мне не нравятся длинные функции аргументов. Однако каждый из этих аргументов абсолютно необходим на 100%.

Есть ли еще haskell-ish способ абстрагироваться от общих частей этих функций? Все предыдущие учетные данные здесь используются для создания URL-адреса, поэтому я буду нуждаться в нем, и то, что он обозначает, полностью зависит от функции. Некоторые вещи согласуются, хотя, например, Key и Account, и мне интересно, что лучше всего абстрагироваться от этих аргументов.

Спасибо!

+2

Объявить тип, объединяющий все эти отдельные аргументы в одно значение? (Попытка найти логические группировки - это, как правило, забавная часть.) Вы также можете определить «новый тип» или хотя бы псевдоним «type» для всех этих значений «Int». – MathematicalOrchid

+0

Я попробовал кучу разных идей в Strive, привязав API к Страве. В конечном итоге я получил настройки и линзы. Ознакомьтесь с [этой проблемой] (https://github.com/tfausak/strive/issues/44) для обсуждения и [в этом примере] (https://github.com/tfausak/strive/tree/v1.0.1 # segment-leaderboard) для того, как это выглядит сейчас. –

ответ

2

Вы можете объединить их в более описательные типы данных:

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

Тогда, возможно, есть type с или newtypes, чтобы сделать другие аргументы более описательным:

-- 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 

Если Config всегда необходимо, то я бы рекомендовал работать в монаде Reader, который вы можете легко сделать как

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 

Использует библиотеку mtl для монадных трансформаторов. Если вы не хотите, чтобы ввести это ограничение снова и снова, вы можете также использовать расширение ConstraintKinds псевдоним его:

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

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

... 

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

В зависимости от конкретного применения, можно также разделить его на многочисленные функции. Я видел много API, перед тем, что было что-то вроде

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, это не принято вообще).

Если вы обнаружите, что у вас слишком много типов подписей, даже после применения некоторых из этих методов, возможно, вы приближаетесь к проблеме с неправильного направления, возможно, эти функции можно разбить на более простые промежуточные этапы , возможно, некоторые из этих аргументов могут быть объединены логически в более конкретные типы данных, возможно, вам просто нужна более крупная структура записей для обработки сложных операций. Сейчас это довольно открыто.

+0

Я люблю последний. Спасибо за все это – Steve

+0

@bhelkilr Как работает функция runReaderT? – Steve

+0

@Steve принимает действие ReaderT и значение, которое нужно вернуть из запроса на вызов, а затем запускает действие с использованием этого значения. Если вы использовали государственную монаду, прежде чем думать о монаде читателя как о фиксированном состоянии, вы можете получить ее, а не установить. Он часто используется для статической конфигурации. – bheklilr

Смежные вопросы