类型约束,多态性和木薯导管

时间:2016-11-23 18:42:27

标签: haskell polymorphism typeclass conduit

在玩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并不能保证aFoldable。所以我定义了以下管道:

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对象列表就足够了。

感觉我错过了关于类型类以及如何组合函数的重要观点,或者在这里以多态方式管道对象。

非常感谢您的回答!

2 个答案:

答案 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 ()

要将pipeline2length一起使用,您需要通过类型注释指定字段的类型:

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 ()