Манипулирование порядка аргументов для создания конструкторов
Я написал что-то вроде этого:
instance Functor (Either e) where
fmap _ (Left a) = Left a
fmap f (Right b) = Right (f b)
Как сделать то же самое, если я хочу, чтобы fmap
изменил значение, только если оно Left
?
Я имею в виду, какой синтаксис я использую, чтобы указать, что я использую тип Either _ b
вместо Either a _
?
Ответы
Ответ 1
Я не думаю, что есть способ сделать это напрямую, к сожалению. С помощью функции вы можете использовать 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
и Product
, являющихся экземплярами Monoid
для числовых типов. В противном случае вы можете иметь только один экземпляр для каждого типа.
Кроме того, в зависимости от того, что вы хотите сделать, другой вариант - игнорировать Functor
и определить свой собственный тип типа следующим образом:
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)
Очевидно, что этот класс в два раза больше, чем обычный Functor
. Конечно, вы не можете сделать экземпляр Monad
из этого очень легко.
Ответ 2
Вы не можете создать экземпляр, который вы ищете напрямую.
Для того, чтобы типы ввода и типы классов работали, существует определенное позиционное смещение в упорядочении аргументов в типах. Было показано, что если мы допустили произвольное переупорядочение аргументов при экземплярировании классов типов, то вывод типа становится трудноразрешимым.
Вы можете использовать класс 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
)
В конечном счете, порядок аргументов в конструкторе типов важен для определения того, какие классы вы можете создать. Возможно, вам придется использовать newtype wrappers (например, Flip
выше), чтобы поместить аргументы, где они должны быть, чтобы претендовать на создание экземпляра другого типа. Это цена, которую мы платим за вывод ограничений типа класса.
Ответ 3
Вам по сути нужен комбинатор "flip" для типов. Как заявляет Camcann, обертка newtype, которая инвертирует порядок, должна работать. Обратите внимание: вы не можете использовать синоним типа, поскольку они не могут быть частично применены.