Haskell: Недостаток в описании законов аппликативного функтора в хакете Control.Applicative article?: it says Applicative определяет Functor
Я думаю, что я нашел ошибку в статье хака для Control.Applicative
.
В качестве описания законов прикладного функтора говорится:
class Functor f => Applicative f where
Функтор с применением, обеспечивающий операции вставлять чистые выражения (pure
) и вычислять последовательности и комбинировать их результаты (<*>
).
Минимальное полное определение должно включать реализацию этих функций, удовлетворяющих следующим законам:
идентичность
pure id <*> v = v
Состав
pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
гомоморфизм
pure f <*> pure x = pure (f x)
взаимообмен
u <*> pure y = pure ($ y) <*> u
(обратите внимание, что это ничего не говорит о fmap), и он утверждает, что эти законы определяют экземпляр Functor
:
Как следствие этих законов, экземпляр Functor
для f будет удовлетворять
fmap f x = pure f <*> x
Я подумал, что сначала это было явно неправильно. То есть, я догадался, что должен существовать конструктор типа t
, удовлетворяющий следующим двум условиям:
-
t
является экземпляром Applicative
, выполняющим приведенные выше правила, и
- Существуют две различные реализации
instance (Functor t)
(т.е. существуют две различные функции fmap1, fmap2 :: (a->b) -> (t a->t b)
,
удовлетворяющих законам функтора).
Если (и только в том случае, если) выше, то это утверждение должно быть переписано на
Экземпляр Functor для f должен удовлетворять
fmap f x = pure f <*> x
Как следствие этих законов, это удовлетворяет законам Functor
.
что явно правильно, независимо от того, правильна ли моя догадка или нет.
Мой вопрос: моя догадка правильная? Существует ли t
с требуемыми условиями?
Далее следует объяснение того, что я думал при попытке ответить на этот вопрос сам.
Если бы мы были просто математиками, не заинтересованными в реальном программировании Haskell, мы могли бы легко ответить на этот вопрос утвердительно. Фактически,
t = Identity
newtype Identity a = Identity {runIdentity :: a}
соответствует требованиям 1 и 2 выше (фактически, почти все будет сделано). Действительно, Identity
тривиально является экземпляром Functor
и Applicative
, как определено в Data.Functor.Identity
. (Это удовлетворяет fmap f = (pure f <*>)
.)
Чтобы определить другую "реализацию" instance (Functor f)
, возьмем для каждого типа a
две функции
transf_a, tinv_a :: a -> a
такое, что
tinv_a . transf_a = id
(Это теоретически теоретически легко). Теперь определите
instance (Functor Identity) where
fmap (f :: a->b) = Identity . transf_b . f . tinv_a . runIdentity
Это удовлетворяет законам Functor
и является другой реализацией от тривиального, если есть
x :: a
f :: a -> b
такое, что
f x /= transf_b $ f $ tinv_a x
Но совершенно не очевидно, можем ли мы сделать это в Haskell. Есть что-то вроде:
class (Isom a) where
transf, tinv :: a -> a
instance (Isom a) where
transf = id
tinv = id
specialized instance (Isom Bool) where
transf = not
tinv = not
возможно в Haskell?
Изменить
Я забыл написать что-то очень важное. Я признаю Control.Applicative
как часть базового пакета GHC, поэтому меня также интересует, изменяется ли ответ на мой вопрос с любыми языковыми расширениями GHC, такими как FlexibleInstances
или OverlappingInstances
, которые я пока не понимаю.
Ответы
Ответ 1
Любой тип в Haskell может иметь не более одного экземпляра Functor
, поэтому ваше предположение неверно: ни для какого типа t
(независимо от того, является ли он экземпляром Applicative
) не может существовать две разные реализации instance (Functor t)
, Видеть: http://article.gmane.org/gmane.comp.lang.haskell.libraries/15384
Ответ 2
Это свойство типа a -> a
состоит только из двух его обитателей: id :: a -> a
(определяется как id x = x
) и bottom :: a -> a
, определенных как bottom = error "Infinite loop."
.
Если мы ограничимся только первым случаем как "разумным", мы приходим к важной математической теореме, которая заключается в том, что существует не более одной функции fmap
типа forall a. forall b. (a -> b) -> f a -> f b
, удовлетворяющей fmap id = id
и fmap f . fmap g = fmap (f . g)
,
Если мы этого не сделаем, тогда вы правы, у нас есть один случай, когда fmap = undefined
, а другой - fmap = (<*>) . pure
. Но это немного дрянной.