Programación declarativa versus programación imperativa

24

Me siento muy cómodo con la programación imperativa. Nunca tengo problemas para expresar algorítmicamente lo que quiero que haga la computadora una vez que descubrí qué es lo que quiero que haga. Pero cuando se trata de lenguajes como SQL o a menudo me atoro porque mi cabeza está demasiado acostumbrada a la programación imperativa.

Por ejemplo, supongamos que tiene la banda de relaciones (bandName, bandCountry), sede (placeName ,placeCountry), juega (bandName ,placeName), y quiero escribir una consulta que diga: todos los nombres de sede de tal manera que para cada bandCountry haya una banda de ese país que juega en el lugar de ese nombre.

Ejemplo: quiero todos los nombres de los lugares en los que tocaron bandas de todos los países (bandCountry). Además, por "relación" me refiero a una tabla SQL.

En mi mente, inmediatamente voy "para cada Nombre del lugar iterar sobre todos los BandCountries y para cada bandCountry obtener la lista de las bandas que provienen de él. Si ninguno de ellos juega en el Lugar del Nombre, vaya al siguiente Lugar del Nombre. De lo contrario, al final de los BandCountries iteración agregarplaceName al conjunto de buenos lugaresNombre ".

... pero no se puede hablar así en SQL y realmente necesito pensar en cómo formular esto, con la solución Imperativa intuitiva constantemente molesta en la parte posterior de mi cabeza. ¿Alguien más tuvo este problema? ¿Cómo superaste esto? ¿Descubriste un cambio de paradigma? ¿Hizo un mapa de conceptos imperativos a conceptos SQL para traducir soluciones imperativas en declarativas? ¿Lee un buen libro?

PD: no estoy buscando una solución para la consulta anterior, la resolví.

EpsilonVector
fuente
1
Esta es una buena pregunta porque estás expresando una debilidad que muchos (incluido yo mismo) tenemos.
David Weiser
Puede ser útil definir qué quiere decir con "relación" en su pregunta. En el modelo relacional (la matemática detrás de SQL), la "relación" es más o menos análoga a una tabla SQL. Mucha gente dirá "relación" cuando realmente quieren decir "relación".
Jason Baker
Aprenda la teoría de conjuntos y las matemáticas discretas.
1
@ Jase21, personalmente estoy bastante familiarizado con ambos, pero las cosas no triviales en SQL todavía se sienten divertidas. Ninguno de los ejemplos matemáticos limpios se ocupa de cosas extrañas del mundo real. Además, uno puede usar LINQ y, por lo tanto, no molestarse con SQL. Finalmente, para el que pregunta: te acostumbrarás con el tiempo.
Trabajo

Respuestas:

12

La idea detrás de hacer cosas declarativamente es que se supone que debes especificar qué , no cómo .

Para mí, parece que estás en el camino correcto. El problema no es que estés pensando en las cosas de manera incorrecta. Es que vas demasiado lejos. Veamos lo que estás tratando de hacer:

Por ejemplo, supongamos que tiene la banda de relaciones (bandName, bandCountry), sede (placeName ,placeCountry), juega (bandName ,placeName), y quiero escribir una consulta que diga: todos los nombres de sede de tal manera que para cada bandCountry haya una banda de ese país que juega en el lugar de ese nombre.

Hasta ahora, esto es genial. Pero luego haces esto:

En mi mente, inmediatamente voy "para cada Nombre del lugar iterar sobre todos los BandCountries y para cada bandCountry obtener la lista de las bandas que provienen de él. Si ninguno de ellos juega en el Lugar del Nombre, vaya al siguiente Lugar del Nombre. De lo contrario, al final de los BandCountries iteración agregarplaceName al conjunto de buenos lugaresNombre ".

En esencia, estás haciendo un trabajo innecesario. Sabes lo que quieres, que es todo lo que realmente necesitas. Pero luego continúas e intentas descubrir cómo conseguirlo.

