A Applicative
classe typeclass representa funcores monoidais relaxados que preservam a estrutura monoidal cartesiana na categoria de funções digitadas.
Em outras palavras, dados os isomorfismos canônicos que testemunham que (,)
formam uma estrutura monoidal:
-- Implementations left to the motivated reader
assoc_fwd :: ((a, b), c) -> (a, (b, c))
assoc_bwd :: (a, (b, c)) -> ((a, b), c)
lunit_fwd :: ((), a) -> a
lunit_bwd :: a -> ((), a)
runit_fwd :: (a, ()) -> a
runit_bwd :: a -> (a, ())
A classe de tipo e suas leis podem ser equivalentemente escritas assim:
class Functor f => Applicative f
where
zip :: (f a, f b) -> f (a, b)
husk :: () -> f ()
-- Laws:
-- assoc_fwd >>> bimap id zip >>> zip
-- =
-- bimap zip id >>> zip >>> fmap assoc_fwd
-- lunit_fwd
-- =
-- bimap husk id >>> zip >>> fmap lunit_fwd
-- runit_fwd
-- =
-- bimap id husk >>> zip >>> fmap runit_fwd
Pode-se perguntar como seria um functor oplax monoidal em relação à mesma estrutura:
class Functor f => OpApplicative f
where
unzip :: f (a, b) -> (f a, f b)
unhusk :: f () -> ()
-- Laws:
-- assoc_bwd <<< bimap id unzip <<< unzip
-- =
-- bimap unzip id <<< unzip <<< fmap assoc_bwd
-- lunit_bwd
-- =
-- bimap unhusk id <<< unzip <<< fmap lunit_bwd
-- runit_bwd
-- =
-- bimap id unhusk <<< unzip <<< fmap runit_bwd
Se pensarmos nos tipos envolvidos nas definições e leis, a verdade decepcionante será revelada; OpApplicative
não é uma restrição mais específica do que Functor
:
instance Functor f => OpApplicative f
where
unzip fab = (fst <$> fab, snd <$> fab)
unhusk = const ()
No entanto, embora todo Applicative
functor (realmente, qualquer um Functor
) seja trivial OpApplicative
, não há necessariamente uma boa relação entre laxidades Applicative
e OpApplicative
oplaxidades. Para que possamos procurar fortes monoides na estrutura monoidal cartesiana:
class (Applicative f, OpApplicative f) => StrongApplicative f
-- Laws:
-- unhusk . husk = id
-- husk . unhusk = id
-- zip . unzip = id
-- unzip . zip = id
A primeira lei acima é trivial, pois o único habitante do tipo () -> ()
é a função de identidade ativada ()
.
No entanto, as três leis restantes e, portanto, a própria subclasse, não são triviais. Especificamente, nem todo Applicative
é um exemplo legal dessa classe.
Aqui estão alguns Applicative
functores para os quais podemos declarar casos legais de StrongApplicative
:
Identity
VoidF
(->) r
(ver respostas)Monoid m => (,) m
Vec (n :: Nat)
Stream
(infinito)
E aqui estão alguns Applicative
s para os quais não podemos:
[]
Either e
Maybe
NonEmptyList
O padrão aqui sugere que a StrongApplicative
classe é, de certo modo, a FixedSize
classe, onde "tamanho fixo" * significa que a multiplicidade ** de habitantes dea
um habitante de f a
é fixa.
Isso pode ser afirmado como duas conjecturas:
- Cada
Applicative
representação de um contêiner de "tamanho fixo" de elementos de seu argumento de tipo é uma instância deStrongApplicative
- Não
StrongApplicative
existe nenhuma instância em que o número de ocorrências dea
possa variar
Alguém pode pensar em contra-exemplos que refutam essas conjecturas, ou algum raciocínio convincente que demonstra por que são verdadeiros ou falsos?
* Percebo que não defini corretamente o adjetivo "tamanho fixo". Infelizmente, a tarefa é um pouco circular. Não conheço nenhuma descrição formal de um contêiner de "tamanho fixo" e estou tentando criar um.StrongApplicative
é a minha melhor tentativa até agora.
Para avaliar se essa é uma boa definição, no entanto, preciso de algo para compará-la. Dada uma definição formal / informal do que significa para um functor ter um determinado tamanho ou multiplicidade em relação aos habitantes de seu tipo de argumento, a questão é se a existência de umStrongApplicative
instância distingue precisamente os funcetores de tamanho fixo e variável.
Não estando ciente de uma definição formal existente, estou fazendo um apelo à intuição ao usar o termo "tamanho fixo". No entanto, se alguém já conhece um formalismo existente para o tamanho de um functor e pode compararStrongApplicative
a ele, tanto melhor.
** Por "multiplicidade", refiro-me, em sentido lato, a "quantos" elementos arbitrários do tipo de parâmetro do functor ocorrem em um habitante do tipo de codomain do functor. Isso não leva em consideração o tipo específico ao qual o functor é aplicado e, portanto, não considera nenhum habitante específico do tipo de parâmetro.
Não ser preciso quanto a isso causou alguma confusão nos comentários, então aqui estão alguns exemplos do que eu consideraria o tamanho / multiplicidade de vários functores:
VoidF
: fixo, 0Identity
: fixo, 1Maybe
: variável, mínimo 0, máximo 1[]
: variável, mínimo 0, máximo infinitoNonEmptyList
: variável, mínimo 1, máximo infinitoStream
: fixo, infinitoMonoid m => (,) m
: fixo, 1data Pair a = Pair a a
: fixo, 2Either x
: variável, mínimo 0, máximo 1data Strange a = L a | R a
: fixo, 1
fonte
(->) r
são e são isomórficos da maneira certa.(->) r
; você precisa dos componentes do isomorfismo para preservar a forte estrutura aplicativa. Por alguma razão, aRepresentable
classe de tipo em Haskell possui umatabulate . return = return
lei misteriosa (que nem sequer faz sentido para os functores não monádicos), mas fornece 1/4 de condições que precisamos dizertabulate
ezip
são morfismos de uma categoria adequada de monoides. . Os outros três são leis extras que você precisa exigir.tabulate
eindex
são morfismos de uma categoria adequada ..."return
não é um problema sério.cotraverse getConst . Const
é uma implementação padrão parareturn
/pure
em termos deDistributive
e, uma vez que as distribuições / representantes têm forma fixa, essa implementação é única.Respostas:
Não tenho certeza sobre essa primeira conjectura e, com base em discussões com @AsadSaeeduddin, é provável que seja difícil provar, mas a segunda conjectura é verdadeira. Para ver o porquê, considere a
StrongApplicative
leihusk . unhusk == id
; isto é, para todosx :: f ()
,husk (unhusk x) == x
. Mas, em Haskell,unhusk == const ()
para que a lei é equivalente a dizer para todosx :: f ()
,husk () == x
. Mas isso, por sua vez, implica que só pode existir um valor distinto do tipof ()
: se houvesse dois valoresx, y :: f ()
, entãox == husk ()
ehusk () == y
, portanto,x == y
. Mas se houver apenas umf ()
valor possível , elef
deverá ter uma forma fixa. (por exemplodata Pair a = Pair a a
, para , existe apenas um valor do tipoPair ()
, sendo estePair () ()
, mas existem vários valores do tipoMaybe ()
ou[()]
.)husk . unhusk == id
implica quef
deve ter uma forma fixa.fonte
f ()
" implica "o número de ocorrências dea
não pode variar" na presença de GADTs sofisticados e outras coisas?a
não pode variar” não é uma condição suficiente para umaStrongApplicative
instância; por exemplo,data Writer w a = Writer (w,a)
tem multiplicidade não variável dea
, mas não é aStrongApplicative
. Você realmente precisa que a forma do functor seja invariável, o que acredito ser uma consequência def ()
ser um singleton.f ()
" implica "número de ocorrências dea
não pode variar". Estou objetando que o último passo desse argumento não é claramente verdadeiro; por exemplo, consideredata Weird a where One :: a -> Weird a; None :: Weird Bool
. Há um valor distinto do tipoWeird ()
, mas diferentes construtores têm números variáveis dea
s por dentro. (Não é um contra-exemplo completo aqui porqueFunctor
é difícil, mas como sabemos que não pode ser corrigido?)Weird ()
é um singleton, mas não é de forma fixa. MasWeird
não é umFunctor
, então não pode ser deStrongApplicative
qualquer maneira. Suponho que a conjectura relevante seria: sef
for umFunctor
,f ()
ser um singleton implicaf
uma forma fixa ? Eu suspeito fortemente que isso seja verdade, mas como você observou, ainda não tenho nenhuma prova.Podemos responder pelo menos uma dessas perguntas de forma negativa:
De fato, um dos exemplos de um lícito
StrongApplicative
na questão original está errado. O aplicativo escritorMonoid => (,) m
não éStrongApplicative
, por exemplohusk $ unhusk $ ("foo", ()) == ("", ()) /= ("foo", ())
.Da mesma forma, o exemplo de um contêiner de tamanho fixo:
da multiplicidade fixa 1, não é um aplicativo forte, porque se definirmos
husk = Left
entãohusk $ unhusk $ Right () /= Right ()
, e vice-versa. Uma maneira equivalente de ver isso é que esse é apenas o aplicativo de gravação para sua escolha de monóideBool
.Portanto, existem aplicativos de "tamanho fixo" que não são
StrongApplicative
.StrongApplicative
Ainda não se sabe se todos os s são de tamanho fixo.fonte
Vamos considerar functors representáveis como nossa definição de "contêiner de tamanho fixo":
O real
Representable
tem algumas leis e superclasses, mas, para os fins desta resposta, precisamos de apenas duas propriedades:(Ok, também precisamos de um cumpridor da lei
instance StrongApplicative ((->) r)
. Fácil, você já concorda que ele existe.)Se tomarmos essa definição, posso confirmar essa conjectura 1:
é verdade. Aqui está como:
Há muitas leis para provar, mas vou me concentrar apenas nas quatro grandes que
StrongApplicative
adicionam - você provavelmente já acredita nas iniciaisApplicative
eOpApplicative
, se não acredita , as provas são parecidas com as abaixo ( que por sua vez se parecem bastante). Para maior clareza, vou usarzipf
,huskf
etc. para a instância de função, ezipr
,huskr
, etc. para a instância representável, para que possa manter o controle de qual é qual. (E para que seja fácil verificar se não consideramos o que estamos tentando provar como suposição! Não há problemaunhuskf . huskf = id
em provarunhuskr . huskr = id
, mas seria errado suporunhuskr . huskr = id
nessa mesma prova.)A prova de cada lei procede basicamente da mesma maneira: desenrole as definições, descarte o isomorfismo que
Representable
lhe fornece e use a lei análoga para funções.fonte
instance StrongApplicative f => Representable f where type Rep f = forall x. f x -> x
.index
é fácil. Ainda não elaborei o truquetabulate
, mas parece tentadoramente próximo.StrongApplicative
instância, mas não pude provar as leis. Parabéns por descobrir! Tentei fazer aRepresentable
instância tambémStrongApplicative
, mas não consegui pensar em um bomRep
tipo - ficaria curioso para saber como vocêforall x. f x -> x
consegue isso.forall x. f x -> x
são exatamente aquelas funções que escolhem um furo e retornam o valor nesse furo. (E, ao pensar em como implementartabulate
, propus uma objeção ao tipounhusk
; veja comentários sobre a própria pergunta para obter detalhes.)forall x. f x -> x
funcionará comoRep
. Meu raciocínio é que, usando issoRep
, você pode escreverindex
para qualquer tipo, não apenas umStrongApplicative
- por isso suspeito queforall x. f x -> x
possa ser muito geral.