Можно ли сделать тип экземпляром класса, если его параметры типа находятся в неправильном порядке?
Рассмотрим следующий тип:
data SomeType m a = SomeType (m Integer) [a]
Мы можем легко сделать этот тип экземпляром Functor со следующим кодом:
instance Functor (SomeType m) where
fmap f (SomeType m lst) = SomeType m (map f lst)
Однако, если вместо этого были заменены параметры типа SomeType
:
data SomeType2 a m = SomeType2 (m Integer) [a]
Тогда указанное выше определение экземпляра не работает.
Есть ли способ сделать SomeType2
экземпляр Functor? Если нет, есть ли какие-либо дополнения и дополнения к haskell/ghc, которые позволили бы это сделать?
Ответы
Ответ 1
Предвзятый я, но я думаю, что это отличная возможность использовать Control.Newtype, маленький кусок набора, который просто "cabal install newtype" прочь.
Здесь сделка. Вы хотите перевернуть конструкторы типов, чтобы получить возможность функториальности (например) в другом параметре. Определить новый тип
newtype Flip f x y = Flip (f y x)
и добавьте его в класс Newtype
, тем самым
instance Newtype (Flip f x y) (f y x) where
pack = Flip
unpack (Flip z) = z
Класс Newtype
представляет собой просто сопоставление типов newtypes с их неприкрашенными эквивалентами, предоставляя удобный набор, например. op Flip
является инверсией Flip
: вам не нужно помнить, как вы его назвали.
Для рассматриваемой проблемы теперь мы можем делать такие вещи:
data Bif x y = BNil | BCons x y (Bif x y) deriving Show
Это двухпараметрический тип данных, который оказывается функториальным в обоих параметрах. (Вероятно, мы должны сделать его экземпляром класса Bifunctor, но в любом случае...) Мы можем сделать его Functor
дважды: один раз для последнего параметра...
instance Functor (Bif x) where
fmap f BNil = BNil
fmap f (BCons x y b) = BCons x (f y) (fmap f b)
... и один раз для первого:
instance Functor (Flip Bif y) where
fmap f (Flip BNil) = Flip BNil
fmap f (Flip (BCons x y b)) = Flip (BCons (f x) y (under Flip (fmap f) b))
где under p f
- опрятный способ сказать op p . f . p
.
Я тебе не лгу: давайте попробуем.
someBif :: Bif Int Char
someBif = BCons 1 'a' (BCons 2 'b' (BCons 3 'c' BNil))
а затем получим
*Flip> fmap succ someBif
BCons 1 'b' (BCons 2 'c' (BCons 3 'd' BNil))
*Flip> under Flip (fmap succ) someBif
BCons 2 'a' (BCons 3 'b' (BCons 4 'c' BNil))
В этих обстоятельствах действительно существует много способов, чтобы одно и то же можно было увидеть как Functor
, поэтому правильно, что мы должны сделать некоторый шум, чтобы сказать, к какому способу мы имеем в виду. Но шум не так уж и много, если вы систематически об этом.
Ответ 2
Это невозможно, и я не думаю, что это будет в ближайшее время.
Важным является порядок параметров типа. Последнее значение - это то, которое вы собираетесь "содержать" для использования с Functor и т.д.
Я попытался заставить это работать, указав синоним типа, который перевернул параметры типа вокруг, а затем использовал расширение TypeSynonymInstances
, но он не смог работать.
Ответ 3
Вы можете использовать обертку newtype
, которая меняет параметры типа. Но тогда вы получаете новый тип и должны сделать различие оригинала и обернутого типа в вашем коде.
Ответ 4
Тупой ответ, который вы уже знали, это: перевернуть ваши параметры!
Для GHC для поддержки такого рода вещей без какой-либо дополнительной упаковки вам понадобится что-то вроде lambdas типа уровня, которые, вероятно, в ближайшее время не будут добавлены. (Мне бы хотелось, чтобы в этом не было доказано)
instance Functor (\a -> SomeType2 a m) where
-- fmap :: (a -> b) -> SomeType2 a m -> SomeType2 b m
fmap f (SomeType2 lst m) = SomeType (map f lst) m
Поскольку у нас уже есть TypeSynonymInstances, мы могли бы надеяться на PartialAppliedTypeSynonymInstances когда-то чуть раньше, чем никогда.
type SomeType3 m a = SomeType2 a m
instance Functor (SomeType m) where
-- fmap :: (a -> b) -> SomeType3 m a -> SomeType3 m b
-- or, synonymously:
-- fmap :: (a -> b) -> SomeType2 a m -> SomeType2 a m
fmap f (SomeType2 lst m) = SomeType (map f lst) m