HaskellCategory
类提供了一些方法来处理其对象恰好是某种 Haskell 类型的类别。具体来说,
class Category c where
id :: c x x
(.) :: c y z -> c x y -> c x z
方法的名称应该看起来很熟悉。尤其,
instance Category (->) where
id x = x
f . g = \x -> f (g x)
你可能知道幺半群是有恒等式的半群,用 Haskell 表示
class Monoid a where
mappend :: a -> a -> a
mempty :: a
但另一种数学观点是,它们是只有一个对象的类别。如果我们有一个幺半群,我们可以很容易地把它变成一个类别:
-- We don't really need this extension, but
-- invoking it will make the code below more useful.
{-# LANGUAGE PolyKinds #-}
import Control.Category
import Data.Monoid
import Prelude hiding ((.), id)
newtype Mon m a b = Mon m
instance Monoid m => Category (Mon m) where
id = Mon mempty
Mon x . Mon y = Mon (x `mappend` y)
走另一条路有点棘手。一种方法是选择一种只有一种类型的类型,并查看其唯一对象是该类型的类别(准备好讨厌的代码,如果你愿意,可以跳过;下面的内容不那么可怕)。这表明我们可以在Category
对象是kind'()
中的类型的a 和 a 之间自由转换。类别的箭头成为幺半群的元素。()
Monoid
{-# LANGUAGE DataKinds, GADTs, PolyKinds #-}
data Cat (c :: () -> () -> *) where
Cat :: c '() '() -> Cat c
instance Category c => Monoid (Cat c) where
mempty = Cat id
Cat f `mappend` Cat g = Cat (f . g)
但这很糟糕!哇!从实际的角度来看,将事情如此紧密地固定通常不会完成任何事情。但是我们可以通过玩一个小把戏来获得没有那么多混乱的功能!
{-# LANGUAGE GADTs, PolyKinds #-}
import Control.Category
import Data.Monoid
import Prelude hiding ((.), id)
newtype Cat' (c :: k -> k -> *) (a :: k) (b :: k) = Cat' (c a b)
instance (a ~ b, Category c) => Monoid (Cat' c a b) where
mempty = Cat' id
Cat' f `mappend` Cat' g = Cat' (f . g)
与其把自己局限在一个Category
真正只有一个对象的东西上,我们只是把自己局限在一次只看一个对象上。
现有Monoid
的函数实例让我很难过。我认为使用基于实例的函数的实例会更自然,使用以下方法:Monoid
Category
Cat'
instance a ~ b => Monoid (a -> b) where
mempty = id
mappend = (.)
由于已经有一个Monoid
实例,并且重叠的实例是邪恶的,我们必须凑合着使用newtype
. 我们可以使用
newtype Morph a b = Morph {appMorph :: a -> b}
然后写
instance a ~ b => Monoid (Morph a b) where
mempty = Morph id
Morph f `mappend` Morph g = Morph (f . g)
出于某些目的,这可能是要走的路,但是由于我们已经使用了 a ,newtype
我们通常不妨放弃非标准的相等上下文并使用Data.Monoid.Endo
,它将相等性构建到类型中:
newtype Endo a = Endo {appEndo :: a -> a}
instance Monoid (Endo a) where
mempty = Endo id
Endo f `mappend` Endo g = Endo (f . g)