对于Haskell类型类,您该怎么做呢?

时间:2019-08-01 13:55:44

标签: haskell

我知道类型类对于组织数据和类型检查等非常有用,但是除了序言中已经包含的内容之外,是否有必要定义自己的类?

几乎在任何情况下都可以定义一个数据或新类型,并且无论如何都会得到几乎相同的效果。使用内置的“ Ord”,“ Eq”,“ Show”等,似乎足以完成您想对类进行的任何操作。

当我在Haskell中查找项目的类时,会得到很多类似的示例类:

class Foo a where
    bar :: a -> a -> Bool

如果有使用类型类的理由,那么有没有人有一个很好的项目供初学者练习使用它们或为他们提供一些良好的形式指南?

3 个答案:

答案 0 :(得分:15)

与参数多态性相反,

类型类提供 adhoc 多态性:不需要为每种类型以相同的方式(或根本没有定义)一个函数。此外,它以 open 的方式进行:您无需在定义类本身时枚举实现该类的所有类型。

非标准类型类的一些著名示例是MonadFoo(monad转换器库)提供的各种mtl类,{{1}提供的ToJSONFromJSON 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之外的一些更实际的类型类示例:

由于存在用于以不同方式使用类型类的整个设计空间,因此请多加注意:

  • 具有类型类模式:Tutorial 1Tutorial 2和包data-has

  • 一个与QuickCheck相关的有趣的库是Hedgehog,它消除了strong reason的类型类(教程,通常让人大开眼界)。因此,可能有很多原因要使用而不使用类型类。通常只是已经存在您要查找的类型类。

  • 值得一读加布里埃尔·冈萨雷斯(Gabriel Gonzalez)的Scrap Your Type Classes,其中强调了使用类型类的一些缺点。在博客文章开始时,他的“自从我写这篇文章以来,他对类型类的看法得到了应有的重视,但我仍然批评它,以免对类型类的过剩提出批评。”

    < / li>
  

如果有使用类型类的理由,那么有没有人有一个很好的项目供初学者练习使用它们或为他们提供一些良好的形式指南?

这实际上取决于您是要定义类型类,还是只为现有类型类定义类型类实例,是在基类中使用现有类型类,还是在扩展库中使用某些类型类。

MonoidSemigrouptutorial)的事物定义类型类实例可能很有趣。为某些您可能会感兴趣的(tutorial)JSON数据格式定义自己的ToJSONFromJSON实例也很有趣。

答案 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”,并且对于类型SlugTemperature的读取也不会造成混淆。

现在,如果您想通过一些示例来观看它的运行,我现在可以提出类似...的内容。

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]