En los peligros de las escuelas de Java, Joel analiza su experiencia en Penn y la dificultad de las "fallas de segmentación". Él dice
[los valores predeterminados son difíciles hasta que usted] "respire profundamente y realmente intente forzar a su mente a trabajar en dos niveles diferentes de abstracción simultáneamente".
Dada una lista de causas comunes de segfaults, no entiendo cómo tenemos que trabajar en 2 niveles de abstracción.
Por alguna razón, Joel considera que estos conceptos son fundamentales para la capacidad de abstracción de un programador. No quiero asumir demasiado. Entonces, ¿qué es tan difícil acerca de los punteros / recursividad? Los ejemplos serían buenos.
Respuestas:
Primero me di cuenta de que los consejos y la recursión eran difíciles en la universidad. Había tomado un par de cursos típicos de primer año (uno era C y Assembler, el otro estaba en Scheme). Ambos cursos comenzaron con cientos de estudiantes, muchos de los cuales tenían años de experiencia en programación de nivel secundario (típicamente BASIC y Pascal, en esos días). Pero tan pronto como se introdujeron los punteros en el curso C, y se introdujo la recursividad en el curso Scheme, una gran cantidad de estudiantes, tal vez incluso la mayoría, quedaron completamente desconcertados. Estos eran niños que habían escrito MUCHO código antes y no tenían ningún problema, pero cuando golpearon punteros y recursividad, también golpearon una pared en términos de su capacidad cognitiva.
Mi hipótesis es que los punteros y la recursividad son los mismos, ya que requieren que mantenga dos niveles de abstracción en su cabeza al mismo tiempo. Hay algo acerca de los múltiples niveles de abstracción que requiere un tipo de aptitud mental que es muy posible que algunas personas nunca tengan.
También estaría perfectamente dispuesto a aceptar que es posible enseñar consejos y / o recurrencia a cualquiera ... No tengo ninguna evidencia de una manera u otra. Sé que, empíricamente, ser capaz de comprender realmente estos dos conceptos es un muy, muy buen predictor de la capacidad de programación general y que, en el curso normal de la formación de CS de pregrado, estos dos conceptos son algunos de los mayores obstáculos.
fuente
La recursión no es solo "una función que se llama a sí misma". No vas a apreciar realmente por qué la recursividad es difícil hasta que te encuentres sacando cuadros de pila para descubrir qué salió mal con tu analizador de descenso recursivo. A menudo tendrá funciones recursivas recíprocas (la función A llama a la función B, que llama a la función C, que puede llamar a la función A). Puede ser muy difícil descubrir qué salió mal cuando tienes N stackframes en lo profundo de una serie de funciones recursivas mutuamente.
En cuanto a los punteros, nuevamente, el concepto de punteros es bastante simple: una variable que almacena una dirección de memoria. Pero, de nuevo, cuando algo falla con su estructura de datos complicada de
void**
punteros que apuntan a diferentes nodos, verá por qué puede ser complicado a medida que lucha por descubrir por qué uno de sus punteros apunta a una dirección basura.fuente
goto
.goto
.int a() { return b(); }
puede ser recursivo, pero depende de la definición deb
. Así que no es tan simple como parece ...Java admite punteros (se llaman referencias) y admite recursividad. En la superficie, su argumento parece inútil.
De lo que realmente está hablando es de la capacidad de depurar. Se garantiza que un puntero Java (err, referencia) apunta a un objeto válido. El puntero de CA no lo es. Y el truco en la programación en C, suponiendo que no use herramientas como valgrind , es averiguar exactamente dónde atornilló un puntero (rara vez se encuentra en el punto que se encuentra en un stacktrace).
fuente
El problema con los punteros y la recursividad no es que sean necesariamente difíciles de entender, sino que se les enseña mal, especialmente con respecto a lenguajes como C o C ++ (principalmente porque los idiomas mismos se están enseñando mal). Cada vez que escucho (o leo) que alguien dice "una matriz es solo un puntero" muero un poco por dentro.
Del mismo modo, cada vez que alguien usa la función Fibonacci para ilustrar la recursión, quiero gritar. Es un mal ejemplo porque la versión iterativa no es más difícil de escribir y funciona al menos tan bien o mejor que la recursiva, y no te da una idea real de por qué una solución recursiva sería útil o deseable. Quicksort, transversal del árbol, etc., son ejemplos mucho mejores del por qué y cómo de la recursividad.
Tener que manipular los punteros es un artefacto de trabajar en un lenguaje de programación que los expone. Generaciones de programadores de Fortran estaban construyendo listas y árboles, pilas y colas sin necesidad de un tipo de puntero dedicado (o asignación de memoria dinámica), y nunca escuché a nadie acusar a Fortran de ser un lenguaje de juguete.
fuente
GOTO target
) . Sin embargo, creo que tuvimos que construir nuestras propias pilas de tiempo de ejecución. Esto fue hace tanto tiempo que ya no puedo recordar los detalles.Hay varias dificultades con los punteros:
Es por eso que un programador debe pensar más a fondo cuando usa punteros (no sé acerca de los dos niveles de abstracción ). Este es un ejemplo de los errores típicos cometidos por un novato:
Tenga en cuenta que un código como el anterior es perfectamente razonable en lenguajes que no tienen un concepto de punteros, sino uno de nombres (referencias), objetos y valores, como los lenguajes de programación funcionales y los lenguajes con recolección de basura (Java, Python). .
La dificultad con las funciones recursivas ocurre cuando las personas sin suficientes conocimientos matemáticos (donde la recursividad es común y se requiere conocimiento) intentan abordarlas pensando que la función se comportará de manera diferente dependiendo de cuántas veces se haya llamado antes . Ese problema se agrava porque las funciones recursivas se pueden crear de una manera en la que hay que pensar de esa manera para comprenderlas.
Piense en funciones recursivas con punteros que se pasan, como en una implementación de procedimiento de un árbol rojo-negro en el que la estructura de datos se modifica en el lugar; Es algo más difícil de pensar que una contraparte funcional .
No se menciona en la pregunta, pero el otro problema importante con el que los principiantes tienen dificultades es la concurrencia .
Como otros han mencionado, hay un problema adicional no conceptual con algunas construcciones de lenguaje de programación: es que incluso si entendemos, los errores simples y honestos con esas construcciones pueden ser extremadamente difíciles de depurar.
fuente
malloc()
es más probable que cualquier otra función que lo haga)Los punteros y la recursión son dos bestias separadas y hay diferentes razones que califican a cada una como "difícil".
En general, los punteros requieren un modelo mental diferente que la asignación de variable pura. Cuando tengo una variable de puntero, es solo eso: un puntero a otro objeto, los únicos datos que contiene es la dirección de memoria a la que apunta. Entonces, por ejemplo, si tengo un puntero int32 y le asigno un valor directamente, no estoy cambiando el valor de int, estoy apuntando a una nueva dirección de memoria (hay muchos trucos geniales que puedes hacer con esto ) Aún más interesante es tener un puntero a un puntero (esto es lo que sucede cuando pasa una variable Ref como parámetro de función en C #, la función puede asignar un objeto completamente diferente al parámetro y ese valor aún estará en el alcance cuando la función salidas
La recursión da un pequeño salto mental cuando aprende por primera vez porque está definiendo una función en términos de sí misma. Es un concepto salvaje cuando lo encuentras por primera vez, pero una vez que captas la idea, se convierte en una segunda naturaleza.
Pero volvamos al tema en cuestión. El argumento de Joel no se trata de punteros o recursividad en sí mismos, sino más bien del hecho de que los estudiantes están siendo retirados más de cómo funcionan realmente las computadoras. Esta es la Ciencia en Informática. Hay una diferencia clara entre aprender a programar y aprender cómo funcionan los programas. No creo que se trate tanto de "Lo aprendí de esta manera, así que todos deberían tener que aprenderlo de esta manera" como él, argumentando que muchos programas de CS se están convirtiendo en escuelas de comercio glorificadas.
fuente
Le doy a P. Brian un +1, porque siento que lo hace: la recursividad es un concepto tan fundamental que el que tiene las más mínimas dificultades debería considerar buscar un trabajo en mac donalds, pero incluso si hay recidiva:
Seguramente, la falta de comprensión también tiene que ver con nuestras escuelas. Aquí uno debería introducir números naturales como lo hicieron Peano, Dedekind y Frege, para que no tengamos muchas dificultades más tarde.
fuente
goto top
por alguna razón IME.No estoy de acuerdo con Joel en que el problema es pensar en múltiples niveles de abstracción per se, creo que es más que los punteros y la recursividad son dos buenos ejemplos de problemas que requieren un cambio en el modelo mental que tienen las personas sobre cómo funcionan los programas.
Los punteros son, creo, el caso más simple para ilustrar. Tratar con punteros requiere un modelo mental de ejecución de programas que explique la forma en que los programas funcionan realmente con direcciones de memoria y datos. Mi experiencia ha sido que muchas veces los programadores ni siquiera han pensado en esto antes de aprender sobre los punteros. Incluso si lo saben en sentido abstracto, no lo han adoptado en su modelo cognitivo de cómo funciona un programa. Cuando se introducen los punteros, se requiere un cambio fundamental en la forma en que piensan sobre cómo funciona el código.
La recursión es problemática porque hay dos bloques conceptuales para comprender. El primero es a nivel de máquina, y al igual que los punteros, se puede superar desarrollando una buena comprensión de cómo se almacenan y ejecutan realmente los programas. El otro problema con la recursividad es, creo, que las personas tienen una tendencia natural a tratar de deconstruir un problema recursivo en uno no recursivo, lo que enturbia la comprensión de una función recursiva como gestalt. Este es un problema con personas que tienen una base matemática insuficiente o un modelo mental que no vincula la teoría matemática con el desarrollo de programas.
La cuestión es que no creo que los indicadores y la recursividad sean las dos únicas áreas problemáticas para las personas atrapadas en un modelo mental insuficiente. El paralelismo parece ser otra área en la que algunas personas simplemente se quedan atrapadas y tienen dificultades para adaptar su modelo mental para tener en cuenta, es solo que a menudo los punteros y la recursividad son fáciles de evaluar en una entrevista.
fuente
El concepto de datos y código autorreferenciales subyace a la definición de punteros y recursividad, respectivamente. Desafortunadamente, la exposición generalizada a los lenguajes de programación imperativos ha llevado a los estudiantes de ciencias de la computación a creer que deben comprender la implementación a través del comportamiento operativo de sus tiempos de ejecución cuando deben confiar este misterio al aspecto funcional del lenguaje. Sumar todos los números hasta cien parece una simple cuestión de comenzar con uno y agregarlo al siguiente en la secuencia y hacerlo al revés con la ayuda de funciones circulares autorreferenciales parece perverso e incluso peligroso para muchos no acostumbrados a la seguridad de funciones puras.
El concepto de datos y código auto modificables subyace a la definición de objetos (es decir, datos inteligentes) y macros, respectivamente. Menciono estos, ya que son aún más difíciles de entender, especialmente cuando se espera una comprensión operativa del tiempo de ejecución de una combinación de los cuatro conceptos, por ejemplo, una macro que genera un conjunto de objetos que implementa un analizador recursivo decente con la ayuda de un árbol de punteros. . En lugar de rastrear paso a paso toda la operación del estado del programa a través de cada capa de abstracción a la vez, los programadores imperativos deben aprender a confiar en que sus variables solo se asignan una vez dentro de las funciones puras y que las invocaciones repetidas de la misma función pura con los mismos argumentos siempre producen el mismo resultado (es decir, transparencia referencial), incluso en un lenguaje que también admite funciones impuras, como Java. Correr en círculos después del tiempo de ejecución es un esfuerzo infructuoso. La abstracción debería simplificarse.
fuente
Muy similar a la respuesta de Anon.
Además de las dificultades cognitivas para los novatos, tanto los punteros como la recursividad son muy poderosos y se pueden usar de forma críptica.
La desventaja de un gran poder es que te dan un gran poder para arruinar tu programa de manera sutil.
Almacenar un valor falso en una variable normal es bastante malo, pero almacenar algo falso en un puntero puede causar que ocurran todo tipo de cosas catastróficas demoradas.
Y lo que es peor, esos efectos pueden cambiar a medida que intenta diagnosticar / depurar cuál es la causa del comportamiento extraño del programa.
Del mismo modo con la recursividad. Puede ser una forma muy poderosa de organizar cosas complicadas, al introducir el truco en la estructura de datos ocultos (pila).
Pero, si se hace algo sutilmente incorrecto, puede ser difícil darse cuenta de lo que está sucediendo.
fuente