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
(->) 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, a Representable
classe de tipo em Haskell possui uma tabulate . 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 dizer tabulate
e zip
são morfismos de uma categoria adequada de monoides. . Os outros três são leis extras que você precisa exigir.
tabulate
e index
são morfismos de uma categoria adequada ..."
return
não é um problema sério. cotraverse getConst . Const
é uma implementação padrão para return
/ pure
em termos de Distributive
e, uma vez que as distribuições / representantes têm forma fixa, essa implementação é única.