Lenguajes y paradigmas de programación

Sigue un programa desde el código que escribe una persona hasta las instrucciones binarias que ejecuta un procesador y descubre cómo realizan esa traducción los compiladores, los intérpretes, el código de bytes y los compiladores en tiempo de ejecución. Aprende a distinguir la sintaxis de un lenguaje de su significado, cómo comprueban los lenguajes los tipos de datos y dónde guarda sus datos y cómo libera su memoria un programa en ejecución. Después, compara cuatro formas de escribir programas (imperativa, orientada a objetos, funcional y declarativa) resolviendo los mismos problemas con cada una y aprende a elegir un lenguaje adecuado para un proyecto.

PrincipianteUnidad 5 de 7 del curso Curso de Introducción a la informática

Lo que aprenderás

  1. Sigue un programa desde el código que escribe una persona hasta las instrucciones binarias que ejecuta un procesador y compara las formas de realizar esa traducción: de antemano, durante la ejecución del programa o mediante una combinación de ambas.
    • Compara el código máquina, el lenguaje ensamblador y los lenguajes de alto nivel
      Describe los tres niveles en los que se pueden escribir programas, muestra cómo una línea de código de alto nivel se traduce en varias instrucciones máquina y explica por qué la mayor parte del software se escribe en el nivel más alto.
    • Explica qué hace un compilador
      Describe cómo un compilador traduce un programa completo antes de su ejecución, nombra sus etapas principales y explica qué ocurre con los errores y con el archivo ejecutable final.
    • Explica los intérpretes, el código de bytes y la compilación en tiempo de ejecución
      Describe cómo ejecuta un programa un intérprete, cómo combinan la compilación y la interpretación lenguajes como Java y Python y cómo valorar las ventajas e inconvenientes de estos enfoques.
  2. Separa las reglas de escritura del código de las reglas que determinan lo que hace, reconoce las estructuras básicas que comparten la mayoría de los lenguajes de alto nivel y sigue programas breves para predecir sus resultados.
    • Distingue la sintaxis de la semántica
      Explica la diferencia entre la forma de un programa y su significado, distingue los errores de sintaxis, de ejecución y lógicos y detecta cuándo distintos lenguajes dan un significado diferente a un código de aspecto idéntico.
    • Sigue la ejecución de las estructuras básicas comunes a los programas de alto nivel
      Reconoce las estructuras presentes en casi todos los lenguajes de alto nivel (variables, expresiones, flujo de control y funciones con su ámbito) y sigue un programa breve paso a paso para predecir su salida.
  3. Describe cómo clasifican y comprueban los lenguajes los valores según su tipo, dónde guarda sus datos un programa en ejecución y cómo se reserva y libera la memoria, de forma manual o automática, con las ventajas e inconvenientes de seguridad y velocidad de cada opción.
    • Compara cómo usan y comprueban los lenguajes los tipos de datos
      Explica qué es un tipo de dato, compara el tipado estático y dinámico y las reglas de conversión estrictas y flexibles y predice cómo tratan los lenguajes habituales la mezcla de tipos y los límites numéricos.
    • Describe dónde guarda sus datos un programa en ejecución
      Explica las variables como posiciones de memoria con nombre, las funciones de la pila de llamadas y el montículo de memoria y la diferencia entre copiar un valor y compartir una referencia.
    • Compara la gestión manual y automática de memoria
      Explica cómo se libera la memoria en los lenguajes donde la gestiona quien programa y en los que usan recolección de basura o reglas de propiedad de los datos, y relaciona los errores de memoria con fallos de los programas y problemas de seguridad.
  4. Describe los programas como secuencias de órdenes que cambian el estado almacenado y después como conjuntos de objetos que reúnen datos y comportamiento, y utiliza clases, encapsulación, herencia y polimorfismo para organizar el código.
    • Describe los estilos imperativo y procedimental
      Explica la programación imperativa como órdenes paso a paso que cambian el estado de un programa, muestra cómo organizan esas órdenes los procedimientos y el flujo de control estructurado y reconoce los riesgos del estado compartido que cambia.
    • Modela datos y comportamiento mediante clases y objetos
      Explica cómo agrupa la programación orientada a objetos los datos y sus operaciones en objetos, lee y diseña una clase sencilla y utiliza la encapsulación para proteger los datos de un objeto.
    • Reutiliza y amplía clases mediante herencia, polimorfismo y composición
      Utiliza la herencia para construir clases especializadas, explica cómo permite el polimorfismo que una llamada se comporte de forma distinta con diferentes objetos y decide cuándo combinar objetos en lugar de heredar.
  5. Construye programas con funciones que devuelven valores nuevos en lugar de cambiar los existentes y escribe programas que expresan el resultado deseado mientras el motor del lenguaje decide cómo calcularlo.
    • Crea programas a partir de funciones puras y datos inmutables
      Explica las funciones puras, la inmutabilidad, las funciones de primera clase y de orden superior y la recursión, y reescribe un bucle como una cadena de operaciones con map, filter y reduce.
    • Describe el resultado mediante lenguajes declarativos
      Explica la programación declarativa como la expresión del resultado deseado en lugar de cómo obtenerlo y lee ejemplos de un lenguaje de consulta de bases de datos, un lenguaje lógico y otras notaciones declarativas.
  6. Resuelve el mismo problema con distintos estilos, reconoce que la mayoría de los lenguajes modernos mezclan paradigmas y valora los factores técnicos y prácticos que determinan qué lenguaje se adapta a un proyecto.
    • Resuelve un mismo problema con varios paradigmas
      Escribe la misma tarea pequeña con los estilos imperativo, orientado a objetos, funcional y declarativo, compara qué facilita cada versión y reconoce los lenguajes multiparadigma.
    • Elige un lenguaje de programación para una tarea
      Valora los factores que determinan qué lenguaje se adapta a un proyecto (plataforma, dominio, rendimiento, seguridad, ecosistema y personas) y justifica una elección para un caso concreto.

Guía de estudio de Lenguajes y paradigmas de programación

6 capítulos

1

Del código fuente a las instrucciones en ejecución

Un procesador ejecuta pequeñas instrucciones binarias, mientras que las personas suelen escribir programas en lenguajes organizados en torno al problema que quieren resolver. Los traductores conectan estos dos niveles. Sigue el mismo cálculo a través de esas capas y después compara la construcción de un programa antes de ejecutarlo con su interpretación o la compilación de partes durante la ejecución. La vía elegida afecta a la portabilidad, el tiempo de inicio, la velocidad y el momento en que puede aparecer un error.

1.1 Tres niveles de código de programa

Una línea como x = a + b expresa un cálculo sin mencionar ningún registro del procesador. La abstracción lo hace posible: un lenguaje oculta los detalles de niveles inferiores para que quien programa pueda centrarse en el trabajo que debe realizar el programa.

Qué ejecuta el procesador

El código máquina consiste en instrucciones codificadas como números binarios. La arquitectura del conjunto de instrucciones (ISA) de un procesador define sus instrucciones y sus codificaciones. x86-64 es habitual en computadoras personales y servidores; ARM es habitual en teléfonos, tabletas y muchos portátiles. El código construido para una ISA necesita traducción o emulación para funcionar en otra.

El sistema hexadecimal permite mostrar los bytes de las instrucciones de forma más breve. En x86-64, B8 01 00 00 00 coloca 1 en el registro EAX. Los bytes 01 D8 suman el valor de EBX a EAX. Los registros son pequeñas posiciones de almacenamiento dentro del procesador. Cada instrucción realiza una acción pequeña, como mover datos, sumar, comparar o saltar. El ciclo de búsqueda, decodificación y ejecución ejecuta estas instrucciones.

Nombres para las mismas acciones pequeñas

El lenguaje ensamblador sustituye los bytes de las instrucciones por nombres legibles llamados mnemónicos. Esas dos instrucciones de x86-64 pueden escribirse así:

mov eax, 1
add eax, ebx

Un ensamblador convierte el lenguaje ensamblador en código máquina. Una línea de instrucción habitual suele producir una instrucción máquina. Los nombres de los registros y las instrucciones siguen perteneciendo a una ISA concreta, por lo que usar palabras en lugar de bytes no hace portable al lenguaje ensamblador.

Describe un problema en menos líneas

Un lenguaje de alto nivel proporciona variables, expresiones, condiciones, bucles y funciones. Python, C, Java y JavaScript permiten describir operaciones sin seleccionar registros. Una sentencia suele requerir varias instrucciones máquina. Una traducción simplificada de x = a + b haría lo siguiente:

  1. Cargar el valor de a en un registro.
  2. Cargar el valor de b en otro registro.
  3. Sumar los valores de los registros.
  4. Guardar el resultado para x.

Un compilador real puede combinar o reordenar esos pasos manteniendo el comportamiento requerido.

NivelQué escribe quien programa¿Qué se encarga de los detalles del hardware?
Código máquinaInstrucciones codificadas del procesadorQuien programa o el generador de código
Lenguaje ensambladorMnemónicos y registrosQuien programa, con un ensamblador que codifica las instrucciones
Código de alto nivelVariables y operaciones sobre los datos del problemaUn compilador, un intérprete o una combinación

El código fuente de alto nivel es más fácil de leer, modificar y comprobar. Un código fuente portable en C puede compilarse para un sistema x86-64 y también para un sistema ARM. Cada compilación sigue necesitando el soporte y las bibliotecas adecuados del sistema operativo. La portabilidad del código fuente no convierte un ejecutable nativo en universal.

Por qué siguen importando los niveles inferiores

El lenguaje ensamblador sigue siendo útil en el código de arranque, algunas rutinas de sistemas operativos y controladores, sistemas embebidos pequeños y el análisis de seguridad de programas cuyo código fuente no está disponible. Para el código de aplicaciones habitual, los compiladores modernos suelen producir código máquina con un rendimiento comparable al del ensamblador escrito a mano.

El avance hacia niveles superiores fue gradual. El lenguaje ensamblador se extendió a finales de la década de 1940 y principios de la de 1950. El sistema A-0 de Grace Hopper, de 1952, ayudó a impulsar la traducción automática. IBM publicó FORTRAN en 1957; COBOL apareció en 1959 y Lisp surgió hacia 1958–1960. «Alto nivel» indica la distancia respecto a los detalles del hardware, no la dificultad de aprender el lenguaje.

Puntos clave

  • Un procesador ejecuta instrucciones máquina definidas por su ISA.
  • Un ensamblador codifica las instrucciones del lenguaje ensamblador; este sigue ligado a una ISA.
  • Las sentencias de alto nivel ocultan los pasos del hardware y suelen convertirse en varias instrucciones.
  • El código fuente portable puede traducirse para distintos destinos; el código máquina nativo es específico de su destino.
  • El código de bajo nivel sigue siendo útil cuando lo requiere el acceso al hardware o su análisis.

1.2 Cómo construye un programa un compilador

Un compilador traduce código fuente a código destino. El destino puede ser código máquina nativo o una forma intermedia, como el código de bytes. La compilación anticipada (AOT) se realiza antes de ejecutar el programa; la compilación en tiempo de ejecución se realiza mientras este se ejecuta.

Del texto a las instrucciones

En una construcción anticipada, varias etapas transforman un archivo fuente en instrucciones:

  1. El análisis léxico divide el texto en componentes léxicos: piezas con significado, como nombres, operadores y números.
  2. El análisis sintáctico comprueba la gramática y construye una estructura que muestra cómo encajan las piezas.
  3. El análisis semántico comprueba las reglas de significado, incluidas las declaraciones y las combinaciones de tipos permitidas.
  4. La optimización busca una forma más eficiente de realizar el trabajo requerido.
  5. La generación de código produce las instrucciones de destino.

Considera la sentencia de C total = price * 3;. Sus seis componentes léxicos son total, =, price, *, 3 y ;. Los nombres son identificadores, 3 es un literal entero y el punto y coma termina la sentencia. El análisis sintáctico registra que la multiplicación está dentro de la asignación: calcula price * 3 y después almacena su valor en total.

Estas etapas tienen tareas distintas. Una secuencia de componentes léxicos correctamente escritos puede incumplir la gramática. Una sentencia gramaticalmente correcta puede usar mal un tipo. Identificar los componentes léxicos no basta para establecer que un programa es válido.

Reduce el coste del mismo trabajo

Supón que el código fuente contiene seconds_per_day = 60 * 60 * 24. Los factores son constantes, por lo que el compilador puede multiplicar 60 por 60 para obtener 3600 y después multiplicar por 24 para obtener 86 400 durante la construcción. Puede colocar el valor final en la salida en lugar de repetir la multiplicación durante la ejecución. Esto es el plegado de constantes.

Una optimización debe conservar el comportamiento que exige el lenguaje. No puede sustituir un cálculo por otro más rápido que dé resultados distintos de los requeridos. El objetivo es reducir el trabajo, el tiempo o la memoria manteniendo el significado del programa.

Une las piezas

Los programas grandes suelen construirse a partir de muchos archivos. Un compilador puede producir un archivo objeto por cada archivo fuente. Un archivo objeto contiene código traducido que aún puede necesitar conexiones con otro código. Un enlazador une las piezas y resuelve las llamadas a rutinas de bibliotecas, como funciones para mostrar texto o realizar cálculos matemáticos.

Método de enlaceDe dónde procede el código de la biblioteca durante la ejecución
Enlace estáticoCódigo incluido en el ejecutable durante la construcción
Enlace dinámicoUna biblioteca compartida compatible cargada al inicio o durante la ejecución

El ejecutable resultante se construye para una plataforma de destino, incluidas una ISA y las convenciones del sistema operativo. El usuario no necesita el compilador anticipado para ejecutarlo. Aun así, pueden requerirse bibliotecas compartidas o componentes del entorno de ejecución.

Entiende qué demuestra una construcción correcta

En Java, int count = "five"; no supera una comprobación de tipos antes de la ejecución. Un texto no puede inicializar esa variable entera. El programa debe superar las comprobaciones de compilación requeridas antes de disponer de una versión ejecutable.

Otros fallos sobreviven a la compilación. Dividir por un cero introducido por el usuario depende de los datos de ejecución. Un programa pensado para restar que, en cambio, suma puede ejecutarse y devolver un valor incorrecto. La compilación comprueba las reglas del lenguaje; las pruebas también deben comprobar que el resultado corresponde a la tarea prevista.

Puntos clave

  • La compilación traduce el código fuente a código destino, que no tiene por qué ser código máquina.
  • Las etapas del compilador comprueban los componentes léxicos, la estructura y el significado antes de producir las instrucciones de destino.
  • La optimización conserva el comportamiento requerido; el plegado de constantes evalúa constantes durante la construcción.
  • Un enlazador conecta archivos objeto y código de bibliotecas.
  • Una construcción correcta aún puede contener fallos de ejecución o errores lógicos.

1.3 Interpretación, código de bytes y compilación JIT

Un programa no tiene que convertirse en un ejecutable nativo antes de que un usuario lo inicie. Un intérprete ejecuta el programa mediante otro programa en ejecución. Muchas implementaciones combinan interpretación y compilación, por lo que la pregunta útil es cómo ejecuta el código ese sistema concreto.

Ejecuta mediante un intérprete

Un intérprete debe estar disponible cada vez que ejecuta el programa. Un REPL interactivo repite cuatro acciones: leer una entrada, evaluarla, mostrar un resultado y volver a empezar para la siguiente entrada. Ofrece una respuesta rápida al probar expresiones pequeñas.

Interpretar no significa necesariamente leer el código fuente directamente, línea por línea. CPython, la implementación estándar de Python, primero compila un archivo a código de bytes. Después, una máquina virtual de software ejecuta esas instrucciones internas. El código de bytes pertenece a ese entorno de ejecución; no es el conjunto de instrucciones nativo del procesador.

Por qué algunos errores aparecen antes

Compara dos archivos de Python. Este tiene una sintaxis válida:

print("start")
x = 10
print(x + "5")

Muestra «start», vincula x a 10 y después genera TypeError. La suma de ese entero y una cadena solo falla cuando se ejecuta la expresión. Una operación similar en una rama que no se visita puede pasar inadvertida hasta que se ejecute esa rama.

