繁体   English   中英

你能做什么对Haskell类型类很有用?

[英]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 (单子转换库), ToJSONFromJSON由提供的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之外的类型类的更实用的例子:

由于有一个完整的设计空间以不同的方式使用类型类,这里有一些更多的想法:

  • 具有类型类模式: 教程1教程2和包数据

  • 与QuickCheck相关的一个有趣的库是Hedgehog ,它有很强的理由消除了类型类(教程,通常令人大开眼界)。 因此可能有很多理由使用而不使用类型类; 通常只存在您正在寻找的类型类。

  • 值得一读的是Gabriel Gonzalez的Scrap Your Type Classes ,它强调了类型类使用的一些缺点。 正如博客文章开头的那样, 自从我撰写这篇文章以来 ,他对类型类的看法已经变得圆润,但我仍然把它作为对类型类过度的批评。

如果有理由使用类型类,是否有人有一个好的项目供初学者练习使用它们或者一些好的表格指南?

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

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

定义

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”,并且读取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])

[假,真,真,假,真,真,真]

[假,真,真,假,真,真,真]

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM