A Applicativeclasse 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; OpApplicativenã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 Applicativefunctor (realmente, qualquer um Functor) seja trivial OpApplicative, não há necessariamente uma boa relação entre laxidades Applicativee OpApplicativeoplaxidades. 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 Applicativefunctores para os quais podemos declarar casos legais de StrongApplicative:
IdentityVoidF(->) r(ver respostas)Monoid m => (,) mVec (n :: Nat)Stream(infinito)
E aqui estão alguns Applicatives para os quais não podemos:
[]Either eMaybeNonEmptyList
O padrão aqui sugere que a StrongApplicativeclasse é, de certo modo, a FixedSizeclasse, onde "tamanho fixo" * significa que a multiplicidade ** de habitantes dea um habitante de f aé fixa.
Isso pode ser afirmado como duas conjecturas:
- Cada
Applicativerepresentação de um contêiner de "tamanho fixo" de elementos de seu argumento de tipo é uma instância deStrongApplicative - Não
StrongApplicativeexiste nenhuma instância em que o número de ocorrências deapossa 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
(->) rsã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 Representableclasse de tipo em Haskell possui uma tabulate . return = returnlei misteriosa (que nem sequer faz sentido para os functores não monádicos), mas fornece 1/4 de condições que precisamos dizer tabulatee zipsão morfismos de uma categoria adequada de monoides. . Os outros três são leis extras que você precisa exigir.
tabulatee indexsão morfismos de uma categoria adequada ..."
returnnão é um problema sério. cotraverse getConst . Consté uma implementação padrão para return/ pureem termos de Distributivee, uma vez que as distribuições / representantes têm forma fixa, essa implementação é única.