Programação declarativa vs. programação imperativa

24

Eu me sinto muito confortável com a programação Imperative. Nunca tenho problemas para expressar algoritmicamente o que quero que o computador faça depois de descobrir o que quero que ele faça. Mas quando se trata de linguagens como SQL ou eu geralmente fico presa porque minha cabeça está muito acostumada à programação imperativa.

Por exemplo, suponha que você tenha a banda de relações (bandName, bandCountry), local (venueName, venueCountry), toca (bandName, venueName) e eu quero escrever uma consulta que diga: all venueNames, de modo que para cada banda country existe uma banda de aquele país que joga no local com esse nome.

Exemplo: quero todos os nomes dos locais em que as bandas de todos os países (bandCountry) tocaram. Além disso, por "relação" quero dizer uma tabela SQL.

Na minha opinião, eu imediatamente "para cada local de nome itera sobre todos os países da banda e para cada país da banda obtém a lista de bandas que vêm dele. Se nenhum deles tocar no local do nome, vá para o próximo local do nome. Caso contrário, no final dos países da banda iteração: adicione venueName ao conjunto de good venueNames ".

... mas você não pode falar assim no SQL e eu realmente preciso pensar em como formular isso, com a solução Imperative intuitiva constantemente incomodando a parte de trás da minha cabeça. Alguém mais teve esse problema? Como você superou isso? Você descobriu uma mudança de paradigma? Fez um mapa de conceitos Imperativos para conceitos SQL para traduzir soluções Imperativas em Declarativas? Leia um bom livro?

PS: Não estou procurando uma solução para a consulta acima, resolvi-a.

EpsilonVector
fonte
11
Essa é uma boa pergunta, porque você está expressando uma fraqueza que muitos (inclusive eu) têm.
David Weiser
Pode ser útil definir o que você quer dizer com "relação" em sua pergunta. No modelo relacional (a matemática por trás do SQL), "relação" é aproximadamente análoga a uma tabela SQL. Muitas pessoas dizem "relação" quando realmente querem dizer "relação".
Jason Baker
Aprenda teoria dos conjuntos e matemática discreta.
11
@ Jase21, eu pessoalmente conheço bem os dois, mas coisas não triviais no SQL ainda parecem engraçadas. Nenhum dos exemplos de matemática limpa lida com as coisas estranhas do mundo real. Além disso, pode-se usar o LINQ e, portanto, não se incomodar com o SQL. Finalmente, para o solicitante: você se acostumará com o tempo.
Job

Respostas:

12

A idéia por trás de fazer as coisas declarativamente é que você deve especificar o que , não como .

Para mim, parece que você está no caminho certo. O problema não é que você esteja pensando sobre as coisas da maneira errada. É que você está indo longe demais. Vejamos o que você está tentando fazer:

Por exemplo, suponha que você tenha a banda de relações (bandName, bandCountry), local (venueName, venueCountry), toca (bandName, venueName) e eu quero escrever uma consulta que diga: all venueNames, de modo que para cada banda country existe uma banda de aquele país que joga no local com esse nome.

Até agora, isso é ótimo. Mas então você faz isso:

Na minha opinião, eu imediatamente "para cada local de nome itera sobre todos os países da banda e para cada país da banda obtém a lista de bandas que vêm dele. Se nenhum deles tocar no local do nome, vá para o próximo local do nome. Caso contrário, no final dos países da banda iteração: adicione venueName ao conjunto de good venueNames ".

Em essência, você está fazendo um trabalho desnecessário. Você sabe o que quer, e é tudo o que realmente precisa. Mas então você continua tentando descobrir como obtê-lo.

Se eu fosse você, tentaria adquirir o seguinte hábito:

  1. Defina o que você quer.
  2. Conscientemente, pare de definir como obtê-lo.
  3. Descubra como representar o que você deseja no SQL.

Pode levar algum tempo e esforço de sua parte, mas depois que você realmente adora a programação declarativa, ela se torna muito útil. De fato, você pode se encontrar usando programação declarativa no restante do seu código.

Se você estiver procurando por um livro, recomendo SQL e Teoria Relacional . Realmente ajuda a entender a teoria por trás dos bancos de dados SQL. Lembre-se de seguir as recomendações de Date com um grão de sal. Ele fornece informações muito boas, mas às vezes pode ser um pouco opinativo.

Jason Baker
fonte
Eu não entendo como descobrir como conseguir algo é a abordagem errada. Não importa que tipo de idioma você está usando, você precisa descobrir como dizer a ele o que deseja.
Davidk01
9

pense em termos de conjuntos, não em iteradores; as instruções sql definem as propriedades do conjunto de saída desejado (também conhecido como tabela / relação)

Todos os locais de eventosNomes que, para cada bandaPaís, há uma banda daquele país que toca no local com esse nome

