简体   繁体   English

将函数映射到定义为数据结构列表的newtype

[英]map function to newtype defined as a List of Data structures

I'm new to Haskell and FP and am working my way through LYAH and other sources, but to "learn by doing" I'm trying to write a small program involving JSON parsing. 我是Haskell和FP的新手,我正在通过LYAH和其他来源工作,但要“边做边学”,我正在尝试编写一个涉及JSON解析的小程序。 However, I've coded myself into a corner and can't get out. 但是,我已将自己编入角落,无法离开。 My code is cobbled together from various tutorials and I can sense I'm still "thinking procedurally" about how to put it together nicely, but I haven't gotten to the needed breakthrough to make it work. 我的代码是从各种教程中拼凑而成的,我可以感觉到我仍然“在程序上思考”如何将它整合在一起,但我还没有达到必要的突破才能使它工作。

First, here is a chopped-down version of the multi-level JSON file, it's a weather forecast from the Weather Underground API, reduced to three hours. 首先,这是一个多级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"
    }]
}

Next, here is my Haskell program. 接下来,这是我的Haskell程序。 I'm successfully parsing the JSON into a newtype called ForecastPointCollection which is defined as a List of WeatherPoint , which is a data structure of various things that came from the JSON file. 成功地解析JSON成newtype称为ForecastPointCollection其定义为一个ListWeatherPoint ,这是一个data的,从JSON文件来各种事物的结构。 But then, I can't figure out how to get the [WeatherPoint] list back out (see code comments). 但是,我无法弄清楚如何将[WeatherPoint]列表退出(参见代码注释)。 As a test of "something to do" with the list, I want to convert the Celcius temperature to Kelvin and get a new List that I can work with (output to JSON, do a show on, whatever). 作为对列表中“要做的事情”的测试,我想将Celcius温度转换为开尔文并获得一个我可以使用的新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

Any advice appreciated. 任何建议表示赞赏 Should I not make ForecastPointCollection a newtype ? 我不应该让ForecastPointCollection一个newtype Where am I being idiomatic and where am I just idiotic? 我在哪里惯用,我在哪里愚蠢? :-p :-P

Update based on answer: For posterity, here is a possible (working) implementation of the newly-defined processWeatherPoint function. 基于答案更新:对于后代,这里是新定义的processWeatherPoint函数的可能(工作)实现。 The pieces of a data structure should be thought of as a function! 应该将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

It should be enough to define a function... 它应该足以定义一个函数......

processWeatherPoint :: WeatherPoint -> ProcessedWeatherPoint

... extract the field with the [WeatherPoint] from the newtype and map the function over the list: ...使用[WeatherPoint][WeatherPoint]提取字段,并将该函数映射到列表上:

adjustTemp :: Maybe ForecastPointCollection -> [ProcessedWeatherPoint]
adjustTemp Nothing = []
adjustTemp (Just (ForecastPointCollection points)) = processWeatherPoint <$> points

An alternative to pattern matching on the ForecastPointCollection is using the record accessor for the field. ForecastPointCollection上的模式匹配的替代方法是使用该字段的记录访问器。 That would be specially useful if you didn't intend to export the constructor: 如果您不打算导出构造函数,那将特别有用:

adjustTemp :: Maybe ForecastPointCollection -> [ProcessedWeatherPoint]
adjustTemp Nothing = []
adjustTemp (Just forecast) = processWeatherPoint <$> forecastpointcollection forecast

An arguably more convenient way of writing the definition just above involves using the maybe function instead of doing explicit case analysis on Maybe : 一种可以说是更方便的编写上面定义的方法涉及使用maybe函数而不是对Maybe进行明确的案例分析:

adjustTemp :: Maybe ForecastPointCollection -> [ProcessedWeatherPoint]
adjustTemp = maybe [] (fmap processWeatherPoint . forecastpointcollection)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM