Eu vejo muitos textos, especialmente textos de programação funcional, alegando que certos conceitos de CS "não compõem" . Exemplos são: fechaduras não compõem, mônadas não compõem.
Tenho tido dificuldade em rastrear exatamente o significado dessa frase. Quando penso em composição, penso em composição de funções ou agregação de objetos (como em "favorecer composição sobre herança"), mas esse não parece ser o sentido em que as pessoas a estão usando aqui.
Alguém pode explicar o que essa frase significa quando usada em expressões como os dois exemplos acima (ou seja, bloqueios e mônadas)?
Respostas:
Quando as pessoas dizem "X não compõe", o que elas querem dizer com "compor" realmente significa apenas "montar", e o que e como você as coloca podem ser muito diferentes, dependendo do que exatamente é "X".
Além disso, quando eles dizem "não compõe", eles podem significar algumas coisas um pouco diferentes:
Um exemplo para o nº 1 é analisadores com scanners / lexers. Você pode ouvir a frase "scanners / lexers não compõem". Isso não é verdade. O que eles querem dizer é "analisador que usa um estágio lexing separado não compõe".
Por que você gostaria de compor analisadores? Bem, imagine que você é um fornecedor de IDE como JetBrains, Eclipse Foundation, Microsoft ou Embarcadero e deseja criar um IDE para uma estrutura da web. No desenvolvimento típico da Web, geralmente misturamos idiomas. Você possui arquivos HTML com
<script>
elementos que contêm ECMAScript e<style>
elementos que contêm CSS. Você tem arquivos de modelo que contêm HTML, alguma linguagem de programação e algumas metassintaxe da linguagem de modelo. Você não deseja gravar marcadores de sintaxe diferentes para "Python", "Python incorporado em um modelo", "CSS", "CSS dentro de HTML", "ECMASCript", "ECMAScript dentro de HTML", "HTML", "HTML dentro de" um modelo "e assim por diante. Você deseja escrever um marcador de sintaxe para Python, um para HTML, um para a linguagem de modelo e, em seguida, compor os três em um marcador de sintaxe para um arquivo de modelo.No entanto, um lexer analisa o arquivo inteiro em um fluxo de tokens, o que só faz sentido para esse idioma. O analisador para o outro idioma não pode trabalhar com os tokens que o lexer passa. Por exemplo, os analisadores Python são tipicamente escritos de tal maneira que o lexer controla o recuo e injeta falsos
INDENT
eDEDENT
tokens no fluxo de token, permitindo que o analisador fique livre de contexto, mesmo que a sintaxe do Python não seja. Um lexer HTML, no entanto, ignorará completamente os espaços em branco, pois não tem significado em HTML.Um analisador sem scanner, no entanto, que simplesmente lê caracteres, pode transmitir o fluxo de caracteres para um analisador diferente, que pode devolvê-lo, facilitando sua composição.
Um exemplo para o número 2 são cadeias de caracteres com consultas SQL. Você pode ter duas cadeias de caracteres, cada uma com uma consulta SQL sintaticamente correta, mas se você concatenar as duas cadeias, o resultado poderá não ser uma consulta SQL sintaticamente correta. É por isso que temos de consulta álgebras como
ARel
, o que fazer composição.Bloqueios são um exemplo de # 3. Se você possui dois programas com bloqueios e os combina em um único programa, ainda possui um programa com bloqueios, mas mesmo que os dois programas originais estivessem completamente corretos, livres de impasses e corridas, o programa resultante não tem necessariamente isso propriedade. O uso correto de bloqueios é uma propriedade global de todo o programa e uma propriedade que não é preservada quando você compõe programas. Isso é diferente de, por exemplo, transações que são compostas. Um programa que usa transações corretamente pode ser composto por outro programa e produzirá um programa combinado que usa transações corretamente.
fonte
Composibilidade significa que você pode combinar de maneira fácil e confiável os componentes do programa para produzir componentes maiores e funcionalidades mais complexas.
Algumas coisas que ajudam a tornar os componentes mais compostáveis:
Idempotência. Uma função idempotente sempre produzirá a mesma saída ou efeitos colaterais, se chamados várias vezes com os mesmos valores de parâmetro. Isso melhora a composibilidade porque o resultado de uma chamada de função é previsível.
Transparência referencial. Uma expressão referencialmente transparente sempre será avaliada para o mesmo resultado. Isso melhora a composibilidade, permitindo que expressões idênticas sejam substituídas uma pela outra e permitindo que expressões sejam calculadas independentemente uma da outra (ou seja, em threads diferentes) sem usar bloqueios.
Imutabilidade. O estado de um objeto imutável não pode ser alterado depois de criado. Isso melhora a composibilidade porque você pode confiar em um valor estável do objeto, sem se preocupar se alguma função ou objeto em algum lugar mudou o estado do objeto depois que ele foi criado.
Pureza. As funções puras não têm efeitos colaterais. Eles possuem apenas uma entrada e uma saída, o que os torna mais composíveis porque você pode colocar a saída de uma função na entrada de outra função sem se preocupar se alguma coisa fora da função foi alterada.
Os bloqueios não se compõem porque são um elemento externo no qual você deve confiar ao combinar duas operações que compartilham algum estado e por todos os tipos de razões relacionadas à complexidade inerente ao uso de bloqueios.
A frase "mônadas não compõem" não faz muito sentido para mim. O objetivo principal de uma mônada é pegar algo importante, como entrada de teclado ou saída de tela, e transformá-la em uma forma matemática mais pura que é, de fato, mais compostável.
fonte
IO
tipo que captura "ações com estado", como entrada do teclado. Os valores doIO
tipo, de fato, compõem, mas é todo o tipoIO
que é uma mônada. Esse tipo não compõe com outros tipos de mônadas, como, por exemplo, o tipo de lista. Ou seja, não podemos produzir sistematicamente um tipoIO . List
que se comporte como ambosIO
eList
simultaneamente. Isso faz sentido? Não tenho certeza se expliquei bem.