Es un trabajo en progreso y quién sabe cuándo será el día que lo retome.

Una receta para elaborar cerveza

Ingredientes

  • Agua purificada. Se usan distintos tipos de agua para diferentes tipos de cerveza.
  • Cebada malteada, molida. La cebada es un cereal, el cual pasa por un proceso de malteado que extrae su almidón y sus enzimas.
  • Lúpulo. Las flores de lúpulo son las encargadas de dar el amargor y el aroma característicos de la cerveza.
  • Levadura para cerveza: Son microorganismos encargados de convertir azúcares en alcohol.

Elaboración

  1. Maceración: Se mezcla el agua con la cebada durante cierto periodo de tiempo a cierta temperatura, las enzimas atacan al almidón convirtiéndolo en azúcares. Se filtra la mezcla. Producto: mosto.

  2. Hervido: Se hierve el mosto durante cierto tiempo, esto permite su esterilización. En el hervido también se agrega el lúpulo, además de aportar sabor y aroma, frena los procesos enzimáticos. Se lo deja enfriar. Producto: mosto amargo.

  3. Fermentación: Se le agrega la levadura al mosto. Este proceso dura cierta cantidad de días a cierta temperatura. Luego el mosto fermentado es nuevamente filtrado. Producto: cerveza.

Fuente: http://www.blogdecerveza.com/infografia-cerveza-sajonia-brewing

La receta vista como un programa

En la receta se hace distinción entre los ingredientes y las instrucciones de elaboracción. En un programa sucede algo similar, es posible distinguir a los datos (los ingredientes) del código (las instrucciones); el código es aplicado a los datos para transformarlos y así obtener nuevos datos (los productos), sobre los cuales se puede seguir aplicando código hasta obtener un resultado (o producto) final.

Datos

# cantidades para 15 litros de cerveza artesanal

agua     =  30 litros de agua mineral
malta    = 4,5 kilos  de harina de cebada malteada (malta)
lúpulo   = 200 gramos de lúpulo
levadura =  10 gramos de levadura de cerveza

Código

mosto   = macerar(agua, malta)
amargo  = hervir(mosto, lúpulo)
cerveza = fermentar(amargo, levadura)

Resalta en este programa la estructura renglonada. Cada renglón constituye una instrucción que expresa alguna acción a realizar. Vemos que un programa está conformado por una secuencia de una o más instrucciones.

