【发布时间】:2016-11-24 08:08:21
【问题描述】:
在玩 Haskell 和 conduit 时,我遇到了一个我很难解释的行为。首先让我列出所有需要加载以重现我的问题的模块和语言扩展:
{-# LANGUAGE FlexibleContexts #-}
import Conduit -- conduit-combinators
import Data.Csv -- cassava
import Data.Csv.Conduit -- cassava-conduit
import qualified Data.ByteString as BS -- bytestring
import Data.Text (Text) -- text
import Control.Monad.Except -- mtl
import Data.Foldable
首先我创建了最通用的 CSV 解析管道:
pipeline :: (MonadError CsvParseError m, FromRecord a)
=> ConduitM BS.ByteString a m ()
pipeline = fromCsv defaultDecodeOptions NoHeader
然后,我想输出我的 csv 文件每一行中的元素数量 - 我知道这有点愚蠢和无用,而且还有十亿其他方法可以做这种事情,但这只是一个玩具测试。
所以我打开了 GHCi 并尝试了这个:
ghci> :t pipeline .| mapC length
正如预期的那样,这不起作用,因为约束 FromRecord a 不能保证 a 是 Foldable。所以我定义了以下管道:
pipeline2 :: (MonadError CsvParseError m, FromField a)
=> ConduitM BS.ByteString [a] m ()
pipeline2 = fromCsv defaultDecodeOptions NoHeader
这是一个合法的定义,因为根据 cassava 文档,FromField a => FromField [a] 是 FromRecord 的一个实例。
此时,我感到高兴和充满希望,因为[] 是Foldable 的一个实例。所以,我再次打开 GHCi,然后尝试:
ghci> :t pipeline2 .| mapC length
但我明白了:
<interactive>:1:1: error:
• Could not deduce (FromField a0) arising from a use of ‘pipeline2’
from the context: MonadError CsvParseError m
bound by the inferred type of
it :: MonadError CsvParseError m => ConduitM BS.ByteString Int m ()
at <interactive>:1:1
The type variable ‘a0’ is ambiguous
These potential instances exist:
instance FromField a => FromField (Either Field a)
-- Defined in ‘cassava-0.4.5.0:Data.Csv.Conversion’
instance FromField BS.ByteString
-- Defined in ‘cassava-0.4.5.0:Data.Csv.Conversion’
instance FromField Integer
-- Defined in ‘cassava-0.4.5.0:Data.Csv.Conversion’
...plus 9 others
...plus 11 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the first argument of ‘(.|)’, namely ‘pipeline2’
In the expression: pipeline2 .| mapC length
所以我的理解是我的pipeline2 指定的不够。
但是现在如果我尝试用(几乎)相同的类型伪造一个微不足道的管道:
pipeline3 :: (MonadError CsvParseError m, FromField a)
=> ConduitM a [a] m ()
pipeline3 = awaitForever $ \x -> yield [x]
我再次打开 GHCi 并尝试:
ghci> :t pipeline3 .| mapC length
这次我明白了:
pipeline3 .| mapC length
:: (FromField a, MonadError CsvParseError m) => ConduitM a Int m ()
所以这一次,GHCi 明白我不必进一步指定pipeline3 的定义。
所以我的问题是:为什么pipeline2 有问题?有没有办法在不进一步指定管道输出类型的情况下定义最通用的“管道”?
我认为FromField 对象的列表就足够了。
感觉我错过了关于类型类以及如何以多态方式组合函数或此处的 Conduit 对象的重要一点。
非常感谢您的回答!
【问题讨论】:
标签: haskell polymorphism typeclass conduit