Si fuera usted, trataría de adquirir el siguiente hábito:

  1. Define lo que quieres.
  2. Deténgase conscientemente de definir cómo obtenerlo.
  3. Descubre cómo representar lo que quieres en SQL.

Puede tomar algo de tiempo y esfuerzo de su parte, pero una vez que realmente comprende la programación declarativa, se vuelve muy útil. De hecho, es posible que te encuentres usando programación declarativa en el resto de tu código.

Si está buscando un libro, le recomendaría SQL y la teoría relacional . Realmente te ayuda a entender la teoría detrás de las bases de datos SQL. Solo recuerde tomar las recomendaciones de Date con un grano de sal. Da muy buena información, pero a veces puede ser un poco testarudo.

Jason Baker
fuente
No entiendo cómo averiguar cómo obtener algo es un enfoque incorrecto. No importa qué tipo de lenguaje esté usando, tiene que descubrir cómo decirle que haga lo que quiere.
davidk01 01 de
9

pensar en términos de conjuntos, no iteradores; las instrucciones sql definen las propiedades del conjunto de salida deseado (también conocido como tabla / relación)

Todos los lugares Nombres de tal manera que para cada banda País hay una banda de ese país que toca en el lugar de ese nombre

El resultado de esto (¡si entendiera sus intenciones correctamente!) sería el conjunto de lugares que tienen al menos una banda que toca en ese lugar. La iteración sobre bandCountry es innecesaria, ya que la relación PLAYS ya tiene la información que busca, solo tiene que eliminar los duplicados

entonces en SQL esto sería:

select 
    distinct venueName
from PLAYS

EDITAR: ok, entonces el conjunto real deseado es un poco más complicado. La pregunta que se hace a la base de datos es: ¿qué lugares han acogido bandas de todos los países?

Por lo tanto, definimos los criterios de membresía para un elemento del conjunto deseado como el objetivo, luego trabajamos hacia atrás para completar el conjunto. Un lugar es un miembro del conjunto de salida si tiene una fila PLAYS para al menos una banda de cada país. ¿Cómo obtenemos esta información?

Una forma es contar los distintos países para cada lugar y compararlo con el recuento de todos los países. Pero no tenemos una relación PAÍS. Si pensamos en el modelo dado por un momento, vemos que el conjunto de todos los países no es el criterio correcto; Es el conjunto de todos los países que tienen al menos una banda. Por lo tanto, no necesitamos una tabla de países (aunque para un modelo normalizado deberíamos tener una), y no nos importa el país del lugar, solo podemos contar los países que tienen bandas, por ejemplo (en MS-SQL )

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

Podemos contar los países de la banda para cada lugar

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

y podemos juntar los dos usando una 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

Ahora, esa no es la consulta más bonita posible (GROUP BY y HAVING podrían considerarse una solución más 'elegante' que las variables temporales y una subconsulta) pero es bastante obvio lo que buscamos, así que lo dejaremos así para el propósito del OP .

El objetivo del OP era aprender cómo cambiar la mentalidad de imperativo a declarativo. Con ese fin, observe lo que estaba haciendo la solución imperativa descrita:

para cada nombre de lugar iterar sobre todos los BandCountries y para cada bandCountry obtener la lista de bandas que provienen de él. Si ninguno de ellos juega en el nombre del lugar, vaya al siguiente nombre del lugar. De lo contrario, al final de la iteración bandCountries, agregueplaceName al conjunto de buenos lugaresNames

¿Cuál es el criterio determinante en lo anterior? Creo que es:

... Si ninguno de ellos [el conjunto de bandas de un país en particular] toca en el lugar Nombre ...

Este es un criterio descalificador . El proceso de pensamiento imperativo está comenzando con un balde lleno y tirando cosas que no cumplen con los criterios. Estamos filtrando datos.