Todo texto precedido por un numeral (#) es un comentario. Estos son ignorados por el programa, son útiles para dejar anotaciones que faciliten la lectura del mismo.

También es notorio el uso del signo igual (=). Su función es asignar el valor de la derecha a la variable de la izquierda en lo que se conoce como instrucción de asignación. Un valor es un dato, como mencionamos, una entidad capaz de ser manipulada por el código. A su vez, una variable es un nombre simbólico (también conocido como identificador) que está asociado a una entidad.

La variable es la manera breve de referirse al valor, como el nombre o el DNI de una persona, que sirve para evitar hacer una descripción que la identifique cada vez que la queramos mencionar. A diferencia del nombre o el documento de las personas, que están asociados de por vida a una persona particular, una variable puede cambiar de valor mediante posteriores re-asignaciones.

Tipos de datos

Analizaremos con mayor profundidad la sección ingredientes/datos. Es común que los programas comiencen con algunas deficiones de variables ya que constituyen la materia prima sobre la cual realizar la ejecución del código.

Queremos experimentar un poco, de la receta respetaremos el proceso de elaboración y variaremos las cantidades y las cualidades de las materias primas para obtener diversas cervezas. Asociaremos cantidades con valores y cualidades con tipos. A continuación, una tabla que resume los ingredientes.

     VALOR  |  TIPO
-----------------------
 30 litros  |  agua mineral
4,5 kilos   |  harina de cebada malteada (malta)
200 gramos  |  lúpulo
 10 gramos  |  levadura de cerveza

Podríamos usar menos lúpulo para crear una cerveza menos amarga. Quizás queramos usar más malta para una cerveza más alcohólica. Mientras mantengamos los valores dentro de ciertos rangos y proporciones la receta funcionará. Esto es interesante y esencialmente se debe a que los tipos de ingredientes continúan siendo los mismos. Diferente sería el caso de no contar con levadura de cerveza y querer reemplazarla por levadura de pan; sucede que no sirve para hacer cerveza.

Es así que los datos se diferencian por sus cualidades y que los programas se escriben teniendo en cuenta ciertos tipos de datos y no otros. Algunos lenguajes de programación son escrictos respecto a los tipos —si la receta dice agua mineral entonces sí o sí tiene que ser agua mineral— y otros, como Python, no lo son: mientras los datos se comporten de manera similar a efectos del programa entonces pueden usarse de manera indistinta. Por ejemplo, en vez de agua mineral podríamos utilizar agua filtrada ya que también se puede beber y a efectos de la receta servirá para la obtención de mosto por medio del proceso de maceración.

Funciones

Ahora profundizaremos sobre la sección elaboración/código. En abstracto se trata de una secuencia de operaciones que resuelve un problema (el de hacer cerveza), lo que se conoce como algoritmo. Concretamente, un algoritmo puede ser expresado de diversas maneras, utilizando un lengaje natural como en la receta, un lenguaje formal como en el programa e incluso gráficamente mediante un diagrama.

Las operaciones toman datos de entrada, internamente realizan tareas, y devuelven un dato de salida. Asociaremos el concepto de operación con el de función. A continuación, una tabla que lista las operaciones del proceso de elaboración.

FUNCIÓN    |  ENTRADAS                |  SALIDA
-----------------------------------------------------
macerar    |  agua, malta             |  mosto
hervir     |  mosto, lúpulo           |  mosto amargo
fermentar  |  mosto amargo, levadura  |  cerveza

Las funciones son, como los datos, también entidades. Como sucede con los datos, también se las asocia con un nombre o identificador para poder invocarlas cuando sea necesario. En principio las funciones son operaciones en potencia, son entidades que si bien son capaces de realizar tareas, se encuentran en letargo. Para convertirlas en acto, en la realización de las tareas, las funciones deben ser accionadas, lo que se conoce como llamar a la función. Una manera de lograrlo es mediante el operador llamar a función (), que al acompañar a una función, la activa. Dentro de los paréntesis se especifican los valores (datos) de entrada, los llamados argumentos de la función. Como resultado la función será evaluada—convertida en un valor (dato) de salida.

función(argumentos...)  ->  valor
macerar(agua, malta)    ->  mosto

Caracterizan a una función los argumentos que requiere, en número y en tipo. Por ejemplo macerar requiere dos argumentos, el primero del tipo agua y el segundo del tipo malta. Fallar al proporcionar los valores requeridos por alguna función durante la ejecución del código hará que se detenga.

Dijimos que las funciones internamente realizan tareas. No siempre estas tareas producirán meramente el dato a ser retornado sino que tendrán otros efectos como almacenar datos o imprimirlos en pantalla y a veces importan más estos efectos que el valor de salida.

En la receta asignamos los valores retornados a variables así luego los podemos referenciar.

Ejercicio 1

Elaborar un glosario con las palabras resaltadas en negrita.

Constantes

Las constantes son como las variables solo que mantienen su valor. Es decir que una vez asignado un valor a un nombre (identificador), a este no se le podrá reasignar otro valor en el transcurso del programa.

Operadores

Los operadores son como las funciones solo que se escriben diferente para otorgarle al lenguaje naturalidad y expresividad (facilidad para leerlo y escribirlo). Por ejemplo, las operaciones artiméticas nos resultarán más familiares así

 1 + 1  ->  2
 8 - 1  ->  7

que en una forma funcional.

sumar(1, 1)   ->  2
restar(8, 1)  ->  7

Si bien el orden de escritura es otro, en ambos casos el tipo de dato de los argumentos/operandos tiene que ser compatible con la operación y la operación retornará un valor.

función(argumento_1, argumento_2)  ->  valor
operando_1 operador operando_2     ->  valor

Literales

Los valores surgen de las operaciones, así como la flor de lúpulo sale de una planta—viendo a la planta como un proceso que engendra flores. Por otro lado las operaciones requieren valores de entrada, así como se necesita una semilla para dar origen a una planta. Estamos ante una situación del tipo el huevo y la gallina, en la que se requieren operaciones para obtener valores y valores para obtener operaciones. Para salir de este enriedo los lenguajes de programación proveen literales para algunos tipos de datos, una manera de escribir valores de manera espontánea, sin recurrir a operaciones que les den origen.

Entidades primitivas

Los lenguajes de programación proveen elementos básicos con los cuales crear programas. Llamamos primitivas a las entidades preexistentes en el lenguaje. Las llamamos así para diferenciarlas de las entidades que es posible construir a partir de estas. Por ejemplo, podemos definir nuestras propias funciones, afines al problema a resolver (más adelante veremos cómo). Mientras tanto el lenguaje cuenta con entidades genéricas con las cuales empezar.

Python no es un lenguaje especialmente pensado para elaborar cerveza. Los ingredientes con los que cuenta son otros, esta es una lista no exhaustiva de tipos de datos primitivos, los más frecuentes. Entre paréntesis se encuentra el nombre que les da Python.

  • Nada (NoneType)
  • Booleanos (bool)
  • Numéricos
    • Enteros (int)
    • Flotantes (float)
  • Secuencias
    • Texto (str)
    • Listas (list)
  • Mapeos
    • Diccionarios (dict)

Nada. Este tipo tiene un solo valor (hay una única entidad con este valor). Nos podemos refereir a esta entidad usando la constante primitiva None. Se usa en varias situaciones para significar la ausencia de un valor.

Booleanos. Este tipo tiene dos valores. Estas entidades representan los valores verdadero y falso. Nos podemos referir a estas entidades mediante las constantes primitivas True y False respectivamente.

Enteros. Este tipo tiene infinitos valores, son los números positivos y negativos. Estos valores se pueden referenciar de una manera literal utilizando numerales (0–9). Python interpretará al símbolo 2 como un entero de valor dos.

Flotantes. Son los números reales. Se pueden referenciar literalmente usando numerales y el punto (.) como separador decimal. Python interpretará 3.14 como un valor del tipo flotante. Son representaciones aproximadas de los números reales, es una consesión de precisión por rango, que permite computar números reales muy pequeños y muy grandes eficientemente.

Nota: Las constantes None, True y False son necesarias ya que no existen en nuestro lenguaje natural occidental símbolos para referirnos a la nada, a lo verdadero y a lo falso de manera literal, en la manera en la que sí existen caracteres numéricos para referirnos a los números.

Secuencias

Las secuencias son colecciones ordenadas de datos indexadas por números no negativos. A diferencia de los tipos de datos vistos hasta el momento, el valor de una colección es los datos que contiene. Un cajón de cervezas viene a ser una colección de botellas de cerveza; el cajón es muy útil para manipular todas esas botellas en conjunto. Por ordenado e indexado nos referimos a que cada lugar del cajón está numerado, lo que nos permite colocar o retirar botellas de lugares específicos.

La función primitiva len devuelve la cantidad de ítems de la secuencia—aplicada a un cajón lleno debería devolver el número 12.

len(secuencia)  ->  tamaño de la secuencia
len(cajón)  ->  12

El operador índice [] selecciona una posición en la secuencia y devuelve su contenido; dentro de los corchetes se especifica la posición. En Python, el índice comienza en cero.

secuencia[entero]  ->  dato
cajón[2]  ->  botella de cerveza del lugar número 3

Texto. Normalmente reciben el nombre de strings (cuerdas), son colecciones de caracteres. Los valores del tipo string se pueden generar de manera literal al escribirlos entre comillas simples (') o dobles ("). Por ejemplo el literal "cerveza" se diferencia del identificador cerveza, el intérprete de Python [revisar: no se ha explicado qué es el intérprete] al leer el literal lo transformará en un dato del tipo texto cuyo valor es cerveza, en cambio cuando si lee el identificador —la palabra sin comillas— que podría tratarse del nombre de una variable o una función, invocará a la entidad asociada.

Listas. Son colecciones arbitrarias de datos. Una lista puede contener todo tipo de datos, incluso otras listas. Se pueden crear de manera literal utilizando corchetes ([ ]) como delimitadores y coma (,) para separar los elementos de la lista.

números = [1, 2, 3]
números[0]  ->  1

Nota: Puede resultar ambigüa la utilización de corchetes, ya que sirven tanto para la creación de listas como para el operador índice. Sin embargo se utilizan de manera diferente, lo que rompe la ambigüedad.

Mapeos

Los mapeos son colecciones de datos indexadas por valores arbitrarios. Un ejemplo es una agenda de contactos telefónicos; se trata de una colección de números de teléfono, en la que cada número está relacionado —mapeado— a un nombre de contacto. Relaciona claves (nombres de contacto) con valores (números de teléfono). Las secuencias y los mapeos son parecidos, ambos son colecciones, se diferencian en la forma de indexar los datos, es decir, en la forma de recuperarlos: mediante la posición en el caso de las secuencias, por medio de una clave en el de los mapeos. En la agenda, para encontrar un número de teléfono (el valor), lo buscamos por el nombre del contacto (la clave).

Como sucede con las secuencias, la función len devuelve la cantidad de elementos del mapeo y el operador índice [] selecciona un elemento del mapeo y devuelve su valor; dentro de los corchetes se especifica la clave.

Diccionarios. Es el mapeo paradigmático. Se pueden crear de manera literal utilizando llaves ({ }) como delimitadores, coma (,) para separar los elementos del diccionario, lo que nos recuerda un poco a las listas, solo que los elementos del diccionario son pares clave-valor, utilizándose dos puntos (:) para separar la clave del valor.

proveedores = {'a':1, 'b':2, 'c':3}
proveedores['b']  ->  2

En este ejemplo se usaron strings para las claves y enteros para los valores. Los tipos de datos que pueden cumplir estas funciones no están limitados a estos, los valores pueden ser de cualquier tipo, incluyendo listas y otros diccionarios; las claves están más acotadas, normalmente son strings, podrían ser de otros pero no listas ni diccionarios.