Я записал что-то вроде этого:
instance Functor (Either e) where
fmap _ (Left a) = Left a
fmap f (Right b) = Right (f b)
Как я делаю то же, если я хочу fmap
изменить значение, только если это Left
?
Я имею в виду, какой синтаксис делают я использую, чтобы указать, что я использую тип Either _ b
вместо Either a _
?
К сожалению, я не думаю, что есть способ сделать это напрямую. С функцией вы можете использовать flip
для частичного применения второго аргумента, но это не работает с конструкторами типов, такими как Either
.
Самым простым, вероятно, является обертывание его в newtype
:
newtype Mirror b a = Mirrored (Either a b)
instance Functor (Mirror e) where
fmap _ (Mirrored (Right a)) = Mirrored $ Right a
fmap f (Mirrored (Left b)) = Mirrored $ Left (f b)
Обертывание с newtype
также является стандартным способом создания нескольких экземпляров для одного типа, например Sum
и Произведение
являются экземплярами Monoid
для числовых типов. В противном случае у вас может быть только один экземпляр для каждого типа.
Кроме того, в зависимости от того, что вы хотите сделать, можно также проигнорировать Функтор
и определить свой собственный класс типа следующим образом:
class Bifunctor f where
bimap :: (a -> c) -> (b -> d) -> f a b -> f c d
instance Bifunctor Either where
bimap f _ (Left a) = Left $ f a
bimap _ g (Right b) = Right $ g b
instance Bifunctor (,) where
bimap f g (a, b) = (f a, g b)
Очевидно, что этот класс вдвое веселее, чем обычный Функтор
. Конечно, вы не сможете сделать из этого экземпляр Monad
очень просто.
По сути, вам нужен комбинатор 'flip' для типов. Обертка newtype, которая инвертирует порядок, должна работать, как говорит camccann. Обратите внимание, что вы не можете использовать синоним 'type', так как они могут быть частично неприменимы.
Вы не можете создать экземпляр, который ищете, напрямую.
Для работы вывода типов и классов типов существует определенная позиционная предвзятость в упорядочении аргументов в типах.Было показано, что если мы позволим произвольно переупорядочить аргументы при создании экземпляров классов типов, этот вывод типа станет неразрешимым.
Вы можете использовать класс Bifunctor
, который может отображать оба аргумента по отдельности.
class Bifunctor f where
bimap :: (a -> b) -> (c -> d) -> f a c -> f b d
first :: (a -> b) -> f a c -> f b c
second :: (c -> d) -> f a c -> f a d
first f = bimap f id
second = bimap id
instance Bifunctor Either where
bimap f _ (Left a) = Left (f a)
bimap _ g (Right b) = Right (g b)
instance Bifunctor (,) where
bimap f g (a,b) = (f a, g b)
Или вы можете использовать комбинатор Flip
, например:
newtype Flip f a b = Flip { unFlip :: f b a }
Обобщенные версии обоих из них доступны в дополнительных категориях при взломе. Последний даже включает экземпляр для Functor (Flip Either a)
, потому что Either
является Bifunctor
. (Я, вероятно, должен исправить это, чтобы потребовать только PFunctor
)
В конечном счете, порядок аргументов в конструкторе типа важен при определении того, какие классы вы можете создать. Возможно, вам придется использовать оболочки нового типа (например, Flip
выше), чтобы поместить аргументы там, где они должны быть, чтобы соответствовать требованиям для создания экземпляра другого класса типов. Это цена, которую мы платим за вывод ограничений типа-класса.