【问题标题】:Polymorphic return types and "rigid type variable" error in HaskellHaskell 中的多态返回类型和“刚性类型变量”错误
【发布时间】:2016-03-17 17:52:31
【问题描述】:

有一个简单的记录Column v a 包含来自Data.Vector 家族的Vector(因此v 可以是Vector.Unboxed,只是Vector 等),它的名称和类型(类似简单的枚举ADT SupportedTypes)。我希望能够使用binary 包对其进行序列化。为此,我尝试在下面定义一个Binary 实例。

现在put 工作正常,但是当我尝试在get 函数中定义反序列化并希望将特定类型设置为基于colType 返回的rawVectorU.Vector Int64 时它是 PIntU.Vector Double,当它是 PDouble 等) - 我收到此错误消息:

无法将类型 vU.Vector 匹配

v 是一个刚性类型变量,由src/Quark/Base/Column.hs:75:10 处的实例声明绑定

预期类型:v a

实际类型:U.Vector Int64

错误。

有没有更好的方法来实现我的目标 - 根据 colType 值反序列化不同类型的 Vectors,还是我坚持为所有可能的 Vector / 原始类型组合定义 Binary 实例?不应该这样……

对 Haskell 有点陌生,感谢任何帮助!谢谢!

{-# LANGUAGE OverloadedStrings, TransformListComp, RankNTypes, 
            TypeSynonymInstances, FlexibleInstances, OverloadedLists, DeriveGeneric  #-}

{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts,
         TypeFamilies, ScopedTypeVariables, InstanceSigs #-}

import qualified Data.Vector.Generic as G
import qualified Data.Vector.Unboxed as U

data Column v a = Column {rawVector :: G.Vector v a => v a, colName :: Text, colType :: SupportedTypes }

instance (G.Vector v a, Binary (v a)) => Binary (Column v a) where

  put Column {rawVector = vec, colName = cn, colType = ct} = do put (fromEnum ct) >> put cn >> put vec


  get = do t <- get :: Get Int
         nm <- get :: Get Text
         let pt = toEnum t :: SupportedTypes
         case pt of 
            PInt -> do vec <- get :: Get (U.Vector Int64)
                       return Column {rawVector = vec, colName = nm, colType = pt}
            PDouble -> do vec <- get :: Get (U.Vector Double)
                          return Column {rawVector = vec, colName = nm, colType = pt}

更新感谢您在下面的所有答案,一些非常好的想法!很明显,我想做的事情是不可能正面实现的——这就是我的答案。但是其他建议的解决方案本身就是一个很好的阅读,非常感谢!

【问题讨论】:

  • vec &lt;- get :: Get (U.Vector Int64) 更改为 vec &lt;- get 并让类型推断解决。 vec &lt;- get :: Get (U.Vector Double) 相同。当然,解析的向量不会是整数或双精度向量(它将是使用您的函数的人指定的任何内容),但这不是二进制的问题 - 您的数据类型并不代表您认为它的作用。你可能想要existential quantification
  • get withoutqualification 要么抱怨它无法确定类型,要么读取双精度字节等,如下所述
  • 对存在数据类型进行了更多阅读,并开始意识到@user2407038 你是绝对正确的。这似乎完全实现了我想要的 - 不同类型的向量集合,我可以使用通用向量接口对其进行操作。

标签: haskell vector


【解决方案1】:

你真正想要表示的类型是

data Column v = Column (Either (v Int) (v Double))

但这种表述可能会让您不满意。那么如何在构造函数的“顶层”使用向量本身来编写这种类型呢?

首先,从类型级别而不是值级别的总和 (Either Int Double) 表示开始:

data IsSupportedType a where 
  TInt :: IsSupportedType Int 
  TDouble :: IsSupportedType Double 

从这里Column其实很简单:

data Column v a = Column (IsSupportedType a) (v a) 

但您可能希望a 存在量化,以便按照您的意愿使用它:

data Column v = forall a . Column (IsSupportedType a) (v a) 

二进制实例如下:

instance (Binary (v Int), Binary (v Double)) => Binary (Column v) where
  put (Column t v) = do 
    case t of 
      TInt -> put (0 :: Int) >> put v 
      TDouble -> put (1 :: Int) >> put v 

  get = do 
    t :: Int <- get 
    case t of 
      0 -> Column TInt <$> get  
      1 -> Column TDouble <$> get 

请注意,这里没有对 Vector 的固有依赖 - v 真的可以是任何东西。

【讨论】:

  • 谢谢,问题不只是 Double 或 Int,而是一大堆类型,所以这一切很快变得乏味。当前适用于我的解决方案是相似的 - data GenericColumn = CInt Vector Int | CDouble 向量双 | CWord Vector Word 等。我只是在寻找更少的样板和更少的 ADT 类型打包/装箱。
  • 如果你真的想避免样板,你可以进一步概括这一点,但在某些时候它会变得异常复杂。这个解决方案既好又简单。您真正需要编写的唯一样板是Int -&gt; Maybe (Exists IsSupportedType)IsSupportedType a -&gt; Int 类型的函数。如果你有一些你想避免的样板的具体例子,你可能更有可能得到一个适用于你的实际用例的例子。
  • 我同意,这很好 - 只是它与我上面引用的“GenericColumn”大致相同,尽管更优雅/通用。但是 GenCol 方法中的二进制定义看起来与您的非常相似:instance Binary GenericColumn where put (CInt v) = do put (0 :: Word8) &gt;&gt; put v put (CDouble v) = do put (1 :: Word8) &gt;&gt; put v put (CText v) = do put (4 :: Word8) &gt;&gt; put v get = do t &lt;- get :: Get Word8 case t of 0 -&gt; get &gt;&gt;= return . CInt 1 -&gt; get &gt;&gt;= return . CDouble 4 -&gt; get &gt;&gt;= return . CText
【解决方案2】:

您实际遇到的问题(或者如果您还没有遇到,您会遇到的)是您试图从输入值中确定结果类型。你不能这样做。完全没有。您可以巧妙地将结果类型锁定在一个盒子中并扔掉钥匙,这样该类型从外面看起来是正常的,但是您不能对它做任何事情,因为您将类型锁定在一个盒子中并扔掉了键。您可以使用 GADT 存储有关它的额外信息并将其与类型类实例打包,但这仍然不是一个好主意。

如果您只为 Column 提供两个构造函数来反映是否存在 Ints 或 Doubles 的向量,那么您可以在这里轻松得多。

但实际上,不要这样做。只需让可自动派生的 Binary 实例将任何可反序列化的值反序列化到您的向量中即可。

data Column a = ... deriving (Binary)

使用DeriveAnyClass 扩展,您可以派生任何具有Generic 实现(Binary 具有)的类。然后在需要时反序列化 Column DoubleColumn Int

【讨论】:

  • 感谢您的回复 - 扔掉关键类比很有趣。我以为我仍然可以在 rawVector 上进行模式匹配,但是没有办法找出“a”类型。但是,如果我将它限制为 Num 例如仍然可以在其上使用一堆多态函数 - 所以仍然不能 100% 理解为什么 Haskell 不允许这样做。 ----- Column = CInt Vector Int64 | 的解决方案CDouble Vector Double 等工作,是我目前正在使用的,我只是在想它可以更优雅地概括。
  • PS 自动派生并没有真正起作用,尝试了多种组合,有和没有泛型
  • Haskell 不允许这样做,因为它不能。还没有。类型在运行时被擦除。当你拆箱时,你只会得到你放进盒子里的东西,它从不包括类型。它可以包含类型类字典。这些只是类型检查器知道您必须(或更可能是编译器)正确输入才能首先正确输入框的值,因此当您在其他地方打开框时可以使用它们。类型本身已经消失了。对于自动推导,data Col v a = Col {raw :: v a, cName :: Text, ..} deriving (Binary) 就足够了吗?您可以稍后限制 v。
【解决方案3】:

正如评论所说,您可以简单地不区分类型,并始终调用

vec <- get
return Column {rawVector = vec, colName = nm, colType = pt}

这会正确地满足您的类型签名。但请注意,colType 在这里对您没有用——您无法强制它对应于向量中的类型,因为它只存在于值级别。但这可能没问题,您可能只想从数据结构中完全删除colType,因为您始终可以直接从Column v a 中选择的a 的具体类型派生它。

事实上,Column 类型中的约束也没有起到多大的作用,我认为将它渲染成这样会更好

data Column v a = Column {rawVector :: v a, colName :: Text}

现在您可以在必要时在呼叫站点强制执行 G.Vector 约束...

【讨论】:

  • 是的,这将是一个非常优雅的选择。不幸的是,反序列化并没有真正起作用:No instance for (Data.Vector.Generic.Base.Vector v0 a0) arising from a use of ‘loadColumn’ The type variables ‘v0’, ‘a0’ are ambiguous Note: there are several potential instances: instance Data.Vector.Generic.Base.Vector V.Vector a -- Defined in ‘Data.Vector’ instance Data.Primitive.Types.Prim a =&gt; -- 等等
  • 我敢打赌,如果您在 loadColumn 的调用点指定预期结果类型的类型签名,它会起作用。
  • 啊,好点,需要键入“解码”调用,对。谢谢@sclv,会考虑这种方法,它会在一定程度上改变流程,但对于我想要实现的目标可能非常通用!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-20
  • 2011-03-12
  • 2011-09-09
  • 2017-01-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多