Sei que estou chegando atrasado à festa, mas você teve duas respostas teóricas aqui, e eu queria fornecer uma alternativa prática para mastigar. Estou falando disso como um relativo Haskell noob que, no entanto, recentemente foi forçado a entrar no assunto de Arrows para um projeto no qual estou trabalhando atualmente.
Primeiro, você pode resolver de maneira produtiva a maioria dos problemas no Haskell sem acessar as setas. Alguns Haskellers notáveis realmente não gostam e não os usam (veja aqui , aqui e aqui para mais informações). Portanto, se você está dizendo para si mesmo "Ei, eu não preciso disso", entenda que você pode estar realmente correto.
O que eu achei mais frustrante sobre a Arrows quando as aprendi foi como os tutoriais sobre o assunto inevitavelmente alcançavam a analogia dos circuitos. Se você olhar para o código Arrow - pelo menos a variedade açucarada - ele não se parece em nada com uma linguagem de definição de hardware. Suas entradas estão alinhadas à direita, suas saídas à esquerda e, se você não conseguir conectá-las adequadamente, elas simplesmente não serão acionadas. Eu pensei comigo mesmo: Sério? É aqui que acabamos? Criamos uma linguagem tão completamente de alto nível que mais uma vez consiste em fios de cobre e solda?
A resposta correta para isso, até onde pude determinar, é: Na verdade, sim. O caso de uso matador agora para o Arrows é o FRP (pense em Yampa, jogos, música e sistemas reativos em geral). O problema enfrentado pelo FRP é em grande parte o mesmo problema enfrentado por todos os outros sistemas de mensagens síncronas: como conectar um fluxo contínuo de entradas a um fluxo contínuo de saídas sem deixar cair informações relevantes ou vazamentos. Você pode modelar os fluxos como listas - vários sistemas FRP recentes usam essa abordagem - mas quando você tem muitas entradas, torna-se quase impossível gerenciar. Você precisa se isolar da corrente.
O que as setas permitem nos sistemas de FRP é a composição de funções em uma rede e, ao mesmo tempo, abstraem totalmente qualquer referência aos valores subjacentes que são passados por essas funções. Se você é novo no FP, isso pode ser confuso a princípio e, em seguida, alucinante quando você absorve as implicações disso. Recentemente, você absorveu a ideia de que funções podem ser abstraídas e como entender uma lista como [(*), (+), (-)]
sendo do tipo [(a -> a -> a)]
. Com as setas, você pode empurrar a abstração uma camada ainda mais.
Essa capacidade adicional de abstrair traz consigo seus próprios perigos. Por um lado, ele pode levar o GHC a casos extremos onde não sabe o que fazer com suas suposições de tipo. Você precisará estar preparado para pensar no nível de tipo - esta é uma excelente oportunidade para aprender sobre tipos e RankNTypes e outros tópicos.
Há também vários exemplos do que eu chamaria de "Stupid Arrow Stunts", onde o codificador pega algum combinador de Arrow apenas porque ele ou ela deseja mostrar um truque interessante com as tuplas. (Aqui está minha própria contribuição trivial à loucura .) Sinta-se à vontade para ignorar esse tipo de cachorro quente quando você o encontrar na natureza.
NOTA: Como mencionei acima, sou um noob relativo. Se eu tiver divulgado quaisquer equívocos acima, sinta-se à vontade para me corrigir.
removeAt' n = arr(\ xs -> (xs,xs)) >>> arr (take (n-1)) *** arr (drop n) >>> arr (uncurry (++)) >>> returnA
pode ser escrita de forma mais concisa e clararemoveAt' n = (arr (take $ n-1) &&& arr (drop n)) >>> (arr $ uncurry (++))
.Essa é uma resposta "branda", e não tenho certeza se alguma referência realmente a declara dessa maneira, mas é assim que cheguei a pensar em flechas:
Um tipo de seta
A b c
é basicamente uma função,b -> c
mas com mais estrutura da mesma maneira que um valor monádicoM a
tem mais estrutura do que uma planície antigaa
.Agora, o que acontece com essa estrutura extra depende da instância específica da seta que você está falando. Assim como com mônadas
IO a
eMaybe a
cada um tem uma estrutura adicional diferente.O que você ganha com as mônadas é a incapacidade de passar de uma
M a
para outraa
. Agora isso pode parecer uma limitação, mas na verdade é um recurso: o sistema de tipos está protegendo você de transformar um valor monádico em um valor antigo simples. Você só pode usar o valor participando da mônada via>>=
ou das operações primitivas da instância específica da mônada.Da mesma forma, o que você obtém
A b c
é a incapacidade de construir uma nova "função" produtora de c que consome b. A seta está protegendo você de consumirb
e criar umac
exceção, participando dos vários combinadores de setas ou usando as operações primitivas da instância específica da seta.Por exemplo, as funções de sinal no Yampa são grosseiras
(Time -> a) -> (Time -> b)
, mas adicionalmente elas precisam obedecer a uma certa restrição de causalidade : a saída no momentot
é determinada pelos valores passados do sinal de entrada: você não pode olhar para o futuro. Então, o que eles fazem é, em vez de programar(Time -> a) -> (Time -> b)
, você programaSF a b
e constrói suas funções de sinal a partir de primitivas. Acontece que, comoSF a b
se comporta muito como uma função, a estrutura comum é chamada de "flecha".fonte
b
e criar umac
exceção, participando dos vários combinadores de flechas ou usando as operações primitivas da instância específica da flecha." Pedimos desculpas por responder a essa resposta antiga: essa frase me fez pensar em tipos lineares, ou seja, que os recursos não podem ser clonados ou desaparecerem. Você acha que pode haver alguma conexão?Eu gosto de pensar em Arrows, como Mônadas e Functors, como permitindo que o programador faça composições exóticas de funções.
Sem Mônadas ou Setas (e Functors), a composição de funções em uma linguagem funcional é limitada à aplicação de uma função ao resultado de outra função. Com mônadas e functores, você pode definir duas funções e, em seguida, escrever código reutilizável separado, que especifica como essas funções, no contexto de determinada mônada, interagem entre si e com os dados que são passados para elas. Este código é colocado dentro do código de ligação da Mônada. Portanto, uma mônada é uma visualização única, apenas um contêiner para código de ligação reutilizável. As funções são compostas de maneira diferente no contexto de uma mônada de outra mônada.
Um exemplo simples é a mônada Maybe, onde há código na função de ligação, de modo que, se uma função A é composta por uma função B dentro de uma mônada Maybe e B produz um Nothing, o código de ligação garantirá que a composição da duas funções produzem a Nothing, sem se preocupar em aplicar A ao valor Nothing saindo de B. Se não houvesse mônada, o programador teria que escrever código em A para testar a entrada Nothing.
Mônadas também significam que o programador não precisa digitar explicitamente os parâmetros exigidos por cada função no código-fonte - a função bind lida com a passagem de parâmetros. Portanto, usando mônadas, o código fonte pode começar a parecer mais uma cadeia estática de nomes de funções, em vez de parecer que a função A "chama" a função B com os parâmetros C e D - o código começa a parecer mais um circuito eletrônico do que um máquina em movimento - mais funcional que imperativa.
As setas também conectam funções com uma função de ligação, fornecendo funcionalidade reutilizável e ocultando parâmetros. Porém, as próprias setas podem ser conectadas e compostas e, opcionalmente, podem rotear dados para outras setas em tempo de execução. Agora você pode aplicar dados a dois caminhos de Arrows, que "fazem coisas diferentes" aos dados e remontam o resultado. Ou você pode selecionar para qual ramificação das setas passar os dados, dependendo de algum valor nos dados. O código resultante é ainda mais parecido com um circuito eletrônico, com comutadores, atrasos, integração etc. O programa parece muito estático e você não deve conseguir ver muita manipulação de dados acontecendo. Há cada vez menos parâmetros para se pensar e menos necessidade de pensar sobre quais valores os parâmetros podem ou não levar.
Escrever um programa Arrowized envolve principalmente selecionar Setas prontas como divisores, comutadores, atrasos e integradores, elevar funções nessas Setas e conectar as Setas para formar Setas maiores. Na Programação Reativa Funcional Arrowizada, as Setas formam um loop, com a entrada do mundo combinada com a saída da última iteração do programa, para que a saída reaja à entrada do mundo real.
Um dos valores do mundo real é o tempo. No Yampa, a Seta de Função de Sinal encobre invisivelmente o parâmetro time através do programa de computador - você nunca acessa o valor do tempo, mas se conectar uma seta integradora ao programa, ela produzirá valores integrados ao longo do tempo que você poderá usar para passar para outras flechas.
fonte
Apenas uma adição às outras respostas: Pessoalmente, isso me ajuda muito a entender o que é esse conceito (matematicamente) e como ele se relaciona com outros conceitos que eu conheço.
No caso das flechas, achei o artigo a seguir útil - compara mônadas, functores aplicativos (expressões idiomáticas) e flechas: as expressões idiomáticas são inconscientes, as flechas são meticulosas e as mônadas são promíscuas por Sam Lindley, Philip Wadler e Jeremy Yallop.
Também acredito que ninguém mencionou este link que pode fornecer algumas idéias e literatura sobre o assunto.
fonte