Eso está bien para cosas simples, pero ayuda a pensar en términos de construir el conjunto de resultados deseado; ¿Cuál es el criterio de calificación correspondiente que le permitiría llenar el balde?

  • descalificador: si no hay una banda de un país de banda que toca en un lugar, el lugar está descalificado
  • calificador (parcial): si al menos una banda de un bandCountry toca en un lugar, entonces el lugar podría estar bien; sigue revisando el resto de la banda
  • calificador (completo): si al menos una banda de cada grupo juega en un lugar, entonces el lugar está calificado

El calificador final se puede simplificar usando conteos: un país de banda está 'satisfecho' si al menos una banda de allí toca en un lugar; el número de países de banda "satisfechos" para un lugar debe ser igual al número de países de banda para que el lugar sea calificado.

Ahora podemos razonar a través de las relaciones por navegación:

  • comenzar con la relación VENUE [no la necesitamos para la respuesta, pero es el punto de partida conceptual para la navegación relacional]
  • únete a PLAYS en el lugar
  • únete a BAND en bandName para obtener la banda
  • no nos importa el nombre de la banda; seleccione solo el lugar Nombre y banda País
  • no nos importan los BandCountries redundantes; eliminar duplicados usando DISTRICT o GROUP BY
  • solo nos importa el recuento de distintos países de la banda, no los nombres
  • solo queremos lugares donde el recuento de bandCountries distintos sea el mismo que el número total de bandCountries

que conduce de nuevo a la solución anterior (o un facsímil razonable de la misma)

RESUMEN

  • teoría de conjuntos
  • rutas de navegación relacionales
  • criterios inclusivos vs exclusivos (calificativos vs descalificadores)
Steven A. Lowe
fuente
En realidad, es "un conjunto de lugares donde las bandas de todos los países (bandCountry> =placeCountry) tocaron en ellos".
EpsilonVector
@EpsilonVector: ver ediciones
Steven A. Lowe
4

Una forma de aprender a pensar y programar en un estilo declarativo es aprender un lenguaje de matriz de propósito general como APL o J. SQL probablemente no sea el mejor vehículo para aprender a programar declarativamente. En APL o J, aprende a operar en matrices enteras (vectores, matrices o matrices de rango superior), sin iteraciones ni bucles explícitos. Esto hace que la comprensión de SQL y álgebra relacional sea mucho más fácil. Como un ejemplo muy simple, para seleccionar elementos de un vector V cuyo valor es mayor que 100, en APL escribimos:

(V>100)/V

Aquí V> 100 se evalúa como una matriz booleana de la misma forma que V, con 1 marcando los valores que queremos mantener. Al APLer experimentado no se le ocurre una iteración, solo estamos aplicando una máscara al vector V, devolviendo un nuevo vector. Esto, por supuesto, es conceptualmente lo que está haciendo una operación de restricción de cláusula o álgebra relacional.

No creo que puedas controlar bien la programación declarativa sin hacer mucho, y SQL generalmente es demasiado específico. Debe escribir una gran cantidad de código de propósito general, aprender a prescindir de bucles y estructuras if / then / else y todo el aparato que atiende a la programación imperativa, de procedimiento y de estilo escalar.

Puede haber otros lenguajes funcionales que también ayudan con esta forma de pensar, pero los lenguajes de matriz están muy cerca de SQL.

PAUL Mansour
fuente
+1 para "[no se puede] obtener un buen agarre ... sin hacer mucho". Nadie aprendió programación imperativa (con sus construcciones claramente contraintuitivas como a = a + 1) de la noche a la mañana tampoco. Lleva tiempo aprender estilos declarativos como lógica, funcional, etc., al igual que tomó tiempo aprender la programación imperativa.
SOLO MI OPINIÓN correcta
1

Primero, tienes que aprender ambos. Es posible que tenga una preferencia, pero cuando trabaje en áreas donde el otro es mejor, no luche. Muchos programadores están tentados a usar cursores en bases de datos relacionales, ya que están acostumbrados a recorrer cada registro, pero la base de datos es mucho mejor en los conjuntos. No quieres entrar en la mentalidad de "Sé cómo hacerlo de esta manera y tengo el mayor control, bla, bla, bla".

