如何重新包装类型类实例

时间:2019-02-22 08:16:21

标签: haskell

我想在代码中表示上限的概念,所以我创建了一个有区别的联合:

data UpperBound a = UpperBound a | DoNotCare deriving (Eq, Read, Show)

然后我手动派生了几个有用的类型类实例(出于学习目的):

instance Functor UpperBound where
    fmap _ DoNotCare = DoNotCare
    fmap f (UpperBound x) = UpperBound $ f x

instance Applicative UpperBound where
    pure = UpperBound
    DoNotCare <*> _ = DoNotCare
    _ <*> DoNotCare = DoNotCare
    (UpperBound f) <*> (UpperBound x) = UpperBound $ f x

instance Foldable UpperBound where
    foldr _ s DoNotCare = s
    foldr f s (UpperBound x) = f x s

instance Traversable UpperBound where
    traverse _ DoNotCare = pure DoNotCare
    traverse f (UpperBound x) = fmap UpperBound $ f x

instance Alternative UpperBound where
    empty = DoNotCare
    DoNotCare <|> x = x
    x <|> DoNotCare = x
    x <|> _ = x

instance Monad UpperBound where
    return = pure
    DoNotCare >>= _ = DoNotCare
    (UpperBound x) >>= f = f x

instance MonadPlus UpperBound where
    mzero = empty
    mplus = (<|>)

和一个实用程序功能:

isWithinBound :: Ord a => a -> UpperBound a -> Bool
isWithinBound _ DoNotCare = True
isWithinBound x (UpperBound b) = x <= b

typeclass实例看起来与MaybeJust x-> UpperBound xNothing-> DoNotCare)的实例几乎一样,显然这里有不必要的重复。

我如何以某种方式“包装” Maybe并将类型类实例实现重定向到该类型,并仍然公开isWithinBound函数?

1 个答案:

答案 0 :(得分:3)

最简单的方法是使用newtypeGeneralizedNewtypeDeriving扩展名,如下所示:

{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveTraversable #-}
module UpperBound (UpperBound, isWithinBound) where

import Control.Monad
import Control.Applicative

newtype UpperBound a = UpperBound { unUpperBound :: Maybe a }
  deriving (Functor, Applicative, Foldable, Traversable, Alternative, Monad, MonadPlus)

isWithinBound :: Ord a => a -> UpperBound a -> Bool
isWithinBound x = maybe True ((<=) x) . unUpperBound

因此,您需要的所有实例都会自动重定向到Maybe的实例(除了Traversable,它是使用DeriveTraversable扩展名自动得出的)。