Как я могу переопределить экземпляры класса типов

Я хочу представить концепцию верхней границы в своем коде, поэтому я создал размеченный союз:

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

Экземпляры класса типов выглядят почти так же, как экземпляры для Maybe (Just x -> UpperBound x, Nothing -> DoNotCare), так что кажется, что здесь явно ненужное дублирование.

Как я могу каким-то образом «обернуть» Maybe и перенаправить на него реализации экземпляров класса типов и при этом предоставить функцию isWithinBound?


person rexcfnghk    schedule 22.02.2019    source источник
comment
Не совсем уверен, что понял вопрос, но нельзя ли просто использовать newtype? Или даже синоним типа, если ваши экземпляры на самом деле идентичны (как они кажутся).   -  person Robin Zigmond    schedule 22.02.2019
comment
Что касается isWithinBound, переписать его тривиально, поэтому вместо вашей пользовательской версии используется Maybe a.   -  person Robin Zigmond    schedule 22.02.2019
comment
@RobinZigmond Я имею в виду, что хочу по-прежнему выставлять случаи как UpperBound/DoNotCare, но внутренне перенаправлять объявления экземпляров в реализацию Maybe. Это возможно?   -  person rexcfnghk    schedule 22.02.2019
comment
Экземпляры класса типов выглядят почти так же, как экземпляры класса «Может быть, откуда берется почти часть»?   -  person n. 1.8e9-where's-my-share m.    schedule 22.02.2019
comment
@н.м. вы можете почти выполнить глобальную замену регулярных выражений UpperBound на Just и DoNotCare на None во всех реализациях экземпляров класса типов, и код все равно будет компилироваться для типа Maybe.   -  person rexcfnghk    schedule 22.02.2019
comment
Этот экземпляр Alternative мне кажется схематичным. Предполагается, что он должен быть моноидом для каждого содержащегося типа (с empty в качестве единицы и <|> в качестве операции) - и, в частности, он должен иметь empty <|> x = x <|> empty = x, чего нет у вас.   -  person Daniel Wagner    schedule 22.02.2019
comment
@DanielWagner, ты прав. Я изменил свой код, чтобы исправить эту путаницу.   -  person rexcfnghk    schedule 03.03.2019


Ответы (1)


Самый простой способ — использовать расширения newtype и GeneralizedNewtypeDeriving, например:

{-# 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).

person Karol Samborski    schedule 22.02.2019
comment
Я никогда не понимал, что GeneralizedNewtypeDeriving не работает для Traversable (я никогда не использовал его, кроме как для Functor, Applicative и Monad). Мне интересно, почему это может быть. - person Robin Zigmond; 22.02.2019
comment
@RobinZogmond Если newtype U x = U (T x) и Traversable T, то traverse возвращает f (T x), где вам нужно f (U x). GHC хочет, чтобы между ними было принуждение с нулевой стоимостью, но его нет. Вам понадобится fmap U, а GHC не сделает этого сам по себе. Теперь, поскольку Functor f, морально f всегда должен быть репрезентативным типом role f, и должно быть доступным принуждение, но 1) Traversable не имеет этого ограничения 2) есть некоторые «сломанные ”, но полезные функторы, нарушающие это правило 3) GND, вероятно, не может использовать информацию, даже если она дана и 4) такие ограничения являются очень новым расширением GHC. - person HTNW; 23.02.2019