tim*_*els 13 haskell typeclass
在定义类型类时,如何在类型类定义中包含/排除函数?例如,这两种情况之间有什么区别:
class Graph g where
...
insertNode :: g -> Node -> g
insertNode graph node = ...
Run Code Online (Sandbox Code Playgroud)
VS
class Graph g where
...
insertNode :: (Graph g) => g -> Node -> g
insertNode graph node = ...
Run Code Online (Sandbox Code Playgroud)
Lui*_*las 11
我认为这里有一些紧张的因素.一般认为类型类定义应该是最小的,并且只包含独立的函数.作为bhelkir的回答解释说,如果你的类支持的功能a,b并且c,但是c可以在以下方面实现a和b,这是用于定义参数c外部类.
但是这个总体思路遇到了一些其他相互矛盾的问题.
首先,通常有一组以上的最小操作可以等效地定义同一个类.MonadHaskell 的经典定义是这个(清理了一下):
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
Run Code Online (Sandbox Code Playgroud)
但众所周知,还有其他定义,如下所示:
class Applicative m => Monad m where
join :: m (m a) -> m a
Run Code Online (Sandbox Code Playgroud)
return和>>=足以实现join,但是fmap,pure并且join也足以实现>>=.
类似的事情Applicative.这是规范的Haskell定义:
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)
但以下任何一项都是等效的:
class Functor f => Applicative f where
unit :: f ()
(<*>) :: f (a -> b) -> f a -> f b
class Functor f => Applicative f where
pure :: a -> f a
fpair :: f a -> f b -> f (a, b)
class Functor f => Applicative f where
unit :: f ()
fpair :: f a -> f b -> f (a, b)
class Functor f => Applicative f where
unit :: f ()
liftA2 :: (a -> b -> c) -> f a -> f b -> f c
Run Code Online (Sandbox Code Playgroud)
给定任何这些类定义,您可以将任何其他方法中的任何方法编写为类外的派生函数.为什么选择第一个?我不能权威地回答,但我认为它将我们带到了第三点:性能考虑因素.fpair许多这样的操作通过创建元组来组合f a和f b值,但是对于Applicative类的大多数用途我们实际上并不想要那些元组,我们只想组合从f a和得到的值f b; 规范定义允许我们选择与此组合的功能.
另一个性能考虑因素是,即使某个类中的某些方法可以根据其他方法定义,这些通用定义对于所有类的实例也可能不是最佳的.如果我们以Foldable一个例子为例,foldMap并且foldr是可以相互确定的,但某些类型比另一类更有效.因此,我们经常使用非最小类定义来允许实例提供优化的方法实现.
在类型类的定义中包含一个函数意味着它可以被覆盖.在这种情况下,您需要将它放在Graph类型类中,因为它返回一个Graph g => g,并且每个特定的实例Graph都需要知道如何构造该值.或者,您可以在类型类中指定一个函数,以便构造类型的值,Graph g => g然后insertNode可以在其结果中使用该函数.
将一个函数保存在类型类之外意味着它不能被修改,而且它不会使类混乱.以mapM功能为例.没有必要在Monad课堂上,你可能不希望人们编写自己的实现mapM,它应该在所有上下文中做同样的事情.作为另一个例子,考虑功能
-- f(x) = 1 + 3x^2 - 5x^3 + 10x^4
aPoly :: Num a => a -> a
aPoly x = 1 + 3 * x * x - 5 * x * x * x + 10 * x * x * x * x
Run Code Online (Sandbox Code Playgroud)
显然aPoly不应该是Num类型类的一部分,它只是一个碰巧使用Num方法的随机函数.它与成为一个人的意义无关Num.
真的,它归结为设计.函数通常在类型类中指定,如果它们与作为该类型类的实例的含义不一致.有时函数包含在类型类中,但具有默认定义,因此特定类型可以使其超载以使其更有效,但在大多数情况下,将类成员保持在最低限度是有意义的.查看它的一种方法是提出问题"这个函数是否只能用类的约束来实现?" 如果答案是否定的,则应该在课堂上.如果答案是肯定的,那么绝大多数时候它意味着该函数应该移到该类之外.只有当能够超载它所获得的价值时才应将其移入课堂.如果重载该函数可以破坏其他期望它以特定方式运行的代码,那么它不应该被重载.
另一个需要考虑的情况是,在类型类中有函数具有合理的默认值,但这些默认值是相互依赖的.以Num课堂为例,你有
class Num a where
(+) :: a -> a -> a
(*) :: a -> a -> a
(-) :: a -> a -> a
a - b = a + negate b
negate :: a -> a
negate a = 0 - a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> a
Run Code Online (Sandbox Code Playgroud)
请注意,(-)并negate在对方的条款都实施.如果你把自己的数值类型,那么你就需要实现的一个或两个(-)和negate,否则你就会有你的手无限循环.但是,这些是重载的有用函数,因此它们都保留在类型类中.
| 归档时间: |
|
| 查看次数: |
954 次 |
| 最近记录: |