JeffO
fuente
1

Aprende a pensar declarativamente como aprendió a pensar de manera imperativa: practicando comenzando con problemas más simples y avanzando a medida que lo "entiende".

Sus primeras experiencias con la programación imperativa incluyeron un montón de declaraciones contra-intuitivas (y, de hecho, completamente ridículas) como " a = a + 1". Envolviste tu mente en eso hasta el punto de que ahora probablemente ni siquiera recuerdes el retroceso de la obvia falsedad de la declaración. Su problema con los estilos declarativos es que volvió a donde estaba cuando comenzó con los estilos imperativos: un "nuevo despistado". Peor aún, tiene años de práctica con un estilo que está totalmente en desacuerdo con este nuevo estilo y tiene años de hábitos para deshacer, como el hábito de "controlar a toda costa".

Los estilos declarativos funcionan con un enfoque diferente al que le falta intuición por ahora (a menos que haya mantenido sus habilidades matemáticas muy afiladas a lo largo de los años, lo que la mayoría de las personas no). Tienes que volver a aprender cómo pensar y la única forma de volver a aprender es hacerlo, un simple paso a la vez.

Elegir SQL como su primera incursión en la programación declarativa puede ser un error si realmente quiere aprender los conceptos. Claro, el cálculo de la tupla en el que se basa es tan declarativo como se pone, realmente, pero desafortunadamente la pureza del cálculo de la tupla se ha visto gravemente comprometida por las realidades de implementación y el lenguaje, en efecto, se ha vuelto un poco confuso. Es posible que desee ver otros lenguajes declarativos más directamente útiles (en el sentido en que está acostumbrado) como Lisps (especialmente Esquema ), Haskell y los ML para la programación funcional (en su mayoría) o, como alternativa, Prolog y Mercury para (principalmente) programación lógica.

Aprender estos otros idiomas le dará una mejor idea, en mi opinión, sobre cómo funciona la programación declarativa por algunas razones:

  1. Son útiles para la programación "desde la cuna a la tumba", ya que puedes escribir un programa completo en estos idiomas de principio a fin. Son útiles por sí solos, a diferencia de SQL, que es realmente bastante inútil para la mayoría de las personas como lenguaje independiente.

  2. Cada uno de ellos le da una inclinación diferente en la programación declarativa que puede darle diferentes caminos para finalmente "conseguirlo".

  3. Además, cada uno le da una perspectiva diferente al pensar en la programación en general. Mejorarán su capacidad de razonar sobre problemas y codificación, incluso si nunca los usa directamente.

  4. Las lecciones que aprenda de ellos también lo ayudarán con su SQL, especialmente si repasa el cálculo de la tupla detrás de las bases de datos relacionales para la forma pura de pensar sobre los datos.

Recomiendo especialmente aprender uno de los lenguajes funcionales ( Clojure , como uno de los Lisps, es probablemente una buena opción aquí) y uno de los lenguajes lógicos (me gusta más Mercury, pero Prolog tiene mucho más material útil para aprender) para la máxima expansión del proceso de pensamiento.

SOLO MI OPINIÓN correcta
fuente
1

No está mal pensar imperativamente en un entorno declarativo como SQL. Es solo que el pensamiento imperativo debería estar sucediendo a un nivel un poco más alto de lo que describiste. Siempre que necesito consultar una base de datos que usa SQL, siempre pienso en mí mismo:

  • Aquí están las piezas que necesito.
  • Los voy a juntar de esta manera.
  • Voy a reducir lo que acabo de obtener con los siguientes predicados para llegar a lo que realmente estoy buscando.

