Haskell: Como <*> se pronuncia? [fechadas]

109

Como você pronuncia essas funções na typeclass Applicative:

(<*>) :: f (a -> b) -> f a -> f b
(*>)  :: f a -> f b -> f b
(<*)  :: f a -> f b -> f a

(Ou seja, se eles não fossem operadores, como poderiam ser chamados?)

Como uma observação lateral, se você pudesse renomear purepara algo mais amigável para não matemáticos, como você o chamaria?

J Cooper
fonte
6
@J Cooper ... você poderia ouvir como o pronunciamos? :) Você pode postar uma solicitação em meta.stackoverflow.com para um recurso de gravação e reprodução de voz :).
Kiril
8
É pronunciado "Meu Deus, eles estavam realmente ficando sem operadores, não é?" Além disso, um bom nome para purepode ser makeApplicative.
Chuck
@Lirik, Bem, acho que por pronunciar quero dizer "o que você acha disso" :) @Chuck, poste sua puresugestão como uma resposta e eu irei votar a favor de você
J Cooper
6
(<*>) é a versão Control.Applicative de "ap" de Control.Monad, então "ap" é provavelmente o nome mais apropriado.
Edward KMETT
11
Eu diria que é um ciclope, mas sou só eu.
RCIX

Respostas:

245

Desculpe, eu realmente não sei minha matemática, então estou curioso para saber como pronunciar as funções na typeclass Applicative

Saber sua matemática, ou não, é irrelevante aqui, eu acho. Como você provavelmente sabe, Haskell empresta alguns bits de terminologia de vários campos da matemática abstrata, mais notavelmente a Teoria das Categorias , de onde obtemos functores e mônadas. O uso desses termos em Haskell diverge um pouco das definições matemáticas formais, mas eles geralmente são próximos o suficiente para serem bons termos descritivos de qualquer maneira.

A Applicativeclasse de tipo fica em algum lugar entre Functore Monad, portanto, seria de se esperar que tivesse uma base matemática semelhante. A documentação do Control.Applicativemódulo começa com:

Este módulo descreve uma estrutura intermediária entre um functor e uma mônada: ele fornece expressões puras e sequenciamento, mas sem vinculação. (Tecnicamente, um forte functor monoidal frouxo.)

Hmm.

class (Functor f) => StrongLaxMonoidalFunctor f where
    . . .

Não tão cativante quanto Monad, eu acho.

Tudo isso basicamente se resume a que Applicativenão corresponde a nenhum conceito que seja particularmente interessante matematicamente, então não há termos prontos por aí que capturem a forma como é usado em Haskell. Portanto, deixe a matemática de lado por enquanto.


Se quisermos saber como chamá- (<*>)lo, talvez seja útil saber o que significa basicamente.

Então, o que há com Applicative, de qualquer maneira, e por que não podemos chamá-lo assim?

ApplicativeNa prática, o que equivale a uma maneira de elevar funções arbitrárias a um Functor. Considere a combinação de Maybe(sem dúvida o não trivial mais simples Functor) e Bool(da mesma forma o tipo de dados não trivial mais simples).

maybeNot :: Maybe Bool -> Maybe Bool
maybeNot = fmap not

A função fmappermite-nos levantar notde trabalhar em Boolpara trabalhar em Maybe Bool. Mas e se quisermos levantar (&&)?

maybeAnd' :: Maybe Bool -> Maybe (Bool -> Bool)
maybeAnd' = fmap (&&)

Bem, não é isso que queremos de forma alguma ! Na verdade, é praticamente inútil. Podemos tentar ser inteligente e sneak outro Boolpara Maybeatravés da parte traseira ...

maybeAnd'' :: Maybe Bool -> Bool -> Maybe Bool
maybeAnd'' x y = fmap ($ y) (fmap (&&) x)

... mas isso não é bom. Por um lado, está errado. Por outro lado, é feio . Poderíamos continuar tentando, mas descobrimos que não há como levantar uma função de vários argumentos para trabalhar em um arbitrárioFunctor . Irritante!

Por outro lado, poderíamos fazer isso facilmente se Maybeusássemos a Monadinstância de:

maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool
maybeAnd x y = do x' <- x
                  y' <- y
                  return (x' && y')

Agora, é muito complicado traduzir uma função simples - é por isso que Control.Monadfornece uma função para fazer isso automaticamente liftM2,. O 2 em seu nome se refere ao fato de que ele funciona em funções de exatamente dois argumentos; funções semelhantes existem para funções de 3, 4 e 5 argumentos. Essas funções são melhores , mas não perfeitas, e especificar o número de argumentos é feio e desajeitado.

O que nos leva ao artigo que introduziu a classe do tipo Aplicativa . Nele, os autores fazem essencialmente duas observações:

  • Transformar funções com vários argumentos em um Functoré uma coisa muito natural de se fazer
  • Fazer isso não requer todos os recursos de um Monad

