Eu gostaria de propor uma abordagem mais sistemática para responder a essa pergunta e também mostrar exemplos que não usam truques especiais, como os valores "inferiores" ou tipos de dados infinitos ou algo assim.
Quando os construtores de tipo falham em ter instâncias de classe de tipo?
Em geral, há duas razões pelas quais um construtor de tipos pode falhar ao ter uma instância de uma determinada classe de tipos:
- Não é possível implementar as assinaturas de tipo dos métodos necessários da classe de tipo.
- Pode implementar as assinaturas de tipo, mas não pode atender às leis exigidas.
Exemplos do primeiro tipo são mais fáceis do que aqueles do segundo tipo, porque, para o primeiro tipo, precisamos apenas verificar se é possível implementar uma função com uma determinada assinatura de tipo, enquanto que, para o segundo tipo, somos obrigados a provar que nenhuma implementação possivelmente poderia satisfazer as leis.
Exemplos específicos
Este é um contrafunctor, não um functor, com relação ao parâmetro type a
, porque a
em uma posição contravariante. É impossível implementar uma função com assinatura de tipo (a -> b) -> F z a -> F z b
.
Um construtor de tipo que não é um functor legal, mesmo que a assinatura de tipo fmap
possa ser implementada:
data Q a = Q(a -> Int, a)
fmap :: (a -> b) -> Q a -> Q b
fmap f (Q(g, x)) = Q(\_ -> g x, f x) -- this fails the functor laws!
O aspecto curioso deste exemplo é que nós podemos implementar fmap
do tipo correto, mesmo que F
não pode ser um functor porque ele usa a
em uma posição contravariant. Portanto, essa implementação fmap
mostrada acima é enganosa - mesmo tendo a assinatura de tipo correta (acredito que essa seja a única implementação possível dessa assinatura de tipo), as leis do functor não são cumpridas. Por exemplo, fmap id
≠ id
, porque let (Q(f,_)) = fmap id (Q(read,"123")) in f "456"
é 123
, mas let (Q(f,_)) = id (Q(read,"123")) in f "456"
é 456
.
De fato, F
é apenas um profunctor, - não é um functor nem um contrafunctor.
Um functor legal que não é aplicável porque a assinatura de tipo pure
não pode ser implementada: pegue a mônada do Writer (a, w)
e remova a restrição que w
deve ser um monóide. É então impossível construir um valor do tipo (a, w)
fora de a
.
Um functor que não é aplicativo porque a assinatura tipo de <*>
impossibilidade de aplicação: data F a = Either (Int -> a) (String -> a)
.
Um functor que não é um aplicativo legal, mesmo que os métodos da classe de tipo possam ser implementados:
data P a = P ((a -> Int) -> Maybe a)
O construtor de tipos P
é um functor porque ele usa a
apenas em posições covariantes.
instance Functor P where
fmap :: (a -> b) -> P a -> P b
fmap fab (P pa) = P (\q -> fmap fab $ pa (q . fab))
A única implementação possível da assinatura de tipo <*>
é uma função que sempre retorna Nothing
:
(<*>) :: P (a -> b) -> P a -> P b
(P pfab) <*> (P pa) = \_ -> Nothing -- fails the laws!
Mas essa implementação não satisfaz a lei de identidade para functores aplicativos.
- Um functor que é,
Applicative
mas não um,Monad
porque a assinatura do tipo bind
não pode ser implementada.
Eu não conheço nenhum desses exemplos!
- Um functor que é,
Applicative
mas não é,Monad
porque as leis não podem ser satisfeitas, mesmo que a assinatura do tipo bind
possa ser implementada.
Este exemplo gerou bastante discussão, portanto, é seguro dizer que provar que esse exemplo está correto não é fácil. Mas várias pessoas verificaram isso independentemente por métodos diferentes. Consulte Is `data PoE a = Empty | Emparelhar aa` uma mônada? para discussão adicional.
data B a = Maybe (a, a)
deriving Functor
instance Applicative B where
pure x = Just (x, x)
b1 <*> b2 = case (b1, b2) of
(Just (x1, y1), Just (x2, y2)) -> Just((x1, x2), (y1, y2))
_ -> Nothing
É um tanto complicado provar que não existe um Monad
caso legal . A razão para o comportamento não-monádico é que não há uma maneira natural de implementar bind
quando uma função f :: a -> B b
pode retornar Nothing
ou Just
para valores diferentes de a
.
Talvez seja mais claro considerar Maybe (a, a, a)
, que também não é uma mônada, e tentar implementar join
isso. Descobriremos que não há uma maneira intuitivamente razoável de implementar join
.
join :: Maybe (Maybe (a, a, a), Maybe (a, a, a), Maybe (a, a, a)) -> Maybe (a, a, a)
join Nothing = Nothing
join Just (Nothing, Just (x1,x2,x3), Just (y1,y2,y3)) = ???
join Just (Just (x1,x2,x3), Nothing, Just (y1,y2,y3)) = ???
-- etc.
Nos casos indicados por ???
, parece claro que não podemos produzir de Just (z1, z2, z3)
maneira razoável e simétrica dentre seis valores diferentes de tipo a
. Certamente poderíamos escolher algum subconjunto arbitrário desses seis valores - por exemplo, sempre levar o primeiro não vazio Maybe
- mas isso não satisfaria as leis da mônada. O retorno Nothing
também não satisfará as leis.
- Uma estrutura de dados em forma de árvore que não é uma mônada , embora tenha associação para
bind
- mas falha nas leis de identidade.
A mônada usual de árvore (ou "uma árvore com galhos em forma de functor") é definida como
data Tr f a = Leaf a | Branch (f (Tr f a))
Esta é uma mônada livre sobre o functor f
. A forma dos dados é uma árvore em que cada ponto de ramificação é um "functor-ful" de subárvores. A árvore binária padrão seria obtida com type f a = (a, a)
.
Se modificarmos essa estrutura de dados, fazendo também as folhas na forma do functor f
, obteremos o que chamo de "semimonada" - ela bind
satisfaz as leis de naturalidade e associatividade, mas seu pure
método falha em uma das leis de identidade. "Semimonados são semigrupos na categoria de endofuncionadores, qual é o problema?" Esta é a classe de tipo Bind
.
Para simplificar, defino o join
método em vez de bind
:
data Trs f a = Leaf (f a) | Branch (f (Trs f a))
join :: Trs f (Trs f a) -> Trs f a
join (Leaf ftrs) = Branch ftrs
join (Branch ftrstrs) = Branch (fmap @f join ftrstrs)
O enxerto de ramo é padrão, mas o enxerto de folhas não é padrão e produz a Branch
. Isso não é um problema para a lei de associatividade, mas quebra uma das leis de identidade.
Quando os tipos polinomiais têm instâncias de mônada?
Nenhum dos functores Maybe (a, a)
e Maybe (a, a, a)
pode receber uma Monad
instância legal , embora sejam obviamente Applicative
.
Esses functores não têm truques - Void
nem bottom
em nenhum lugar, nem em preguiça / rigidez complicada, nem em estruturas infinitas nem em restrições de classe de tipo. A Applicative
instância é completamente padrão. As funções return
e bind
podem ser implementadas para esses functores, mas não satisfazem as leis da mônada. Em outras palavras, esses functores não são mônadas porque falta uma estrutura específica (mas não é fácil entender exatamente o que está faltando). Como exemplo, uma pequena alteração no functor pode transformá-lo em uma mônada: data Maybe a = Nothing | Just a
é uma mônada. Outro functor semelhante data P12 a = Either a (a, a)
também é uma mônada.
Construções para mônadas polinomiais
Em geral, aqui estão algumas construções que produzem Monad
s legais a partir de tipos polinomiais. Em todas essas construções, M
há uma mônada:
type M a = Either c (w, a)
onde w
está algum monóide
type M a = m (Either c (w, a))
onde m
está qualquer mônada e w
é qualquer monóide
type M a = (m1 a, m2 a)
onde m1
e m2
qualquer mônada
type M a = Either a (m a)
onde m
tem mônada
A primeira construção é WriterT w (Either c)
, a segunda construção é WriterT w (EitherT c m)
. A terceira construção é um produto de mônadas em pure @M
termos de componentes : é definido como o produto em termos de componentes de pure @m1
e pure @m2
, e join @M
é definido pela omissão de dados de produtos cruzados (por exemplo, m1 (m1 a, m2 a)
é mapeado para m1 (m1 a)
omissão da segunda parte da tupla):
join :: (m1 (m1 a, m2 a), m2 (m1 a, m2 a)) -> (m1 a, m2 a)
join (m1x, m2x) = (join @m1 (fmap fst m1x), join @m2 (fmap snd m2x))
A quarta construção é definida como
data M m a = Either a (m a)
instance Monad m => Monad M m where
pure x = Left x
join :: Either (M m a) (m (M m a)) -> M m a
join (Left mma) = mma
join (Right me) = Right $ join @m $ fmap @m squash me where
squash :: M m a -> m a
squash (Left x) = pure @m x
squash (Right ma) = ma
Eu verifiquei que todas as quatro construções produzem mônadas legais.
I conjecturar que não existem outras construções para monads polinomiais. Por exemplo, o functor Maybe (Either (a, a) (a, a, a, a))
não é obtido através de nenhuma dessas construções e, portanto, não é monádico. No entanto, Either (a, a) (a, a, a)
é monádico, porque é isomorfo ao produto de três mônadas a
, a
, e Maybe a
. Além disso, Either (a,a) (a,a,a,a)
é monádico porque é isomórfico ao produto de a
e Either a (a, a, a)
.
As quatro construções mostradas acima nos permitem obter qualquer soma de qualquer número de produtos de qualquer número de a
's, por exemplo, Either (Either (a, a) (a, a, a, a)) (a, a, a, a, a))
e assim por diante. Todos esses tipos de construtores terão (pelo menos uma) Monad
instância.
Resta saber, é claro, que casos de uso podem existir para essas mônadas. Outra questão é que as Monad
instâncias derivadas pelas construções 1-4 geralmente não são exclusivas. Por exemplo, o construtor de tipos type F a = Either a (a, a)
pode receber uma Monad
instância de duas maneiras: pela construção 4 usando a mônada (a, a)
e pela construção 3 usando o isomorfismo do tipo Either a (a, a) = (a, Maybe a)
. Novamente, encontrar casos de uso para essas implementações não é imediatamente óbvio.
Uma pergunta permanece - dado um tipo de dados polinomial arbitrário, como reconhecer se ele tem uma Monad
instância. Não sei como provar que não há outras construções para mônadas polinomiais. Não acho que exista nenhuma teoria até agora para responder a essa pergunta.
* -> *
) para o qual não existe um adequadofmap
?