Ahora elimina el paréntesis de cierre de la última línea. CPython no puede compilar el archivo porque su sintaxis está incompleta. Informa de un error de sintaxis antes de que se ejecute cualquier sentencia de ese archivo, por lo que no se muestra «start». La comprobación de sintaxis del archivo completo y la comprobación durante la ejecución de una operación ocurren en momentos distintos.

Comparte código de bytes y especialízalo durante la ejecución

Java muestra una vía híbrida habitual. javac Hello.java produce Hello.class, que contiene código de bytes. java Hello inicia una máquina virtual de Java (JVM) compatible. Distintas implementaciones de la JVM permiten ejecutar ese código de bytes en distintas familias de procesadores. El código de bytes es portable entre entornos de ejecución compatibles; cada entorno debe funcionar en su propia plataforma anfitriona. De forma similar, C# se traduce a un lenguaje intermedio utilizado por .NET.

Un compilador en tiempo de ejecución (JIT) genera código máquina mientras se ejecuta el programa. El entorno de ejecución HotSpot de Java suele comenzar con interpretación y compila los métodos más utilizados. Los motores de JavaScript de los navegadores también combinan técnicas de ejecución, incluida la compilación JIT. Compilar las partes más utilizadas puede mejorar la velocidad sostenida, pero consume tiempo y memoria durante la ejecución.

VíaVentaja útilCoste que considerar
Construcción nativa anticipadaEl trabajo se traduce antes del inicio; el usuario no necesita compiladorVersiones compiladas compatibles distintas para las plataformas de destino
InterpretaciónRespuesta rápida al editar y ejecutar con un intérprete instaladoEl trabajo repetido durante la ejecución puede ralentizar el código con muchos cálculos
Código de bytes con máquina virtual y JITCódigo intermedio portable con optimización durante la ejecuciónInstalación del entorno de ejecución, calentamiento y consumo adicional de memoria

Un bucle que suma mil millones de valores suele ejecutarse mucho más rápido en C compilado que en CPython: CPython realiza trabajo de interpretación y comprobaciones de tipos en torno a muchas operaciones. Para un programa breve que cambia el nombre de 20 archivos, el esfuerzo de desarrollo puede importar mucho más que esa diferencia. Mide la carga de trabajo antes de usar la etiqueta de un lenguaje como predicción de velocidad.

Compilado e interpretado describen decisiones de implementación. CPython utiliza interpretación de código de bytes; PyPy utiliza técnicas JIT. Un lenguaje puede tener más de una implementación y una implementación puede usar varios métodos de ejecución.

Puntos clave

  • Un intérprete ejecuta un programa y debe estar presente para hacerlo.
  • CPython compila un archivo a código de bytes antes de ejecutarlo.
  • Un error de sintaxis puede impedir que un archivo se inicie; un error de ejecución aparece cuando se ejecuta su operación.
  • Un compilador JIT crea código nativo durante la ejecución, a menudo para las partes más utilizadas.
  • Los métodos de ejecución pertenecen a las implementaciones, y el rendimiento depende de la carga de trabajo.
2

Lee qué hacen los programas breves

Leer código implica dos preguntas: si su forma sigue las reglas del lenguaje y qué significan sus operaciones. Una expresión de aspecto idéntico puede comportarse de forma distinta en dos lenguajes. Aprende a clasificar errores y después sigue las asignaciones, decisiones, bucles y llamadas a funciones en orden de ejecución. Una pequeña tabla de valores cambiantes hace visible la diferencia entre lo que se pretendía programar y lo que realmente hace el programa.

2.1 Sintaxis, significado y tres tipos de errores

La sintaxis establece las reglas para que el texto de un programa sea válido. La semántica da significado a ese texto. Un programa puede seguir todas las reglas gramaticales y aun así calcular un resultado incorrecto, por lo que comprobar su apariencia es solo la primera parte de su lectura.

Reconoce las piezas y la gramática

Los componentes léxicos incluyen palabras clave como if, identificadores como total, literales como 3 o "hello", operadores como + y == y signos de puntuación. Una gramática describe cómo pueden formar expresiones y sentencias estas piezas.

Python requiere dos puntos después de if x > 3: y utiliza la sangría para marcar las sentencias controladas por esa condición. C, Java y JavaScript utilizan llaves para los bloques; C y Java también requieren punto y coma después de muchas sentencias. En esos lenguajes, la sangría ayuda a quien lee, pero no selecciona el bloque.

Una sintaxis distinta puede expresar una acción similar. Estas sentencias muestran un saludo:

print("hello")
printf("hello\n");
System.out.println("hello");

La secuencia de escape de C \n añade un salto de línea. Conocer la idea de mostrar texto ayuda al aprender cómo la expresa cada lenguaje.

Los mismos símbolos pueden significar cosas distintas
Expresión y lenguajeResultadoRegla
7 / 2 en Python o JavaScript3.5La división produce un resultado fraccionario
7 / 2 con operandos enteros en C o Java3La división entera trunca hacia cero
-7 // 2 en Python-4La división por suelo redondea hacia abajo
-7 / 2 con enteros en C-3El truncamiento se acerca a cero
"3" * 4 en Python"3333"Se repite una cadena
"3" * 4 en JavaScript12El texto se convierte en un número para la multiplicación

La apariencia familiar de un símbolo no determina su significado en distintos lenguajes.

El orden de evaluación también importa. La precedencia hace que la multiplicación y la división se realicen antes que la suma. Con a = 4 y b = 6, average = a + b / 2 divide primero 6 entre 2 y después suma 4, lo que produce 7,0. Los paréntesis de average = (a + b) / 2 hacen que la suma se realice primero y dan 5,0.

Distingue los errores

Un error de sintaxis incumple la gramática, como la falta de dos puntos en Python. Un error de ejecución ocurre durante una operación ejecutada, como la división por cero en Python. Un error lógico permite la ejecución, pero da una respuesta no deseada, como en el promedio incorrecto.

Algunas expresiones válidas omiten deliberadamente una operación. En Python, x != 0 and 10 / x > 2 utiliza evaluación en cortocircuito. Cuando x es cero, la primera condición es falsa, por lo que el lado derecho no se evalúa y no se realiza ninguna división. Invertir estas condiciones eliminaría esa protección. Python escribe and, or y not; C, Java y JavaScript utilizan &&, || y !.

La asignación y la comparación son otra distinción importante. = almacena un valor; == comprueba la igualdad. C permite if (x = 5): asigna 5 y trata ese valor distinto de cero como verdadero. Python rechaza if x = 5: por ser sintaxis no válida. Por tanto, el mismo error puede detectarse en una etapa distinta según el lenguaje.

Puntos clave

  • La sintaxis define la forma válida; la semántica define el significado.
  • Los operadores pueden tener significados distintos en distintos lenguajes.
  • La precedencia y los paréntesis determinan qué operaciones se agrupan.
  • Los errores de sintaxis, de ejecución y lógicos requieren distintos tipos de comprobaciones.
  • La evaluación en cortocircuito puede omitir una operación insegura.
  • La asignación almacena un valor; la comparación de igualdad comprueba valores.

2.2 Sigue los valores a través del flujo de control y las llamadas

Un programa breve es más fácil de entender si sigues las sentencias ejecutadas una a una. Mantén visibles los valores actuales en lugar de saltar de la primera línea a un resultado supuesto.

Expresiones y asignaciones

Una expresión produce un valor: price * 3, x > 10 y len(name) son ejemplos. Una sentencia realiza una acción, como asignar un valor o controlar un bloque. En total = price * 3, la expresión de la derecha se evalúa antes de asignar su resultado a total.

Una variable es un nombre que hace referencia a un valor. La asignación da al nombre su valor actual; no es una afirmación matemática de que ambos lados siempre hayan sido iguales. Si count contiene 4, count = count + 1 primero lee 4, suma 1 y vincula count a 5.

Sigue la siguiente sentencia que se ejecuta

Tres estructuras organizan el flujo de control:

  • La secuencia ejecuta las sentencias en orden.
  • La selección utiliza una condición para elegir un camino, como if y else.
  • La iteración repite un bloque mediante un bucle for o while.

El teorema del programa estructurado, asociado a Böhm y Jacopini en 1966, muestra que estas estructuras pueden expresar cualquier algoritmo. Un bucle for suele recorrer los elementos de una colección. Un bucle while se repite mientras su condición siga siendo verdadera.

Considera este bucle de Python:

total = 0
for n in [3, 8, 5]:
    total = total + n

La asignación con sangría se ejecuta una vez por cada elemento. Una tabla de seguimiento registra el estado después de cada actualización:

Momenton actualtotal
Antes del bucleAún no hay elemento0
Primera actualización33
Segunda actualización811
Tercera actualización516

Cada total nuevo utiliza el anterior. Reiniciar total dentro del bucle produciría un cálculo distinto porque se perderían las sumas anteriores.

Entra en una función y sal de ella

Una función agrupa una tarea bajo un nombre. Un parámetro es un nombre de la definición; un argumento es un valor proporcionado por una llamada:

def area(width, height):
    return width * height

result = area(3, 4)

La llamada vincula width a 3 y height a 4. La multiplicación produce 12 y return lo envía al código que realizó la llamada. La ejecución continúa en la asignación, por lo que result recibe 12. Los nombres describen una tarea que puede reutilizarse con otros argumentos.

Un nombre tiene un ámbito

El ámbito determina dónde puede usarse un nombre. Los nombres creados dentro de una función suelen ser locales a esa llamada. Un nombre del nivel superior es global en su módulo. Las llamadas distintas tienen vinculaciones locales independientes aunque utilicen nombres idénticos.

count = 7

def reset():
    count = 0

reset()
print(count)

Esto muestra 7. La asignación dentro de reset crea un count local; no sustituye la vinculación global. En su lugar, una función puede devolver un resultado y dejar que el código que la llama lo asigne.

El ámbito y el tiempo de vida responden a preguntas distintas. Un nombre local no está disponible para el código externo solo porque el objeto al que hacía referencia siga existiendo. Los objetos devueltos pueden seguir vivos, y una función anidada devuelta puede conservar el acceso a las variables de la llamada que la contiene; ese entorno conservado forma parte de una clausura.

Puntos clave

  • Evalúa la expresión de la derecha antes de actualizar el destino de una asignación.
  • Sigue la secuencia, la selección y la iteración en el orden en que realmente se ejecutan.
  • Los parámetros son nombres de la definición; los argumentos son valores proporcionados.
  • Un valor de retorno devuelve un resultado al código que realizó la llamada.
  • Los nombres locales y globales pueden tener vinculaciones independientes; el ámbito difiere del tiempo de vida.
3

Tipos, referencias y memoria

Los valores necesitan un tipo, un lugar en la memoria y un tiempo de vida. Estas decisiones explican por qué un texto que parece numérico puede rechazar operaciones aritméticas, por qué dos nombres pueden mostrar la misma lista modificada y por qué a veces los datos sin uso siguen ocupando memoria reservada. Compara las comprobaciones que realizan los lenguajes, sigue una llamada a una función a través de la memoria y examina tres formas de liberar almacenamiento. Cada enfoque cambia el equilibrio entre control, comodidad y seguridad.

3.1 Los tipos deciden qué operaciones tienen sentido

Un tipo de dato especifica los valores posibles y las operaciones que admiten. Un entero admite operaciones aritméticas con números enteros; un valor booleano representa verdadero o falso. Las cadenas, listas, registros y objetos combinan u organizan otros valores. Los tipos también orientan cómo se representan los valores en memoria, aunque el tamaño de un valor puede depender del propio valor y de la implementación.

¿Cuándo se comprueban los tipos?
EnfoqueCuándo se realizan las comprobacionesEjemplo
Tipado estáticoAntes de la ejecuciónJava rechaza int count = "five";
Tipado dinámicoDurante la ejecuciónPython comprueba si una operación ejecutada es compatible con sus operandos

En Python, count = 5 seguido de count = "five" es válido. El nombre ahora hace referencia a una cadena. Evaluar después count + 1 genera TypeError. El tipado dinámico da tipos definidos a los valores; no elimina los tipos.

El tipado estático no exige una etiqueta de tipo visible en cada variable. La inferencia de tipos permite al compilador deducir un tipo a partir del código que lo rodea. Kotlin infiere un entero para val n = 5, y Rust puede inferir un entero para let n = 5;. Las anotaciones de tipo opcionales de Python son distintas: herramientas independientes como mypy pueden comprobarlas, pero la ejecución habitual de Python no las hace cumplir.

¿Cuándo se convierten los valores?

El momento de comprobación y la rigidez de las conversiones son cuestiones distintas. Python rechaza "3" + 4; la conversión explícita en int("3") + 4 da 7. JavaScript aplica distintas reglas de conversión automática según el operador:

  • "3" + 4 da la cadena "34".
  • "3" * 4 da el número 12.
  • "3" - 4 da el número -1.

La conversión automática se llama coerción. Las etiquetas de tipado «fuerte» y «débil» son formas informales de hablar de la rigidez de las conversiones, no otros nombres para el tipado estático y dinámico. Python es dinámico y estricto con la suma de tipos distintos del ejemplo anterior; C es estático y aun así permite muchas conversiones numéricas implícitas.

La función input() de Python devuelve una cadena. Si una persona escribe «30», el resultado sigue teniendo el tipo str. age + 1 falla, mientras que int(age) + 1 da 31 cuando age contiene ese texto. Lo que aparece en pantalla no basta para determinar el tipo.

Los números tienen límites de representación

Un entero con signo de 32 bits representa valores desde −2 147 483 648 hasta 2 147 483 647. El tipo int de Java pasa cíclicamente del máximo al mínimo al sumar 1. El desbordamiento con signo en C tiene un comportamiento indefinido, por lo que no debe suponerse ese mismo retorno cíclico en C. Los enteros de Python crecen según lo permita la memoria: 2 ** 100 es exactamente 1 267 650 600 228 229 401 496 703 205 376.

Los números en coma flotante representan la mayoría de las fracciones decimales de forma aproximada en binario. En Python y JavaScript, 0.1 + 0.2 produce 0.30000000000000004. Comparar ese resultado directamente con 0.3 da falso.

Cuando un cálculo admite un pequeño error, compara los valores con una tolerancia elegida para la tarea, por ejemplo, abs(a - b) < 1e-9. Una tolerancia absoluta tan pequeña solo es adecuada para algunas escalas. Para una contabilidad decimal exacta, utiliza números enteros de la unidad mínima o un tipo decimal adecuado. Cambiar la comparación no puede convertir una representación aproximada en exacta.

Puntos clave

  • Un tipo define valores y operaciones admitidas.
  • El tipado estático y dinámico describen el momento de comprobación, mientras que la coerción se refiere a las reglas de conversión.
  • La inferencia de tipos permite el tipado estático; las anotaciones de Python necesitan herramientas de comprobación independientes.
  • Los enteros de ancho fijo pueden desbordarse, con un comportamiento específico de cada lenguaje.
  • El redondeo en coma flotante afecta a las pruebas de igualdad y a la elección de la representación numérica.

3.2 Sigue nombres, objetos y llamadas a funciones en la memoria

La memoria principal almacena los datos de un programa en ejecución. Cada byte tiene una dirección. Los lenguajes proporcionan nombres para trabajar con valores sin escribir direcciones directamente en cada operación.

Las llamadas usan marcos; los objetos tienen tiempos de vida

En C, un int suele ocupar 4 bytes, y &x obtiene la dirección de x. Los lenguajes de nivel superior suelen ocultar las direcciones mediante referencias. La distinción importante es entre una vinculación que da nombre a un objeto y el propio objeto.

La pila de llamadas lleva el seguimiento de las llamadas activas a funciones. Cada llamada tiene un marco de pila con información como los parámetros, las vinculaciones locales y el punto donde debe continuar la ejecución tras el retorno. Si main llama a area(3, 4), se añade un marco de area sobre el marco del código que llama. La función calcula 12, retorna y se retira su marco. En las llamadas anidadas habituales, la llamada más reciente retorna primero.