o resultado disso (se eu entendi suas intenções corretamente!) seria o conjunto de locais que possuem pelo menos uma banda que toca nesse local. A iteração sobre bandCountry é desnecessária, pois a relação PLAYS já possui as informações que você procura, basta eliminar as duplicatas

então no SQL isso seria:

select 
    distinct venueName
from PLAYS

EDIT: ok, então o conjunto real desejado é um pouco mais complicado. A pergunta que está sendo feita ao banco de dados é: quais locais hospedaram bandas de todos os países?

Assim, definimos os critérios de associação para um elemento do conjunto desejado como a meta e, em seguida, trabalhamos para trás para preencher o conjunto. Um local é um membro do conjunto de saída se tiver uma linha do PLAYS para pelo menos uma banda de cada país. Como obtemos essa informação?

Uma maneira é contar os países distintos para cada local e compará-lo com a contagem de todos os países. Mas não temos uma relação com PAÍS. Se pensarmos no modelo dado por um momento, veremos que o conjunto de todos os países não é o critério certo; é o conjunto de todos os países que têm pelo menos uma banda. Portanto, não precisamos de uma tabela de países (embora, para um modelo normalizado, devêssemos ter uma), e não nos importamos com o país do local, podemos apenas contar os países que têm bandas, por exemplo (no MS-SQL )

declare @BandCountryCount int
select
    @BandCountryCount = COUNT(distinct bandCountry)
from BAND

Podemos contar os países da banda para cada local

select
    P.venueName, COUNT(distinct B.bandCountry) as VenueBandCountryCount
from PLAYS P
    inner join BAND B on B.bandName = P.bandName

e podemos juntar os dois usando uma subconsulta

select
    venueName
from (
    select
        P.venueName, COUNT(distinct B.bandCountry) as VenueBandCountryCount
    from PLAYS P
        inner join BAND B on B.bandName = P.bandName
) X
where X.VenueBandCountryCount = @BandCountryCount

Agora, essa não é a consulta mais bonita possível (GROUP BY e HAVING pode ser considerado uma solução mais 'elegante' do que variáveis ​​temporárias e uma subconsulta), mas é bastante óbvio o que buscamos, portanto, deixamos isso para o objetivo do OP .

O objetivo do OP era aprender a mudar a mentalidade de imperativo para declarativo. Para esse fim, observe o que a solução imperativa descrita estava fazendo:

para cada local de nome, repita todos os países da banda e, para cada país da banda, obtenha a lista de bandas que vêm dele. Se nenhum deles jogar no venueName, vá para o próximo venueName. Caso contrário, no final da banda, a iteração dos países adiciona venueName ao conjunto de boas instalaçõesNames

Quais são os critérios determinantes acima? Eu acho que é:

... Se nenhum deles [o conjunto de bandas de um país em particular] tocar no localNome ...

Este é um critério desqualificante . O processo de pensamento imperativo está começando com um balde cheio e jogando fora coisas que não se encaixam nos critérios. Estamos filtrando os dados.

Isso é bom para coisas simples, mas ajuda a pensar em termos de construção do conjunto de resultados desejado; quais são os critérios de qualificação correspondentes que permitiriam preencher o balde?

  • desqualificador: se não houver banda de um país da banda que toca em um local, o local é desqualificado
  • qualificador (parcial): se pelo menos uma banda de um país da banda tocar em um local, o local poderá estar ok; continue verificando o resto da banda
  • qualificador (completo): se pelo menos uma banda de cada país tocar em um local, o local será qualificado

O qualificador final pode ser simplificado usando contagens: um país da banda fica 'satisfeito' se pelo menos uma banda dali tocar em um local; o número de países da banda 'satisfeitos' para um local de reunião deve ser igual ao número de países da banda para que o local seja qualificado.

Agora podemos raciocinar através das relações pela navegação:

  • comece com a relação VENUE [não precisamos dela para a resposta, mas é o ponto de partida conceitual para a navegação relacional]
  • junte-se ao PLAYS no local
  • junte-se ao BAND no nome da banda para obter a banda
  • nós não nos importamos com o nome da banda; selecione apenas o localNome e bandaPaís
  • nós não nos importamos com os países de banda redundantes; eliminar duplicatas usando DISTRICT ou GROUP BY
  • nos preocupamos apenas com a contagem de países de banda distintos, não com os nomes
  • queremos apenas locais onde a contagem de bandCountries distintos seja igual ao número total de bandCountries

que leva de volta à solução acima (ou a um fax razoável)

RESUMO

  • teoria de conjuntos
  • caminhos de navegação relacional
  • critérios inclusivos x exclusivos (qualificação x desqualificação)
Steven A. Lowe
fonte
Na verdade, é "conjunto de locais que bandas de todos os países (bandCountry> = venueCountry) tocam neles".
EpsilonVector
@EpsilonVector: veja edições
Steven A. Lowe
4

