如何在 Haskell 中从 URL 读取 CSV 格式

问题描述 投票:0回答:1

我 24 小时前开始学习 Haskell 语言。我开始明白了,但目前还不像Python那么简单。我已经尝试了很多并阅读了文档,但我没有取得进展。也就是说,我无法从 URL 读取 CSV 格式,我怀疑我的 ByteString 库有问题,有人知道吗?

{-# LANGUAGE OverloadedStrings #-}

import Network.HTTP.Conduit (simpleHttp)
import Data.Csv
import qualified Data.ByteString.Lazy as BL
import qualified Data.Vector as V

readCSV :: String -> IO (Either String (V.Vector BL.ByteString))
readCSV url = do
    csvData <- simpleHttp url
    return $ decode HasHeader csvData

main :: IO ()
main = do
    let url = "https://earthquake.usgs.gov/fdsnws/event/1/query?format=csv&starttime=2023-12-30&endtime=2023-12-31&minlatitude=32&maxlatitude=42&minlongitude=-124&maxlongitude=-114&minmagnitude=3"
    result <- readCSV url
    case result of
        Left err -> print err
        Right csv -> print csv

编辑:

错误信息。

    • Couldn't match expected type ‘BL.ByteString’
                  with actual type ‘bytestring-0.11.5.3:Data.ByteString.Lazy.Internal.ByteString’
      NB: ‘BL.ByteString’
            is defined in ‘Data.ByteString.Lazy.Internal’
                in package ‘bytestring-0.12.0.2’
          ‘bytestring-0.11.5.3:Data.ByteString.Lazy.Internal.ByteString’
            is defined in ‘Data.ByteString.Lazy.Internal’
                in package ‘bytestring-0.11.5.3’
    • In the second argument of ‘decode’, namely ‘csvData’
      In the second argument of ‘($)’, namely ‘decode HasHeader csvData’
      In a stmt of a 'do' block: return $ decode HasHeader csvData
   |
11 |     return $ decode HasHeader csvData
csv haskell url
1个回答
0
投票

我无法重现您的错误,但您的

bytestring
版本之间似乎不匹配。解决方案可能是放宽您的
cabal
文件中的限制。

但是,即使我们修复了该特定错误,您的代码也将无法工作,因此让我们解决实际问题。

在类型签名

readCSV :: String -> IO (Either String (V.Vector BL.ByteString))
中,
Either
的第二个参数(在我们的例子中为
V.Vector BL.ByteString
)将告诉我们
decode
尝试将数据解码为哪种数据类型。因此,我们试图告诉它将文件的每一行解码为字节串。这实际上没有意义,因为 CSV 包含多行记录,但我们告诉它将记录的每个字段合并为一个。

相反,我们真正想要的是将文件解码为记录向量。事实上,

cassava
中有一个类型别名可以帮助解决这个问题:
Record
!如果我们将类型签名更改为
readCSV :: String -> IO (Either String (V.Vector Record))
,我们的代码就可以工作了!现在,如果您运行代码,它将打印数据中字段列表的列表。

这可能还不太令我们满意,因为所有字段现在都是字节串,选择特定字段的唯一方法是用整数对其进行索引。我想说不是很方便或类型安全。

相反,我们可能希望有自己的类型来表示数据,例如(为了简洁而缩写):

data EarthQuake = EarthQuake
    { time :: String
    , latitude :: Double
    , longitude :: Double
    }

但是,当然

decode
无法神奇地知道如何使用它,所以我们不能只是将它放入类型签名中并完成它。我们将使该类型成为类型类的实例
FromRecord
:

instance FromRecord EarthQuake where
    parseRecord v =
        EarthQuake
        <$> v .! 0
        <*> v .! 1
        <*> v .! 2

这个实例将告诉

decode
函数如何将记录转换为我们自己的数据类型。现在我们已经完成了,我们可以再次修改我们的类型签名:
readCSV :: String -> IO (Either String (V.Vector EarthQuake))
。方便多了。

© www.soinside.com 2019 - 2024. All rights reserved.