La recursión añade una llamada por cada nivel pendiente. Si las llamadas siguen acumulándose, puede agotarse el espacio disponible en la pila. Python normalmente genera RecursionError después de unas 1000 llamadas anidadas, con un límite que depende de su configuración. Para que termine un cálculo recursivo finito, debe alcanzarse una condición de parada.

El montículo de memoria contiene objetos cuya memoria se reserva dinámicamente, como una lista que crece o un objeto que sobrevive a la función que lo creó. La reserva de memoria aparta espacio de almacenamiento. Una lista devuelta puede permanecer en el montículo después de que termine la llamada que la creó.

Copiar una referencia comparte un objeto

Una referencia identifica un objeto. Un puntero en C o C++ representa explícitamente una dirección utilizada para acceder a datos. Copiar una referencia copia el acceso al objeto, no todos sus datos.

a = [1, 2]
b = a
b.append(3)

Después de la primera línea hay una lista. La segunda línea vincula otro nombre a ella. La última línea modifica esa lista, por lo que tanto a como b muestran [1, 2, 3]. Esta forma de compartir es el uso de alias. Usar b = a.copy() crearía una lista externa independiente; en esta lista de enteros, añadir un elemento a la nueva lista dejaría a sin cambios.

Compara la asignación con la mutación:

x = 5
y = x
y = y + 1

El entero 5 es inmutable. La suma produce 6 y solo se vuelve a vincular y. x sigue haciendo referencia a 5. Los números, cadenas y tuplas de Python son inmutables; las listas y los diccionarios son mutables. El contenido fijo de una tupla puede incluir una referencia a una lista mutable, por lo que la inmutabilidad del contenedor externo no inmoviliza todos los objetos anidados.

Aplica las mismas reglas a los argumentos

Cuando Python pasa una lista a una función, el parámetro hace referencia al mismo objeto. Llamar a items.append(4) dentro de la función cambia esa lista. Asignar items = [] vincula solo el nombre del parámetro local a una lista nueva. El nombre del código que llama sigue vinculado al objeto original.

C pasa los valores de los argumentos por valor. Para permitir que una función actualice una variable del código que llama mediante su dirección, pasa un puntero a ella. En cada lenguaje, hazte dos preguntas distintas durante el seguimiento: «¿A qué objeto hace referencia este nombre?» y «¿Esta operación cambia ese objeto o vincula un nombre a otra cosa?».

Puntos clave

  • La pila de llamadas lleva el seguimiento de las llamadas pendientes; el montículo contiene objetos cuya memoria se reserva dinámicamente.
  • Un objeto puede sobrevivir a la función que lo creó.
  • Copiar una referencia comparte un objeto; no duplica sus datos.
  • La mutación cambia un objeto, mientras que la revinculación cambia aquello a lo que hace referencia un nombre.
  • El paso de argumentos sigue la misma distinción entre objetos compartidos y vinculaciones locales.

3.3 Libera memoria sin perder datos vivos

La memoria reservada debe poder reutilizarse en algún momento. El reto consiste en liberar los datos después de su último uso y conservar todos los objetos que el programa aún necesita. Los lenguajes asignan esta responsabilidad a quien programa, a un sistema de ejecución o a reglas de propiedad de los datos comprobadas por el compilador.

La gestión manual y sus fallos

En C, malloc(n) solicita n bytes y devuelve un puntero al bloque si tiene éxito. free(p) libera un bloque obtenido mediante una reserva. Quien programa debe utilizarlo en el momento adecuado y exactamente una vez. C++ también admite la gestión manual, aunque sus herramientas estándar pueden automatizar la liberación mediante los tiempos de vida de los objetos.

Varios errores tienen causas distintas:

FalloQué salió mal
Fuga de memoriaNo se libera almacenamiento reservado que ya no se necesita
Uso después de liberar memoriaSe utiliza un puntero después de liberar el almacenamiento al que apunta
Doble liberación de memoriaSe libera dos veces el mismo bloque reservado
Desbordamiento de búferUna escritura supera los límites de un bloque de memoria

Un puntero que sigue haciendo referencia a almacenamiento liberado es un puntero colgante. Ese almacenamiento podría reutilizarse para otra cosa, por lo que acceder a él puede corromper datos o hacer que el programa deje de funcionar.

En un arreglo de C int scores[10], las posiciones válidas son de 0 a 9. Escribir en scores[10] supera los límites del arreglo, y C no proporciona una comprobación automática de límites. Python genera IndexError cuando se utiliza el índice 10 en una lista de diez elementos, lo que impide que ese acceso sobrescriba memoria adyacente.

Rastrea la accesibilidad o cuenta las referencias

Un recolector de basura por rastreo parte de raíces vivas, como referencias activas, sigue los enlaces entre objetos y puede recuperar la memoria de los objetos inaccesibles. Java, C#, Go y JavaScript utilizan enfoques de rastreo. La recolección requiere trabajo durante la ejecución y memoria; según el recolector, puede introducir pausas.

El conteo de referencias lleva la cuenta de las referencias a un objeto. Para una lista nueva de Python, la secuencia simplificada es: a = [1, 2] proporciona una referencia; b = a proporciona una segunda; del a elimina una; b = None elimina la otra. CPython normalmente recupera la memoria de esta lista habitual cuando su cuenta llega a cero.

Un grupo de objetos puede mantener las cuentas de los demás por encima de cero aunque ninguna parte viva del programa pueda acceder al grupo. Esto es un ciclo de referencias. CPython complementa el conteo de referencias con la recolección de ciclos. Tanto el conteo de referencias como el rastreo son técnicas de gestión automática de memoria.

Comprueba la propiedad antes de la ejecución

Rust da un propietario a cada valor y comprueba las reglas del préstamo de referencias. Cuando termina la propiedad, el código de limpieza generado libera el valor. Rust seguro rechaza los usos de referencias que podrían sobrevivir al objeto válido al que apuntan, sin necesitar un recolector de basura por rastreo. Swift utiliza conteo automático de referencias insertado por el compilador.

La seguridad de memoria importa más allá de los fallos del programa. Microsoft informó en 2019 de que aproximadamente el 70 % de las vulnerabilidades a las que asignaba CVE cada año afectaban a la seguridad de memoria; Chromium informó de una proporción similar de errores graves en 2020. En diciembre de 2023, las agencias estadounidenses CISA, NSA y FBI y socios internacionales instaron a los fabricantes de software a planificar la transición hacia lenguajes con seguridad de memoria.

La recuperación automática sigue conservando los objetos accesibles. Una lista global que no deja de crecer puede mantener vivos todos los registros antiguos incluso después de que la aplicación haya terminado de utilizarlos. Un recolector no puede decidir que estos registros accesibles ya no son necesarios para la tarea; el programa debe dejar de retenerlos.

Puntos clave

  • La reserva manual requiere liberar la memoria en el momento adecuado.
  • Las fugas, el uso después de liberar memoria, la doble liberación y el desbordamiento de búfer tienen causas distintas.
  • El rastreo encuentra objetos inaccesibles; el conteo de referencias responde a la pérdida de referencias.
  • CPython añade la recolección de ciclos al conteo de referencias.
  • La propiedad de los datos puede organizar la liberación sin un recolector por rastreo.
  • La recolección automática no puede recuperar la memoria de los objetos que el programa sigue reteniendo.
4

Organiza el estado cambiante y los objetos que colaboran

Una forma de describir un programa es mediante órdenes que actualizan valores. Otra agrupa esos valores con las operaciones que los controlan. Estas perspectivas suelen combinarse: un método de un objeto puede contener un bucle habitual. Sigue primero los cambios de estado y después construye un pequeño modelo de cuentas para ver cómo protegen las clases las reglas y cómo permiten la herencia, el polimorfismo y la composición que objetos relacionados compartan una interfaz sin asumir responsabilidades inadecuadas.

4.1 Órdenes, procedimientos y estado compartido

Un paradigma de programación es una forma de organizar el código y de pensar en la computación. En la programación imperativa, un programa da órdenes que cambian su estado: los valores que contienen actualmente las variables y los objetos.

Haz visibles los cambios de estado

Un total acumulado es un cálculo imperativo sencillo:

total = 0
for n in [3, 8, 5]:
    total = total + n

El estado inicial es total = 0. Sumar 3 lo cambia a 3. Sumar 8 lo cambia a 11. Sumar 5 lo cambia a 16. Cada asignación utiliza el estado que dejó el paso anterior, por lo que el orden forma parte del método.

Este estilo se ajusta a los cambios paso a paso que realiza el procesador en los registros y la memoria. También encaja con algoritmos que actualizan deliberadamente una colección en su propia ubicación, como una rutina de ordenación que intercambia elementos.

Da nombres a las tareas

La programación procedimental organiza el código imperativo en funciones o procedimientos. Un programa de informes podría llamar a read_scores(), average(scores) y print_report(avg). Los nombres permiten ver la tarea general sin seguir todas las líneas internas a la vez. Cada procedimiento puede entenderse y probarse por separado.

C, Pascal, Fortran y COBOL son lenguajes procedimentales clásicos. Python también admite este estilo: un programa breve puede utilizar funciones con nombre sin definir ninguna clase.

La programación estructurada organiza el flujo de control con secuencia, selección, bucles y llamadas a procedimientos. Evita los saltos arbitrarios que dificultan predecir la siguiente sentencia que se ejecutará. La carta de Edsger Dijkstra de 1968 «La instrucción Go To se considera perjudicial» ayudó a popularizar este enfoque. Las salidas controladas como break, continue y return siguen siendo útiles: permiten salir de una iteración concreta, de un bucle o de una función.

Los efectos pueden ir más allá de un valor de retorno

Un efecto lateral cambia algo fuera del resultado que devuelve una función. Mostrar información, guardar un archivo, enviar un mensaje por la red y cambiar datos compartidos son efectos laterales. Los programas necesitan estas acciones para interactuar con el mundo.

Los problemas surgen cuando fragmentos de código sin relación cambian silenciosamente el mismo estado. Supón que apply_discount() reduce una variable global price un 10 % y add_shipping() añade 5. Partiendo de 100, aplicar primero el descuento da 90 y después el envío da 95. Añadir primero el envío da 105 y después aplicar el descuento da 94,5. Ambos procedimientos realizan correctamente sus propios pasos, pero el resultado combinado depende de su orden.

Pasar valores y devolver resultados nuevos puede hacer visible ese orden:

price = add_shipping(apply_discount(100))

La llamada interna se ejecuta primero: descuento y después envío. Un anidamiento distinto expresa una regla distinta. Un flujo de datos claro ayuda a revelar si el propio envío debe recibir el descuento.

OrganizaciónPregunta principal al leer
Órdenes imperativas¿Qué valor cambia a continuación?
Procedimientos con nombre¿Qué tarea realiza esta llamada?
Actualizaciones globales compartidas¿Qué acción anterior cambió este valor?

El código imperativo sigue siendo habitual dentro de los métodos de objetos y los manejadores de eventos modernos. Elegir un diseño general orientado a objetos o funcional no impide que una pequeña parte utilice órdenes directas cuando esos pasos aclaran el algoritmo.

Puntos clave

  • El código imperativo describe pasos que cambian el estado almacenado.
  • Los procedimientos dan nombre y límites a tareas coherentes.
  • El flujo de control estructurado facilita seguir los caminos de ejecución.
  • Los efectos laterales incluyen cambios en los datos compartidos e interacciones con el mundo exterior.
  • Las actualizaciones compartidas pueden hacer que los resultados dependan del orden de las llamadas.

4.2 Las clases mantienen los datos junto con sus reglas

La programación orientada a objetos (POO) modela un programa como objetos que colaboran. Cada objeto combina datos con operaciones sobre esos datos. Una cuenta puede contener su titular y su saldo y ofrecer operaciones de depósito y retirada.

Separa la plantilla de sus instancias

Una clase define los atributos y métodos que proporcionan sus objetos. Un objeto, o instancia, es un miembro concreto de esa clase. BankAccount es la plantilla; la cuenta de Ana con 150 y la de Ben con 40 son instancias distintas. Sus saldos pueden cambiar de forma independiente.

Un atributo almacena datos asociados a un objeto. Un método es una operación que se llama a través de un objeto, como acct.deposit(50). En un método de instancia de Python, self hace referencia al objeto que recibe la llamada.

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self._balance = balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("amount must be positive")
        self._balance = self._balance + amount

Llamar a BankAccount("Ana", 100) construye una instancia nueva. Su método __init__ inicializa los datos de la instancia. Un constructor es el proceso de preparación de un objeto nuevo; Python separa la creación de esta etapa de inicialización. Llamar a acct.deposit(50) sobre esa cuenta cambia su saldo de 100 a 150.

Coloca la regla en el punto de actualización

La encapsulación reúne los datos y las operaciones que mantienen sus reglas. Supón que una cuenta nunca debe tener un saldo negativo. El método de retirada comprueba el cambio propuesto antes de realizarlo:

def withdraw(self, amount):
    if amount <= 0:
        raise ValueError("amount must be positive")
    if amount > self._balance:
        raise ValueError("insufficient funds")
    self._balance = self._balance - amount

Este método pertenece al interior de BankAccount. Partiendo de un saldo de 150, withdraw(200) genera un error y deja 150. withdraw(30) tiene éxito y deja 120. La llamada fallida no resta primero y descubre después el problema; la comprobación ocurre antes de la actualización.

El resto del código debe utilizar esos métodos en lugar de asignar un saldo sin comprobarlo. Java y C# pueden restringir el acceso directo con private. El guion bajo inicial de _balance en Python señala por convención un detalle interno; no impone privacidad.

La ocultación de información va más allá: el código que llama depende de las operaciones que ofrece una clase sin necesitar conocer su representación interna. Más adelante, el saldo podría almacenarse de otra forma mientras las operaciones públicas de depósito y retirada conservan su significado.

Deja que el problema sugiera las responsabilidades

Un sistema de biblioteca tiene libros, miembros y préstamos. Esos sustantivos sugieren posibles clases o atributos; acciones como prestar y renovar sugieren métodos. Elige una responsabilidad clara para cada clase. Una clase de cuenta que además envía todos los correos electrónicos y da formato a todos los informes resulta más difícil de modificar porque mezcla tareas sin relación.

Las clases y los objetos aparecieron en Simula 67 en 1967; Smalltalk ayudó a difundir el enfoque en la década de 1970. Java, C#, C++ y Python admiten POO, pero utilizar uno de esos lenguajes no obliga a organizar todos los programas en clases. La pregunta útil de diseño es qué datos y reglas deben estar juntos.

Puntos clave

  • Una clase define atributos y métodos; cada instancia tiene sus propios datos de instancia.
  • Los métodos actúan sobre el objeto que recibe la llamada.
  • La encapsulación reúne la validación y los cambios de estado.
  • Los guiones bajos de Python señalan atributos internos por convención; algunos lenguajes imponen el acceso privado.
  • La ocultación de información permite que el código que llama dependa de las operaciones públicas y no de los detalles internos.
  • Una clase debe tener una responsabilidad coherente.

4.3 Especializa el comportamiento y combina objetos

Los objetos suelen necesitar compartir operaciones y diferir en los detalles. La herencia, el polimorfismo y la composición resuelven partes distintas de ese problema de diseño.

Especializa un tipo de objeto existente

La herencia define una subclase a partir de una superclase. En Python, class SavingsAccount(BankAccount): da a la clase especializada acceso al comportamiento heredado de la cuenta. Puede añadir add_interest(rate): un saldo de 1000 al que se añade un interés del 2 % pasa a ser 1020.

La relación prevista es es un. Una cuenta de ahorro sigue siendo una cuenta bancaria y debe respetar las reglas que se esperan de las cuentas bancarias. Reutilizar unas pocas líneas no es motivo suficiente para afirmar que un tipo de objeto es otro.

Una subclase puede redefinir un método con su propia implementación. Supón que una cuenta corriente cobra una comisión de 1 por cada retirada realizada con éxito. Con un saldo de 100, retirar 30 cuesta 31 y deja 69. Para conservar la regla de la cuenta, el método debe comprobar la retirada más la comisión antes de cambiar el saldo. Si ese coste combinado supera el saldo, rechaza toda la operación.

Haz que una llamada funcione con distintos objetos