Lo anterior es un algoritmo imperativo de alto nivel y funciona bastante bien para mí en la configuración de SQL. Creo que esto se considera un enfoque de arriba hacia abajo y Steven A. Lowe describe un enfoque bastante bueno de abajo hacia arriba .

davidk01
fuente
1

La clave de su pregunta está en lo que dijo en el penúltimo párrafo: "No se puede hablar así en SQL". Puede ser más útil para usted en esta etapa acercarse a SQL como un idioma extranjero en lugar de un lenguaje de programación. Si lo piensa de esta manera, escribir una consulta SQL realmente está traduciendo una declaración en inglés de lo que quiere en "SQLish". La computadora entiende perfectamente SQLish y hará exactamente lo que usted dice, por lo que no debe preocuparse por la implementación siempre que traduzca correctamente.

Dicho esto, ¿cuál es la mejor manera de aprender un idioma extranjero? Obviamente, debe aprender la gramática y el vocabulario, que puede obtener de su documentación SQL. Lo más importante es la práctica. Debería leer y escribir tanto SQL como sea posible, y no sienta que primero debe conocer la sintaxis a fondo; puedes y debes buscar cosas a medida que avanzas. Sabrá que lo tiene cuando le resulte más fácil describir qué datos desea en SQL que en inglés.

Larry Coleman
fuente
1

También me llevó mucho tiempo comprender SQL. Hicimos alguna teoría relacional en la universidad y en ese momento, que solo sirvió para complicar las cosas. Al final, mi proceso de aprendizaje fue muy basado en pruebas y errores gracias a varios materiales de aprendizaje y ejemplos que encontré útiles en el camino. Esencialmente, eventualmente te acostumbrarás, y agregar una nueva forma de pensar sobre datos y consultas será de algún valor para tu desarrollo mental.

Descubrí que podía acelerar mi aprendizaje al construir gradualmente una colección de scripts simples que demostraban cómo usar cada función del lenguaje y cómo lograr ciertos resultados en una tabla conocida (las definiciones de tabla se incluyen como referencia).

A principios de este año realicé una capacitación formal que involucraba un proyecto de migración de datos en una desordenada base de datos Oracle, donde tuve que juntar gradualmente fragmentos de mi biblioteca para filtrar los resultados de la consulta de varias maneras hasta que obtuve exactamente lo que quería, luego transformarlos como necesario, y así sucesivamente. Algunas de las consultas se volvieron muy complejas y difíciles de depurar. Dudo que pueda leerlos ahora, pero espero poder llegar a una solución similar nuevamente usando mis bloques de construcción de referencia.

Otras formas de aumentar su conocimiento intuitivo de los espacios declarativos y funcionales son el aprendizaje de la teoría de conjuntos y los lenguajes de programación más adecuados para un paradigma particular. Actualmente estoy en el proceso de aprender algo de Haskell, por ejemplo, para mantener y mejorar mis habilidades matemáticas.

IanGilham
fuente
0

Cuando enfrenta un problema, generalmente piensa cómo resolverlo. ¡Pero si sabes cómo la computadora lo resuelve por ti! Entonces te preocupa cómo serán eliminados.

Trato de decir cómo sucede.

Es posible que ya esté familiarizado con los programas recursivos, en los programas recursivos, usted define el problema en lugar de decir cómo se resuelve. Usted define la base y define n basado en n-1 . (por ejemplo factorial(n) = n * factorial(n-1)) Pero ya puede saber cómo lo resuelve la computadora. comienza desde la función y llama a la función de forma recursiva hasta que alcanza una definición base, luego evalúa todas las demás funciones en función del valor base.

Es lo que sucede en la programación declarativa. define todo en función de las definiciones existentes. Y la computadora sabe cómo derivar la respuesta en función de las funciones básicas.

En SQL, es posible que no relacione las definiciones entre sí, pero relaciona objetos o información entre sí, especifica lo que desea y la computadora busca algo (objeto, información) en función de las relaciones que ha proporcionado.

Ahmad
fuente