将函数映射到定义为数据结构列表的新类型
map function to newtype defined as a List of Data structures
我是 Haskell 和 FP 的新手,我正在研究 LYAH 和其他资源,但是 "learn by doing" 我正在尝试编写一个涉及 JSON 的小程序解析。然而,我已经把自己困在一个角落里,出不去了。我的代码是从各种教程中拼凑而成的,我可以感觉到我仍然 "thinking procedurally" 想知道如何将它们很好地组合在一起,但我还没有达到使其工作所需的突破。
首先,这里是多级 JSON 文件的精简版,它是 Weather Underground API 的天气预报,缩减为三个小时。
{
"response": {
"version": "0.1",
"termsofService": "http://www.wunderground.com/weather/api/d/terms.html",
"features": {
"hourly": 1
}
},
"hourly_forecast": [{
"FCTTIME": {
"hour": "8",
"epoch": "1479736800",
"pretty": "8:00 AM CST on November 21, 2016"
},
"temp": {
"english": "27",
"metric": "-3"
},
"condition": "Partly Cloudy"
}, {
"FCTTIME": {
"hour": "9",
"epoch": "1479740400",
"pretty": "9:00 AM CST on November 21, 2016"
},
"temp": {
"english": "32",
"metric": "0"
},
"condition": "Partly Cloudy"
}, {
"FCTTIME": {
"hour": "10",
"epoch": "1479744000",
"pretty": "10:00 AM CST on November 21, 2016"
},
"temp": {
"english": "35",
"metric": "2"
},
"condition": "Clear"
}]
}
接下来,这是我的 Haskell 程序。我 成功地 将 JSON 解析为名为 ForecastPointCollection
的 newtype
,它被定义为 WeatherPoint
的 List
,这是来自 JSON 文件的各种事物的 data
结构。但是,我不知道如何让 [WeatherPoint]
列表退出(参见代码注释)。作为对列表的 "something to do" 的测试,我想将摄氏温度转换为开尔文并获得一个我可以使用的新 List
(输出到 JSON,执行 show
上,随便)。
{-# LANGUAGE OverloadedStrings #-}
-- {-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Data.Aeson
import Data.Aeson.Types
import Control.Applicative ((<$>), (<*>))
import Control.Monad (mzero)
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Text as T
import qualified Data.Vector as V
type MetricTemperature = Int
type KelvinTemperature = Int
newtype ForecastPointCollection = ForecastPointCollection
{forecastpointcollection :: [WeatherPoint]} deriving Show
data WeatherPoint = WeatherPoint
{ epoch :: T.Text
, prettyTime :: T.Text
, tempMetric :: MetricTemperature
, condition :: T.Text
} deriving Show
instance FromJSON ForecastPointCollection where
parseJSON (Object o) =
ForecastPointCollection <$> o .: "hourly_forecast"
parseJSON _ = mzero
data ProcessedWeatherPoint = ProcessedWeatherPoint
{ newEpoch :: T.Text
, newPrettyTime :: T.Text
, newTempKelvin :: KelvinTemperature
, newCondition :: T.Text
} deriving Show
instance FromJSON WeatherPoint where
parseJSON =
withObject "Root Object Arbitrary Name" $ \o -> do
fctO <- o .: "FCTTIME"
epoch <- fctO .: "epoch" -- contained within FCTTIME
pretty <- fctO .: "pretty" -- contained within FCTTIME
tempO <- o .: "temp"
metric <- tempO .: "metric" -- contained within temp
condition <- o .: "condition" -- at top level under hourly_forecast
return $ WeatherPoint epoch pretty (read metric) condition
-- parseJSON _ = mzero
kelvinizeTemp :: MetricTemperature -> KelvinTemperature
kelvinizeTemp x = x + 273 -- hey, close enough
adjustTemp :: Maybe ForecastPointCollection -> [ProcessedWeatherPoint]
adjustTemp Nothing = []
adjustTemp x = [] -- HERE IS WHERE I AM LOSING MY WAY!
-- HOW CAN I WALK THROUGH THE LIST INSIDE ForecastPointCollection
-- TO map kelvinizeTemp ACROSS THAT LIST AND
-- GET A [ProcessedWeatherPoint] LIST BACK TO PLAY WITH?
getSampleForecast = BSL.readFile "/home/mypath/test/forecastsubmit.json"
main = do
textOfJson <- getSampleForecast
let (forecasts2 :: Maybe ForecastPointCollection) = decode textOfJson
case forecasts2 of
Just (ForecastPointCollection forecasts2) -> do
putStrLn ("Success!")
putStrLn . show $ forecasts2
_ -> putStrLn "Could not parse ForecastPointCollection JSON correctly."
-- So far so good, we've extracted data from the JSON and stored it in memory.
-- But now, how can we manipulate that data and start doing stuff with it?
-- Currently, the "adjustTemp" function returns an empty list no matter what.
let (processed2 :: [ProcessedWeatherPoint]) = adjustTemp forecasts2
putStrLn ("More success (OK, not really, yet)!")
putStrLn . show $ processed2
任何建议表示赞赏。我不应该让 ForecastPointCollection
变成 newtype
吗?我在哪里是惯用的,我在哪里只是白痴? :-p
根据答案更新:对于后代,这是新定义的 processWeatherPoint 函数的可能(有效)实现。 data
结构的各个部分应该被认为是一个函数!
processWeatherPoint :: WeatherPoint -> ProcessedWeatherPoint
processWeatherPoint x = ProcessedWeatherPoint
(epoch x)
(prettyTime x)
(kelvinizeTemp (tempMetric x))
(condition x)
kelvinizeTemp :: MetricTemperature -> KelvinTemperature
kelvinizeTemp x = x + 273 -- this works OK because both types are type of Int
定义一个函数应该就够了...
processWeatherPoint :: WeatherPoint -> ProcessedWeatherPoint
...从新类型中提取带有 [WeatherPoint]
的字段并将函数映射到列表中:
adjustTemp :: Maybe ForecastPointCollection -> [ProcessedWeatherPoint]
adjustTemp Nothing = []
adjustTemp (Just (ForecastPointCollection points)) = processWeatherPoint <$> points
ForecastPointCollection
上模式匹配的替代方法是使用字段的记录访问器。如果您不打算导出构造函数,那将特别有用:
adjustTemp :: Maybe ForecastPointCollection -> [ProcessedWeatherPoint]
adjustTemp Nothing = []
adjustTemp (Just forecast) = processWeatherPoint <$> forecastpointcollection forecast
一种可以说更方便的编写上述定义的方法涉及使用 maybe
函数而不是对 Maybe
进行显式案例分析:
adjustTemp :: Maybe ForecastPointCollection -> [ProcessedWeatherPoint]
adjustTemp = maybe [] (fmap processWeatherPoint . forecastpointcollection)
我是 Haskell 和 FP 的新手,我正在研究 LYAH 和其他资源,但是 "learn by doing" 我正在尝试编写一个涉及 JSON 的小程序解析。然而,我已经把自己困在一个角落里,出不去了。我的代码是从各种教程中拼凑而成的,我可以感觉到我仍然 "thinking procedurally" 想知道如何将它们很好地组合在一起,但我还没有达到使其工作所需的突破。
首先,这里是多级 JSON 文件的精简版,它是 Weather Underground API 的天气预报,缩减为三个小时。
{
"response": {
"version": "0.1",
"termsofService": "http://www.wunderground.com/weather/api/d/terms.html",
"features": {
"hourly": 1
}
},
"hourly_forecast": [{
"FCTTIME": {
"hour": "8",
"epoch": "1479736800",
"pretty": "8:00 AM CST on November 21, 2016"
},
"temp": {
"english": "27",
"metric": "-3"
},
"condition": "Partly Cloudy"
}, {
"FCTTIME": {
"hour": "9",
"epoch": "1479740400",
"pretty": "9:00 AM CST on November 21, 2016"
},
"temp": {
"english": "32",
"metric": "0"
},
"condition": "Partly Cloudy"
}, {
"FCTTIME": {
"hour": "10",
"epoch": "1479744000",
"pretty": "10:00 AM CST on November 21, 2016"
},
"temp": {
"english": "35",
"metric": "2"
},
"condition": "Clear"
}]
}
接下来,这是我的 Haskell 程序。我 成功地 将 JSON 解析为名为 ForecastPointCollection
的 newtype
,它被定义为 WeatherPoint
的 List
,这是来自 JSON 文件的各种事物的 data
结构。但是,我不知道如何让 [WeatherPoint]
列表退出(参见代码注释)。作为对列表的 "something to do" 的测试,我想将摄氏温度转换为开尔文并获得一个我可以使用的新 List
(输出到 JSON,执行 show
上,随便)。
{-# LANGUAGE OverloadedStrings #-}
-- {-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Data.Aeson
import Data.Aeson.Types
import Control.Applicative ((<$>), (<*>))
import Control.Monad (mzero)
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Text as T
import qualified Data.Vector as V
type MetricTemperature = Int
type KelvinTemperature = Int
newtype ForecastPointCollection = ForecastPointCollection
{forecastpointcollection :: [WeatherPoint]} deriving Show
data WeatherPoint = WeatherPoint
{ epoch :: T.Text
, prettyTime :: T.Text
, tempMetric :: MetricTemperature
, condition :: T.Text
} deriving Show
instance FromJSON ForecastPointCollection where
parseJSON (Object o) =
ForecastPointCollection <$> o .: "hourly_forecast"
parseJSON _ = mzero
data ProcessedWeatherPoint = ProcessedWeatherPoint
{ newEpoch :: T.Text
, newPrettyTime :: T.Text
, newTempKelvin :: KelvinTemperature
, newCondition :: T.Text
} deriving Show
instance FromJSON WeatherPoint where
parseJSON =
withObject "Root Object Arbitrary Name" $ \o -> do
fctO <- o .: "FCTTIME"
epoch <- fctO .: "epoch" -- contained within FCTTIME
pretty <- fctO .: "pretty" -- contained within FCTTIME
tempO <- o .: "temp"
metric <- tempO .: "metric" -- contained within temp
condition <- o .: "condition" -- at top level under hourly_forecast
return $ WeatherPoint epoch pretty (read metric) condition
-- parseJSON _ = mzero
kelvinizeTemp :: MetricTemperature -> KelvinTemperature
kelvinizeTemp x = x + 273 -- hey, close enough
adjustTemp :: Maybe ForecastPointCollection -> [ProcessedWeatherPoint]
adjustTemp Nothing = []
adjustTemp x = [] -- HERE IS WHERE I AM LOSING MY WAY!
-- HOW CAN I WALK THROUGH THE LIST INSIDE ForecastPointCollection
-- TO map kelvinizeTemp ACROSS THAT LIST AND
-- GET A [ProcessedWeatherPoint] LIST BACK TO PLAY WITH?
getSampleForecast = BSL.readFile "/home/mypath/test/forecastsubmit.json"
main = do
textOfJson <- getSampleForecast
let (forecasts2 :: Maybe ForecastPointCollection) = decode textOfJson
case forecasts2 of
Just (ForecastPointCollection forecasts2) -> do
putStrLn ("Success!")
putStrLn . show $ forecasts2
_ -> putStrLn "Could not parse ForecastPointCollection JSON correctly."
-- So far so good, we've extracted data from the JSON and stored it in memory.
-- But now, how can we manipulate that data and start doing stuff with it?
-- Currently, the "adjustTemp" function returns an empty list no matter what.
let (processed2 :: [ProcessedWeatherPoint]) = adjustTemp forecasts2
putStrLn ("More success (OK, not really, yet)!")
putStrLn . show $ processed2
任何建议表示赞赏。我不应该让 ForecastPointCollection
变成 newtype
吗?我在哪里是惯用的,我在哪里只是白痴? :-p
根据答案更新:对于后代,这是新定义的 processWeatherPoint 函数的可能(有效)实现。 data
结构的各个部分应该被认为是一个函数!
processWeatherPoint :: WeatherPoint -> ProcessedWeatherPoint
processWeatherPoint x = ProcessedWeatherPoint
(epoch x)
(prettyTime x)
(kelvinizeTemp (tempMetric x))
(condition x)
kelvinizeTemp :: MetricTemperature -> KelvinTemperature
kelvinizeTemp x = x + 273 -- this works OK because both types are type of Int
定义一个函数应该就够了...
processWeatherPoint :: WeatherPoint -> ProcessedWeatherPoint
...从新类型中提取带有 [WeatherPoint]
的字段并将函数映射到列表中:
adjustTemp :: Maybe ForecastPointCollection -> [ProcessedWeatherPoint]
adjustTemp Nothing = []
adjustTemp (Just (ForecastPointCollection points)) = processWeatherPoint <$> points
ForecastPointCollection
上模式匹配的替代方法是使用字段的记录访问器。如果您不打算导出构造函数,那将特别有用:
adjustTemp :: Maybe ForecastPointCollection -> [ProcessedWeatherPoint]
adjustTemp Nothing = []
adjustTemp (Just forecast) = processWeatherPoint <$> forecastpointcollection forecast
一种可以说更方便的编写上述定义的方法涉及使用 maybe
函数而不是对 Maybe
进行显式案例分析:
adjustTemp :: Maybe ForecastPointCollection -> [ProcessedWeatherPoint]
adjustTemp = maybe [] (fmap processWeatherPoint . forecastpointcollection)