El polimorfismo permite que la misma operación funcione con distintos tipos de objetos. Tanto un círculo como un rectángulo pueden proporcionar area():

for shape in shapes:
    print(shape.area())

El bucle no necesita un if para cada clase de figura. Cada objeto aporta su propia implementación. Seleccionar esa implementación durante la ejecución es el despacho dinámico.

Para Circle(2), el área es aproximadamente 12,57 unidades cuadradas. Para Rectangle(3, 4), es de 12 unidades cuadradas. Su área combinada es aproximadamente 24,57 unidades cuadradas. Un Triangle posterior puede incorporarse a la colección si proporciona una operación area() compatible; el propio bucle no necesita otra rama.

Una interfaz establece las operaciones que debe ofrecer un tipo. La declaración de Java interface Shape { double area(); } exige que las clases que la implementan proporcionen el método de área. Una clase abstracta puede aportar parte de la implementación compartida y dejar otros métodos para las subclases. Python suele utilizar el tipado de pato: un objeto puede funcionar en el bucle si proporciona la operación requerida, incluso sin una superclase compartida.

Contén una pieza en lugar de convertirte en ella

La composición almacena otros objetos como componentes. Un automóvil tiene un motor y ruedas. Puede llamar al método start() de su motor sin heredar de Engine.

RelaciónDiseño adecuadoMotivo
Una cuenta de ahorro es una cuentaHerenciaConserva el comportamiento esperado de una cuenta
Un automóvil tiene un motorComposiciónEl motor es un componente, no la categoría del automóvil
Distintas figuras ofrecen un áreaOperación o interfaz compartidaEl código que llama puede solicitar la operación de forma uniforme

Las cadenas profundas de herencia pueden propagar los efectos de un cambio en una superclase a muchas clases. Una subclase que debe desactivar comportamiento heredado puede indicar una relación «es un» inadecuada. La composición suele mantener estas dependencias más reducidas y claras.

Los cuatro pilares habituales de la POO encajan aquí: la encapsulación protege los datos mediante operaciones, la abstracción oculta los detalles de implementación, la herencia especializa un tipo y el polimorfismo permite que el código que llama utilice una operación compartida entre distintas implementaciones.

Puntos clave

  • La herencia modela una relación «es un» y debe conservar el comportamiento esperado.
  • La redefinición sustituye la implementación de un método en una clase especializada.
  • El polimorfismo permite que una operación invoque distintas implementaciones.
  • Una interfaz establece las operaciones requeridas; el tipado de pato se basa en las operaciones que proporciona un objeto.
  • La composición modela objetos que tienen otros objetos como componentes.
5

Calcula valores nuevos y describe los resultados deseados

Un programa puede centrarse en transformar valores o en el resultado que debe producir un motor. Las funciones puras hacen explícitas las dependencias, mientras que los datos inmutables evitan cambios mediante referencias compartidas. Las consultas declarativas permiten que un motor especializado elija los pasos de ejecución. Sigue una cadena de procesamiento de datos y una consulta de base de datos para ver qué facilitan estos enfoques y dónde siguen interviniendo la entrada, la salida y las instrucciones máquina concretas.

5.1 Funciones puras y cadenas de procesamiento de datos

La programación funcional construye resultados combinando funciones. Su práctica central es producir valores nuevos a partir de las entradas y limitar la mutación compartida. Lisp introdujo ideas funcionales de uso extendido hacia 1958; Haskell, Erlang, Clojure, F# y OCaml desarrollan esas ideas de distintas formas.

Reconoce qué hace pura a una función

Una función pura da el mismo resultado para los mismos argumentos y no tiene efectos laterales. def with_tax(price): return price * 1.2 ilustra la idea: with_tax(50) da 60,0. Una función que además añade un elemento a una lista global o muestra un mensaje tiene un efecto más allá de su valor de retorno. Un resultado que depende de la hora actual también depende de algo más que de los argumentos.

Una llamada pura puede sustituirse por su valor de retorno sin cambiar el comportamiento del programa. Esta propiedad es la transparencia referencial. Facilita las pruebas aisladas y permite ejecutar llamadas independientes en paralelo sin competir por actualizar el estado compartido.

La inmutabilidad mantiene sin cambios un valor existente. [n + 1 for n in nums] crea una lista nueva y deja nums intacta. Un programa puede seguir representando información cambiante produciendo versiones nuevas; no necesita sobrescribir la versión anterior.

Pasa comportamiento como datos

Las funciones son de primera clase cuando pueden asignarse a nombres, pasarse como argumentos y devolverse como valores. Una función de orden superior recibe una función o devuelve una. Una expresión lambda es una expresión de función sin nombre, como lambda n: n * n.

Tres operaciones habituales de orden superior forman una cadena de procesamiento:

  • filter conserva los elementos que cumplen una prueba.
  • map transforma cada elemento restante.
  • reduce combina los elementos en un resultado mediante un acumulador.

Para sumar los cuadrados de los números pares del 1 al 6, el filtrado deja 2, 4 y 6; elevarlos al cuadrado da 4, 16 y 36; sumarlos da 56.

sum(map(lambda n: n * n,
        filter(lambda n: n % 2 == 0, range(1, 7))))

Aquí % 2 == 0 comprueba si el número es par, y range(1, 7) proporciona del 1 al 6. Haskell expresa el mismo cálculo como sum (map (^2) (filter even [1..6])).

Con reduce importado de functools de Python, reduce(lambda acc, n: acc + n, [3, 8, 5], 0) combina 0 con 3 para obtener 3, después con 8 para obtener 11 y después con 5 para obtener 16. La cadena de procesamiento describe transformaciones sin un bucle externo que actualice un total compartido.

Conserva un entorno o llama a un problema más pequeño

def multiplier(k): return lambda x: k * x devuelve una función que conserva el acceso a k. Así, multiplier(3)(10) devuelve 30 incluso después de que termine la llamada externa. La función devuelta y el entorno conservado forman una clausura.

La recursión hace que una función se llame a sí misma con un problema más pequeño. El factorial devuelve 1 cuando n == 0; en caso contrario, devuelve n * factorial(n - 1). Para 5, los productos dan 120. Un cálculo finito necesita un caso base alcanzable. Las llamadas anidadas pendientes de Python acaban generando RecursionError; los lenguajes con soporte adecuado de llamadas de cola pueden reutilizar el espacio de la pila para las llamadas en posición final.

Usar funciones por sí solo no hace funcional al código: las funciones procedimentales pueden modificar el estado. Los programas funcionales reales también leen entradas y escriben salidas, y suelen mantener los efectos cerca de los límites del programa y los cálculos centrales puros.

Puntos clave

  • Las funciones puras dependen solo de sus argumentos y no tienen efectos laterales.
  • Las actualizaciones inmutables crean valores nuevos.
  • Las funciones de primera clase pueden pasarse y devolverse; las de orden superior reciben o devuelven funciones.
  • El filtrado selecciona, la aplicación de una función transforma y la reducción combina.
  • Una clausura conserva el entorno en el que se creó.
  • Un cálculo recursivo finito necesita un caso base que realmente alcance.

5.2 Expresa un resultado y deja que un motor lo encuentre

La programación declarativa describe el resultado deseado y deja los pasos de ejecución al motor de un lenguaje. El código funcional suele agruparse bajo esta idea amplia, junto con los lenguajes de consulta y la programación lógica.

Lee una consulta como una solicitud

Una base de datos relacional almacena datos en tablas. Supón que students tiene las filas de nombres y calificaciones Ana 92, Ben 75, Chen 80 y Dara 64:

SELECT name
FROM students
WHERE grade >= 80
ORDER BY name;

FROM indica la tabla, WHERE da la condición, SELECT elige el campo que se devuelve y ORDER BY solicita el orden alfabético. Ana y Chen cumplen la condición, incluida la calificación de Chen de exactamente 80. El resultado son sus nombres en ese orden.

Un programa imperativo crearía una lista de resultados, recorrería las filas, comprobaría cada calificación, añadiría los nombres que cumplen la condición y ordenaría la lista. SQL da a la base de datos el resultado requerido sin escribir ese bucle.

Un optimizador de consultas elige un plan de ejecución. Podría recorrer la tabla o utilizar un índice disponible, una estructura de búsqueda que puede acelerar las búsquedas adecuadas. Muchos índices utilizan estructuras ordenadas; otros utilizan disposiciones distintas. Añadir un índice puede cambiar el plan sin cambiar la consulta. Más datos siguen suponiendo trabajo: declarar el resultado no garantiza que todas las consultas tengan poco coste.

Pregunta qué se deduce de los hechos y las reglas

La programación lógica describe hechos y relaciones y después busca valores que satisfagan una pregunta. En Prolog:

parent(tom, bob).
parent(bob, ann).
grandparent(X, Z) :- parent(X, Y), parent(Y, Z).

La regla significa que X es abuelo o abuela de Z cuando existe un Y para el que se cumplen ambas relaciones de progenitor. La consulta ?- grandparent(tom, Who). encuentra Who = ann: Tom es progenitor de Bob y Bob es progenitor de Ann. Los nombres con mayúscula inicial representan variables; los nombres en minúscula identifican a personas concretas.

Reconoce las herramientas declarativas a tu alrededor

Una fórmula de hoja de cálculo =SUM(B2:B5) establece una dependencia. La hoja de cálculo la recalcula cuando cambian las celdas pertinentes. La expresión regular [0-9]{3}-[0-9]{4} describe tres dígitos, un guion y cuatro dígitos, como en «555-0199». Un motor de coincidencia de texto realiza la búsqueda.

CSS establece la apariencia con una regla como h1 { color: navy; }; HTML marca la estructura de la página. Son notaciones declarativas, normalmente clasificadas como lenguajes de estilos y de marcado. Su función difiere de la de un lenguaje de propósito general capaz de expresar una amplia variedad de cálculos.

Tipo de lenguajeEnfoque de la tareaEjemplos
Lenguaje específico de dominioUn tipo concreto de trabajoSQL, expresiones regulares, CSS
Lenguaje de propósito generalMuchos tipos de programasPython, Java, Haskell

El código declarativo puede mantenerse próximo al enunciado de un problema y permitir que un motor mejore su método. También da a quien programa menos control directo sobre los pasos individuales, lo que puede dificultar el diagnóstico de un plan lento. Por debajo siguen ejecutándose instrucciones concretas: «declarativo» describe cómo expresa la tarea quien programa, no un procesador que funciona sin pasos.

Puntos clave

  • El código declarativo expresa un resultado deseado; un motor elige los pasos de ejecución.
  • SQL selecciona filas y campos mediante condiciones y un orden solicitado.
  • Un optimizador puede cambiar el plan de una consulta sin cambiar la consulta.
  • La programación lógica deduce respuestas a partir de hechos y reglas.
  • Los lenguajes específicos de dominio se dirigen a tareas concretas; los de propósito general admiten muchos tipos de programas.
6

Elige un estilo y un lenguaje para la tarea

Los distintos estilos de programación pueden producir la misma respuesta organizando el trabajo de formas distintas. Compararlos en una misma tarea facilita ver sus ventajas e inconvenientes. La elección del lenguaje añade restricciones prácticas: la plataforma de destino, las bibliotecas disponibles, los límites de memoria y velocidad, las necesidades de seguridad y las personas que mantienen el código. Un proyecto con varias partes puede utilizar un lenguaje distinto y una combinación de estilos diferente para cada parte.

6.1 Una tarea con varios estilos de programación

Un paradigma cambia cómo se expresa una solución, no la respuesta numérica que debe producir una solución correcta. Considera pedidos con importes [120, 45, 300, 80]: calcula el total de los importes estrictamente mayores que 100.

Sigue los cambios de estado

La versión imperativa hace explícitos el bucle y el acumulador:

total = 0
for amount in amounts:
    if amount > 100:
        total = total + amount

El primer importe cambia total a 120. El segundo lo deja sin cambios. El tercero lo cambia a 420 y el cuarto lo deja ahí. El programa hace visibles la prueba y el orden de actualización.

Transforma una colección

Una expresión compacta de Python con estilo funcional es sum(a for a in amounts if a > 100). Selecciona 120 y 300 y los combina en 420. Otra versión es sum(filter(lambda a: a > 100, amounts)). Aquí el predicado describe qué elementos se incluyen y sum combina los valores seleccionados.

Una expresión generadora o una llamada a filter utiliza las características multiparadigma de Python; su presencia no hace puras todas las partes de la aplicación que la rodean.

Coloca las reglas junto con los objetos

En un diseño orientado a objetos, un Order almacena un amount y ofrece is_large(), que devuelve self.amount > 100. Un OrderBook contiene objetos de pedido y ofrece large_total(). Ese método puede recorrer los pedidos, preguntar a cada uno si cumple la condición y sumar los importes que la cumplen.

El total sigue siendo 420. La diferencia está en dónde se sitúan las responsabilidades: la regla para un pedido grande pertenece a Order, y el trabajo sobre la colección pertenece a OrderBook. Otras funcionalidades pueden reutilizar la regla sin copiar la comprobación del umbral.

Pídeselo a la base de datos

Si los datos ya están en una base de datos relacional, SQL puede solicitar el total:

SELECT SUM(amount)
FROM orders
WHERE amount > 100;

La base de datos elige el plan de recuperación y agregación. La consulta no indica al código que llama que recorra todas las filas ni que mantenga un acumulador local.

EstiloQué destaca el ejemplo
ImperativoPruebas ordenadas y actualizaciones de un total
Orientado a objetosObjetos de pedidos y de colección con responsabilidades definidas
FuncionalSelección y combinación de valores
SQL declarativoEl resultado requerido sobre los datos almacenados
Combina estilos donde ayuden

Python admite técnicas procedimentales, orientadas a objetos y funcionales. JavaScript proporciona objetos, funciones de primera clase y clausuras. C++, C#, Kotlin, Scala, Swift y Rust también mezclan estilos. Java está muy asociado a la POO, pero admite expresiones lambda desde Java 8 en 2014; Haskell es puramente funcional.

Una aplicación web puede utilizar clases para usuarios y pedidos, transformaciones funcionales para los datos de informes, actualizaciones imperativas dentro de métodos y SQL para consultas a la base de datos. Estas decisiones actúan en capas distintas.

La programación dirigida por eventos añade otra idea de organización: registrar un manejador que se ejecuta cuando llega un evento. En JavaScript, button.addEventListener("click", showMenu) pasa showMenu como valor de función. Un clic posterior la invoca. El propio manejador puede utilizar objetos, transformaciones puras o pasos imperativos. Su función dirigida por eventos indica cuándo se ejecuta, no el único estilo que puede utilizar su cuerpo.

Puntos clave

  • Las soluciones correctas en distintos paradigmas pueden calcular el mismo resultado.
  • Un estilo cambia dónde aparecen los pasos, el estado, las reglas y las responsabilidades.
  • Los lenguajes modernos suelen admitir varios paradigmas.
  • Un manejador de eventos se ejecuta en respuesta a un evento y puede utilizar otros estilos internamente.

6.2 Elige según las restricciones y los compromisos entre objetivos

Ningún lenguaje de programación es el más adecuado para todos los proyectos. Los lenguajes de propósito general suelen ser completos en el sentido de Turing: con tiempo y memoria ilimitados, pueden expresar la misma clase de cálculos. Las decisiones reales dependen de si una solución se adapta a la plataforma, los recursos, las herramientas y las personas disponibles.

Empieza por lo que debe funcionar

La plataforma suele ser una restricción ineludible. Los navegadores ejecutan JavaScript y pueden ejecutar módulos compilados mediante WebAssembly. TypeScript añade comprobaciones estáticas y se compila a JavaScript. Swift es una elección habitual para aplicaciones de las plataformas de Apple; Kotlin se recomienda para Android. SQL es el lenguaje habitual para las consultas relacionales.

Después considera el dominio y el ecosistema. Python tiene bibliotecas de datos y aprendizaje automático de uso extendido, como NumPy y pandas. R se centra en la estadística. C y C++ son habituales en sistemas operativos, motores de juegos y código para sistemas embebidos. Java y C# son habituales en grandes sistemas empresariales; Bash es útil para automatizar tareas del sistema operativo.