Uma maneira de aprender a pensar e programar em um estilo declarativo é aprender uma linguagem de matriz de uso geral como APL ou J. SQL provavelmente não é o melhor veículo para aprender a programar declarativamente. No APL ou J, você aprende a operar em matrizes inteiras (vetores, matrizes ou matrizes de classificação mais alta), sem loop ou iteração explícita. Isso facilita muito o entendimento de SQL e álgebra relacional. Como um exemplo muito simples, para selecionar itens de um vetor V cujo valor é maior que 100, em APL, escrevemos:

(V>100)/V

Aqui V> 100 avalia uma matriz booleana da mesma forma que V, com 1 marcando os valores que queremos manter. Não ocorre para o APLer experiente que há iteração, estamos apenas aplicando uma máscara ao vetor V, retornando um novo vetor. É claro que isso é conceitualmente o que uma cláusula where SQL ou operação restrita de álgebra relacional está fazendo.

Eu não acho que você possa entender bem a programação declarativa sem fazer muito, e o SQL geralmente é muito específico. Você precisa escrever muito código de propósito geral, aprendendo como fazer sem loops e estruturas if / then / else e todo o equipamento que atende à programação imperativa, procedural e no estilo escalar.

Pode haver outras linguagens funcionais que também ajudam nessa maneira de pensar, mas as linguagens de array estão muito próximas do SQL.

PAUL Mansour
fonte
+1 para "[você não pode] se controlar ... sem fazer muito". Também ninguém aprendeu programação imperativa (com suas construções claramente contra-intuitivas a = a + 1) da noite para o dia. Leva tempo para aprender estilos declarativos como lógica, funcional etc., assim como levou tempo para aprender programação imperativa.
APENAS MINHA OPINIÃO correta
1

Primeiro, você precisa aprender os dois. Você pode ter uma preferência, mas ao trabalhar em áreas onde o outro é melhor, não lute contra isso. Muitos programadores são tentados a usar cursores em bancos de dados relacionais, pois costumam percorrer cada registro, mas o banco de dados é muito melhor em conjuntos. Você não quer entrar na mentalidade de "Eu sei fazer dessa maneira e tenho mais controle, blá, blá, blá".

JeffO
fonte
1

Você aprende a pensar declarativamente como aprendeu a pensar imperativamente: praticando começando com problemas mais simples e trabalhando à medida que o "entende".

Suas primeiras experiências com programação imperativa incluíram um monte de declarações contra-intuitivas (e, de fato, totalmente ridículas) como " a = a + 1". Você pensou nisso a tal ponto que agora provavelmente nem se lembra do recuo da óbvia mentira da afirmação. Seu problema com os estilos declarativos é que você está de volta onde estava quando começou com estilos imperativos: um "newb à nora". Pior ainda, você tem anos de prática com um estilo que está totalmente em desacordo com esse novo estilo e tem anos de hábitos para desfazer - como o hábito de "controlar a todo custo".

Estilos declarativos funcionam com uma abordagem diferente da qual você ainda não tem intuição (a menos que tenha mantido suas habilidades matemáticas muito acentuadas ao longo dos anos - o que a maioria das pessoas não faz). Você precisa reaprender a pensar e a única maneira de reaprender isso é fazê-lo, um simples passo de cada vez.

Escolher SQL como sua primeira incursão na programação declarativa pode ser um erro se você realmente quiser aprender os conceitos. Certamente, o cálculo da tupla em que se baseia é o mais declarativo possível, mas infelizmente a pureza do cálculo da tupla foi comprometida pelas realidades da implementação e a linguagem, de fato, tornou-se um pouco confusa. Em vez disso, você pode procurar outras linguagens declarativas mais diretamente úteis (no sentido em que você está acostumado) como Lisps (especialmente Scheme ), Haskell e MLs (principalmente) para programação funcional ou, em alternativa, Prolog e Mercury para (principalmente) programação lógica.

Aprender essas outras linguagens fornecerá uma melhor compreensão, na minha opinião, de como a programação declarativa funciona por alguns motivos:

  1. Eles são úteis para a programação "do começo ao fim" - pois você pode escrever um programa completo nessas linguagens do começo ao fim. Eles são úteis sozinhos, ao contrário do SQL, que é realmente bastante inútil para a maioria das pessoas como uma linguagem independente.

  2. Cada um deles oferece uma inclinação diferente na programação declarativa que pode fornecer caminhos diferentes para finalmente "obtê-lo".

  3. Cada um deles também oferece uma inclinação diferente ao pensar em programação em geral. Eles melhorarão sua capacidade de raciocinar sobre problemas e códigos, mesmo que você nunca os use diretamente.

  4. As lições que você aprende com eles também o ajudarão com o seu SQL - especialmente se você analisar o cálculo da tupla por trás dos bancos de dados relacionais para a forma pura de pensar sobre os dados.

