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 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 escrever 7
é 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.