Son asociaciones prácticas, no territorios exclusivos. Una biblioteca adecuada puede ahorrar mucho más trabajo de desarrollo que una pequeña diferencia en la sintaxis del lenguaje.

Examina las necesidades de recursos y seguridad

Un microcontrolador con solo unos pocos kilobytes de RAM puede necesitar un control preciso de la memoria y de la sobrecarga durante la ejecución. C, C++ o Rust pueden ser adecuados cuando el sistema de destino los admite. Los componentes exigentes de juegos o bases de datos también suelen favorecer esos lenguajes, pero los motores y los recursos disponibles difieren: Unity utiliza C# y algunas bases de datos utilizan Java o Go.

Antes de elegir por velocidad, encuentra el cuello de botella: la parte que limita el rendimiento. Un programa que espera principalmente una respuesta de red no será mucho más rápido solo porque un pequeño bucle local se compile de forma más eficiente. Mide trabajo representativo en la plataforma de destino.

La seguridad de memoria reduce errores como el acceso no válido a memoria. Rust seguro comprueba las reglas de propiedad durante la compilación; los lenguajes con gestión automática, como Java, C#, Go y Python, proporcionan gestión de memoria durante la ejecución. El tipado estático detecta muchas operaciones incompatibles antes de la ejecución, lo que puede ayudar en una base de código grande. Ni la seguridad de memoria ni el tipado estático demuestran que la lógica del programa sea correcta.

Incluye el coste de mantener el resultado

Los conocimientos del equipo, el personal de desarrollo disponible, los depuradores, las herramientas de pruebas, las bibliotecas, las licencias y el código existente afectan a la decisión. Un lenguaje conocido y con buen soporte puede reducir los costes de entrega y mantenimiento. Reescribir un sistema que funciona genera trabajo de migración y riesgos aunque el nuevo lenguaje tenga características atractivas.

En una red meteorológica urbana, los distintos componentes tienen necesidades distintas:

ComponenteElección razonable de lenguajeMotivo
Firmware de sensores pequeñosC o RustControl de memoria y compatibilidad con el hardware de destino
Lecturas almacenadasSQLFiltrado y agregación en la base de datos
Limpieza y análisisPythonBibliotecas de procesamiento de datos
Visualización interactiva en el navegadorJavaScript o TypeScriptCompatibilidad con el navegador y herramientas para interfaces

Una justificación útil vincula cada elección con un requisito: «El equipo ya mantiene el servicio de Java y sus bibliotecas admiten la nueva funcionalidad». Una posición en una clasificación de popularidad no puede aportar por sí sola ese razonamiento. Las encuestas pueden indicar una comunidad grande o una amplia oferta de personal; no establecen el mejor lenguaje para una carga de trabajo concreta.

Puntos clave

  • Comprueba las restricciones de la plataforma antes que las preferencias.
  • Las bibliotecas y las herramientas conectan un lenguaje con su dominio de problemas.
  • Mide el cuello de botella antes de elegir principalmente por velocidad.
  • La seguridad de memoria y las comprobaciones estáticas reducen algunos fallos, pero la lógica sigue necesitando pruebas.
  • Las capacidades del equipo, el código existente y los costes de mantenimiento forman parte de la decisión.
  • Distintos componentes pueden justificar distintos lenguajes.

Ejemplos de Lenguajes y paradigmas de programación

6 Lecciones Interactivas

1

Sigue un programa desde el código fuente hasta la ejecución

13 pasos
Sigue las capas de traducción, examina una construcción y compara las vías de ejecución.
  1. 1
    IntroducciónUn cálculo atraviesa varias capas

    Escribes un cálculo, pero un procesador necesita pequeñas instrucciones codificadas llamadas código máquina. Un traductor aporta los detalles que faltan.

    Sigue x = a + b desde el código fuente hacia los niveles inferiores y después sigue el programa a través de su construcción y ejecución. Separa dos preguntas: ¿en qué forma está el código y qué software lo está ejecutando? Un compilador, un intérprete y un compilador JIT responden de forma distinta a la segunda. Esa distinción explicará tanto la portabilidad como el momento en que aparecen los errores.

  2. 2
    EjemploUn cálculo se convierte en varias acciones

    Para x = a + b, imagina cuatro acciones de nivel inferior:

    1. Cargar el valor de a en un registro.
    2. Cargar el valor de b en otro registro.
    3. Sumar los valores de los dos registros.
    4. Guardar el resultado para x.

    La sentencia de alto nivel oculta la selección de registros y los detalles de almacenamiento. Las instrucciones reales generadas pueden diferir porque un compilador puede optimizar el trabajo. El cálculo requerido sigue siendo el mismo.

  3. 3
    ExplicaciónLas instrucciones legibles siguen dirigidas a un procesador

    El lenguaje ensamblador utiliza nombres de instrucciones como mov y add. Un ensamblador las codifica como instrucciones máquina.

    En x86-64, mov eax, 1 coloca 1 en EAX; add eax, ebx le suma EBX. Esos registros y codificaciones pertenecen a esa arquitectura del conjunto de instrucciones. Sustituir bytes por nombres legibles ayuda a una persona, pero no convierte las instrucciones en una descripción portable de alto nivel.

  4. 4
    DesafíoTraslada el programa a otro procesador

    Un equipo tiene código fuente portable en C y un ejecutable nativo x86-64. Necesita un ejecutable nativo ARM. ¿Qué archivo puede utilizar como punto de partida y qué trabajo hace falta?

    ---reveal---

    Utilizar el código fuente y compilarlo para el destino ARM, con el soporte de plataforma requerido. El ejecutable x86-64 contiene instrucciones para una ISA distinta. La portabilidad del código fuente permite otra traducción; no convierte esas instrucciones máquina existentes en código ARM nativo.

  5. 5
    EjemploIdentifica los componentes léxicos antes que la estructura

    Considera total = price * 3; en C.

    El análisis léxico encuentra seis componentes: total, =, price, *, 3 y ;. El análisis sintáctico agrupa después price * 3 como la expresión cuyo resultado se asigna a total.

    Son tareas distintas. El análisis léxico identifica las piezas; el sintáctico establece cómo encajan. El análisis semántico puede comprobar después cuestiones como si los nombres están declarados y los tipos de los operandos son compatibles.

  6. 6
    EjemploTraslada los cálculos constantes a la construcción

    La expresión fuente 60 * 60 * 24 utiliza solo constantes. El plegado de constantes puede evaluarla antes de la ejecución: primero 3600 y después 86 400.

    La generación de código puede utilizar ese resultado en lugar de instrucciones que repitan las multiplicaciones. Es una optimización porque cambia cómo se realiza el trabajo y conserva el resultado requerido. Sustituir un valor por una aproximación meramente conveniente no estaría justificado por esta regla.

  7. 7
    ExplicaciónConecta los archivos traducidos con las rutinas de las bibliotecas

    La compilación puede producir archivos objeto independientes. Un enlazador conecta esos archivos y resuelve las referencias a las operaciones de las bibliotecas.

    El enlace estático incluye el código de las bibliotecas en el ejecutable construido. El enlace dinámico depende de una biblioteca compartida compatible que se carga después. Ninguno de los dos enfoques exige que la computadora del usuario tenga el compilador anticipado, pero un ejecutable con enlace dinámico sigue necesitando sus dependencias de ejecución.

  8. 8
    DesafíoUna construcción correcta da una respuesta incorrecta

    Un programa de C se construye correctamente. Su informe debería restar una cantidad utilizada de una cantidad inicial, pero el código las suma. ¿Ha dejado el compilador de comprobar alguna regla del lenguaje?

    ---reveal---

    No. La suma puede tener una sintaxis válida y tipos compatibles y, aun así, expresar una tarea incorrecta. Esto es un error lógico. El compilador comprueba las reglas del lenguaje y genera código; hace falta una prueba con un resultado esperado conocido para revelar la diferencia entre el cálculo y su finalidad prevista.

  9. 9
    ExplicaciónUn entorno de ejecución puede ejecutar instrucciones intermedias

    CPython compila un archivo de Python válido a código de bytes y después ejecuta ese código de bytes en su máquina virtual. El procesador ejecuta las instrucciones máquina del entorno de ejecución mientras este realiza las operaciones de Python.

    Java también utiliza código de bytes: javac produce archivos de clase para una JVM compatible. El código de bytes pertenece al modelo de instrucciones de un entorno de ejecución, por lo que no debe confundirse con un conjunto universal de instrucciones de procesador.

  10. 10
    EjemploCompara dos momentos en los que puede detenerse la ejecución

    En este archivo de Python válido, la primera línea se ejecuta:

    print("start")
    x = 10
    print(x + "5")
    

    Muestra «start» antes de que la última línea genere TypeError. Ahora elimina el último paréntesis de cierre: CPython no puede compilar el archivo, por lo que no se ejecuta ninguna sentencia de ese archivo. Un fallo de sintaxis durante la preparación y un fallo de tipos durante una operación ejecutada detienen el programa en etapas distintas.

  11. 11
    ExplicaciónCompila el código más utilizado durante la ejecución

    Un compilador JIT produce código máquina mientras se ejecuta el programa. Un entorno de ejecución puede interpretar primero, observar los métodos utilizados con frecuencia y compilarlos.

    Esto desplaza los costes: la compilación utiliza tiempo y memoria durante la ejecución, mientras que la ejecución repetida puede ser más rápida después. Un servicio de larga duración puede beneficiarse más de ese calentamiento que un programa diminuto de un solo uso. El resultado depende de la implementación y de la carga de trabajo.

  12. 12
    DesafíoSepara la portabilidad del código de bytes de la velocidad nativa

    Un archivo de clase de Java se ejecuta mediante una JVM compatible en dos ISA distintas. Durante cada ejecución, el entorno compila un método utilizado con frecuencia. ¿Por qué puede compartirse un archivo de clase mientras el código máquina generado es distinto?

    ---reveal---

    El archivo de clase contiene código de bytes de la JVM, que ambos entornos de ejecución compatibles entienden. Cada JIT se dirige a su propio procesador anfitrión. La portabilidad procede del formato intermedio compartido y de los entornos de ejecución compatibles; las instrucciones máquina producidas durante la ejecución siguen siendo específicas de cada plataforma.

  13. 13
    RepasoDistingue los niveles de las vías de ejecución

    El código máquina se dirige a una ISA; el lenguaje ensamblador da nombres legibles a sus instrucciones; el código fuente de alto nivel oculta más detalles del hardware.

    Un compilador traduce código, un intérprete lo ejecuta y un JIT compila durante la ejecución. Un entorno de ejecución puede combinar estos métodos. Para explicar un sistema real, identifica sus formas fuente, intermedia y nativa y después indica qué etapa comprueba la sintaxis y qué etapa ejecuta la operación que puede fallar.

2

Sigue el significado a través de un programa breve

10 pasos
Lee la gramática, el orden de evaluación, los cambios de estado y los límites de las funciones.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónLa forma y el significado responden a preguntas distintas
  2. 2
    EjemploUn código válido puede agrupar mal un cálculo
  3. 3
    ExplicaciónLa primera condición puede proteger la segunda
  4. 4
    DesafíoPredice si se ejecuta la división
  5. 5
    ExplicaciónUna asignación lee antes de actualizar
  6. 6
    EjemploSigue cada actualización del acumulador
  7. 7
    EjemploSigue una llamada y su valor de retorno
  8. 8
    DesafíoUna actualización local puede dejar sin cambios el código que llama
  9. 9
    DesafíoSigue juntos la gramática y el estado
  10. 10
    RepasoLee el camino ejecutado
3

Sigue los tipos y los objetos compartidos

14 pasos
Compara el momento de comprobación, sigue los alias y decide cuándo puede liberarse la memoria.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónSigue los valores, los objetos y el almacenamiento
  2. 2
    ExplicaciónLos valores con tipo pueden pasar de un nombre a otro
  3. 3
    Idea claveLa inferencia y la conversión responden a preguntas distintas
  4. 4
    DesafíoInterpreta los dígitos según el valor realmente devuelto
  5. 5
    ExplicaciónLa representación afecta a los resultados numéricos
  6. 6
    ExplicaciónUn marco de llamada puede terminar antes que un objeto
  7. 7
    EjemploDos nombres apuntan a una lista
  8. 8
    DesafíoUn parámetro puede modificar un objeto y después apuntar a otro
  9. 9
    ExplicaciónIdentifica exactamente cómo se utiliza mal el almacenamiento
  10. 10
    EjemploSigue las referencias hasta que desaparezca la última
  11. 11
    Idea claveUn ciclo puede no tener ningún punto de entrada vivo
  12. 12
    ExplicaciónLa propiedad de los datos comprueba el acceso antes de la ejecución
  13. 13
    DesafíoDescubre por qué la recolección no puede recuperar la memoria de una lista
  14. 14
    RepasoSigue tres decisiones distintas
4

Protege las reglas y elige las relaciones entre objetos

12 pasos
Sigue órdenes que dependen del orden y después utiliza encapsulación, redefinición de métodos y composición.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónLos pasos necesitan límites fiables
  2. 2
    EjemploEl orden de las operaciones cambia el estado compartido
  3. 3
    ExplicaciónHaz útil el límite de un procedimiento
  4. 4
    DesafíoPredice una actualización con otro orden
  5. 5
    ExplicaciónDa a cada cuenta su propio estado
  6. 6
    EjemploValida antes de cambiar un saldo
  7. 7
    DesafíoLocaliza una regla eludida
  8. 8
    EjemploEspecializa sin incumplir el contrato
  9. 9
    ExplicaciónDeja que el objeto elija su método de área
  10. 10
    ConexiónContén el componente que realiza el trabajo
  11. 11
    DesafíoElige dos relaciones en un diseño nuevo
  12. 12
    RepasoAjusta los límites a las responsabilidades
5

Transforma una colección y consulta un resultado

13 pasos
Sigue una cadena de procesamiento funcional y una consulta declarativa, comprobando los efectos y la ejecución.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónExpresa una transformación o una solicitud
  2. 2
    ExplicaciónLa pureza describe algo más que el número devuelto
  3. 3
    EjemploSelecciona primero los valores pares
  4. 4
    EjemploTransforma y combina los valores seleccionados
  5. 5
    DesafíoAplica la cadena de procesamiento a un caso nuevo
  6. 6
    ExplicaciónDevuelve un comportamiento que recuerde su factor
  7. 7
    EjemploReduce el problema hasta que se aplique un caso directo
  8. 8
    EjemploLee SQL como una solicitud sobre filas
  9. 9
    Idea claveMantén el resultado y cambia el plan
  10. 10
    EjemploConecta dos hechos mediante una regla
  11. 11
    DesafíoLee una consulta nueva e incluye su valor límite
  12. 12
    DesafíoCombina una consulta con una transformación pura
  13. 13
    RepasoMantén visible la relación entre la entrada y el resultado
6

Compara estilos y justifica la elección de un lenguaje

10 pasos
Mantén fija una tarea y después elige herramientas para componentes con restricciones distintas.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónMantén fija la tarea
  2. 2
    EjemploMantén fija la tarea y cambia el estilo
  3. 3
    EjemploSitúa la misma regla en objetos o en una consulta
  4. 4
    DesafíoDecide si un diseño tiene un único estilo exclusivo
  5. 5
    ExplicaciónDescarta primero los destinos sin soporte
  6. 6
    EjemploElige para un sistema meteorológico completo
  7. 7
    ConsejoMide qué consume el tiempo
  8. 8
    DesafíoDefiende una elección con restricciones en conflicto
  9. 9
    DesafíoConecta la elección del lenguaje con la organización del programa
  10. 10
    RepasoDa una razón vinculada al requisito

Ejercicios de Lenguajes y paradigmas de programación

50 Preguntas de Práctica en 5 sets

Set 1: Lee código e identifica la vía de ejecución10 Preguntas de Práctica

Pregunta 1 de 10

Una persona que desarrolla software sustituye los bytes de las instrucciones por nombres como mov y jmp. ¿En qué nivel de lenguaje está escrito ahora el programa?
  1. Código máquina
  2. Lenguaje ensamblador
  3. Un lenguaje de alto nivel
  4. Un lenguaje de consulta de bases de datos
