qual é o propósito das setas?

62

Estou aprendendo programação funcional com Haskell, e eu tento pegar conceitos primeiro entendendo por que eu preciso deles.

Gostaria de saber o objetivo das setas nas linguagens de programação funcionais. Que problema eles resolvem? Eu verifiquei o link e link . Tudo o que entendo é que eles são usados para descrever gráficos para cálculos e permitem codificação de estilo livre de pontos mais fácil.

O artigo supõe que o estilo free point é geralmente mais fácil de entender e escrever. Isso parece bastante subjetivo para mim. Em outro artigo ( link ), um jogo de carrasco é implementado, mas não consigo ver como as setas fazem esta implementação natural.

Eu pude encontrar muitos trabalhos descrevendo o conceito, mas nada sobre a motivação.

O que eu sinto falta?

    
por Simon 17.10.2011 / 13:40
fonte

4 respostas

41

Eu percebo que estou chegando atrasado para a festa, mas você teve duas respostas teóricas aqui, e eu queria fornecer uma alternativa prática para mastigar. Eu estou chegando a isso como um noob Haskell relativo que, no entanto, foi recentemente forçado através do assunto Arrows para um projeto no qual estou trabalhando atualmente.

Primeiro, você pode resolver de maneira produtiva a maioria dos problemas em Haskell sem usar as setas. Alguns notáveis Haskellers genuinamente não gostam e não os usam (veja aqui , aqui e aqui para saber mais sobre isso). Então, se você está dizendo para si mesmo "Ei, eu não preciso disso", entenda que você pode realmente estar correto.

O que eu achei mais frustrante sobre as setas quando as aprendi foi como os tutoriais sobre o assunto inevitavelmente alcançaram a analogia dos circuitos. Se você olhar para o código Arrow - a variedade açucarada, pelo menos - não se parece muito com uma linguagem de defnição de hardware. Suas entradas se alinham à direita, suas saídas à esquerda e, se você não conseguir conectá-las corretamente, elas simplesmente não disparam. Eu pensei comigo mesmo: Realmente? É aqui que acabamos? Já 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 eu consegui determinar, é: Na verdade, sim. O caso de uso matador agora para Arrows é FRP (pense em Yampa, jogos, música e sistemas reativos em geral). O problema enfrentado pelo FRP é basicamente o mesmo problema enfrentado por todos os outros sistemas de mensagens síncronas: como conectar um fluxo contínuo de entradas em um fluxo contínuo de saídas sem descartar informações relevantes ou vazamentos de informações. Você pode modelar os fluxos como listas - vários sistemas recentes de FRP usam essa abordagem - mas quando você tem muitas listas de entradas torna-se quase impossível de gerenciar. Você precisa se isolar da corrente.

O que as setas permitem nos sistemas FRP é a composição das funções em uma rede e, ao mesmo tempo, abstrai inteiramente qualquer referência aos valores subjacentes que estão sendo passados por essas funções. Se você é novo em FP, isso pode ser confuso a princípio, e depois alucinante quando você absorveu as implicações disso. Você só recentemente absorveu a ideia de que as funções podem ser abstraídas e como entender uma lista como [(*), (+), (-)] como sendo do tipo [(a -> a -> a)] . Com as setas, você pode empurrar a abstração mais uma camada.

Essa capacidade adicional de abstração traz consigo seus próprios perigos. Por um lado, isso pode levar o GHC a casos que não sabem o que fazer com suas suposições de tipo. Você terá que estar preparado para pensar no nível do 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 alcança algum combinador de flechas apenas porque ele ou ela quer mostrar um truque com tuplas. (Aqui está minha própria contribuição trivial para a loucura .) Sinta-se livre para ignorar esse cachorro-quente quando você se deparar com ele na natureza.

NOTA: Como mencionei acima, sou um noob relativo. Se eu promulguei quaisquer equívocos acima, sinta-se à vontade para me corrigir.

    
por 09.11.2011 / 23:48
fonte
28

Esta é uma resposta "suave", e não tenho certeza se alguma referência realmente diz isso dessa maneira, mas é assim que cheguei a pensar em setas:

Um tipo de seta A b c é basicamente uma função b -> c mas com mais estrutura da mesma forma que um valor monádico M a tem mais estrutura que um antigo a .