Eu recomendaria especialmente o aprendizado de uma das linguagens funcionais ( Clojure , como um dos Lisps, provavelmente é uma boa escolha aqui) e uma das linguagens lógicas (eu gosto mais de Mercúrio, mas o Prolog tem muito mais material útil para aprender) para expansão máxima do processo de pensamento.

APENAS MINHA OPINIÃO correta
fonte
1

Não é errado pensar imperativamente em um cenário declarativo como o SQL. É só que o pensamento imperativo deve estar acontecendo em um nível um pouco mais alto do que o que você descreveu. Sempre que preciso consultar um banco de dados que usa SQL, sempre penso comigo:

  • Aqui estão as peças que eu preciso.
  • Vou reuni-los dessa maneira.
  • Vou detalhar o que acabei de obter com os seguintes predicados para obter o que realmente estou procurando.

O acima é um algoritmo imperativo de alto nível e funciona muito bem para mim na configuração SQL. Eu acho que isso é considerado uma abordagem de cima para baixo e Steven A. Lowe descreve uma boa abordagem de baixo para cima .

davidk01
fonte
1

A chave da sua pergunta está no que você disse no penúltimo parágrafo: "Você não pode falar assim no SQL". Pode ser mais útil, neste estágio, abordar o SQL como uma língua estrangeira em vez de uma linguagem de programação. Se você pensar dessa maneira, escrever uma consulta SQL está realmente traduzindo uma instrução em inglês do que você deseja em "SQLish". O computador entende perfeitamente o SQLish e fará exatamente o que você diz, para que você não precise se preocupar com a implementação desde que traduza corretamente.

Dito isto, qual é a melhor maneira de aprender uma língua estrangeira? Obviamente, você precisa aprender a gramática e o vocabulário, que podem ser obtidos na documentação do SQL. O mais importante é a prática. Você deve ler e escrever o máximo de SQL possível e não sentir que precisa conhecer a sintaxe completamente primeiro; você pode e deve procurar as coisas à medida que avança. Você saberá que o possui quando achar mais fácil descrever quais dados deseja no SQL do que em inglês.

Larry Coleman
fonte
1

Levei muito tempo para entender também o SQL. Fizemos uma teoria relacional na universidade e na época, que apenas serviu para complicar as coisas. No final, meu processo de aprendizado foi muito tentador e erro, informado por vários materiais e exemplos de aprendizado que achei úteis ao longo do caminho. Essencialmente, você se acostumará a isso eventualmente, e adicionar uma nova maneira de pensar sobre dados e consultas terá algum valor para o seu desenvolvimento mental.

Eu descobri que era capaz de acelerar meu aprendizado, construindo gradualmente uma coleção de scripts simples, demonstrando como usar cada recurso de idioma e como obter determinados resultados em uma tabela conhecida (definições de tabela incluídas para referência).

No início deste ano, fiz um treinamento formal envolvendo um projeto de migração de dados em um banco de dados Oracle bagunçado, onde tive que reunir gradualmente fragmentos da minha biblioteca para filtrar os resultados da consulta de várias maneiras até ter exatamente o que queria, depois transformá-los como necessário e assim por diante. Algumas das consultas se tornaram muito complexas e difíceis de depurar. Duvido que eu possa lê-los agora, mas espero poder chegar a uma solução semelhante novamente usando meus blocos de referência.

Outras maneiras de aumentar sua percepção intuitiva dos espaços declarativos e funcionais são a teoria dos conjuntos de aprendizado e as linguagens de programação mais adequadas a um paradigma específico. Atualmente, estou aprendendo alguns Haskell, por exemplo, para manter e melhorar minhas habilidades matemáticas.

IanGilham
fonte
0

Quando você enfrenta um problema, geralmente pensa em como resolvê-lo. Mas se você sabe como o computador resolve isso para você! Então você está preocupado sobre como será eliminado.

Eu tento dizer como isso acontece.

Você já deve estar familiarizado com programas recursivos; em programas recursivos, você define o problema em vez de dizer como ele é resolvido. você define a base e define n com base em n-1 . (por exemplo factorial(n) = n * factorial(n-1)) Mas você já deve saber como o computador o resolve. ele inicia a partir da função e chama a função recursivamente até atingir uma definição de base e, em seguida, avalia todas as outras funções com base no valor base.

É o que acontece na programação declarativa. você define tudo com base nas definições existentes. E o computador sabe como obter a resposta para você com base nas funções básicas.

No SQL, você pode não relacionar definições entre si, mas relaciona objetos ou informações, especifica o que deseja e pesquisa no computador com algo (objeto, informação) com base nas relações que você forneceu.

Ahmad
fonte