Mostrar respuestaOcultar respuesta
Respuesta: B
El lenguaje ensamblador escribe las operaciones del procesador con mnemónicos y nombres de registros, lo que facilita su lectura. Sigue ligado a una arquitectura del conjunto de instrucciones. Los nombres legibles por sí solos no lo convierten en un lenguaje de alto nivel, porque quien programa sigue especificando operaciones individuales de bajo nivel.

Pregunta 2 de 10

Durante una construcción, una herramienta conecta varios archivos objeto y resuelve una llamada a una rutina de biblioteca. ¿Qué herramienta realiza esta tarea?
  1. Un ensamblador
  2. Un analizador sintáctico
  3. Un intérprete
  4. Un enlazador
Mostrar respuestaOcultar respuesta
Respuesta: D
Un enlazador conecta las partes traducidas y resuelve las referencias a código definido en otro lugar, incluidas las rutinas de bibliotecas. Un compilador puede haber producido los archivos objeto antes durante la construcción. El análisis sintáctico comprueba la estructura del código fuente; no conecta los archivos resultantes para formar un programa completo.

Pregunta 3 de 10

Un archivo de Python contiene una sentencia para mostrar texto en la primera línea y un paréntesis sin cerrar en la última. ¿Qué ocurre cuando CPython ejecuta el archivo?
  1. Informa de un error de sintaxis antes de ejecutar la primera sentencia.
  2. Muestra la primera línea y después informa de un error de sintaxis.
  3. Muestra la primera línea y luego genera un error en tiempo de ejecución.
  4. Omite solo la última línea y ejecuta las líneas válidas.
Mostrar respuestaOcultar respuesta
Respuesta: A
CPython debe compilar el archivo antes de ejecutar sus sentencias, y el paréntesis sin cerrar impide esa preparación. Por tanto, la primera sentencia nunca se ejecuta. Mostrar texto antes del fallo correspondería a un error de ejecución en código válido, pero este archivo tiene un error de sintaxis que se detecta antes.

Pregunta 4 de 10

Un programa válido de Python muestra un promedio incorrecto porque suma un valor después de dividir el otro entre dos. ¿Qué tipo de error es?
  1. Un error de sintaxis
  2. Un error de ejecución
  3. Un error lógico
  4. Un error de incompatibilidad de tipos
Mostrar respuestaOcultar respuesta
Respuesta: C
Las operaciones se completan, pero su significado no corresponde al promedio previsto. Por eso el fallo es un error lógico. Un error de sintaxis incumpliría la gramática del lenguaje, mientras que un error de ejecución implicaría el fallo de una operación ejecutada; ninguno se indica en este cálculo que termina correctamente, pero da un resultado incorrecto.

Pregunta 5 de 10

¿Qué valor final almacena este código de Python en total?

total = 1
for n in [4, 7, 2]:
    total = total + n
  1. 13
  2. 3
  3. 15
  4. 14
Mostrar respuestaOcultar respuesta
Respuesta: D
El total empieza en 1, después pasa a 5 al sumar 4, a 12 al sumar 7 y a 14 al sumar 2. El bucle conserva el resultado de cada actualización anterior. Obtener 13 ignoraría el valor inicial y calcularía solo la suma de la lista, que no es el cálculo completo escrito aquí.

Pregunta 6 de 10

En Python, label = "18" va seguido de label = 18. El nombre puede cambiar de tipo, pero "18" + 2 genera un error. ¿Cómo se describen estos dos hechos?
  1. Tipado estático con reglas estrictas para sumar tipos distintos
  2. Tipado dinámico con reglas estrictas para sumar tipos distintos
  3. Tipado dinámico con suma numérica automática
  4. Tipado estático con suma numérica automática
Mostrar respuestaOcultar respuesta
Respuesta: B
Volver a vincular un nombre de texto a un entero es coherente con el tipado dinámico, mientras que la suma fallida muestra que esta operación no convierte silenciosamente el texto en un número. El momento de comprobación y el comportamiento de las conversiones son cuestiones distintas. El tipado dinámico no significa que los tipos incompatibles se combinen automáticamente.

Pregunta 7 de 10

¿Qué contiene first después de ejecutar este código de Python?

first = [4, 8]
second = first
second.append(6)
  1. [4, 8]
  2. [6]
  3. [4, 8, 6]
  4. [4, 8, [6]]
Mostrar respuestaOcultar respuesta
Respuesta: C
Ambos nombres hacen referencia a una lista porque asignar la referencia no copia su contenido. Añadir un elemento mediante second modifica esa lista compartida, por lo que first también muestra el 6 añadido. Mantener [4, 8] requeriría una lista independiente, como la creada mediante una copia antes de añadir el elemento.

Pregunta 8 de 10

Un programa de informes se divide en read_data(), calculate_total() y save_report(), cada uno con órdenes secuenciales. ¿Qué organización ilustra?
  1. Programación procedimental
  2. Programación lógica
  3. Programación funcional pura
  4. Herencia de clases
Mostrar respuestaOcultar respuesta
Respuesta: A
La programación procedimental agrupa pasos imperativos en tareas con nombre, que es exactamente cómo se organizan estas operaciones de informes. Los nombres de las funciones por sí solos no demuestran programación funcional pura. En particular, leer y guardar son efectos, y nada en la descripción exige que los cálculos eviten cambios compartidos.

Pregunta 9 de 10

Una función recursiva de Python devuelve 0 cuando n == 0; en caso contrario, se llama a sí misma con n + 1. Partiendo del valor positivo 2, ¿por qué el caso base escrito no hace que termine el cálculo?
  1. Las entradas llegan a cero, pero devolver cero reinicia la recursión.
  2. El caso base garantiza la terminación incluso con entradas crecientes.
  3. La recursión retorna hacia cero antes de comprobar el caso base.
  4. Las entradas recursivas se alejan de cero en lugar de alcanzarlo.
Mostrar respuestaOcultar respuesta
Respuesta: D
Las entradas sucesivas son 2, 3, 4 y así sucesivamente, por lo que ninguna cumple la prueba de parada. La progresión recursiva debe alcanzar realmente un caso base; su presencia en el código fuente por sí sola no basta. Devolver 0 en esa rama no visitada no puede cambiar los argumentos proporcionados por las llamadas que sí se ejecutan.

Pregunta 10 de 10

Una solicitud de un panel realiza dos pasos secuenciales: espera 180 ms los datos de la red y después tarda 20 ms en darles formato localmente. Un lenguaje nuevo reduciría el formato a 5 ms sin cambiar la espera de red. ¿Cuál es la mejor valoración de este cambio por sí solo?
  1. Ahorra 15 ms, mientras la red sigue siendo el retardo principal.
  2. Ahorra 150 ms al aplicar a la solicitud la mejora de velocidad por un factor de cuatro.
  3. Ahorra 5 ms porque el nuevo tiempo de formato es el ahorro.
  4. Ahorra 20 ms porque la compilación elimina el paso de formato.
Mostrar respuestaOcultar respuesta
Respuesta: A
El trabajo secuencial indicado baja de 200 ms a 185 ms, con un ahorro de 15 ms. La espera de red de 180 ms no cambia y sigue dominando la solicitud. Aplicar la mejora de velocidad por un factor de cuatro del formato a toda la solicitud supondría incorrectamente que la misma mejora afecta también a la espera de red.
Set 2: Aplica las reglas de tipos, objetos y consultas10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Un equipo puede compilar el mismo código fuente portable para dos ISA distintas. ¿Cuál es la relación correcta entre su código fuente y las salidas nativas?
  1. El código fuente se comparte; ambas salidas contienen código para la misma ISA.
  2. El código fuente debe diferir; cada salida se dirige a su propia ISA.
  3. El código fuente se comparte; cada salida se dirige a su propia ISA.
  4. El código fuente debe diferir; ambas salidas contienen código para la misma ISA.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Un entorno de ejecución traduce un método ejecutado con frecuencia a instrucciones nativas después de iniciar la aplicación. ¿Qué técnica es?
  1. Compilación en tiempo de ejecución
  2. Compilación anticipada
  3. Enlace estático
  4. Análisis léxico
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Con a = 8 y b = 12 en Python, ¿qué valores producen a + b / 2 y (a + b) / 2, respectivamente?
  1. 10,0 y 10,0
  2. 14,0 y 14,0
  3. 10,0 y 14,0
  4. 14,0 y 10,0
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Una función de C libera un bloque reservado, pero un código posterior lo lee mediante un puntero antiguo. ¿Qué fallo crea esa lectura posterior?
  1. Una fuga de memoria
  2. Un uso después de liberar memoria
  3. Una doble liberación de memoria
  4. Un desbordamiento de búfer
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Una clase define un atributo balance y un método deposit. Se crean dos cuentas con saldos iniciales distintos. ¿Dónde corresponden esos saldos distintos?
  1. En la definición compartida del depósito
  2. En las dos instancias de cuenta
  3. En el propio nombre de la clase
  4. En los nombres de los parámetros del constructor
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Un objeto de bicicleta almacena un objeto de sistema de frenos y le pide que detenga las ruedas. ¿Qué relación se ajusta mejor a este diseño?
  1. Composición: la bicicleta tiene un sistema de frenos.
  2. Herencia: la bicicleta es un sistema de frenos.
  3. Redefinición: la bicicleta sustituye al sistema de frenos.
  4. Tipado de pato: la bicicleta es una subclase del sistema de frenos.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

A partir de [1, 4, 7, 10], una cadena de procesamiento conserva los valores mayores que 3, resta 1 a cada valor conservado y después los suma. ¿Cuál es el resultado?
  1. 21
  2. 17
  3. 18
  4. 22
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Una base de datos puede ejecutar una consulta SQL sin cambios recorriendo las filas o utilizando un índice disponible. ¿Qué elige entre esos planes?
  1. El analizador sintáctico de consultas SQL
  2. El enlazador de la base de datos
  3. El comprobador de tipos de SQL
  4. El optimizador de consultas
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Un método de una clase de Python utiliza un bucle para actualizar una lista y una función pura para transformar cada elemento. ¿Cómo deben describirse sus estilos?
  1. Organización con objetos que excluye el trabajo imperativo
  2. Organización funcional que hace puros todos los métodos
  3. Organización con objetos y trabajo imperativo y funcional
  4. Organización imperativa que impide los métodos de objetos
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

¿Cuáles son los valores de x e y después de este código de Python?

x = [12]
y = x
x = x + [3]
y.append(6)
  1. x es [12, 3]; y es [12, 6].
  2. x es [12, 3, 6]; y es [12, 3, 6].
  3. x es [12, 3, 6]; y es [12].
  4. x es [12, 6]; y es [12, 3].
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 3: Sigue las llamadas, los efectos y los contratos de los objetos10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Un compilador sustituye minutes = 60 * 12 por el valor precalculado 720. ¿Qué optimización se utiliza y qué debe conservar?
  1. Enlace dinámico, conservando los nombres de archivo de las bibliotecas
  2. Análisis léxico, conservando cada carácter del código fuente
  3. Calentamiento JIT, conservando las cuentas de llamadas de ejecución
  4. Plegado de constantes, conservando el comportamiento requerido
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

¿Qué muestra este programa de Python?

score = 11
def change():
    score = 4
    return score + 2
print(change())
print(score)
  1. 6 y después 4
  2. 6 y después 11
  3. 4 y después 11
  4. 13 y después 11
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

¿Cuál es el valor de items después de la llamada?

items = [3]
def revise(values):
    values.append(8)
    values = [1]
    values.append(5)
revise(items)
  1. [3, 8]
  2. [1, 5]
  3. [3, 8, 5]
  4. [3]
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Dos objetos hacen referencia el uno al otro. Se eliminan todas las referencias a esos objetos desde las raíces vivas del programa. ¿Por qué puede pasarlos por alto el conteo de referencias por sí solo, mientras que el rastreo puede recuperar su memoria?
  1. Las cuentas internas llegan a cero, por lo que hace falta el rastreo para liberar objetos con cuenta cero.
  2. Las cuentas internas siguen siendo positivas, por lo que ambos objetos deben seguir siendo accesibles.
  3. Las cuentas internas siguen siendo positivas, pero ninguno de los objetos es accesible.
  4. Las cuentas internas igualan el número de raíces, por lo que ambos objetos tienen cuentas de cero.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Partiendo de 120, una operación aplica un descuento del 25 % y otra suma 8. ¿Qué valores finales se obtienen al descontar y después sumar, y al sumar y después descontar?
  1. 96 y 98
  2. 98 y 98
  3. 98 y 96
  4. 96 y 96
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Un método de cuenta comprueba el importe de una retirada antes de cambiar el saldo almacenado. ¿Qué idea de diseño mantiene esta regla junto a la actualización que controla?
  1. Encapsulación
  2. Herencia
  3. Plegado de constantes
  4. Inferencia de tipos
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Una cuenta especializada cobra una comisión de 2 por cada retirada realizada con éxito y nunca debe tener un saldo negativo. Su saldo es 43. ¿Qué debería ocurrir ante una solicitud de retirada de 42?
  1. Aceptar la solicitud y dejar el saldo en 1.
  2. Aceptar la solicitud y dejar el saldo en −1.
  3. Rechazar la solicitud después de reducir el saldo a 1.
  4. Rechazar la solicitud y mantener el saldo en 43.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Una función devuelve un cálculo fijo a partir de sus argumentos y también muestra un mensaje de registro. ¿Es pura y por qué?
  1. Sí; su resultado es predecible.
  2. No; mostrar texto es un efecto lateral.
  3. Sí; el registro no son datos almacenados.
  4. No; todo el código tiene efectos laterales.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Una página web registra showDetails para un evento de clic. ¿Por qué puede ese mismo manejador seguir utilizando métodos de objetos y asignaciones imperativas?
  1. Los eventos hacen que sea orientado a objetos solo si cambia atributos.
  2. Los eventos deciden cuándo se ejecuta; su cuerpo puede utilizar otros estilos.
  3. Los eventos exigen funciones de devolución de llamada puras, por lo que las asignaciones deben moverse.
  4. Los eventos lo hacen declarativo, por lo que el motor elige sus actualizaciones.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Una nueva funcionalidad de análisis debe utilizar una biblioteca consolidada disponible solo para Python, y el equipo ya mantiene servicios de Python dentro de los límites de recursos requeridos. ¿Qué hechos indicados respaldan elegir Python?
  1. La velocidad de los bucles y la ausencia de costes de memoria durante la ejecución
  2. La compatibilidad con navegadores y las anotaciones de tipo de Python impuestas
  3. La aritmética exacta en coma flotante y todos los destinos de hardware
  4. La disponibilidad de la biblioteca requerida y la experiencia previa del equipo
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 4: Razona entre los límites de la ejecución y el diseño10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Una sentencia de alto nivel se convierte en un número distinto de instrucciones en dos construcciones optimizadas, pero ambas cumplen el mismo comportamiento del lenguaje. ¿Hace el distinto número de instrucciones que alguna construcción sea incorrecta?
  1. No; una sentencia no tiene por qué corresponder a un número fijo de instrucciones.
  2. Sí; cada sentencia debe corresponder a una instrucción máquina.
  3. Sí; la portabilidad de alto nivel exige una salida nativa idéntica.
  4. No; la salida optimizada puede cambiar los resultados requeridos.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Un ejecutable nativo utiliza enlace dinámico. Quien lo recibe no tiene compilador y carece de una biblioteca compartida compatible requerida. ¿Cuál de las ausencias indicadas explica por qué podría no iniciarse?
  1. Falta el compilador anticipado.
  2. Falta el archivo fuente original.
  3. Falta la biblioteca compartida requerida.
  4. Falta el árbol de análisis sintáctico del compilador.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Una JVM compatible ejecuta el mismo archivo de clase en dos ISA y compila con JIT su método más utilizado en cada una. Alguien del equipo afirma que esto demuestra que el archivo de clase ya contiene instrucciones nativas para ambos procesadores. ¿Cuál es el diagnóstico correcto?
  1. El archivo contiene código nativo; la JVM solo enlaza bibliotecas de la plataforma.
  2. El archivo contiene código de bytes; la salida JIT también es independiente de la ISA.
  3. El archivo contiene texto fuente; la JVM debe recompilar el código fuente original.
  4. El archivo contiene código de bytes; cada JIT genera su propio código nativo.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Con n = 0 en Python, compara n != 0 and 36 / n > 4 con 36 / n > 4 and n != 0. ¿Qué ocurre?
  1. Ambas son falsas porque contienen las mismas dos pruebas.
  2. La primera es falsa; la segunda genera un error de división.
  3. Ambas generan un error de división porque ambas mencionan la división.
  4. La primera genera un error; la segunda es falsa.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

