[英]What can you do that is useful with Haskell Type Classes?
我理解类型类对于组织数据和类型检查等非常有用,但除了已经包含在前奏中的内容之外,是否需要定义自己的类?
几乎在任何情况下,人们都可以定义数据或新类型,并且无论如何都会获得几乎相同的效果。 使用内置的“Ord”,“Eq”,“Show”等,似乎足以做任何你想要做的事情。
当我在Haskell中查找类的项目时,我得到了很多像这样的示例类:
class Foo a where
bar :: a -> a -> Bool
如果有理由使用类型类,是否有人有一个好的项目供初学者练习使用它们或者一些好的表格指南?
类型类提供ad hoc多态,而不是参数多态:函数不需要以相同的方式(或根本不是)为每种类型定义。 此外,它以开放的方式实现:在定义类本身时,您不需要枚举实现该类的所有类型。
非标准型类的一些显着的例子是各种MonadFoo
由提供的类mtl
(单子转换库), ToJSON
和FromJSON
由提供的aeson
图书馆和IsString
,这使得OverloadedString
扩展的工作。
如果没有类型类,则可以定义适用于单个参数类型的函数
foo :: Int -> Int
或适用于所有参数类型的
foo :: a -> Int
对某些类型子集起作用的唯一方法是使用和类型
foo :: Either Int Bool -> Int
但是你不能在以后为Float
定义foo
而不改变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
扩展使其更加明确。 例如,以下是等效的。
foo True == 1
foo @Bool True == 1 -- foo :: ToInt a => a -> Int, but foo @Bool :: Bool -> Int
除了chepner对类型类有用性的解释之外,这里还有一些Prelude之外的类型类的更实用的例子:
Arbitrary
来自QuickCheck( QuickCheck教程:生成器 )。 Example
,它类似但不完全等效。 WithLog
(或更确切地说, HasLog
)。 SafeCopy
是另一个序列化类,但具有与Aeson的FromJSON
, ToJSON
不同的约束,因为它还处理数据格式迁移。 由于有一个完整的设计空间以不同的方式使用类型类,这里有一些更多的想法:
与QuickCheck相关的一个有趣的库是Hedgehog ,它有很强的理由消除了类型类(教程,通常令人大开眼界)。 因此可能有很多理由使用而不使用类型类; 通常只存在您正在寻找的类型类。
值得一读的是Gabriel Gonzalez的Scrap Your Type Classes ,它强调了类型类使用的一些缺点。 正如博客文章开头的那样, 自从我撰写这篇文章以来 ,他对类型类的看法已经变得圆润,但我仍然把它作为对类型类过度的批评。
如果有理由使用类型类,是否有人有一个好的项目供初学者练习使用它们或者一些好的表格指南?
这实际上取决于您是要定义类型类,还是仅为现有类型类定义类型类实例,在base中使用现有类型类,或在扩展库中使用某种类型类。
为Monoid
或Semigroup
( 教程 )定义类型类实例会很有趣。 为您可能感兴趣的某些JSON数据格式定义自己的ToJSON
和FromJSON
实例也很有趣( 教程 )。
定义
class Foo a where
bar :: a -> a -> Bool
是非常相似的
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
在这里,你可以找到它有多么有用:
想象一下你有slu ,,并想知道它们是否可以生育,并且有一种具有雌雄同体类型的稀有物种,你可以使用你的类型类:
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])
[假,真,真,假,真,真,真]
[假,真,真,假,真,真,真]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.