2015-09-07 2 views
5

Я пишу небольшую библиотеку для взаимодействия с несколькими внешними API. Один набор функций построит действительный запрос для yahoo api и проанализирует результат на тип данных. Другой набор функций будет искать текущее местоположение пользователей на основе IP и возвращает тип данных, представляющий текущее местоположение. Несмотря на то, что код работает, кажется, ему явно нужно сопоставить шаблон для последовательности нескольких функций типа IO (возможно, a).Цепочки функций типа IO (Возможно, a)

-- Yahoo API 

constructQuery :: T.Text -> T.Text -> T.Text 
constructQuery city state = "select astronomy, item.condition from weather.forecast" <> 
          " where woeid in (select woeid from geo.places(1)" <> 
          " where text=\"" <> city <> "," <> state <> "\")" 

buildRequest :: T.Text -> IO ByteString 
buildRequest yql = do 
    let root = "https://query.yahooapis.com/v1/public/yql" 
     datatable = "store://datatables.org/alltableswithkeys" 
     opts = defaults & param "q" .~ [yql] 
          & param "env" .~ [datatable] 
          & param "format" .~ ["json"] 
    r <- getWith opts root 
    return $ r ^. responseBody 

run :: T.Text -> IO (Maybe Weather) 
run yql = buildRequest yql >>= (\r -> return $ decode r :: IO (Maybe Weather)) 


-- IP Lookup 
getLocation:: IO (Maybe IpResponse) 
getLocation = do 
    r <- get "http://ipinfo.io/json" 
    let body = r ^. responseBody 
    return (decode body :: Maybe IpResponse) 

- Combinator

runMyLocation:: IO (Maybe Weather) 
runMyLocation = do 
    r <- getLocation 
    case r of 
     Just ip -> getWeather ip 
     _ -> return Nothing 
    where getWeather = (run . (uncurry constructQuery) . (city &&& region)) 

Можно ли нить getLocation и работать вместе, не прибегая к явному шаблону, чтобы «выйти» из Maybe монады?

ответ

3

Некоторые считают, что это анти-шаблон, но вы можете использовать MaybeT IO a вместо IO (Maybe a). Проблема в том, что вы имеете дело только с одним из способов: getLocation может сбой — он также может выбросить исключение IO. С этой точки зрения вы также можете сбросить Maybe и просто выбросить свое собственное исключение, если декодирование не удастся, ловя его, где угодно.

+2

Кстати, почему это анти-шаблон? – Yuuri

+4

@Yuuri, потому что похоже, что вы всегда получите результат или «ничего», но вы можете получить, скажем, исключение таймаута в сети. – dfeuer

+0

Я не понимаю, почему вы думаете, что это выглядит как «всегда результат« Ничего ». Большинство Haskellers должны быть достаточно знакомы с трансформаторами монады, чтобы знать, как они работают «наизнанку». – leftaroundabout

5

Вы можете счастливо гнезда do блоков, соответствующих различные монады, так что это нормально, чтобы иметь блок типа Maybe Weather в середине вашего IO (Maybe Weather) блока.

Например,

runMyLocation :: IO (Maybe Weather) 
runMyLocation = do 
    r <- getLocation 
    return $ do ip <- r; return (getWeather ip) 
    where 
    getWeather = run . (uncurry constructQuery) . (city &&& region) 

Этот простой шаблон do a <- r; return f a означает, что вам не нужно монаду экземпляр для Maybe вообще, хотя - простой fmap достаточно

runMyLocation :: IO (Maybe Weather) 
runMyLocation = do 
    r <- getLocation 
    return (fmap getWeather r) 
    where 
    getWeather = run . (uncurry constructQuery) . (city &&& region) 

и теперь вы видите что снова появится тот же шаблон, поэтому вы можете написать

runMyLocation :: IO (Maybe Weather) 
runMyLocation = fmap (fmap getWeather) getLocation 
    where 
    getWeather = run . (uncurry constructQuery) . (city &&& region) 

, где внешний fmap отображается по вашему действию IO, а внутренний fmap сопоставляет ваше значение Maybe.


я неправильно истолкованы тип getWeather (см комментарии ниже), так что вы будете в конечном итоге с IO (Maybe (IO (Maybe Weather))), а не IO (Maybe Weather).

Что вам нужно, это «присоединиться» через двухслойный стек монады. Это, по существу, что монада трансформатор обеспечивает для вас (см @ ответ dfeuer), но можно написать этот комбинатор вручную в случае Maybe -

import Data.Maybe (maybe) 

flatten :: (Monad m) => m (Maybe (m (Maybe a))) -> m (Maybe a) 
flatten m = m >>= fromMaybe (return Nothing) 

в этом случае вы можете написать

runMyLocation :: IO (Maybe Weather) 
runMyLocation = flatten $ fmap (fmap getWeather) getLocation 
    where 
    getWeather = run . (uncurry constructQuery) . (city &&& region) 

, который должен иметь правильный тип. Если вы собираетесь связать несколько функций, подобных этому, вам понадобится несколько вызовов для flatten, и в этом случае может быть проще построить вместо этого блок трансформатора монады (с ответом на предупреждение @ dfeuer).

Возможно, существует каноническое имя для функции, которую я назвал «сплющиванием» в библиотеках трансформаторов или mtl, но я не могу ее найти в данный момент.

Обратите внимание, что функция fromMaybe от Data.Maybe по существу делает анализ для вас, но абстрагирует его на функцию.

+0

Оцените ответ, однако это приводит к функции с типом: runMyLocation :: IO (Возможно (IO (Возможно, Погода))). Любой способ избежать вложения IO (возможно (IO Maybe v))? – user2726995

+0

Извиняюсь, я неправильно читаю тип 'getWeather' как' IpResponse -> Weather', а не 'IpResponse :: IO (Maybe Weather)'. Я дам разъяснение. –

0

изменить getWeather Maybe IpResponse->IO.. и использовать >>= для его реализации, а затем вы можете сделать getLocation >>= getWeather. >>= в getWeather - это один из вариантов Maybe, который будет иметь дело с Just и Nothing, а другой getLocation>>= getWeather - с IO.
вы можете даже абстрагироваться от Maybe и использовать любые Monad: getWeather :: Monad m -> m IpResponse -> IO .. и будете работать.

+0

Я не уверен, что вы пытаетесь сказать здесь, но все, что кажется, отсутствует. – dfeuer

+0

изменить getWeather, чтобы иметь возможность IpResponse-> IO .. и использовать >> = для его реализации, а затем вы можете сделать getLocation >> = getWeather. >> = in getWeather - это один из вариантов Maybe, который будет иметь дело с Just и Nothing, а другой getLocation >> = getWeather - один из IO –

+0

Вы можете отредактировать свой ответ, чтобы включить это предложение. Мне это не нравится лично, но это законно. В его нынешнем виде ваш ответ не является ответом. – dfeuer

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