¿Qué valor final se almacena en total?

def bump(x):
    return x + 2
total = 0
for n in [2, 5, 8]:
    if n > 3:
        total = total + bump(n)
  1. 17
  2. 15
  3. 19
  4. 21
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Un compilador infiere todos los tipos de las variables locales antes de la ejecución, aunque quien programa no escribe anotaciones de tipo. ¿Qué enfoque de comprobación demuestra?
  1. Tipado dinámico con inferencia durante la ejecución
  2. Tipado débil sin información de tipos
  3. Tipado dinámico con conversión explícita
  4. Tipado estático con inferencia de tipos
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Un lenguaje de tipado estático infiere un tipo entero a partir de un inicializador y permite una conversión numérica implícita concreta. ¿Qué puede concluirse sobre esas dos características?
  1. La inferencia hace dinámicas estas variables pese al comprobador estático.
  2. La conversión implícita significa que la variable inferida no tiene un tipo fijo.
  3. La inferencia y la conversión pueden coexistir con la comprobación estática.
  4. La comprobación estática implica que la conversión debe escribirse explícitamente.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Un informe recorre objetos con un bucle y llama a item.render(). Una clase nueva sin relación con las anteriores proporciona un método render() compatible. El código de Python que llama no exige ninguna superclase. ¿Por qué puede incorporarse el objeto al bucle sin una rama específica para su tipo?
  1. La herencia común es necesaria antes de poder ejecutar su método.
  2. El tipado de pato acepta el objeto; el despacho selecciona su método.
  3. Los nombres de método coincidentes hacen que todos los receptores ejecuten un cuerpo compartido.
  4. El código que llama debe seleccionar la clase concreta antes de la llamada.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Una fábrica devuelve una función que multiplica su entrada por el factor de la fábrica. El factor era 4 y después se llama a la función devuelta con 7. ¿Qué resultado y qué mecanismo lo explican?
  1. 28, utilizando un marco de pila
  2. 11, utilizando una clausura
  3. 28, utilizando una clausura
  4. 28, utilizando una conversión de tipo
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Un equipo considera que una clasificación de popularidad es prueba suficiente de que un lenguaje puede ejecutarse en el destino de despliegue requerido. ¿Qué comprobación de compatibilidad falta?
  1. Si la plataforma de destino admite el entorno de ejecución necesario
  2. Si los tutoriales utilizan la sangría preferida del equipo
  3. Si su nombre ocupa un puesto alto en otra encuesta de popularidad
  4. Si tiene un historial de versiones más corto que el de las alternativas
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 5: Diagnostica los límites y combina las ideas10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Una clase de Java marca su campo de saldo como privado y ofrece un método de retirada con validación. El código de una clase externa sin relación intenta asignar directamente un saldo negativo. ¿Qué protege la regla de la clase en ese punto de acceso?
  1. El método de retirada valida las asignaciones directas.
  2. La asignación crea un saldo local independiente.
  3. El acceso privado permite escribir, pero bloquea todas las lecturas.
  4. El compilador rechaza el acceso al campo privado.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Una implementación basada en un intérprete dedica tiempo a ejecutar código de bytes. Una segunda implementación del mismo lenguaje añade un JIT y se vuelve más rápida en un cálculo largo y repetido. ¿Qué conclusión se obtiene sin cambiar la definición del lenguaje?
  1. El lenguaje pasó a tener tipado estático porque incorporó un compilador.
  2. La estrategia de ejecución cambió, mientras que la semántica del lenguaje puede mantenerse.
  3. El primer entorno ejecutaba directamente el código fuente, por lo que el código de bytes no se compilaba.
  4. El lenguaje compartido debe cambiar el significado de sus operadores para ganar velocidad.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

¿Qué muestra este programa de Python?

value = 6
def step(value):
    value = value + 3
    return value
for n in [1, 2]:
    result = step(value)
print(value, result)
  1. 12 12
  2. 6 12
  3. 6 9
  4. 9 9
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Un servicio con recolección de basura almacena cada registro procesado en una lista global que sigue siendo accesible. Eliminar las variables locales no reduce la memoria retenida por los registros. ¿Qué cambio aborda la causa indicada?
  1. Eliminar de la lista global las referencias a los registros obsoletos.
  2. Invocar repetidamente la recolección y dejar el archivo sin cambios.
  3. Eliminar cada vinculación local y conservar las entradas del archivo global.
  4. Sustituir la lista del archivo por una copia con los mismos registros.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Dos procedimientos actualizan el mismo valor global. double() lo multiplica por 2; add_three() suma 3. Partiendo del mismo valor, ¿cuál es siempre la diferencia entre duplicar y después sumar, y sumar y después duplicar?
  1. Duplicar y después sumar da un resultado 3 mayor.
  2. Sumar y después duplicar da un resultado 6 mayor.
  3. Ambos órdenes siempre dan el mismo resultado.
  4. Sumar y después duplicar da un resultado 3 mayor.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Una clase hereda de una cola solo para reutilizar su almacenamiento, pero desactiva la operación de extracción de la cola. El código que espera una cola utilizable falla. ¿Qué rediseño aborda el problema de relación indicado?
  1. Mantener la herencia de la cola y ocultar la extracción en la interfaz de la subclase.
  2. Contener una cola internamente y ofrecer las operaciones que promete la clase.
  3. Mantener la herencia de la cola y hacer que la extracción no haga nada sin avisar.
  4. Mantener la herencia de la cola y documentar que debe evitarse la extracción.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Una tabla readings tiene las filas (level, active): (50, true), (65, true), (20, false), (85, true) y (90, false). Una consulta solicita SUM(level) donde active = true AND level > 50. Después de añadir un índice adecuado, ¿qué total se requiere y puede cambiar el plan de ejecución?
  1. 150; el plan puede cambiar conservando el total.
  2. 200; el plan puede cambiar conservando el total.
  3. 240; el plan puede cambiar conservando el total.
  4. 150; el nuevo índice puede cambiar tanto el plan como el total.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Prolog tiene parent(lea, noah). y parent(noah, sora)., además de la regla grandparent(X, Z) :- parent(X, Y), parent(Y, Z).. ¿Qué encuentra grandparent(lea, Who)?
  1. Who = noah
  2. Who = lea
  3. Who = sora
  4. Tanto Who = lea como Who = noah
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Un bucle imperativo, un método de objeto y una consulta SQL deben sumar los importes estrictamente superiores a 50 de [50, 65, 20, 85]. ¿Cuáles deben ser sus resultados correctos?
  1. 200 en las tres versiones
  2. 150 en las tres versiones
  3. 150 para el bucle y 200 para el método y la consulta
  4. 200 para el bucle y 150 para el método y la consulta
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Una función devuelve una lista recién creada con memoria reservada, y el código que llama la guarda para utilizarla después. ¿Debe desaparecer la lista cuando se retire el marco de llamada de la función?
  1. Sí; terminar la llamada anula la referencia del código que llama.
  2. Sí; devolver copia su contenido y la libera.
  3. No; su marco de creación debe seguir activo para conservarla.
  4. No; el código que llama sigue manteniendo una referencia a la lista.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

¿Te atascas con una pregunta tuya? Ayuda con Tareas

Términos Clave

94 Términos Clave, cada uno con su definición

A

Abstracción
La abstracción oculta los detalles de implementación tras un modelo o una interfaz más sencillos para que quien programa pueda centrarse en la tarea que expresa.
Una variable es una abstracción que permite al código dar nombre a un valor sin especificar su dirección física de memoria.
Análisis léxico
El análisis léxico divide el texto fuente en componentes léxicos con significado, incluidos identificadores, literales, operadores y signos de puntuación.
Durante el análisis léxico, price * 3 se separa en un nombre, un operador de multiplicación y un número.
Análisis sintáctico
El análisis sintáctico comprueba los componentes léxicos según la gramática de un lenguaje y representa la estructura de las expresiones y sentencias.
El análisis sintáctico registra que la multiplicación de total = price * 3; pertenece al interior de la asignación.
Arquitectura del conjunto de instrucciones
Una arquitectura del conjunto de instrucciones define las operaciones que admite una familia de procesadores y las codificaciones utilizadas para representarlas.
Un ejecutable x86-64 se dirige a una arquitectura del conjunto de instrucciones distinta de la del código máquina nativo de ARM.
Asignación
La asignación vincula un nombre o un destino de almacenamiento a un valor y utiliza el estado actual para evaluar primero la expresión de la derecha.
La asignación count = count + 1 lee el contador anterior antes de sustituirlo por el valor incrementado.
Atributo
Un atributo es un dato o miembro con nombre asociado a un objeto o una clase, al que se accede a través de ellos.
El atributo de saldo de una cuenta registra su estado actual por separado del saldo de otra cuenta.

Á

Ámbito
El ámbito es la parte de un programa donde está disponible un nombre concreto, independientemente del tiempo de vida del objeto al que hace referencia.
El ámbito de un nombre local no se extiende al código que llama solo porque la función devuelva el objeto al que daba nombre.

C

Caso base
Un caso base proporciona directamente un resultado recursivo y permite que un cálculo finito deje de realizar más llamadas recursivas.
El caso base del factorial devuelve 1 para cero, de modo que puedan terminar las llamadas de multiplicación pendientes.
Clase
Una clase define la estructura y el comportamiento de sus instancias y describe sus atributos y métodos disponibles.
La clase Loan de una biblioteca puede definir el miembro, el libro, la fecha de devolución y la operación de renovación de cada préstamo.
Clausura
Una clausura es una función junto con el acceso a variables del entorno en el que se creó, que pueden sobrevivir a la llamada que la contiene.
Una clausura multiplicadora conserva su factor después de que haya retornado la función que creó el multiplicador.
Código de bytes
El código de bytes es una forma intermedia de instrucciones diseñada para un entorno de ejecución de software, en lugar de para su ejecución directa por el procesador anfitrión.
CPython genera código de bytes a partir de un archivo de Python válido antes de que su máquina virtual ejecute el programa.
Código máquina
El código máquina es una secuencia de instrucciones de procesador codificadas en binario que siguen una arquitectura del conjunto de instrucciones concreta.
El procesador ejecuta código máquina después de que un compilador o ensamblador haya producido instrucciones compatibles.
Compilación anticipada
La compilación anticipada traduce el código antes de la ejecución del programa y traslada el trabajo de traducción a una etapa de construcción independiente.
La compilación anticipada puede producir un ejecutable nativo que funciona sin tener instalado su compilador.
Compilación en tiempo de ejecución
La compilación en tiempo de ejecución genera código máquina nativo durante la ejecución del programa, a menudo para las partes de uso frecuente.
La compilación en tiempo de ejecución puede mejorar un bucle de larga duración después de que el entorno observe su ejecución repetida.
Compilador
Un compilador traduce código fuente a una representación de destino, que puede ser instrucciones máquina nativas o código de bytes intermedio.
Un compilador puede rechazar una asignación incompatible antes de producir el programa que ejecutará el usuario.
Completitud de Turing
La completitud de Turing es la capacidad de expresar cualquier algoritmo computable cuando se dispone de tiempo y memoria ilimitados en un modelo idealizado.
La completitud de Turing no indica a un equipo si un lenguaje tiene el soporte de navegador o las bibliotecas que necesita su proyecto.
Componente léxico
Un componente léxico es una pieza de texto fuente con significado, como una palabra clave, un identificador, un literal, un operador o un signo de puntuación.
El punto y coma de una asignación de C es un componente léxico aunque no nombre una variable ni un valor numérico.
Composición
La composición construye un objeto a partir de otros objetos que contiene como componentes o colaboradores y expresa una relación «tiene un».
La composición da a un automóvil un objeto de motor a cuyos métodos puede llamar sin convertirse en un tipo de motor.
Conteo de referencias
El conteo de referencias lleva la cuenta de las referencias a un objeto y normalmente recupera su almacenamiento cuando la cuenta llega a cero.
El conteo de referencias de CPython necesita ayuda de la recolección de ciclos para los objetos inaccesibles que mantienen referencias entre sí.
Conversión implícita de tipos
La conversión implícita de tipos es una conversión que realizan las reglas del lenguaje cuando una operación necesita un valor de otro tipo.
La conversión implícita de tipos de JavaScript convierte el texto numérico para multiplicar, pero puede concatenarlo al utilizar la suma.
Cuello de botella
Un cuello de botella es la parte de un sistema que limita el rendimiento general y hace menos eficaces las mejoras en otras partes.
Si la espera de red es el cuello de botella, una aritmética local más rápida puede cambiar muy poco el tiempo total de la solicitud.

D

Desbordamiento
El desbordamiento ocurre cuando un resultado aritmético queda fuera del rango de representación admitido por una representación numérica de ancho fijo.
El desbordamiento de enteros de Java produce un retorno cíclico al superar su valor máximo, mientras que el desbordamiento con signo de C tiene un comportamiento indefinido.
Desbordamiento de búfer
Un desbordamiento de búfer escribe más allá de los límites de un bloque de almacenamiento y puede dañar datos cercanos o el estado del programa.
Escribir en el índice 10 de un arreglo de C de diez elementos provoca un desbordamiento de búfer en lugar de añadir un undécimo elemento.
Despacho dinámico
El despacho dinámico selecciona una implementación de un método durante la ejecución según el objeto que recibe la llamada.
El despacho dinámico dirige shape.area() a la implementación del rectángulo cuando el receptor es un rectángulo.
Doble liberación de memoria
Una doble liberación de memoria intenta liberar dos veces el mismo bloque de memoria reservado, incumpliendo el ciclo de vida esperado por el gestor de reservas.
Volver a llamar a free sobre una reserva ya liberada puede causar una doble liberación de memoria y un comportamiento impredecible.

E

Ecosistema
Un ecosistema es el conjunto de bibliotecas, herramientas, documentación y apoyo de la comunidad disponibles en torno a una tecnología.
Un ecosistema sólido de análisis de datos puede hacer práctico Python para un equipo aunque la velocidad de sus bucles no sea su principal ventaja.
Efecto lateral
Un efecto lateral es una acción observable más allá de devolver un resultado, incluidas las salidas o los cambios en datos compartidos del programa.
Escribir un archivo de informe es un efecto lateral aunque la función deje sin cambios todas las variables numéricas globales.
Encapsulación
La encapsulación agrupa los datos con operaciones controladas que mantienen sus reglas y limitan los cambios inseguros realizados desde otro código.
La encapsulación permite que una cuenta rechace una retirada excesiva antes de que su saldo pueda volverse negativo.
Enlazador
Un enlazador conecta los archivos objeto traducidos y el código de bibliotecas requerido, resolviendo las referencias entre las partes de un programa.
Un enlazador conecta la llamada traducida de un archivo fuente con la rutina para mostrar texto que proporciona una biblioteca.
Ensamblador
Un ensamblador es un traductor que codifica las instrucciones del lenguaje ensamblador como código máquina para un conjunto de instrucciones específico.
Un ensamblador transforma add eax, ebx en los bytes correspondientes de la instrucción x86-64.
Error de ejecución
Un error de ejecución ocurre cuando una operación ejecutada no puede continuar según las reglas del lenguaje o las condiciones disponibles.
Dividir por cero en Python produce un error de ejecución cuando esta llega a ese cálculo.
Error lógico
Un error lógico hace que un programa calcule un resultado no deseado aunque sus operaciones ejecutadas puedan completarse correctamente.
Utilizar a + b / 2 para calcular el promedio de dos valores es un error lógico porque la división ocurre antes que la suma.
Evaluación en cortocircuito
La evaluación en cortocircuito omite un operando lógico cuando un operando anterior ya determina el resultado de la expresión.
La evaluación en cortocircuito permite que una comprobación de valor distinto de cero evite una división insegura a la derecha de and.
Expresión
Una expresión es un fragmento de código que se evalúa para producir un valor, que después puede utilizar una sentencia que lo contiene.
La expresión width * height proporciona el valor que devuelve una función de área de rectángulos.
Expresión lambda
Una expresión lambda es una expresión de función sin un nombre de función declarado, útil para proporcionar una operación pequeña como valor.
La expresión lambda de Python lambda n: n * n proporciona una operación de elevar al cuadrado a una cadena de transformaciones.
Expresión regular
Una expresión regular describe un patrón de texto para un motor de coincidencias mediante reglas de caracteres, repetición y agrupación.
La expresión regular [0-9]{3}-[0-9]{4} coincide con un grupo de tres dígitos seguido de un guion y cuatro dígitos.

