我知道这个问题已被多次询问和回答,但我仍然不明白为什么对数据类型设置约束是一件坏事.
例如,我们来看看Data.Map k a.所有涉及Map需要Ord k约束的有用函数.所以对于定义有一个隐含的约束Data.Map.为什么更好地保持隐含而不是让编译器和程序员知道Data.Map需要可订购密钥.
此外,在类型声明中指定最终类型是常见的,可以将其视为"超级"约束数据类型的方式.
例如,我可以写
data User = User { name :: String }
这是可以接受的.然而,这不是一个受限制的版本
data User' s = User' { name :: s }
我会为写功能的所有99%后User类型并不需要String和这将很可能只需要少数s是IsString和Show.
那么,为什么松散的版本被User认为是坏的:
data (IsString s, Show s, ...) => User'' { name :: s }
而这两个User和User'被认为是好?
我问这个,因为很多时候,我觉得我不必要地缩小我的数据(甚至函数)定义,只是为了不必传播约束.
据我所知,数据类型约束仅适用于构造函数,不会传播.那么我的问题是,为什么数据类型约束不能按预期工作(和传播)?无论如何,这是一个扩展,那么为什么没有一个新的扩展data正常,如果它被社区认为是有用的?
And*_*ewC 16
TL; DR:
使用GADT提供隐式数据上下文.
如果你可以使用Functor实例等,请不要使用任何类型的数据约束
.无论如何,Map太旧而无法更改为GADT.如果要查看User使用GADT 的实现,请滚动到底部
让我们使用一个Bag的案例研究,我们关心的是它中有多少次.(就像一个无序序列.我们几乎总是需要一个Eq约束来做任何有用的事情.
我将使用低效的列表实现,以免混淆Data.Map问题.
做你想做的事的简单方法是使用GADT:
请注意下面的Eq约束如何强制您在制作GADTBags时使用带有Eq实例的类型,它会在GADTBag构造函数出现的任何地方隐式提供该实例.这就是为什么count不需要Eq上下文,而是countV2- 它不使用构造函数:
{-# LANGUAGE GADTs #-}
data GADTBag a where
   GADTBag :: Eq a => [a] -> GADTBag a
unGADTBag (GADTBag xs) = xs
instance Show a => Show (GADTBag a) where
  showsPrec i (GADTBag xs) = showParen (i>9) (("GADTBag " ++ show xs) ++)
count :: a -> GADTBag a -> Int -- no Eq here
count a (GADTBag xs) = length.filter (==a) $ xs  -- but == here
countV2 a = length.filter (==a).unGADTBag
size :: GADTBag a -> Int
size (GADTBag xs) = length xs
ghci> count 'l' (GADTBag "Hello")
2
ghci> :t countV2
countV2 :: Eq a => a -> GADTBag a -> Int
现在,当我们找到袋子的总尺寸时,我们不需要Eq约束,但无论如何它都没有弄乱我们的定义.(我们也可以使用size = length . unGADTBag.)
现在让我们做一个仿函数:
instance Functor GADTBag where
  fmap f (GADTBag xs) = GADTBag (map f xs)
哎呀!
DataConstraints_so.lhs:49:30:
    Could not deduce (Eq b) arising from a use of `GADTBag'
    from the context (Eq a)
这是不可修复的(使用标准的Functor类)因为我不能限制类型fmap,但需要为新列表.
我们能按你的要求做吗?嗯,是的,除了你必须在你使用构造函数的地方不断重复Eq约束:
{-# LANGUAGE DatatypeContexts #-}
data Eq a => EqBag a = EqBag {unEqBag :: [a]}
  deriving Show
count' a (EqBag xs) = length.filter (==a) $ xs
size' (EqBag xs) = length xs   -- Note: doesn't use (==) at all
让我们去ghci找一些不那么漂亮的东西:
ghci> :so DataConstraints
DataConstraints_so.lhs:1:19: Warning:
    -XDatatypeContexts is deprecated: It was widely considered a misfeature, 
    and has been removed from the Haskell language.
[1 of 1] Compiling Main             ( DataConstraints_so.lhs, interpreted )
Ok, modules loaded: Main.
ghci> :t count
count :: a -> GADTBag a -> Int
ghci> :t count'
count' :: Eq a => a -> EqBag a -> Int
ghci> :t size
size :: GADTBag a -> Int
ghci> :t size'
size' :: Eq a => EqBag a -> Int
ghci> 
所以我们的EqBag计数'函数需要一个Eq约束,我认为这是完全合理的,但我们的尺寸'函数也需要一个,这不太漂亮.这是因为EqBag构造函数的类型是EqBag :: Eq a => [a] -> EqBag a,并且每次都必须添加此约束.
我们不能在这里制作一个仿函数:
instance Functor EqBag where
   fmap f (EqBag xs) = EqBag (map f xs)
与GADTBag完全相同的原因
data ListBag a = ListBag {unListBag :: [a]}
  deriving Show
count'' a = length . filter (==a) . unListBag
size'' = length . unListBag
instance Functor ListBag where
   fmap f (ListBag xs) = ListBag (map f xs)
现在count''和show''的类型完全符合我们的预期,我们可以使用像Functor这样的标准构造函数类:
ghci> :t count''
count'' :: Eq a => a -> ListBag a -> Int
ghci> :t size''
size'' :: ListBag a -> Int
ghci> fmap (Data.Char.ord) (ListBag "hello")
ListBag {unListBag = [104,101,108,108,111]}
ghci> 
GADTs版本在使用构造函数的每个地方自动传播Eq约束.类型检查器可以依赖于存在Eq实例,因为您不能将构造函数用于非Eq类型.
DatatypeContexts版本强制程序员手动传播Eq约束,如果你想要的话,这对我来说很好,但是因为它不会给你提供任何比GADT更多的东西并被许多人视为毫无意义和令人讨厌的东西而被弃用.
无约束版本很好,因为它不会阻止你制作Functor,Monad等实例.约束是在需要时准确写入的,不多或少.Data.Map使用无约束版本,部分原因是因为无约束通常被认为是最灵活的,但也部分是因为它比GADT早一些边缘,并且需要有一个令人信服的理由可能破坏现有代码.
User榜样怎么样?我认为这是一个单一目的数据类型的一个很好的例子,它受益于对类型的约束,我建议你使用GADT来实现它.
(也就是说,有时我有一个单一用途的数据类型,最终只是因为我喜欢使用Functor(和Applicative),而fmap不是mapBag因为我觉得它更清晰.
{-# LANGUAGE GADTs #-}
import Data.String
data User s where 
   User :: (IsString s, Show s) => s -> User s
name :: User s -> s
name (User s) = s
instance Show (User s) where  -- cool, no Show context
  showsPrec i (User s) = showParen (i>9) (("User " ++ show s) ++)
instance (IsString s, Show s) => IsString (User s) where
  fromString = User . fromString
通知自fromString 的确构造类型的值User a,我们需要明确的背景下.毕竟,我们用构造函数编写User :: (IsString s, Show s) => s -> User s.该User构造消除了,当我们模式匹配(毁灭),原因是其已实施的约束时,我们用它作为构造一个明确的情况下的需要.
我们在Show实例中不需要Show上下文,因为我们(User s)在模式匹配的左侧使用了它.
Sho*_*hoe 11
问题是约束不是数据类型的属性,而是对它们进行操作的算法/函数的属性.不同的功能可能需要不同且唯一的约束.
Box例子举个例子,我们假设我们要创建一个Box只包含2个值的容器.
data Box a = Box a a
我们希望它:
sort应用两者的约束Ord和Show数据类型是否有意义?不,因为数据类型本身只能显示或仅排序,因此约束与其使用有关,而不是它的定义.
instance (Show a) => Show (Box a) where
    show (Box a b) = concat ["'", show a, ", ", show b, "'"]
instance (Ord a) => Ord (Box a) where
    compare (Box a b) (Box c d) =
        let ca = compare a c
            cb = compare b d
        in if ca /= EQ then ca else cb
Data.Map案子Data.MapOrd只有当容器中有> 1个元素时,才真正需要对类型的约束.否则,即使没有Ord钥匙,容器也是可用的.例如,这个算法:
transf :: Map NonOrd Int -> Map NonOrd Int
transf x = 
    if Map.null x
        then Map.singleton NonOrdA 1
        else x
在没有Ord约束的情况下工作正常,并且始终生成非空映射.