Embora eu trabalhe como programador em minha vida cotidiana e use todas as linguagens da moda (Python, Java, C, etc), ainda não tenho uma visão clara do que é a programação funcional. Pelo que li, uma propriedade das linguagens funcionais é que as estruturas de dados são imutáveis . Para mim, isso por si só levanta muitas questões. Mas primeiro vou escrever um pouco do que entendo de imutabilidade e, se estiver errado, fique à vontade para me corrigir.
Minha compreensão da imutabilidade:
- Quando um programa inicia, ele possui estruturas de dados fixas com dados fixos
- Não se pode adicionar novos dados a essas estruturas
- Não há variáveis no código
- Você pode simplesmente "copiar" dos dados já calculados ou atualmente calculados
- Por tudo isso, a imutabilidade adiciona enorme complexidade de espaço a um programa
Minhas perguntas:
- Se as estruturas de dados devem permanecer como são (imutáveis), como diabos alguém adiciona um novo item em uma lista?
- Qual é o sentido de ter um programa que não pode obter novos dados? Digamos que você tenha um sensor conectado ao seu computador que deseja alimentar dados para o programa. Isso significa que não podemos armazenar os dados recebidos em nenhum lugar?
- Como a programação funcional é boa para o aprendizado de máquina nesse caso? Como o aprendizado de máquina se baseia no pressuposto de atualizar a "percepção" do programa - armazenando assim novos dados.
data-structures
functional-programming
Pithikos
fonte
fonte
Respostas:
Isso é um pouco de um equívoco. Ele tem uma forma fixa e um conjunto fixo de regras de reescrita, mas essas regras de reescrita podem explodir em algo muito maior. Por exemplo, a expressão [1..100000000] em Haskell é representada por uma quantidade muito pequena de código, mas sua forma normal é massiva.
Sim e não. O subconjunto puramente funcional de uma linguagem como Haskell ou ML não pode obter dados do mundo exterior, mas qualquer linguagem para programação prática possui um mecanismo para inserir dados do mundo externo no subconjunto puramente funcional. No Haskell, isso é feito com muito cuidado, mas no ML você pode fazer isso sempre que quiser.
Isso é verdade, mas não confunda isso com a idéia de que nada pode ser nomeado. Você nomeia expressões úteis o tempo todo e as reutiliza constantemente. Também o ML e o Haskell, todos os Lisp que experimentei, e os híbridos como o Scala, todos têm um meio de criar variáveis. Eles simplesmente não são comumente usados. E novamente os subconjuntos puramente funcionais de tais idiomas não os possuem.
Você pode executar o cálculo reduzindo-o para a forma normal. A melhor coisa a fazer é provavelmente escrever programas em uma linguagem funcional para ver como eles realmente fazem cálculos.
Por exemplo, "soma [1..1000]" não é um cálculo que eu quero executar, mas é bastante prático por Haskell. Demos uma pequena expressão que tinha significado para nós e Haskell nos deu o número correspondente. Portanto, ele definitivamente realiza o cálculo.
Você não adiciona um novo item a uma lista, cria uma nova lista a partir do antigo. Como o antigo não pode ser modificado, é perfeitamente seguro usá-lo na nova lista ou em qualquer outro lugar que você desejar. Muito mais dados podem ser compartilhados com segurança nesse esquema.
Quanto à entrada do usuário, qualquer linguagem de programação prática tem uma maneira de obter entrada do usuário. Isto acontece. No entanto, há um subconjunto totalmente funcional desses idiomas em que você escreve a maior parte do seu código e colhe as vantagens dessa maneira.
Esse seria o caso do aprendizado ativo, mas a maioria dos aprendizado de máquina com os quais trabalhei (eu trabalho como um macaco de código em um grupo de aprendizado de máquina e o faço há alguns anos) tem um processo de aprendizado único, onde todos os dados de treinamento são carregados de uma vez. Mas para o aprendizado ativo, você não pode fazer as coisas de forma 100% puramente funcional. Você precisará ler alguns dados do mundo exterior.
fonte
Imutabilidade ou mutabilidade não são conceitos que fazem sentido na programação funcional.
O contexto computacional
Essa é uma pergunta muito boa, que é um acompanhamento interessante (não duplicado) de outra recente: Qual é a diferença entre atribuição, avaliação e vinculação de nome?
Em vez de responder às suas declarações uma a uma, estou tentando aqui fornecer uma visão geral estruturada do que está em jogo.
Há vários problemas a serem considerados para responder a você, incluindo:
O que é um modelo de computação e quais conceitos fazem sentido para um determinado modelo
Qual é o significado das palavras que você está usando e como isso depende do contexto
O estilo de programação funcional parece tolo porque você o vê com um olhar imprescindível para o programador. Mas é um paradigma diferente, e seus conceitos e percepção imperativos são estranhos, fora de lugar. Os compiladores não têm tais preconceitos.
Mas a conclusão final é que é possível escrever programas de uma maneira puramente funcional, inclusive para aprendizado de máquina, pois a programação funcional não possui o conceito de armazenamento de dados. Eu pareço discordar nesse ponto com outras respostas.
Na esperança de que alguns se interessem, apesar da duração desta resposta.
Paradigmas computacionais
A questão é sobre programação funcional (também conhecida como programação aplicativa), um modelo específico de computação, cujo representante teórico e mais simples é o cálculo lambda.
Se você permanecer no nível teórico, existem muitos modelos de computação: a máquina de Turing (TM), a máquina RAM e outros , o cálculo lambda, lógica combinatória, teoria da função recursiva, sistemas semi-Thue etc. provou-se modelos equivalentes em termos do que eles podem abordar, e essa é a essência da tese de Church-Turing .
Um conceito importante é a redução de modelos entre si, que é a base para estabelecer as equivalências que levam à tese de Church-Turing. Visto da perspectiva dos programadores, reduzir um modelo para outro é basicamente o que geralmente é chamado de compilador. Se você usa a programação lógica como seu modelo de computação, é bem diferente do modelo fornecido pelo PC que você comprou em uma loja e o compilador converte programas escritos em linguagem de programação lógica para o modelo computacional representado pelo seu PC (praticamente computador RAM).
Na prática, as linguagens de programação que usamos tendem a misturar conceitos de diferentes origens teóricas, tentando fazê-lo para que partes selecionadas de um programa possam se beneficiar das propriedades de algum modelo, quando apropriado. Da mesma forma, as pessoas que constroem sistemas podem escolher idiomas diferentes para componentes diferentes, para melhor adequar o idioma à tarefa em questão.
Portanto, você raramente vê um paradigma de programação em estado puro em uma linguagem de programação. As linguagens de programação ainda são classificadas de acordo com o paradigma dominante, mas as propriedades da linguagem podem ser afetadas quando conceitos de outros paradigmas estão envolvidos, muitas vezes obscurecendo distinções e questões conceituais.
Normalmente, linguagens como Haskell e ML ou CAML são consideradas funcionais, mas podem permitir um comportamento imperativo ... Por que alguém poderia falar do " subconjunto puramente funcional "?
Então, pode-se afirmar que, você pode fazer isso ou aquilo na minha linguagem de programação funcional, mas não está realmente respondendo a uma pergunta sobre programação funcional quando se baseia no que pode ser considerado extra-funcional.
As respostas devem estar mais precisamente relacionadas a um paradigma específico, sem os extras.
O que é uma variável?
Outro problema é o uso da terminologia. Em matemática, uma variável é uma entidade que representa um valor indeterminado em algum domínio. É utilizado para vários fins. Utilizado em uma equação, pode representar qualquer valor que permita que a equação seja verificada. Essa visão é usada na programação lógica sob o nome de " variável lógica ", provavelmente porque a variável name já tinha outro significado quando a programação lógica foi desenvolvida.
Na programação imperativa tradicional, uma variável é entendida como algum tipo de contêiner (ou localização da memória) que pode memorizar a representação de um valor e, possivelmente, obter seu valor atual substituído por outro).
Na programação funcional, uma variável tem o mesmo propósito que em matemática que um espaço reservado para algum valor, ainda a ser fornecido. Na programação imperativa tradicional, esse papel é realmente desempenhado por constante (não deve ser confundido com literais cujo valor determinado é expresso com uma notação específica para esse domínio de valores, como 123, true, ["abdcz", 3,14]).
Variáveis de qualquer tipo, bem como constantes, podem ser representadas por identificadores.
A variável imperativa pode ter seu valor alterado e essa é a base da mutabilidade. A variável funcional não pode.
As linguagens de programação geralmente permitem a criação de entidades maiores a partir das menores na linguagem.
Linguagens imperativas permitem que essas construções incluam variáveis e é isso que fornece dados mutáveis.
Como ler um programa
Um programa é fundamentalmente uma descrição abstrata do seu algoritmo, é uma linguagem, seja um design pragmático ou uma linguagem paradigmaticamente pura.
Em princípio, você pode aceitar todas as afirmações do que é suposto significar abstratamente. Em seguida, o compilador traduzirá isso para alguma forma apropriada para o computador executar, mas esse não é o seu problema na primeira aproximação.
Obviamente, a realidade é um pouco mais dura, e geralmente é bom ter uma idéia do que acontece, para evitar estruturas com as quais o compilador não saberá lidar com uma execução eficiente. Mas isso já é otimização ... para quais compiladores podem ser muito bons, geralmente melhores que os programadores.
Programação funcional e mutabilidade
A mutabilidade é baseada na existência de variáveis imperativas que podem conter valores, a serem alteradas por atribuição. Como estes não existem na programação funcional, tudo pode ser visto como imutável.
A programação funcional lida exclusivamente com valores.
Suas quatro primeiras afirmações sobre imutabilidade estão na maioria corretas, mas descreva com visão imperativa algo que não é imperativo. É um pouco como descrever com cores em um mundo onde todos são cegos. Você está usando conceitos estranhos à programação funcional.
Você tem apenas valores puros e uma matriz de números inteiros é um valor puro. Para obter outra matriz que difere apenas em um elemento, você deve usar um valor diferente. Alterar um elemento é apenas um conceito que não existe neste contexto. Você pode ter uma função que possui uma matriz e alguns índices como argumento e retorna um resultado que é uma matriz quase idêntica, que difere apenas onde indicado pelos índices. Mas ainda é um valor de matriz independente. Como esses valores são representados não é problema seu. Talvez eles "compartilhem" muito a tradução imperativa para o computador ... mas esse é o trabalho do compilador ... e você nem quer saber para que tipo de arquitetura de máquina está compilando.
Você não copia valores (não faz sentido, é um conceito estranho). Você apenas usa valores que existem nos domínios que você definiu em seu programa. Você os descreve (como literais) ou eles são o resultado da aplicação de uma função a alguns outros valores. Você pode dar um nome a eles (definindo assim uma constante) para garantir que o mesmo valor seja usado em diferentes locais do programa. Observe que o aplicativo de funções não deve ser percebido como um cálculo, mas como o resultado do aplicativo para os argumentos fornecidos. Escrever
5+2
ou escrever7
é o mesmo. O que é consistente com o parágrafo anterior.Não há variáveis imperativas. Nenhuma atribuição é possível. Você pode vincular nomes apenas a valores (para formar constantes), diferentemente de linguagens imperativas nas quais é possível vincular nomes a variáveis atribuíveis.
Se isso tem um custo em complexidade é totalmente incerto. Por um lado, a referência à complexidade diz respeito a paradigmas imperativos. Ele não está definido como tal para programação funcional, a menos que você opte por ler seu programa funcional como um imperativo, que não é a intenção do designer. De fato, a visão funcional visa deixar você não se preocupar com esses problemas e se concentrar no que está sendo calculado. É um pouco como especificação versus implementação.
O compilador precisa cuidar da implementação e ser inteligente o suficiente para melhor adaptar o que deve ser feito ao hardware que o fará, seja ele qual for.
Não estou dizendo que programadores nunca se preocupam com isso. Também não estou dizendo que linguagens de programação e tecnologia de compiladores são tão maduras quanto gostaríamos que elas fossem.
Respondendo as perguntas
Você não modifica o valor existente (conceito de alienígena), mas calcula novos valores que diferem onde desejado, possivelmente por ter um elemento extra que é uma lista.
O programa pode obter novos dados. O ponto principal é como você expressa isso no idioma. Você pode, por exemplo, considerar que o programa funciona com um valor específico, possivelmente sem tamanho, chamado de fluxo de entrada. É um valor que deveria estar ali (se já é conhecido por completo ou não, não é seu problema). Então você tem uma função que retorna um par composto pelo primeiro elemento do fluxo e pelo restante do fluxo.
Você pode usar isso para criar redes de componentes de comunicação de maneira puramente aplicativa (corotinas)
O aprendizado de máquina é apenas outro problema quando você precisa acumular dados e modificar valores. Na programação funcional, você não faz isso: apenas calcula novos valores que diferem adequadamente de acordo com os dados de treinamento. A máquina resultante também funcionará. O que você se preocupa é calcular a eficiência de tempo e espaço. Mas, novamente, essa é uma questão diferente, que idealmente deve ser tratada pelo compilador.
Considerações finais
É bem claro, pelos comentários ou pelas outras respostas, que as linguagens de programação funcional práticas não são puramente funcionais. Isso reflete o fato de que nossa tecnologia ainda precisa ser aprimorada, especialmente no que diz respeito à compilação.
É possível escrever em um estilo puramente aplicativo? A resposta é conhecida há cerca de 40 anos e é "sim". O próprio objetivo da semântica denotacional, tal como surgiu nos anos 70, era justamente traduzir (compilar) linguagens para um estilo puramente funcional, considerado melhor entendido matematicamente e, portanto, considerado um melhor financiamento para definir a semântica dos programas.
O aspecto interessante disso é que a estrutura de programação imperativa, incluindo variáveis, pode ser convertida em um estilo funcional, introduzindo domínios de valores apropriados, como um armazenamento de dados. E, apesar do estilo funcional, ele permanece surpreendentemente semelhante ao código dos compiladores reais escritos em estilo imperativo.
fonte
É um equívoco que os programas funcionais não possam armazenar dados, e não acho que a resposta de Jakes tenha realmente explicado isso muito bem.
Programas funcionais são, como qualquer programa, realmente funcionam mapeando números inteiros para números inteiros. Qualquer programa imperativo operando em estruturas de dados mutáveis tem uma contrapartida funcional. Este é apenas outro meio de alcançar o mesmo fim.
A maneira funcional de armazenar dados experimentais de alguma fonte seria chamar a função de armazenamento com a estrutura de dados como argumento e produzir uma concatenação da estrutura de dados existente e dos novos dados e, portanto, os dados são armazenados sem a noção de estruturas de dados mutáveis.
Pela minha própria experiência , acho que o conceito de estruturas de dados imutáveis está levando os desenvolvedores convencionais a pensar que há certas coisas que são impraticáveis ou mesmo impossíveis de fazer em um ambiente funcional. Este não é o caso.
fonte