在玩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
为什么会出现问题?有没有办法定义最通用的管道&#34;没有进一步指定管道输出的类型?
我认为FromField
对象列表就足够了。
感觉我错过了关于类型类以及如何组合函数的重要观点,或者在这里以多态方式管道对象。
非常感谢您的回答!
答案 0 :(得分:2)
pipeline3
是一个类似ConduitM a [a] m ()
的管道(忽略现在的约束)。因此,当您将length
映射到它时,您会得到ConduitM a Int m ()
; a
仍然存在于第一个类型参数中,因此FromField a
约束可以保留,等待在使用站点实例化。
pipeline2
是一个类似ConduitM BS.ByteString [a] m ()
的管道。现在,如果您将length
映射到它,您将获得ConduitM BS.ByteString Int m ()
。在任何类型的地方都没有a
,因此无法在使用地点选择FromField a
实例。相反,它必须立即选择。但是pipeline2 .| mapC length
中的任何内容都没有说a
应该是什么。这就是为什么它抱怨a
含糊不清。
据我所知(不熟悉管道),这也是你的第一个定义的唯一问题。 FromRecord
不保证Foldable
,但它的实例为Foldable
;你只需要确定使用的类型,因为length
不会这样做。您可以在使用时pipeline
使用表达式签名,TypeApplication
扩展名,更少的多态定义(不需要像[{1}}那样重新实现;您可以{ {1}}如果您在pipeline2
上有正确的签名。
答案 1 :(得分:2)
你得到的错误......
• 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
...说a0
含糊不清,这使得无法确定应该使用FromField
的哪个实例。是什么让它含糊不清?错误消息还提到了表达式的推断类型:
it :: MonadError CsvParseError m => ConduitM BS.ByteString Int m ()
此类型中没有a0
。这导致了歧义,因为没有可以指定FromField
实例的此类型的特化 - 没有足够的材料供类型检查器使用。在你的第三个例子中,另一方面......
pipeline3 .| mapC length
:: (FromField a, MonadError CsvParseError m) => ConduitM a Int m ()
...字段的类型确实显示在整体类型中,因此避免了歧义。
值得强调的是pipeline2
本身没有任何问题。问题只是因为length
从整体类型中消除了有用的信息。相比之下,例如,这可以正常工作:
GHCi> :t pipeline2 .| mapC id
pipeline2 .| mapC id
:: (MonadError CsvParseError m, FromField a) =>
ConduitM BS.ByteString [a] m ()
要将pipeline2
与length
一起使用,您需要通过类型注释指定字段的类型:
GHCi> -- Arbitrary example.
GHCi> :t (pipeline2 :: MonadError CsvParseError m => ConduitM BS.ByteString [Int] m ()) .| mapC length
(pipeline2 :: MonadError CsvParseError m => ConduitM BS.ByteString [Int] m ()) .| mapC length
:: MonadError CsvParseError m => ConduitM BS.ByteString Int m ()
注释的替代方法包括使用TypeApplications
扩展名(感谢ben的答案提醒我)...
GHCi> :set -XTypeApplications
GHCi> :t pipeline2 @_ @Int .| mapC length
pipeline2 @_ @Int .| mapC length
:: MonadError CsvParseError m => ConduitM BS.ByteString Int m ()
...并通过代理参数指定字段类型。
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleContexts #-}
import Data.Proxy
-- etc.
rowLength :: forall m a. (MonadError CsvParseError m, FromField a)
=> Proxy a -> ConduitM BS.ByteString Int m ()
rowLength _ = p2 .| mapC length
where
p2 :: (MonadError CsvParseError m, FromField a)
=> ConduitM BS.ByteString [a] m ()
p2 = pipeline2
GHCi> :t rowLength (Proxy :: Proxy Int)
rowLength (Proxy :: Proxy Int)
:: MonadError CsvParseError m => ConduitM BS.ByteString Int m ()