2015-05-01 2 views
1

Я пишу Haskell SDK, у меня все работает, но я хочу представить более сильные типы для моих поисковых фильтров (параметры url).Данные Haskell, пользовательские значения строк

Образец вызова выглядит следующим образом:

-- list first 3 positive comments mentioned by females 
comments "tide-pods" [("limit", "3"),("sentiment", "positive"),("gender", "female")] config 

Хотя это не слишком ужасно для меня, я действительно хотел бы быть в состоянии передать что-то вроде:

comments "tide-pods" [("limit", "3"),(Sentiment, Positive),(Gender, Male)] config 

Или что-то подобное ,

В DataRank.hs вы можете увидеть мой URL-адрес типа параметра type QueryParameter = (String, String), а также код для преобразования аргументы для HTTP-канала convertParameters :: [QueryParameter] -> [(ByteString, Maybe ByteString)]

Я экспериментировал с данными/типов, например:

data Gender = Male | Female | Any 
-- desired values of above data types 
-- Male = "male" 
-- Female = "female" 
-- Any = "male,female" 

Api также должен оставаться достаточно гибким для любого произвольного строкового ключа, значения String, потому что я хотел бы, чтобы SDK сохранял возможность предоставлять новые фильтры, не завися от обновления SDK. Для любопытных Список существующих фильтров поиска находится в недавно построенном Java SDK

У меня возникли проблемы с поиском хорошего способа предоставить интерфейс поиска в Haskell. Заранее спасибо!

+0

'data FilterKey = Sentiment SentimentValue | Пол ГендерValue | Arbitrary String String', тогда вы можете делать «комментарии» tide-pods «[Произвольный» предел »« 3 », Sentiment Positive, Gender Male] config' – bheklilr

+0

У вас могут быть комментарии: String -> [FilterKey] -> Config -> Result' и 'commentsRaw :: String -> [(String, String)] -> Config -> Result', где' comments' вызывает 'commentsRaw', и просто напишите функцию' filterKeyToPair :: FilterKey -> (String, String) '. Это было бы довольно просто. – bheklilr

+0

Спасибо! Теперь, когда я повторяю свой список параметров, чтобы перейти к http-conduit, как мне получить желаемые значения (в частности, ANY = «мужчина, женщина»? Я просто делаю охрану и возвращаю правильную строку? –

ответ

1

Самый простой способ сохранить это простой, но небезопасно, это просто использовать основной 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 очень читабельным.

+0

Я знаю, что уже сегодня сказал 10x, но огромное спасибо! –

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

  • Нет связанных вопросов^_^