Самый простой способ сохранить это простой, но небезопасно, это просто использовать основной ADT с Arbitrary
поле, которое принимает String
ключ и значение:
data FilterKey
= Arbitrary String String
| Sentiment Sentiment
| Gender Gender
deriving (Eq, Show)
data Sentiment
= Positive
| Negative
| Neutral
deriving (Eq, Show, Bounded, Enum)
data Gender
= Male
| Female
| Any
deriving (Eq, Show, Bounded, Enum)
Тогда вам нужна функция для преобразования FilterKey
к вашему API, базового (String, String)
типа фильтра
filterKeyToPair :: FilterKey -> (String, String)
filterKeyToPair (Arbitrary key val) = (key, val)
filterKeyToPair (Sentiment sentiment) = ("sentiment", showSentiment sentiment)
filterKeyToPair (Gender gender) = ("gender", showGender gender)
showSentiment :: Sentiment -> String
showSentiment s = case s of
Positive -> "positive"
Negative -> "negative"
Neutral -> "neutral"
showGender :: Gender -> String
showGender g = case g of
Male -> "male"
Female -> "female"
Any -> "male,female"
и, наконец, вы можете просто обернуть comments
функции вашего базового API, так что параметр фильтрации более типизированный, и это Conver Ted к (String, String)
форме внутри отправить запрос
comments :: String -> [FilterKey] -> Config -> Result
comments name filters conf = do
let filterPairs = map filterKeyToPair filters
commentsRaw name filterPairs conf
Это будет работать достаточно хорошо и довольно проста в использовании:
comments "tide-pods" [Arbitrary "limits" "3", Sentiment Positive, Gender Female] config
Но это не очень расширяемой. Если пользователь вашей библиотеки хочет расширить его добавить Limit Int
поле, они должны написать его как
data Limit = Limit Int
limitToFilterKey :: Limit -> FilterKey
limitToFilterKey (Limit l) = Arbitrary "limit" (show l)
И было бы вместо того, чтобы выглядеть
[limitToFilterKey (Limit 3), Sentiment Positive, Gender Female]
, не особенно приятно, особенно если они пытаются добавить много разных полей и типов.Комплексное, но расширяемое решение должно состоять в том, чтобы иметь один тип Filter
, и на самом деле для простоты он способен представлять один фильтр или список фильтров (попробуйте реализовать его там, где Filter = Filter [(String, String)]
, это немного сложнее сделать чисто):
import Data.Monoid hiding (Any)
-- Set up the filter part of the API
data Filter
= Filter (String, String)
| Filters [(String, String)]
deriving (Eq, Show)
instance Monoid Filter where
mempty = Filters []
(Filter f) `mappend` (Filter g) = Filters [f, g]
(Filter f) `mappend` (Filters gs) = Filters (f : gs)
(Filters fs) `mappend` (Filter g) = Filters (fs ++ [g])
(Filters fs) `mappend` (Filters gs) = Filters (fs ++ gs)
Тогда есть класс для представления преобразования в Filter
(так же, как Data.Aeson.ToJSON
):
class FilterKey kv where
keyToString :: kv -> String
valToString :: kv -> String
toFilter :: kv -> Filter
toFilter kv = Filter (keyToString kv, valToString kv)
экземпляр для Filter
является довольно простым
instance FilterKey Filter where
-- Unsafe because it doesn't match the Fitlers contructor
-- but I never said this was a fully fleshed out API
keyToString (Filter (k, _)) = k
valToString (Filter (_, v)) = v
toFilter = id
Быстрый трюк вы можете сделать здесь легко комбинировать значения этого типа
-- Same fixity as <>
infixr 6 &
(&) :: (FilterKey kv1, FilterKey kv2) => kv1 -> kv2 -> Filter
kv1 & kv2 = toFilter kv1 <> toFilter kv2
Тогда вы можете написать экземпляры FilterKey
класса, которые работают с:
data Arbitrary = Arbitrary String String deriving (Eq, Show)
infixr 7 .=
(.=) :: String -> String -> Arbitrary
(.=) = Arbitrary
instance FilterKey Arbitrary where
keyToString (Arbitrary k _) = k
valToString (Arbitrary _ v) = v
data Sentiment
= Positive
| Negative
| Neutral
deriving (Eq, Show, Bounded, Enum)
instance FilterKey Sentiment where
keyToString _ = "sentiment"
valToString Positive = "positive"
valToString Negative = "negative"
valToString Neutral = "neutral"
data Gender
= Male
| Female
| Any
deriving (Eq, Show, Bounded, Enum)
instance FilterKey Gender where
keyToString _ = "gender"
valToString Male = "male"
valToString Female = "female"
valToString Any = "male,female"
Добавить немного сахара:
data Is = Is
is :: Is
is = Is
sentiment :: Is -> Sentiment -> Sentiment
sentiment _ = id
gender :: Is -> Gender -> Gender
gender _ = id
И вы можете писать запросы, такие как
example
= comments "tide-pods" config
$ "limit" .= "3"
& sentiment is Positive
& gender is Any
Этот API все еще может быть безопасным, если вы не экспортировать конструкторами Filter
и если вы не экспортировать toFilter
. Я оставил это как метод на typeclass просто так, чтобы Filter
мог переопределить его с помощью id
для эффективности. Затем пользователь вашей библиотеки просто делает
data Limit
= Limit Int
deriving (Eq, Show)
instance FilterKey Limit where
keyToString _ = "limit"
valToString (Limit l) = show l
Если они хотели сохранить is
стиль они могли бы использовать
limit :: Is -> Int -> Limit
limit _ = Limit
И написать что-то вроде
example
= comments "foo" config
$ limit is 3
& sentiment is Positive
& gender is Female
Но что показано здесь просто как пример одного из способов сделать EDSL в Haskell очень читабельным.
'data FilterKey = Sentiment SentimentValue | Пол ГендерValue | Arbitrary String String', тогда вы можете делать «комментарии» tide-pods «[Произвольный» предел »« 3 », Sentiment Positive, Gender Male] config' – bheklilr
У вас могут быть комментарии: String -> [FilterKey] -> Config -> Result' и 'commentsRaw :: String -> [(String, String)] -> Config -> Result', где' comments' вызывает 'commentsRaw', и просто напишите функцию' filterKeyToPair :: FilterKey -> (String, String) '. Это было бы довольно просто. – bheklilr
Спасибо! Теперь, когда я повторяю свой список параметров, чтобы перейти к http-conduit, как мне получить желаемые значения (в частности, ANY = «мужчина, женщина»? Я просто делаю охрану и возвращаю правильную строку? –