Agora, o que essa estrutura extra é depende da instância de seta específica da qual você está falando. Assim como as mônadas, IO a e Maybe a têm estruturas adicionais diferentes.

O que você ganha com as mônadas é a incapacidade de ir de um M a para um a . Agora isso pode parecer uma limitação, mas na verdade é uma característica: 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 monad específica.

Da mesma forma, o que você obtém de A b c é uma incapacidade de construir uma nova "função" produtora de b-c. A seta está protegendo você de consumir o b e criar um c , exceto participando dos vários combinadores de setas ou usando as operações primitivas da instância de seta específica.

Por exemplo, as funções de sinal em Yampa são aproximadamente (Time -> a) -> (Time -> b) , mas além disso elas têm que obedecer a certa restrição causalidade : a saída no tempo t é determinada pelos valores passados da entrada sinal: você não pode olhar para o futuro. Então, o que eles fazem é, em vez de programar com (Time -> a) -> (Time -> b) , você programa com SF a b e constrói suas funções de sinal a partir de primitivos. Acontece que como SF a b se comporta muito como uma função, então essa estrutura comum é chamada de "flecha".

    
por 17.10.2011 / 23:17
fonte
14

Eu gosto de pensar em Setas, como Monads e Functors, como permitindo ao programador fazer composições exóticas de funções.

Sem Monads ou Arrows (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 monads e functores, você pode definir duas funções e, em seguida, escrever um código reutilizável separado que especifique como essas funções, no contexto da monad particular, interagem umas com as outras e com os dados que são passados para elas. Este código é colocado dentro do código de ligação da Mônada. Assim, uma mônada é uma visão única, apenas um contêiner para código de ligação reutilizável. As funções compõem diferentemente dentro do contexto de uma mônada de outra mônada.

Um exemplo simples é a monad de Maybe, onde há código na função bind, de tal forma que se uma função A é composta com uma função B dentro de uma Maybe monad, e B produz um Nothing, então o código bind assegurará que composição das duas funções gera um 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 uma entrada Nothing.

Monads também significa que o programador não precisa especificar explicitamente os parâmetros que cada função requer no código-fonte - a função bind lida com a passagem de parâmetros. Então, usando monads, o código fonte pode começar a parecer mais com 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 móvel - mais funcional do que imperativa.

As setas também conectam funções junto com uma função de ligação, fornecendo funcionalidade reutilizável e ocultando parâmetros. Mas as Setas podem ser conectadas juntas e compostas, e podem opcionalmente rotear dados para outras Setas em tempo de execução. Agora você pode aplicar dados a dois caminhos de setas, que "fazem coisas diferentes" aos dados e remontam o resultado. Ou você pode selecionar qual ramificação das setas para transmitir os dados, dependendo de algum valor nos dados. O código resultante é ainda mais parecido com um circuito eletrônico, com switches, atrasos, integração, etc. O programa parece muito estático, e você não deve ser capaz de ver muita manipulação de dados acontecendo. Há menos e menos parâmetros para pensar e menos necessidade de pensar sobre quais valores os parâmetros podem ou não tomar.

Escrever um programa Arrowized envolve principalmente a seleção de flechas como divisores, interruptores, atrasos e integradores, levantando funções para essas flechas e conectando as flechas para formar flechas maiores. Na Programação Reativa Funcional Arrowized, as setas formam um loop, com a entrada do mundo sendo 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. Em Yampa, a Flecha de Função de Sinal encadeia o parâmetro de tempo invisivelmente através do programa de computador - você nunca acessa o valor de tempo, mas se você conecta uma flecha de integrador no programa, ele produzirá valores integrados ao longo do tempo que você pode usar para passar outras flechas.

    
por 09.10.2012 / 13:28
fonte
3

Apenas uma adição às outras respostas: Pessoalmente, isso me ajuda muito a entender o que é um conceito (matematicamente) e como ele se relaciona com outros conceitos que conheço.

No caso das setas, achei o seguinte artigo útil - ele compara monads, functores aplicativos (expressões idiomáticas) e setas: Idiomas são inconscientes, flechas são meticulosas, mônadas são promíscuas por Sam Lindley, Philip Wadler e Jeremy Yallop.

Além disso, acredito que ninguém mencionou este link que possa fornecer algumas idéias e literatura sobre o assunto.

    
por 09.10.2012 / 20:32
fonte