domingo, 29 de noviembre de 2015

8. Primero esto, después esto, después esto otro,... ¿o no?

Pensamiento denotacional vs. pensamiento operacional y su importancia en la abstracción.

 
  En varios posts anteriores hemos insistido con la importancia del pensamiento denotacional por sobre el pensamiento operacional. ¿Por qué insistimos tanto en esto? La razón, que discutiremos en este post, es que las personas NO solemos pensar de manera operacional, sino de manera denotacional, y por lo tanto entender y manejar los programas de manera denotacional es fundamental para poder lograr una verdadera habilidad de programación.

  Es innegable que los programas son, en el fondo, entidades operacionales; o sea, un programa es, si lo reducimos a su forma más elemental, un elemento destinado a guiar a una máquina durante su ejecución. La forma tradicional en la que interpretamos esta naturaleza operacional es a través de una secuencia de instrucciones (aunque hoy día, con máquinas de múltiples procesadores, ya no sea cierto que un programa es simplemente una secuencia de instrucciones).
  Consecuentemente, la mayoría de los cursos tradicionales de programación comienzan con esta definición, y se concentran en determinar la secuencia adecuada para resolver determinado problema. Es notable que la gran mayoría de las personas cuando se enfrentan a un problema simple de programación, empiezan por pensar de esta manera. En una conversación reciente sobre este fenómeno, un docente de programación sugirió que podía ser así por la forma en que están planteados los problemas... Es un tema fascinante que da para mayor indagación.

  Sin embargo, cuando las personas pensamos en solucionar un problema, no lo hacemos de forma operacional. Si por ejemplo se plantease como problema "realizar un viaje a un país vecino", no es natural empezar pensando en levantarse de la silla, caminar hasta la puerta, subir al auto, etc., aunque estas son operaciones que DEBERÁN ser parte de la solución. Es mucho más natural para una persona pensar en algunas actividades grandes vinculadas al viaje, por ejemplo, decidir el medio de transporte, decidir el tipo de alojamiento que vamos a utilizar, decidir qué tipo de cosas vamos a llevar con nosotros. Luego, cada una de ellas involucra nuevas tareas: por ejemplo, si decidimos que el medio de transporte es por avión, deberemos reservar pasajes, decidir la forma de pago, ver cómo nos movilizaremos al aeropuerto, etc. En lugar de concentrarnos en las operaciones invididuales que serán necesarias para solucionar el problema, lo que hacemos naturalmente es *descomponer el problema en subproblemas* cuyas soluciones combinadas permitan solucionar el problema mayor. A esto es a lo que nos referimos cuando hablamos del *pensamiento denotacional*.

  ¿Por qué si nuestra forma de pensar es naturalmente denotacional, cuando pensamos un programa lo hacemos de manera operacional? La respuesta es simple: en la definición clásica se le da más importancia a la máquina que soluciona el problema que al problema en sí, o a su solución. Son las operaciones primitivas de la máquina las que pasan a primer plano, y para ejemplos pequeños resulta más fácil pensar en esos términos. ¿Puede hacerse de otra manera? Creemos que sí, y por eso el enfoque didáctico Gobstones utiliza otra definición del concepto de programa, y otro enfoque para comenzar a pensar los programas.

  Nuestra propuesta de definición para el concepto de programa es **descripción ejecutable** de la solución a un problema computacional. Esta definición es suficientemente general, e incluye por partes iguales los aspectos denotacional y operacional. Así como no es sabio focalizarse exclusivamente en el comportamiento operacional de un programa, tampoco lo es hacerlo en el sentido opuesto: un programador tiene que formarse para poder ver *ambos* niveles y focalizarse en el que haga falta en cada momento. Sin embargo, y dado que el aspecto operacional se impone de una manera ineludible, nuestra propuesta didáctica resalta mucho más el aspecto denotacional, e intenta que el mismo sea tenido en cuenta desde el principio.

  De esta forma, la propuesta incluye comenzar enseñando procedimientos (ver "¡Dame más comandos!"), que las expresiones y los comandos sean enseñados en conjunto, sin privilegiar a ninguno de ellos (ver "¿Comando mata expresión?"), que la representación de información se pueda abstraer soslayando las definiciones con bolitas (ver "¡Está lleno de bolitas! (o "It's full of stones")"), así como otra serie de elementos que permiten poner el foco en el aspecto denotacional y que serán tema de futuros posts (precondiciones, ausencia de debugging, definición precisa de nociones como parámetros o formas de repetición, etc.)

  Para ir cerrando el post, creemos que la conceptualización de un programa como una descripción de la solución a un problema, o sea, su naturaleza denotacional es un elemento imprescindible en la formación de pensamiento abstracto, ya que permite soslayar detalles irrelevantes de la ejecución para concentrarse en qué solución se describe. Dicho en otras palabras, el pensamiento denotacional es una forma de abstraer los programas, y por ello es tan importante a la hora de enseñar abstracción.

  Gobstones tiene esto en cuenta, y por eso es importante.

 

8 comentarios:


  1. El Lenguaje quizás tiene algo de culpa también. Considerá un lenguaje de programación funcional (fragmento puro) donde no hay comandos y mucho menos secuencia. ¿No sería más fácil enseñar a programar en ese marco donde la única visión es la denotacional habida cuenta que postulás que es la manera "natural" de pensar? En experiencias pasadas en la UNQ enseñando un primer curso de programación den Haskell, los resultados no fueron muy alentadores.

    Será que la visión operacional es más elemental en su naturaleza (de hecho en Gobstones primero aprenden lo que es un comando y secuenciación). Quizás tenga que ver con el aprendizaje a través de la observación. Los comandos causan efectos. Los efectos son observables. Eso induce a la experimentación para lograr efectos deseados. Y todo ello es bien operacional. Entender esto es prerequisito para poder pensar de manera denotacional. Quizás ese sea el motivo por el cual los resultados con Haskell no fueron alentadores.

    ResponderEliminar
  2. Es algo que vale la pena discutir y que de hecho ya está planificado sea tema de algún post futuro.
    Mi impresión es que la programación funcional es demasiado abstracta en su naturaleza, y por ello si el objetivo es enseñar a abstraer, es complicado arrancar por ella. Tiene más sentido si el estudiante trae una base previa de matemáticas, pero si no suponemos eso, comenzar por PF, como vos decís, no arroja buenos resultados, justamente por esa naturaleza abstracta.

    Por otro lado es cierto que los efectos son más observables y por lo tanto más concretos, y eso produce una inmediatez que, en mi opinión, los vuelve aptos para comenzar. Por eso Gobstones comienza por ahí. Sin embargo, la intención de este post es remarcar que la enseñanza de programación no debe quedarse pegada a esta naturaleza concreta, sino intentar por todos los medios despegarse de ella, y poner el foco en la visión denotacional, justamente para poder formar ese pensamiento abstracto que es el objetivo.

    Esto es lo que intento decir en el post: todas las herramientas conceptuales deben estar sincronizadas con esta visión denotacional, para evitar incurrir en el defecto de pensar los programas comando a comando, por prueba y error. Dado que no es algo común en la mayoría de los cursos, creo que es importante poner este énfasis. No veo al lenguaje como "culpable", sino como vehículo para, comenzando por ahí, llegar a otro destino. ¡¡El tema es no extraviarse por el camino!!

    ResponderEliminar
  3. Hola,

    por lo que entiendo, el énfasis de la diferencia entre lo que se llama "enfoque operacional" y "enfoque denotacional" está en la granularidad, si pensás cada instrucción, o si sos capaz de pensar a más alto nivel, definiendo los elementos (procedimientos, funciones, estructuras de datos) que te permitan trabajar a ese nivel más alto.
    Hay otros dos conceptos que, al menos para mí, están ligados a la diferencia entre enfoques: los de secuencia u orden, y estado.
    Hasta donde yo entiendo, en un enfoque operacional la secuencia de los elementos que componen un programa es relevante, y también hay una conciencia clara de que hay un estado que se va modificando, que tiene un estado inicial, uno final, y muchos intermedios. Un enfoque denotacional se concentra más en pensar a un programa como una función, entra un valor, sale otro.

    Por lo que conozco de Gobstones, y de cómo se da la materia basada en Gobstones en la Univ. Nacional de Quilmes, se le da importancia a ambos aspectos del "enfoque denotacional". Se alienta a pensar en procedimientos y funciones, se desalienta a pensar en la secuencia específica, por eso no hay un debugger de Gobstones, y tampoco interesa que lo haya, ni ninguna forma de ver cómo está el tablero "en el medio" de un programa. También por eso las estructuras de datos son inmutables, y no hay break.

    Creo que conviene separar estos dos aspectos. En lo personal, estoy de acuerdo en subir el nivel, pero no en darle poca relevancia a las ideas de secuencia y estado. En un programa Gobstones, y en la abrumadora mayoría de los programas con los que los estudiantes van a tener que lidiar, la secuencia es relevante, y hay un estado que se va transformando paulatinamente. Yo quiero que los estudiantes puedan hacer una "prueba de escritorio" de un programa sencillo, y que eso los pueda ayudar a entender por qué un programa no hace lo que ellos quieren. Poder ver un problema en un programa de una forma puramente denotacional, "porque no dice lo que querías decir", implica un nivel de razonamiento matemático alto, que la mayor parte de los alumnos sencillamente no está en condiciones de hacer.
    En mi experiencia, un buen punto de entrada al enfoque denotacional son las funciones booleanas. Inducir a que los estudiantes se pregunten "cuándo quiero que devuelva True", incluyendo operadores lógicos, y después a escribir eso, sin pensar en secuencia ni estado ni nada.
    Finalmente, creo que las ideas de secuencia y estado por un lado, y de "programa como expresión" por otro, son ambas relevantes en la formación de un programador, y que por lo tanto conviene que ambos enfoques se combinen en un curso inicial sobre programación.

    PD: creo que en particular, un error de condición de borde en un while es prácticamente imposible de explicar sin un poco de prueba de escritorio. Al menos a alumnos de 35 años para arriba.

    ResponderEliminar
  4. Otra cosa: no estoy de acuerdo en que la mayor parte de las personas razone en forma denotacional. En particular, la idea de secuencia está siempre presente. Es muy difícil convencer a la mayor parte de la gente de que dé una parte por hecha y que piense en la siguiente, deben razonar parte por parte en el orden en que debe hacerse. Preguntale a un albañil qué es levantar una pared, a un cocinero qué es preparar un pollo a la sal, a un empleado administrativo en qué consiste hacer un balance, a un obrero, o a un ingeniero industrial, que te defina la fabricación de una pieza. Creo que en la abrumadora mayoría de los casos, la respuesta va a ser de la forma "primero se hace esto, después lo otro, después lo de más allá ...".
    Obviamente es una opinión personal, no sabría cómo decidir cuál es la visión más cercana a la realidad.

    ResponderEliminar


  5. Carlos, ¿podrás proveer ejemplos de cómo y cuándo un estudiante precisa sí o sí el enfoque operacional?
    Porque es posible que en cada uno de esos exista un enfoque denotacional que no está siendo visto...

    El caso típico que mencionás, el de las funciones booleanas, por ejemplo, es paradgimático. Tanto que yo acuñé un nombre para denominar al pensamiento operacional aplicado a funciones booleanas: "Miedo al booleano". En gente que aprendió a programar con un enfoque operacional es típico ver el siguiente código:

    function esMayorQueCeroOper(x) {
    if (x>0) then { resultado := True } else { resultado := False }
    return (resultado)
    }

    en lugar del totalmente denotacional

    function esMayorQueDenot(x) { return (x>0) }

    O sea, en lugar de privilegiar la naturaleza denotacional de la expresión (x>0), se les hace NECESARIO utilizar la expresión en una alternativa condicional y asignar una variable de resultado.
    Esto es "miedo al booleano".

    Existen formas aún más graves de miedo al booleano. En la primera, la condición del if necesita ser comparada contra un booleano:

    function mayorQueCeroOperPP(x) {
    if ((x>0)==True) then { exito := True } else { exito := False }
    return (exito)
    }

    pero a mi gusto la más terriblemente operacional, yo diría incluso dañina para el pensamiento denotacional es la que hace uso de la secuencia y el estado de esta manera:

    function esMayorQueCeroOperMalo(x) {
    exito := False
    if (x>0) then { exito := True }
    return (exito)
    }

    TODAS estas formas se han visto tanto en la materia de Introducción a la Programación, como en las subsiguientes. Podrás argumentar que no es necesario que la alternativa condicional se escriba con if-then-else, sino utilizando doble dispatching como se hace en objetos o en lambda cálculo puro, pero en ese caso estamos hablando de formas tan abstractas (y podría decirse "esotéricas") que es complejo que un estudiante inicial las vea. (E incluso, si argumentás de esa manera, de alguna forma me estás dando la razón de que en el caso de los booleanos la secuencia y el estado NO son necesarios; yo estoy convencido de que es así también para otros ejemplos que puedan darse... ;)).

    ResponderEliminar
  6. DISCLAIMER: no sé cómo hacer que este editor me conserve la indentación del código...

    ResponderEliminar
  7. Sobre tu observación acerca de que la gente "piensa" de manera operacional en otras disciplinas, yo creo que no es tan así. El ejemplo del cocinero, por ejemplo, me sirve. Si vos le preguntás cómo hacer el pollo a la sal, seguramente te va a dar los pasos para cocinarlos, y si vos no tenés experiencia en la cocina, vas a tener que deducir por vos mismo muchas cosas. Si en cambio le pedís que explicite COMPLETAMENET en QUÉ COSAS PENSÓ para preparar un pollo a la sal, va a estar incluída la tarea de asegurarse tener los ingredientes, la de precalentar el horno, la de preparar los utensilios necesarios y el espacio adecuado, y la de procesar y combinar los ingredientes... Y si el tipo es un jefe de cocina, con ayudantes, eso se va a evidenciar aún más, porque cada uno de los ayudantes puede ser que procese ingredientes diferentes. O sea, NO es cierto que el cocinero piense operacionalmente; simplemente le es más fácil expresarse operacionalmente por falta de costumbre en explicitar los detalles denotacionales.

    ResponderEliminar
  8. Finalmente, coincidimos en un punto: yo no digo en ningún momento que la secuencia y el estado no vayan a ser importantes, ni que no deban ser tratados, y de hecho la secuencia didáctica Gobstones incluye elementos para razonar de esa forma. Lo que yo digo es que es tan fácil caer en formas operacionales de pensar y son tantos los cursos que hacen foco en eso, que un curso que busca formar pensamiento abstracto DEBE hacer FOCO en el pensamiento denotacional, no desplazar completamente al pensamiento operacional. Fijate que eso está dicho en el post.

    ResponderEliminar