Haskell 如何为可重叠实例、单态容器和新类型包装器实现默认的关联类型族?

Haskell 如何为可重叠实例、单态容器和新类型包装器实现默认的关联类型族?,haskell,type-families,Haskell,Type Families,我有以下Haskell代码: type family Element t class ToList t where toList :: t -> [Element t] 早些时候有人建议我将元素作为关联的类型族: 我试图实施这种方法。但这对我的案子不起作用。以下是全部代码: {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeFamilies #-} import Prelude hiding (toList) import

我有以下Haskell代码:

type family Element t

class ToList t where
    toList :: t -> [Element t]
早些时候有人建议我将
元素
作为关联的类型族:

我试图实施这种方法。但这对我的案子不起作用。以下是全部代码:

{-# 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
   |          ^^^^^^^^^^^^^^^^^

如何修复此错误?我不知道如何使它与关联的类型族一起可编译…

基本问题是不能使用类型类重叠来生成重叠的类型族。这根本没有意义——类型族从输入类型计算类型,结果类型可能不取决于编译器如何选择类型类实例(否则它就不是函数——因为函数的输出可能只取决于输入)。这个问题很常见,但是如何解决它完全取决于您的特定用例

最简单的解决方案是使用
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
这允许您为所有可折叠的
类型编写实例,而无需给出实现:

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

通过这种方法,您可以完全免除typeclass;它唯一剩下的用途是获得实例唯一性。

您不能-打开类型族不允许重叠。您可以使用多参数typeclass。然而,我看不出
WrappedList
实例的用途。@user240738我想这是一个简单的健全性检查,以确保稍微复杂的类型可以是实例,也可以是简单的实例。@user2407038另一个让我满意的解决方案是使用
type元素的默认实现
type族,并使用覆盖此实现的能力@关于健全性检查的HTNW点是正确的:)但在我的例子中,我有
类ToList t=>Container t
其中
Container
有15个方法。这个
WrappedList
包装器通过
Container
实例为
[a]
实现所有
Container
方法。如果您想使用
Container
方法,但不想为您的数据类型实现~15个方法,则可能会很有用。@user2407038对此问题的回答建议使用“默认关联内射类型族”,但我不知道如何使用它:谢谢!这是非常有用的。现在我明白了问题所在。