A aplicação da função normal é escrita pela simples justaposição de termos, então para tornar a "aplicação elevada" o mais simples e natural possível, o artigo apresenta operadores infixados para substituir a aplicação, elevada para oFunctor , e uma classe de tipo para fornecer o que é necessário para isso .

Tudo isso nos leva ao seguinte ponto: (<*>)simplesmente representa a aplicação da função - então por que pronunciá-lo de forma diferente do que você faz com o "operador de justaposição" de espaço em branco?

Mas se isso não for muito satisfatório, podemos observar que o Control.Monadmódulo também fornece uma função que faz a mesma coisa para mônadas:

ap :: (Monad m) => m (a -> b) -> m a -> m b

Onde ap, é claro, é a abreviação de "aplicar". Como qualquer um Monadpode ser Applicative, e apprecisa apenas do subconjunto de recursos presentes neste último, talvez possamos dizer que, se (<*>)não fosse um operador, deveria ser chamado ap.


Também podemos abordar as coisas de outra direção. A Functoroperação de levantamento é chamada fmapporque é uma generalização da mapoperação em listas. Que tipo de função nas listas funcionaria (<*>)? Há o que apfunciona nas listas, é claro, mas isso não é particularmente útil por si só.

Na verdade, talvez haja uma interpretação mais natural para listas. O que vem à mente quando você olha a seguinte assinatura de tipo?

listApply :: [a -> b] -> [a] -> [b]

Há algo muito tentador na ideia de alinhar as listas em paralelo, aplicando cada função da primeira ao elemento correspondente da segunda. Infelizmente para nosso velho amigo Monad, essa operação simples viola as leis da mônada se as listas tiverem comprimentos diferentes. Mas vale uma multa Applicative, caso em que (<*>)se torna uma forma de encadear uma versão generalizada de zipWith, então talvez possamos imaginar chamá-la fzipWith?


Esta ideia compactada realmente nos traz um círculo completo. Lembra daquela coisa de matemática anterior, sobre functores monoidais? Como o nome sugere, essas são uma forma de combinar a estrutura de monoides e functores, ambos os quais são classes do tipo Haskell familiares:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

class Monoid a where
    mempty :: a
    mappend :: a -> a -> a

Como eles ficariam se você os colocasse em uma caixa e sacudisse um pouco? A partir de Functor, manteremos a ideia de uma estrutura independente de seu parâmetro de tipo e de Monoidmanteremos a forma geral das funções:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ?
    mfAppend :: f ? -> f ? -> f ?

Não queremos presumir que existe uma maneira de criar um verdadeiro "vazio" Functore não podemos conjurar um valor de um tipo arbitrário, portanto, corrigiremos o tipo de mfEmptyas f ().

Também não queremos forçar mfAppenda necessidade de um parâmetro de tipo consistente, então agora temos isto:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f ?

Qual é o tipo de resultado mfAppend? Temos dois tipos arbitrários dos quais nada sabemos, portanto, não temos muitas opções. O mais sensato é apenas manter ambos:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f (a, b)

Nesse ponto mfAppendagora é claramente uma versão generalizada de zipnas listas, e podemos reconstruir Applicativefacilmente:

mfPure x = fmap (\() -> x) mfEmpty
mfApply f x = fmap (\(f, x) -> f x) (mfAppend f x)

Isso também nos mostra que pureestá relacionado ao elemento de identidade de a Monoid, então outros nomes bons para ele podem ser qualquer coisa que sugira um valor unitário, uma operação nula ou algo parecido.


Isso foi demorado, para resumir:

  • (<*>) é apenas um aplicativo de função modificada, então você pode lê-lo como "ap" ou "aplicar", ou elimina-o inteiramente da maneira que faria com um aplicativo de função normal.
  • (<*>)também generaliza zipWithem listas, de modo que você pode lê-lo como "zip functores com", similarmente a ler fmapcomo "mapear um functor com".

O primeiro está mais próximo da intenção da Applicativeclasse de tipo - como o nome sugere - então é isso que eu recomendo.

Na verdade, eu encorajo o uso liberal e a não pronúncia de todos os operadores de aplicativos suspensos :

  • (<$>), que transforma uma função de argumento único em um Functor
  • (<*>), que encadeia uma função com vários argumentos por meio de um Applicative
  • (=<<), que vincula uma função que insere um Monadem um cálculo existente

Todos os três são, no fundo, apenas aplicações de funções regulares, um pouco apimentadas.

