Манипулирование порядка аргументов для создания конструкторов

Я написал что-то вроде этого:

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, которая инвертирует порядок, должна работать. Обратите внимание: вы не можете использовать синоним типа, поскольку они не могут быть частично применены.