【问题标题】:How to implement default associated type family for OVERLAPPABLE instance, monomorphic container and newtype wrapper?如何为 OVERLAPPABLE 实例、单态容器和新类型包装器实现默认关联类型族?
【发布时间】:2017-12-15 03:15:47
【问题描述】:

我有以下 Haskell 代码:

type family Element t

class ToList t where
    toList :: t -> [Element t]

之前有人建议我将 Element 设为关联类型族:Foldable IntSet

我尝试实施这种方法。但这不适用于我的情况。这是整个代码:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}

import Prelude hiding (toList)
import qualified Data.Foldable as Foldable (toList)
import Data.Text (Text, unpack)

class ToList t where
    type Element t :: *
    toList :: t -> [Element t]

-- | This instance makes 'ToList' compatible and overlappable by 'Foldable'.
instance {-# OVERLAPPABLE #-} Foldable f => ToList (f a) where
    type Element (f a) = a
    toList = Foldable.toList

instance ToList Text where
    type Element Text = Char
    toList = unpack

newtype WrappedList l = WrappedList l

instance ToList l => ToList (WrappedList l) where
    type Element (WrappedList l) = Element l
    toList (WrappedList l) = toList l

当我使用 GHC-8.2.2 编译此代码时,我看到以下错误:

Element.hs:14:10: error:
    Conflicting family instance declarations:
      Element (f a) = a -- Defined at Element.hs:14:10
      Element (WrappedList l) = Element l -- Defined at Element.hs:24:10
   |
14 |     type Element (f a) = a
   |          ^^^^^^^^^^^^^^^^^

我该如何解决这个错误?我不知道如何使它与关联的类型族兼容...

【问题讨论】:

  • 你不能 - 开放类型族不允许重叠。您可以使用多参数类型类。但是,我看不到 WrappedList 实例的用途。
  • @user240738 我猜这是一个简单的健全性检查,以确保稍微复杂的类型既可以是实例也可以是简单的。
  • @user2407038 另一个让我满意的解决方案是拥有type Element 类型族的默认实现,并且能够覆盖这个实现。 @HTNW 关于健全性检查的观点是正确的 :) 但在我的情况下,我有 class ToList t => Container t 其中 Container 有大约 15 种方法。而这个WrappedList 包装器通过Container 实例为[a] 实现了所有Container 方法。如果您想使用 Container 方法但又不想为您的数据类型实现约 15 种方法,这可能会很有用。
  • @user2407038 对这个问题的回答建议使用_默认关联单射类型family_,但我不知道如何使用它:stackoverflow.com/questions/46206079/…

标签: haskell type-families


【解决方案1】:

根本问题是您不能使用类型重叠来制作重叠的类型系列。它根本没有意义 - 类型族从输入类型计算类型,并且结果类型可能不取决于编译器如何选择类型类实例(否则它不会是函数 - 因为输出一个函数可能只依赖于输入)。这个问题很常见,但如何解决它完全取决于您的具体用例。

最简单的解决方案是使用DefaultSignatures 提供默认实现。请注意,关联的类型族也可以有默认值:

type family ElementDefault (t :: *) :: * where
  ElementDefault (f a) = a

class ToList t where
    type Element t :: *
    type Element t = ElementDefault t

    toList :: t -> [Element t]
    default toList :: (Foldable f, t ~ f a, Element t ~ a) => t -> [Element t]
    toList = Foldable.toList

这允许您为所有Foldable 类型编写实例,而无需提供实现:

instance ToList [a]
instance ToList (Maybe a)
-- etc...

如果您想避免编写此类实例(甚至是实例头),则需要将关联类型移动到类实例头中。由于只有类可能重叠,而不是开放类型系列,因此这样做允许“元素”类型也重叠。

class ToList t e | t -> e where
  toList :: t -> [e]

instance {-# OVERLAPPABLE #-} (a ~ a', Foldable f) => ToList (f a) a' where
  toList = Foldable.toList

instance ToList Text Char where
  toList = unpack

instance ToList l a => ToList (WrappedList l) a where
  toList (WrappedList l) = toList l

提供多个默认定义的最简单方法是在类之外提供它们。如果你有 15 个类函数,这确实很乏味。在这种情况下,我会用一个记录来实现这个类:

data ToList' t e = ToList'
  { toList' :: t -> [e] {- 14 more fields... -} }

class ToList t where
  type Element t 
  toList_impl :: ToList' t (Element t)

-- For consumers of ToList
toList :: ToList t => t -> [Element t]
toList = toList' toList_impl

instance ToList Text where
  type Element Text = Char
  toList_impl = ToList' unpack

toList_Foldable_default :: Foldable f => ToList' (f a) a
toList_Foldable_default = ToList' Foldable.toList

toList_Wrapped_list :: ToList l => ToList' l (Element l)
toList_Wrapped_list = ToList' toList

使用这种方法,您可以完全省去类型类;它唯一剩下的用途是获取实例唯一性。

【讨论】:

  • 谢谢!那非常有用。现在我看到了问题。
猜你喜欢
  • 2016-01-06
  • 1970-01-01
  • 2013-05-06
  • 2018-10-14
  • 2014-10-02
  • 1970-01-01
  • 1970-01-01
  • 2021-05-19
  • 2018-10-14
相关资源
最近更新 更多