Оптимизация обработки JSON на основе линз - PullRequest
0 голосов
/ 29 января 2019

В моем текущем проекте "Learning Haskell" я пытаюсь получить данные о погоде из стороннего API.Я хочу извлечь значения name и main.temp из следующего тела ответа:

{
  ...
  "main": {
    "temp": 280.32,
    ...
  },
  ...
  "name": "London",
  ...
}

Я написал сервис getWeather для выполнения ввода-вывода и преобразования ответа для построения GetCityWeather данных:

....

data WeatherService = GetCityWeather String Double
    deriving (Show)

....

getWeather :: IO (ServiceResult WeatherService)
getWeather = do
  ...

  response <- httpLbs request manager

  ...

  -- work thru the response
  return $ case ((maybeCityName response, maybeTemp response)) of
    (Just name, Just temp) -> success name temp
    bork                   -> err ("borked data >:( " ++ show bork))

  where
    showStatus r    = show $ statusCode $ responseStatus r
    maybeCityName r = (responseBody r)^?key "name"._String
    maybeTemp r     = (responseBody r)^?key "main".key "temp"._Double
    success n t     = Right (GetCityWeather (T.unpack n) t)
    err e           = Left (SimpleServiceError e)

Я застрял, оптимизируя часть анализа JSON в maybeCityName и maybeTemp, мои мысли:

  1. В настоящее время JSON анализируется дважды (я применяю ^?два раза по необработанному ответу responseBody r).
  2. Я хотел бы получить данные «одним выстрелом».?.. может получить список значений.Но я извлекаю различные типы (String, Double), поэтому ?.. здесь не подходит.

Я ищу более элегантные / более естественные способы безопасного анализа JSON, читайтежелаемые значения и применить их к конструктору данных GetCityWeather.Заранее благодарен за любую помощь и обратную связь.

Обновление: используя складки, я могу решить проблему с двумя совпадениями случаев

getWeather :: IO (ServiceResult WeatherService)
getWeather = do
  ...
  let value = decode $ responseBody response
  return $ case value of
    Just v -> case (v ^? weatherService) of 
        Just wr -> Right wr
        Nothing -> err "incompatible data"
    Nothing     -> err "bad json"

  where
     err t = Left (SimpleServiceError t)

weatherService :: Fold Value WeatherService
weatherService = runFold $ GetCityWeather
  <$> Fold (key "name" . _String . unpacked)
  <*> Fold (key "main" . key "temp" . _Double)

1 Ответ

0 голосов
/ 30 января 2019

Как указывает @jpath, реальная проблема, с которой вы здесь сталкиваетесь, связана с lens и обработкой JSON.Суть проблемы, кажется, в том, что вы хотите выполнить операцию с объективом одновременно.Для этого обратите внимание на удобный ReifiedFold: требуемая «параллельная» функциональность упакована в экземпляр Applicative.

import Control.Lens
import Data.Aeson
import Data.Aeson.Lens
import Data.Text.Lens ( unpacked )

-- | Extract a `WeatherService` from a `Value` if possible
weatherService :: Fold Value WeatherService
weatherService = runFold $ GetCityWeather
  <$> Fold (key "name" . _String . unpacked)
  <*> Fold (key "main" . key "temp" . _Double))

Затем вы можете попытаться получитьWeatherService все сразу:

...
-- work thru the response
let body = responseBody r
return $ case body ^? weatherService of
  Just wr -> Right wr
  Nothing -> Left (SimpleServiceError ("borked data >:( " ++ show body))

Однако, для сообщений об ошибках, может быть лучше воспользоваться aeson ToJSON / FromJSON, если вы планируетена масштабирование это больше.

...