我知道类型类对于组织数据和类型检查等非常有用,但是除了序言中已经包含的内容之外,是否有必要定义自己的类?
几乎在任何情况下都可以定义一个数据或新类型,并且无论如何都会得到几乎相同的效果。使用内置的“ Ord”,“ Eq”,“ Show”等,似乎足以完成您想对类进行的任何操作。
当我在Haskell中查找项目的类时,会得到很多类似的示例类:
class Foo a where
bar :: a -> a -> Bool
如果有使用类型类的理由,那么有没有人有一个很好的项目供初学者练习使用它们或为他们提供一些良好的形式指南?
答案 0 :(得分:15)
类型类提供 adhoc 多态性:不需要为每种类型以相同的方式(或根本没有定义)一个函数。此外,它以 open 的方式进行:您无需在定义类本身时枚举实现该类的所有类型。
非标准类型类的一些著名示例是MonadFoo
(monad转换器库)提供的各种mtl
类,{{1}提供的ToJSON
和FromJSON
1}}库和aeson
,使IsString
扩展名有效。
没有类型类,您可以定义一个适用于单个参数类型的函数
OverloadedString
或一种适用于所有参数类型的
foo :: Int -> Int
处理某些类型子集的唯一方法是使用求和类型
foo :: a -> Int
但是您以后不能在不更改foo :: Either Int Bool -> Int
本身类型的情况下为foo
定义Float
foo
或
foo :: Either Int (Either Bool Float) -> Int
使用哪个或哪个都麻烦。
类型类可让您一次使用一种类型,并以非介入方式添加新类型。
data IntBoolFloat = T1 Int | T2 Bool | T3 Float
foo :: IntBoolFloat -> Int
class ToInt a where
foo :: a -> Int
instance ToInt Int where foo = id
instance ToInt Bool where
foo True = 1
foo False = 2
instance ToInt Float where
foo x = 3 -- Kind of pointless, but valid
的实例可以在任何地方定义,尽管在实践中最好在定义类本身的模块或定义实例化类型的模块中定义它。
在内部,方法(由类型类定义的函数)本质上是类型到函数的映射。 ToInt
扩展名使其更加明确。例如,以下等同。
TypeApplications
答案 1 :(得分:7)
除chepner对类型类的用途的解释之外,这是Prelude之外的一些更实际的类型类示例:
Arbitrary
来自QuickCheck(A QuickCheck Tutorial: Generators)。Example
,这是相似的但不完全相同。WithLog
(或更确切地说,HasLog
)来自co-log。SafeCopy
是另一种序列化类,但是与Aeson的FromJSON
,ToJSON
相比具有不同的约束,因为它还处理数据格式迁移。由于存在用于以不同方式使用类型类的整个设计空间,因此请多加注意:
具有类型类模式:Tutorial 1,Tutorial 2和包data-has。
一个与QuickCheck相关的有趣的库是Hedgehog,它消除了strong reason的类型类(教程,通常让人大开眼界)。因此,可能有很多原因要使用而不使用类型类。通常只是已经存在您要查找的类型类。
值得一读加布里埃尔·冈萨雷斯(Gabriel Gonzalez)的Scrap Your Type Classes,其中强调了使用类型类的一些缺点。在博客文章开始时,他的“自从我写这篇文章以来,他对类型类的看法得到了应有的重视,但我仍然批评它,以免对类型类的过剩提出批评。”
< / li>如果有使用类型类的理由,那么有没有人有一个很好的项目供初学者练习使用它们或为他们提供一些良好的形式指南?
这实际上取决于您是要定义类型类,还是只为现有类型类定义类型类实例,是在基类中使用现有类型类,还是在扩展库中使用某些类型类。
为Monoid
或Semigroup
(tutorial)的事物定义类型类实例可能很有趣。为某些您可能会感兴趣的(tutorial)JSON数据格式定义自己的ToJSON
和FromJSON
实例也很有趣。
答案 2 :(得分:3)
定义
class Foo a where
bar :: a -> a -> Bool
非常类似于
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
这是您可以发现它有用的地方:
想象一下,您有,并且想知道它们是否能够繁殖,并且有一种雌雄同体类型的稀有物种,您可以使用您的类型类:
data Slug = M | F | H
class Foo a where
bar :: a -> a -> Bool
instance Foo Slug where
bar H _ = True
bar _ H = True
bar F M = True
bar M F = True
bar _ _ = False
或者,在温度下: 您想知道混合水是否能给您带来温水:
data Temp = Cold | Hot | Warm
instance Foo Temp where
bar Warm _ = True
bar _ Warm = True
bar Hot Cold = True
bar Cold Hot = True
bar _ _ = False
因此,该类型类现在可以命名为“ Mixable”和方法“ mix”,并且对于类型Slug
和Temperature
的读取也不会造成混淆。
现在,如果您想通过一些示例来观看它的运行,我现在可以提出类似...的内容。
mix :: Foo a => [a] -> [a] -> [Bool]
mix xs ys = zipWith bar xs ys
$> mix [M,M,H,F] [F,F,F,F]
=> [True,True,True,False]
但是混合有一个限制,你可以只混合可混合的东西。因此,如果您这样做:
mix [1,1] [2,2]
将中断:
9:1: error:
• No instance for (Foo Bool) arising from a use of ‘mix’
• In the expression: mix [True, True] [False, True]
In an equation for ‘it’: it = mix [True, True] [False,
这意味着,您可以根据其结构或您的需要来组织数据类型以满足mix
函数的需求。
第2级:
如果您希望Slug和Temp的默认实现怎么办?因为您看到了它们的相似之处,所以可以这样做:
class (Bounded a, Eq a) => Mixable a where
mix :: a -> a -> Bool
mix e1 e2 = e1 /= e2 || any (\x -> x /= minBound && x /= maxBound) [e1, e2]
data Slug = F | H | M deriving (Bounded, Eq, Show)
data Temp = Cold | Warm | Hot deriving (Bounded, Eq, Show)
instance Mixable Slug
instance Mixable Temp
mixAll :: Mixable a => [a] -> [a] -> [Bool]
mixAll xs ys = zipWith mix xs ys
main = do
putStrLn $ show (mixAll [F,F,F,M,M,M,H] [F,M,H,M,F,H,H])
putStrLn $ show (mixAll [Cold,Cold,Cold,Hot,Hot,Hot,Warm] [Cold,Hot,Warm,Hot,Cold,Warm,Warm])
[False,True,True,False,True,True,True]
[False,True,True,False,True,True,True]