CA McCann
fonte
6
@Colin Cochrane: Tem certeza de que não escreveu "prolixo" aqui? :) Mas hey, eu vou levar! Sempre sinto que Applicativee o estilo idiomático funcional que ele promove não conseguem amor suficiente, então não resisti à chance de exaltar um pouco suas virtudes como um meio de explicar como eu (não) me pronuncio (<*>).
CA McCann de
+1! Sinta-se à vontade para ser igualmente prolixo em stackoverflow.com/questions/2104446/…
Greg Bacon
6
Será que Haskell tinha sintaxe de açúcar para Applicative's! Algo assim [| f a b c d |](como sugerido pelo artigo original). Então não precisaríamos do <*>combinador e você se referiria a tal expressão como um exemplo de "aplicação de função em um contexto funcional"
Tom Crockett
1
@FredOverflow: Não, eu quis dizer Monad. Ou Functorou Monoidou qualquer outra coisa que tenha um termo bem estabelecido envolvendo menos de três adjetivos. "Aplicativo" é meramente um nome pouco inspirador, embora razoavelmente descritivo, colocado em algo que precisava de um.
CA McCann
1
@pelotom: consulte [ stackoverflow.com/questions/12014524/… onde pessoas gentis me mostraram duas maneiras de obter quase essa notação.
AndrewC de
21

Como não tenho ambições de melhorar a resposta técnica do CA McCann , vou abordar a mais fofa:

Se você pudesse renomear purepara algo mais amigável para podunks como eu, como você chamaria?

Como alternativa, especialmente porque não há fim para as constantes angústias e traições gritadas contra a Monadversão, chamada " return", proponho outro nome, que sugere sua função de uma forma que pode satisfazer o mais imperativo dos programadores imperativos e, o mais funcional de ... bem, espero que, todos podem reclamar o mesmo sobre: inject.

Pegue um valor. "Inject"-lo no Functor, Applicative, Monad, ou que-ter-você. Eu voto em " inject" e aprovei esta mensagem.

BMeph
fonte
4
Eu geralmente me inclino para algo como "unidade" ou "elevação", mas esses já têm muitos outros significados em Haskell. injecté um nome excelente e provavelmente melhor que o meu, embora como uma observação secundária, "injetar" seja usado em - eu acho - Smalltalk e Ruby para algum tipo de método de dobra à esquerda. Eu nunca entendi essa escolha de nome, porém ...
CA McCann
3
Este é um tópico muito antigo, mas acho que injectem Ruby & Smalltalk é usado porque é como se você estivesse "injetando" um operador entre cada elemento da lista. Pelo menos, é assim que sempre pensei nisso.
Jonathan Sterling
1
Para novamente pegar aquele velho lado-thread: Você não está injetando operadores, você está substituindo (eliminando) construtores que já estão lá. (Visto ao contrário, você está injetando dados antigos em um novo tipo.) Para listas, a eliminação é justa foldr. (Você substitui (:)e [], onde (:)recebe 2 args e []é uma constante, portanto, foldr (+) 0 (1:2:3:[])1+2+3+0.) BoolEle é apenas if- then- else(duas constantes, escolha uma) e para Maybeisso é chamado maybe... Haskell não tem um único nome / função para isso, pois todos têm tipos diferentes (em geral, elim é apenas recursão / indução)
ninguém
@CAMcCann Smalltalk obteve esse nome por causa de uma canção de Arlo Guthrie sobre o draft para a guerra do Vietnã, na qual jovens infelizes foram reunidos, selecionados, às vezes rejeitados e, de outra forma, injetados.
Tom Anderson
7

Em resumo:

  • <*>você pode chamá-lo de aplicável . Portanto, Maybe f <*> Maybe apode ser pronunciado como aplicar ao Maybe flongoMaybe a .

  • Você pode renomear purepara of, como muitas bibliotecas JavaScript fazem. Em JS você pode criar um Maybecom Maybe.of(a).

Além disso, o wiki de Haskell tem uma página sobre a pronúncia dos operadores de idioma aqui

Marcelo Lazaroni
fonte
3
(<*>) -- Tie Fighter
(*>)  -- Right Tie
(<*)  -- Left Tie
pure  -- also called "return"

Fonte: Haskell Programming from First Principles , de Chris Allen e Julie Moronuki

dmvianna
fonte
Esses nomes não pegaram exatamente, pelo que eu posso dizer.
dfeuer
@dfeuer espere pela próxima geração de Haskellers que está usando aquele livro como seu principal material de aprendizagem.
dmvianna
1
Poderia acontecer. Os nomes são terríveis, pois não têm nenhuma conexão com os significados.
dfeuer
1
@dfeuer, não vejo uma boa solução em lugar nenhum. "ap" / "apply" é tão vago quanto "tie fighter". Tudo é aplicação de função. No entanto, um nome que surge do nada pode adquirir significado com o uso. "Apple" é um bom exemplo. A propósito, o retorno da Mônada é Aplicativo puro. Nenhuma invenção aqui.
dmvianna