F

Fuga de memoria
Una fuga de memoria retiene almacenamiento reservado que ya no se necesita y reduce la memoria disponible para trabajo útil.
Una caché que crece sin eliminar entradas puede causar una fuga de memoria incluso en una aplicación con recolección de basura.
Función de devolución de llamada
Una función de devolución de llamada es una función proporcionada a otro código para que este pueda invocarla cuando una operación o un evento lo requiera.
Pasar showMenu como función de devolución de llamada permite que un sistema de botones llame a esa función cuando el usuario hace clic.
Función de orden superior
Una función de orden superior recibe una función como argumento o devuelve una función, lo que permite combinar el propio comportamiento.
La función de orden superior filter recibe un predicado que decide qué valores de entrada conservar.
Función de primera clase
Una función de primera clase puede tratarse como un valor, lo que incluye asignarla a un nombre, pasarla como argumento y devolverla desde una llamada.
Una función de primera clase puede proporcionarse a map como la operación que se aplicará a cada elemento de una colección.
Función pura
Una función pura devuelve el mismo resultado para los mismos argumentos y no realiza efectos laterales más allá de devolver ese resultado.
Una función pura para un multiplicador fijo puede probarse sin preparar un estado global ni capturar la salida.

H

Herencia
La herencia define una clase especializada a partir de una existente y reutiliza o amplía el comportamiento mediante una relación «es un».
La herencia se adapta a una cuenta de ahorro que debe seguir cumpliendo el comportamiento esperado de una cuenta bancaria.

I

Implementación
Una implementación de un lenguaje es el software que proporciona una forma de traducir y ejecutar programas escritos en ese lenguaje.
CPython y PyPy son implementaciones distintas, por lo que el código de Python puede utilizar estrategias de ejecución diferentes.
Inferencia de tipos
La inferencia de tipos deduce los tipos a partir del código circundante y permite realizar comprobaciones estáticas aunque se omitan las anotaciones de tipo.
La inferencia de tipos permite que un compilador determine un tipo entero a partir de un inicializador en lugar de una etiqueta de tipo escrita.
Inmutabilidad
La inmutabilidad significa que un valor existente no puede cambiarse en su propia ubicación; una actualización debe producir o seleccionar un valor distinto.
La inmutabilidad mantiene disponible una colección original cuando una transformación construye una versión nueva.
Interfaz
Una interfaz especifica las operaciones que deben proporcionar los tipos que la implementan y permite que el código que llama se base en un contrato común.
Una interfaz de figura puede exigir un método area() y dejar que cada figura lo calcule de forma distinta.
Intérprete
Un intérprete ejecuta un programa mediante un sistema de software en ejecución, que puede ejecutar código fuente o una representación interna.
Un intérprete puede ejecutar un programa breve sin proporcionar primero al usuario un ejecutable nativo independiente.

L

Lenguaje de alto nivel
Un lenguaje de alto nivel expresa programas mediante conceptos como variables y funciones y oculta muchos detalles del hardware.
Un lenguaje de alto nivel permite solicitar una suma sin elegir qué registros del procesador utilizar.
Lenguaje ensamblador
El lenguaje ensamblador representa instrucciones de bajo nivel mediante mnemónicos legibles y nombres de registros o posiciones de almacenamiento.
Un analista lee lenguaje ensamblador para seguir las operaciones de un programa cuyo código fuente de alto nivel no está disponible.
Lenguaje específico de dominio
Un lenguaje específico de dominio está diseñado para un ámbito concreto de problemas, como consultas de bases de datos, patrones o estilos de páginas.
Un lenguaje específico de dominio como SQL puede expresar una solicitud de datos de forma más directa que un bucle de búsqueda escrito a mano.
Lenguaje multiparadigma
Un lenguaje multiparadigma admite varios estilos de organización de la computación y permite distintos enfoques dentro de un programa.
Python es un lenguaje multiparadigma en el que un método de clase puede utilizar un bucle imperativo y una transformación funcional.

M

Manejador de eventos
Un manejador de eventos es una función u operación registrada para responder cuando el entorno de ejecución entrega un evento concreto.
Un manejador de eventos de clic puede actualizar un menú mientras otro manejador procesa un mensaje entrante de la red.
Máquina virtual
Una máquina virtual es software que proporciona un modelo de ejecución para instrucciones, como el código de bytes utilizado por el entorno de ejecución de un lenguaje.
Una máquina virtual de Java compatible permite ejecutar el mismo archivo de clase en computadoras con procesadores distintos.
Marco de pila
Un marco de pila almacena información asociada a una llamada activa a una función, como las vinculaciones locales y la posición de retorno.
Cada llamada recursiva pendiente necesita su propio marco de pila, salvo que una implementación pueda eliminarlo mediante una optimización.
Método
Un método es una operación asociada a una clase o un objeto que a menudo actúa sobre el estado de la instancia que recibe la llamada.
Un método de retirada comprueba el importe solicitado antes de cambiar el saldo de la cuenta receptora.
Montículo de memoria
El montículo de memoria es la memoria utilizada para objetos cuya memoria se reserva dinámicamente, incluidos los que pueden sobrevivir a la función que los crea.
Una lista devuelta puede permanecer en el montículo después de que termine la función que la creó y desaparezca su marco de llamada.
Mutación
La mutación cambia un objeto existente en su propia ubicación, por lo que cualquier referencia a ese mismo objeto observa el contenido actualizado.
Añadir un elemento a una lista compartida es una mutación; asignar un nombre a una lista nueva cambia solo esa vinculación.

N

Número en coma flotante
Un número en coma flotante representa un valor con precisión binaria limitada, lo que hace aproximadas la mayoría de las fracciones decimales.
Un número en coma flotante puede introducir una diferencia diminuta entre 0.1 + 0.2 y el valor almacenado de 0.3.

O

Objeto
Un objeto es una entidad de un programa que combina datos con operaciones disponibles, a menudo como una instancia concreta de una clase.
Dos objetos de cuenta pueden utilizar el mismo método de retirada y conservar sus propios saldos distintos.
Ocultación de información
La ocultación de información mantiene los detalles de implementación tras una interfaz para que el resto del código dependa de las operaciones prometidas.
La ocultación de información permite que una cuenta cambie su representación de almacenamiento y conserve el comportamiento de los depósitos.
Optimizador de consultas
Un optimizador de consultas elige un plan para ejecutar una consulta de base de datos y conserva el resultado que solicita la consulta.
Un optimizador de consultas puede utilizar un índice disponible después de que cambien los datos, sin reescribir la solicitud SQL.

P

Paradigma de programación
Un paradigma de programación es un estilo de organización de la computación, con formas concretas de expresar datos, operaciones y control.
Un paradigma de programación puede destacar las actualizaciones de estado mientras otro destaca la composición de funciones puras.
Parámetro
Un parámetro es un nombre de la definición de una función que recibe un valor proporcionado por un argumento cuando se llama a la función.
El parámetro width recibe 3 cuando el código que llama evalúa area(3, 4).
Pila de llamadas
La pila de llamadas lleva el seguimiento de las llamadas activas a funciones en orden anidado para que la ejecución pueda regresar al código que realizó cada llamada.
Una llamada a una función area añade una entrada a la pila de llamadas y la retira cuando su resultado vuelve al código que llamó.
Plataforma
Una plataforma es el entorno de hardware y software en el que debe ejecutarse un programa, incluidos los entornos de ejecución y servicios del sistema admitidos.
Una plataforma de navegador puede ejecutar JavaScript directamente mediante su motor y módulos compilados mediante WebAssembly.
Plegado de constantes
El plegado de constantes evalúa expresiones formadas por constantes durante la compilación y conserva el comportamiento requerido del programa.
El plegado de constantes permite que un compilador sustituya 60 * 60 * 24 por 86 400 antes de que se inicie el programa.
Polimorfismo
El polimorfismo permite que una operación compartida funcione con distintos tipos de objetos y que cada objeto proporcione su comportamiento adecuado.
El polimorfismo permite que un informe llame a area() en círculos y rectángulos sin una rama independiente para cada uno.
Programación declarativa
La programación declarativa especifica un resultado o una relación requeridos y deja los detalles de ejecución al motor de un lenguaje.
La programación declarativa permite que una consulta SQL solicite las filas que cumplen una condición sin especificar un bucle fila por fila.
Programación dirigida por eventos
La programación dirigida por eventos organiza el trabajo en torno a manejadores que se invocan cuando ocurren eventos, como clics o mensajes entrantes.
La programación dirigida por eventos espera un clic en un botón antes de ejecutar la actualización de interfaz registrada.
Programación estructurada
La programación estructurada organiza el control mediante secuencias, selecciones, bucles y llamadas en lugar de saltos arbitrarios.
La programación estructurada permite seguir bloques anidados para ver qué sentencia se ejecutará a continuación.
Programación funcional
La programación funcional organiza los cálculos componiendo funciones y produciendo valores nuevos mientras limita la mutación compartida.
La programación funcional puede expresar el cálculo de un informe como selección, transformación y combinación de valores.
Programación imperativa
La programación imperativa expresa un cálculo como órdenes secuenciales que actualizan el estado del programa mediante asignaciones y flujo de control.
La programación imperativa hace explícito el nuevo valor de un acumulador en cada pasada de un bucle.
Programación lógica
La programación lógica establece hechos y reglas y después busca valores que hagan que una consulta satisfaga esas relaciones.
La programación lógica puede deducir una relación de abuelos conectando dos relaciones de progenitor conocidas.
Programación procedimental
La programación procedimental agrupa órdenes imperativas en funciones o procedimientos con nombre, cada uno encargado de una tarea coherente.
La programación procedimental separa la lectura de datos, el cálculo de un resultado y la presentación de un informe en tareas con nombre.
Propiedad de los datos
La propiedad de los datos asigna la responsabilidad del tiempo de vida de un valor y permite que las reglas del compilador comprueben el acceso y organicen la liberación de memoria.
La propiedad de los datos de Rust rechaza, en código seguro, el acceso mediante una referencia que sobreviviría al valor al que apunta.
Puntero colgante
Un puntero colgante hace referencia a almacenamiento que ya se ha liberado y que ya no es un destino válido de acceso.
Un puntero colgante puede seguir conteniendo una dirección antigua incluso después de que esa dirección se haya reutilizado para otros datos.

R

Recolección de basura
La recolección de basura identifica y recupera automáticamente el almacenamiento de los objetos a los que el programa en ejecución ya no puede acceder.
La recolección de basura por rastreo puede recuperar la memoria de un grupo inaccesible aunque sus objetos sigan haciendo referencia entre sí.
Recursión
La recursión resuelve un problema mediante llamadas a la misma función y suele reducirlo hacia un caso que no necesita más llamadas.
La recursión del factorial multiplica por enteros sucesivamente menores hasta alcanzar el resultado definido para cero.
Redefinición de métodos
La redefinición de métodos da a una subclase su propia implementación de un método heredado y mantiene la operación disponible para el código que llama.
Redefinir un método de retirada puede añadir una comisión y conservar la regla que impide un saldo negativo.
Referencia
Una referencia es un valor que identifica un objeto y permite acceder a él sin duplicar los datos que almacena.
Copiar una referencia a una lista da a otro nombre acceso a la misma lista y a sus mutaciones posteriores.
REPL
Un REPL es un bucle interactivo de lectura, evaluación e impresión que acepta una entrada, la evalúa, muestra un resultado y espera otra.
Utilizas un REPL para probar una expresión pequeña y ver su resultado antes de introducir otra expresión.
Reserva de memoria
La reserva de memoria aparta memoria para los datos del programa, y ese almacenamiento no está disponible para reutilizarse hasta que se libera.
Crear una colección que crece puede requerir más reservas de memoria a medida que almacena elementos adicionales.

S

Seguridad de memoria
La seguridad de memoria evita categorías de accesos no válidos a memoria, como acceder a almacenamiento liberado o escribir fuera de límites válidos.
La seguridad de memoria puede bloquear un acceso peligroso mientras el programa sigue conteniendo un error independiente en su cálculo.
Semántica
La semántica es el significado asignado a las estructuras de un programa, incluido cómo se comportan los operadores y cómo cambia los valores la ejecución.
La semántica de la división entera explica por qué una división de aspecto idéntico puede dar resultados distintos según el lenguaje.
Sentencia
Una sentencia es una instrucción completa de un programa, como una asignación, un bloque condicional o un bucle.
La sentencia total = price * 3 asigna el resultado de una multiplicación a una variable.
Sintaxis
La sintaxis es el conjunto de reglas que define qué combinaciones de componentes léxicos forman expresiones, sentencias y programas válidos.
La sintaxis de Python exige dos puntos después de una condición if y sangría para el bloque controlado.
SQL
SQL es un lenguaje para trabajar con datos relacionales, con consultas que expresan los campos solicitados, las condiciones de las filas y el orden.
SQL puede solicitar los nombres de estudiantes que superan un umbral de calificación y ordenar esos nombres alfabéticamente.

T

Tabla de seguimiento
Una tabla de seguimiento registra los valores de las variables pertinentes después de los pasos ejecutados para poder seguir el camino y el estado cambiante.
Una tabla de seguimiento de un acumulador registra el nuevo total después de que cada elemento pase por el bucle.
Tipado de pato
El tipado de pato acepta un objeto según las operaciones que proporciona, sin exigir necesariamente una superclase declarada compartida.
El tipado de pato de Python permite que un objeto sin relación se incorpore a un cálculo de áreas si proporciona un método area() compatible.
Tipado dinámico
El tipado dinámico comprueba los tipos de los valores durante la ejecución y permite que un nombre haga referencia a valores de tipos distintos a lo largo del tiempo.
El tipado dinámico de Python permite que un nombre pase de un entero a una cadena, pero las operaciones aritméticas incompatibles siguen fallando.
Tipado estático
El tipado estático comprueba la compatibilidad de tipos antes de la ejecución y permite rechazar muchas combinaciones no válidas durante el desarrollo.
El tipado estático permite que un compilador de Java rechace texto utilizado para inicializar una variable entera antes de ejecutar el programa.
Tipo de dato
Un tipo de dato especifica un conjunto de valores posibles y las operaciones permitidas sobre ellos, orientando su representación y uso.
Un tipo de dato de cadena admite operaciones de texto aunque los caracteres del valor sean dígitos.

U

Uso de alias
El uso de alias ocurre cuando distintos nombres o referencias llevan al mismo objeto, lo que hace visibles sus mutaciones a través de todos ellos.
Después de b = a, el uso de alias explica por qué añadir un elemento mediante b también puede cambiar lo que se ve mediante a.
Uso después de liberar memoria
El uso después de liberar memoria accede al almacenamiento mediante un puntero después de que se haya liberado su reserva, lo que provoca un acceso no válido a memoria.
El uso después de liberar memoria puede sobrescribir un objeto nuevo que ocupe por casualidad un bloque liberado anteriormente.

Sobre esta unidad

Por StudeliInformar de un error

Crea tus propios Materiales de Estudio

Convierte tus notas, diapositivas y documentos en Guías de Estudio, Preguntas de Práctica, Flashcards y 6 herramientas más.

Comenzar