Adrianistán https://blog.adrianistan.eu El blog de Adrián Arroyo es Diario, a fast and safe blog engine Pascal. Un homenaje a Niklaus Wirth https://blog.adrianistan.eu/pascal-homenaje-niklaus-wirth https://blog.adrianistan.eu/pascal-homenaje-niklaus-wirth A principios de año tuvimos la triste noticia de la muerte de Niklaus Wirth, uno de los pioneros de la programación tal y como la conocemos. Wirth participó en muchas discusiones y debates que darían forma al mundo de la informática actual. Si bien no forma parte de la primera generación de informáticos (quizá eso sería más apropiado de Turing, Church o Von Neumman), sí lo es de la primera generación donde la informática empieza a ser algo independiente y separado de otras disciplinas.

De entre todas las cosas en las que Wirth trabajó, la más conocida de todas es seguramente Pascal. Un lenguaje de programación, derivado de ALGOL, con modificaciones que Wirth no había conseguido introducir en el estándar. Pascal fue diseñado inicialmente como un lenguaje didáctico, pero pronto consiguió hacerse un hueco en la programación más profesional. En concreto, Turbo Pascal fue uno de los entornos de programación más populares en MS-DOS. Y en el mundo Machintosh también Pascal fue uno de los lenguajes más usados para desarrollar aplicaciones.

Hoy en día, Pascal ya no es ni de lejos tan popular como llegó a ser. La popularización de la OOP, y el paso de MS-DOS a Windows no le sentó bien, a pesar de que existe Object Pascal y Delphi. El mundo UNIX tampoco había sido muy fan de Pascal (eran más de C). Tampoco Wirth creía que fuese el lenguaje definitivo, y siguió diseñando lenguajes como Modula-2 u Oberon, que son sucesores de Pascal que nunca lograron la popularidad del primigenio.

Por eso, vamos a recordar (o en mi caso, aprender de cero) como se programa en Pascal. Pascal, un lenguaje cuya virtud es ser un lenguaje imperativo que trata de ser simple y a la vez, eficaz y seguro (con un tipado bastante interesante para la época).

Fibonacci en Pascal

Vamos a comenzar con un pequeño programa de la sucesión de Fibonacci en Pascal. Es muy sencillo, pero ya nos sirve para identificar algunas cosas. En primer lugar, un programa Pascal empieza con una definición de programa y el nombre de este. A continuación van opcionalmente unas secciones, donde declaramos constantes, tipos, variables, etc... En Pascal es obligatorio declarar las variables que vamos a usar antes de empezar el código. No existe función main, sino que simplemente tras esta sección iniciamos con BEGIN y acabamos con END. (nótese el punto al final). Las variables se asignan con :=. Pascal no distingue mayúsculas de minúsculas y. Podemos crear más bloques con begin/end. Los statements se separan entre líneas con ; (por eso las últimas líneas no llevan).


PROGRAM Fibonacci;
CONST
   N = 12;
VAR
   A, B, C : integer;
   Fib	   : integer;
BEGIN
   A := 1;
   B := 1;
   writeln(A);
   writeln(B);
   for fib := 3 to N do
      begin
	 C := A + B;
	 A := B;
	 B := C;
	 writeln(C)
      end
END.

Este programa podemos guardarlo en un fichero fib.pas y usar el compilador FreePascal para generar un ejecutable. Pascal fue diseñado para ser muy rápido de compilar, y FreePascal lo es.


$ fpc fib.pas
Free Pascal Compiler version 3.2.2 [2023/10/16] for x86_64
Copyright (c) 1993-2021 by Florian Klaempfl and others
Target OS: Linux for x86-64
Compiling fib.pas
Linking fib
19 lines compiled, 0.1 sec
$ ./fib
1
1
2
3
5
8
13
21
34
55
89
144

Estructuras de control, funciones y procedimientos

Pascal es un lenguaje imperativo, sin más paradigmas, pero dentro de estos, es un lenguaje estructurado, es decir, usar estructuras de control y funciones en vez de GOTO (saltar a una línea del programa). Hoy en día que un lenguaje sea estructurado nos parece evidente, pero en aquella época no lo era tanto. Hay que tener en cuenta que las CPUs no tienen esa estructuración, se trata de una abstracción que introduce el lenguaje y que luego el compilador tiene que traducir a saltos tipo GOTO, que es lo que hace la CPU. Realmente Pascal soporta el GOTO, el propio Wirth dijo que no era algo de lo que estuviese muy orgulloso pero en aquella época era todavía demasiado rompedor hacer un lenguaje sin GOTO.

Ya hemos visto la primera estructura de control, el bucle FOR. Este se usa cuando el bucle tendrá un número determinado de iteraciones. Se introduce una variable contador que va creciendo (to) o decreciendo (downto) hasta un valor.


for I := 0 to N do
for I := 0 downto N do

Seguidamente va un statement, si queremos introducir varios statements deberemos usar begin/end.

La siguiente estructura es el IF, un condicional. Si se da la condición se ejecuta lo que va a continuación del THEN. Opcionalmente puede haber un ELSE que se ejecuta cuando no se cumple la condición.

El bucle WHILE evalúa una condición al comienzo de cada iteración para saber si debe continuar.

El bucle REPEAT UNTIL evalúa una condición al final de cada iteración para saber si debe continuar. Curiosamente el bucle REPEAT UNTIL no necesita que escribamos begin/end en el cuerpo del bucle.

Disponemos de un CASE OF para evaluar muchos IF seguidos, o para dentro de un dato de tipo RECORD, discernir entre variantes (¡tipos suma!)

>Por último disponemos de procedimientos y funciones. La diferencia es que las funciones devuelven un valor (tendremos que asignar al nombre de la función el valor que queramos que devuelva) y los procedimientos no. Por lo demás, podemos pasar las variables como VAR o no. Si las pasamos como VAR, el argumento se pasará por referencia, por lo que podremos hacer una modificación de la variable que afecte de forma externa a la función.

Mucha cháchara, hagamos algo interesante. Vamos a implementar la solución del problema del Advent of Code 2020 día 1. Para ello he decidido usar varias cosas como ficheros, arrays, funciones, procedimientos y varios tipos de bucle. En realidad puede hacerse muy sencillo pero la idea es que se vean varias cosas a la vez.


PROGRAM day1;
CONST
   FileName = 'input.dat';
   FileSize = 200;
   Puzzle   = 2020;
VAR
   Nums	     : array [0..FileSize] of integer;
   Prod	     : longint;

PROCEDURE ReadFile();
VAR
   F	  : TextFile;
   I, Num : integer;
BEGIN
   Assign(F, FileName);
   Reset(F);
   I := 0;
   WHILE NOT Eof(F) DO
      BEGIN
	 ReadLn(F, Num);
	 Nums[I] := Num;
	 I := I + 1
      END
END;

FUNCTION FindProd(Puzzle : integer): longint;
VAR
   I, J, Sum : integer;
BEGIN
   I := 0;
   J := 0;
   REPEAT
      REPEAT
	 Sum := Nums[I] + Nums[J];
	 FindProd := Nums[I] * Nums[J];
	 J := J + 1
      UNTIL (Sum = Puzzle) OR (J = FileSize - 1);
      J := 0;
      I := I + 1
   UNTIL Sum = Puzzle;
END;

BEGIN
   ReadFile();
   Prod := FindProd(Puzzle);
   WriteLn(Prod)
END.

Tipos

Uno de los aspectos más interesantes de Pascal es que es un lenguaje relativamente expresivo para crear tipos personalizados.


PROGRAM types;
TYPE
   DaysOfWeek	  = (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
   DaysOfWorkWeek =  Monday..Friday;
VAR
   Day : DaysOfWorkWeek;
BEGIN
   Day := Friday;
   WriteLn(Day)
END.

Podemos crear el tipo DaysOfWeek con 7 miembros y luego un subtipo que es un subrango del anterior.

Los array, de tamaño estático, se definen mediante rangos también, y por ello es posible en Pascal tanto que los arrays empiecen en 0 como en 1 (como en otro número).

Aparte de arrays, Pascal también incluye sets, con sintaxis muy similar.


PROGRAM types;
TYPE
   DaysOfWeek	  = (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
   DaysOfWorkWeek = Monday..Friday;
   Salary	  = array[DaysOfWorkWeek] of integer;
VAR
   Day	     : DaysOfWorkWeek;
   ManSalary : Salary;
BEGIN
   for Day := Monday to Friday do
      ManSalary[Day] := 50;
END.

Pascal soporta los record, un tipo compuesto de otros tipos, identificador por un nombre. Podemos acceder a cada campo del record mediante un punto o usando WITH.


PROGRAM types;
TYPE
   Contact =  record
		 Name	 : string;
		 Age	 : integer;
		 City	 : string;
	      end;
VAR
   ContactA : Contact;
BEGIN
   WITH ContactA DO
      BEGIN
	 Name := 'Adrián';
	 Age := 25;
	 City := 'Valladolid'
      END;
   WriteLn(ContactA.City)
END.

Los records soportan variantes, lo que los convierte en tipos suma. Es decir, en base a una variable, el record tendrá unos campos u otros. Veamos este ejemplo:


PROGRAM types;
TYPE
   IpAddressType = (IPv4, IPv6);
   IpAddress	 = record
		      case Version : IpAddressType of
			IPv4 : (A,B,C,D : integer);
			IPv6 : (Address : string);
		      end;
VAR
   IPs : array[0..1] of IpAddress;
   I   : integer;
BEGIN
   IPs[0].Version := IPv6;
   IPs[0].Address := '::1';
   IPs[1].Version := IPv4;
   IPs[1].A := 127;
   IPs[1].B := 0;
   IPs[1].C := 0;
   IPs[1].D := 1;

   FOR I := 0 TO 1 DO
      CASE IPs[I].Version OF
	IPv4 : WriteLn(IPs[I].A);
	IPv6 : WriteLn(IPs[I].Address);
        ELSE WriteLn('Other IP version')
      END
END.

Con este pequeño artículo espero que hayáis podido conocer o recordar algo sobre Pascal, el lenguaje de programación diseñado por Niklaus Wirth.

Dejo por aquí un famoso póster que explica, de forma bonita, toda la sintaxis de Apple Pascal. Más información aquí

]]>
https://blog.adrianistan.eu/pascal-homenaje-niklaus-wirth Wed, 7 Feb 2024 19:08:08 +0000
Curva de Hilbert en Prolog https://blog.adrianistan.eu/curva-hilbert-prolog https://blog.adrianistan.eu/curva-hilbert-prolog La curva de Hilbert es una curva fractal descubierta por David Hilbert en 1891. Es un tipo de curva de Peano. Estas curvas tienen la peculiaridad de que cubren todo el plano de forma continua. Esto les otorga propiedades muy interesantes, una curva de Hilbert de nivel N es la aproximación enésima al límite de la curva, ocupando el mismo espacio pero cada vez rellenando más huecos, así hasta el infinito, donde sería un cuadrado sólido. El objetivo de este post no es entrar en detalle de sus propiedades matemáticas ni de sus aplicaciones, sino como podemos generar curvas de Hilbert bonitas usando Prolog.

SVG y DCGs

Para generar la curva de Hilbert de nivel N vamos a usar dos elementos claves. Uno, es el formato de imágenes vectoriales SVG. SVG es un formato, estándar por la W3C, para representar gráficos vectoriales, es decir gráficos que no se definen píxel a píxel sino mediante comandos. Es un formato de texto, basado en XML. Nuestro programa generará un SVG, que luego podremos visualizar en un navegador web o editar posteriormente en programas como Adobe Illustrator o Inkscape.

Cuando nos encontramos ante la tesitura de generar texto en Prolog, siempre es muy interesante valorar las opciones de usar DCGs. Las DCGs nos permiten describir listas de forma muy cómoda tal y como ya vimos hace tiempo. Además, en algunos sistemas como Scryer Prolog, los strings son listas de caracteres.

Generar XML con DCGs

¿Cómo podemos generar un XML con DCGs? Fácil, podemos definir unos non_terminal para las etiquetas. De esa forma, las etiquetas y los atributos se escribirán siempre correctamente.


:- use_module(library(dcgs)).
:- use_module(library(format)).

tag(T, GRBody) -->
    format_("<~w>", [T]),
    GRBody,
    format_("", [T]).

Cuando usamos este non_terminal vemos como el string de salida respeta la sintaxis XML.

?- phrase(tag(text, "Hola"), X).
   X = "Hola".

De forma general, también nos interesará añadir atributos y tener etiquetas que se cierran sin tener contenido en su interior.


tag(T, GRBody) -->
    format_("<~w>", [T]),
    GRBody,
    format_("", [T]).
    
tag(T, Attrs, GRBody) -->
    format_("<~w", [T]),
    tag_attrs(Attrs),
    ">",
    GRBody,
    format_("", [T]).

closed_tag(T, Attrs) -->
    format_("<~w", [T]),
    tag_attrs(Attrs),
    "/>".

tag_attrs([]) --> "".
tag_attrs([Attr|Attrs]) -->
    { Attr =.. [Name, Value], chars_si(Value) },
    format_(" ~w=\"", [Name]),
    Value,
    "\"",
    tag_attrs(Attrs).
tag_attrs([Attr|Attrs]) -->
    { Attr =.. [Name, Value], integer_si(Value) },
    format_(" ~w=\"~d\"", [Name, Value]),
    tag_attrs(Attrs).

Con estos non_terminals ya podemos escribir cualquier XML de forma cómoda.


?- phrase(tag(svg, [width(50), height(50), xmlns("http://www.w3.org/2000/svg")], (closed_tag(circle, [cx(50), cx(50), r(50)]), closed_tag(rect, [width(30), height(30)]))), X).
   X = ""
;  ... .

La imagen generada es la siguiente:

La curva de Hilbert

La curva de Hilbert es fractal. Eso quiere decir que los niveles N de la curva se construyen usando como bloques los niveles previos de N de la curva. Es decir, para elaborar la curva Hilbert N=3, tendremos que juntar trozos de varias curvas de Hilbert N = 2. Y una curva N = 2 a su vez de compone de varias curvas N = 1. Lógicamente tiene sentido pensar en idear una recursión para generarlas.

Un detalle de la curva de Hilbert es que las curvas que usamos para componer la curva grande, están rotadas. En SVG existen comandos de rotación, pero en este caso vamos a implementar la rotación "manualmente". Es decir, vamos a tener cuatro casos, uno para rotación. Cada caso tendrá su caso base, cuando N = 1, y los casos de N > 1. A los casos los llamo: a, b, c y d.

Adicionalmente, es necesario saber cuáles van a ser las dimensiones de nuestro SVG. Con otro pequeño predicado recursivo podemos calcular el tamaño del SVG de la curva N, teniendo en cuenta que he decidido que para las rectas serán de 8 unidades.


hilbert_width(1, 8).
hilbert_width(H, N) :-
    #H1 #= H - 1,
    hilbert_width(1, Base),
    hilbert_width(H1, N1),
    #N #= N1*2 + Base.

hilbert_only(H) -->
    { hilbert_width(H, Width) },
    tag(svg, [version("1.1"), width(Width), height(Width), xmlns("http://www.w3.org/2000/svg")], hilbert_a(H, 1, Width, 0, Width, Width, "black")).

hilbert_a(1, F, X0, Y0, X0, Y, Color) -->
    { #X #= X0 - 8 * F, #Y #= Y0 + 8 * F },
    line(X0, Y0, X, Y0, Color),
    line(X, Y0, X, Y, Color),
    line(X, Y, X0, Y, Color).

hilbert_a(H, F, X0, Y0, X0, Y, Color) -->
    { #H1 #= H - 1, #X2 #= #X1 - 8 * F, #Y3 #= #Y2 + 8 * F},
    hilbert_b(H1, F, X0, Y0, X1, Y1, Color),
    line(X1, Y1, X2, Y1, Color),
    hilbert_a(H1, F, X2, Y1, X3, Y2, Color),
    line(X3, Y2, X3, Y3, Color),
    hilbert_a(H1, F, X3, Y3, X4, Y4, Color),
    line(X4, Y4, X1, Y4, Color),
    hilbert_c(H1, F, X1, Y4, X0, Y, Color).

hilbert_b(1, F, X0, Y0, X, Y0, Color) -->
    { #X #= X0 - 8 * F, #Y #= Y0 + 8 * F},
    line(X0, Y0, X0, Y, Color),
    line(X0, Y, X, Y, Color),
    line(X, Y, X, Y0, Color).

hilbert_b(H, F, X0, Y0, X, Y0, Color) -->
    { #H1 #= H - 1, #Y2 #= #Y1 + 8 * F, #X3 #= #X2 - 8 * F},
    hilbert_a(H1, F, X0, Y0, X1, Y1, Color),
    line(X1, Y1, X1, Y2, Color),
    hilbert_b(H1, F, X1, Y2, X2, Y3, Color),
    line(X2, Y3, X3, Y3, Color),
    hilbert_b(H1, F, X3, Y3, X, Y4, Color),
    line(X, Y4, X, Y1, Color),
    hilbert_d(H1, F, X, Y1, X, Y0, Color).

hilbert_c(1, F, X0, Y0, X, Y0, Color) -->
    { #X #= X0 + 8 * F, #Y #= Y0 - 8 * F },
    line(X0, Y0, X0, Y, Color),
    line(X0, Y, X, Y, Color),
    line(X, Y, X, Y0, Color).

hilbert_c(H, F, X0, Y0, X, Y0, Color) -->
    { #H1 #= H - 1, #Y2 #= #Y1 - 8 * F, #X3 #= #X2 + 8 * F},
    hilbert_d(H1, F, X0, Y0, X1, Y1, Color),
    line(X1, Y1, X1, Y2, Color),
    hilbert_c(H1, F, X1, Y2, X2, Y3, Color),
    line(X2, Y3, X3, Y3, Color),
    hilbert_c(H1, F, X3, Y3, X, Y4, Color),
    line(X, Y4, X, Y1, Color),
    hilbert_a(H1, F, X, Y1, X, Y0, Color).

hilbert_d(1, F, X0, Y0, X0, Y, Color) -->
    { #X #= X0 + 8 * F, #Y #= Y0 - 8 * F},
    line(X0, Y0, X, Y0, Color),
    line(X, Y0, X, Y, Color),
    line(X, Y, X0, Y, Color).

hilbert_d(H, F, X0, Y0, X0, Y, Color) -->
    { #H1 #= H - 1, #X2 #= #X1 + 8 * F, #Y3 #= #Y2 - 8 * F},
    hilbert_c(H1, F, X0, Y0, X1, Y1, Color),
    line(X1, Y1, X2, Y1, Color),
    hilbert_d(H1, F, X2, Y1, X3, Y2, Color),
    line(X3, Y2, X3, Y3, Color),
    hilbert_d(H1, F, X3, Y3, X4, Y4, Color),
    line(X4, Y4, X1, Y4, Color),
    hilbert_b(H1, F, X1, Y4, X0, Y, Color).

    
line(X1, Y1, X2, Y2, Color) -->
    closed_tag(line, [x1(X1), x2(X2), y1(Y1), y2(Y2), stroke(Color), 'stroke-width'("1")]).

Con esto podemos llamar a hilbert_only y obtener la curva N de Hilbert.


Curva N = 6

Generar múltiples curvas superpuestas

Algo que queda muy bonito es superponer las curvas de N niveles, una encima de otra. Esto supondría que habría líneas que se transitarían muchas veces. Una opción mejor es ir escalando las curvas y centrándolas. Para eso era la variable F que he usado antes. Juntando todos estos componentes y dibujando cada curva de un color diferente, podemos llegar a esto:


main :-
    phrase_to_file(hilbert_all(["black", "blue", "green", "red", "yellow"]), "hilbert.svg").

hilbert_all(Colors) -->
    {
	length(Colors, H),
	hilbert_width(H, Width),
	#W1 #= Width + 20,
	phrase(format_("-10 -10 ~d ~d", [W1, W1]), ViewBox),
	Colors = [Color|Colors1]
    },
    tag(svg, [version("1.1"), width(Width), height(Width), viewBox(ViewBox), xmlns("http://www.w3.org/2000/svg")],(
	    hilbert_a(H, 1, Width, 0, Width, Width, Color),
	    hilbert_sub(Colors1, 1, Width))
       ).

hilbert_sub([], _, _) --> [].
hilbert_sub(Colors, F, TotalWidth) -->
    {
	length(Colors, N),
	#F1 #= F * 2,
	hilbert_width(N, Width),
	#FWidth #= Width * F1,
	#HalfF #= (TotalWidth - FWidth) / 2,
	phrase(format_("translate(~d, ~d)", [HalfF, HalfF]), Transform),
	Colors = [Color|Colors1]
    },
    tag(g, [transform(Transform)], hilbert_a(N, F1, FWidth, 0, FWidth, FWidth, Color)),
    hilbert_sub(Colors1, F1, TotalWidth).

Que da como resultado esta bonita imagen.

]]>
https://blog.adrianistan.eu/curva-hilbert-prolog Tue, 30 Jan 2024 22:05:15 +0000
Diversión con shaders en WGSL https://blog.adrianistan.eu/diversion-shaders-wgsl https://blog.adrianistan.eu/diversion-shaders-wgsl Estaba experimentando con Bevy, un motor de videojuegos muy prometedor, escrito en Rust, cuando me surgió la necesidad (o en ese momento lo creía) de tener que hacer un hack usando shaders. Los shaders son pequeños programas que se ejecutan en la GPU. Tras mis aventuras, he podido comprender como funciona, más en detalle, los gráficos 3D modernos. Una ventaja que tiene Bevy respecto a otros motores, es que, actualmente te permite acceder al nivel base WebGPU de forma muy rápida y cómoda, sin las complicaciones habituales de hacer todo el setup. Es por ello que usaremos Bevy, para tener acceso rápido a WGSL y poder programar en la GPU.

Setup inicial

Para empezar deberemos crear un proyecto en Rust. Añadiremos la dependencia de Bevy a nuestro Cargo.toml:

bevy = "0.12.1"

Cámara

Una cosa que vamos a necesitar es una cámara. Bevy dispone de cámaras específicas de 2D y de 3D. Usaremos una de 3D sin muchos ajustes adicionales, aunque hay muchos parámetros modificables.

use bevy::prelude::*;

fn setup_camera(mut commands: Commands) {
    commands.spawn(Camera3dBundle {
        transform: Transform::from_xyz(0.0, 0.0, 2.0).looking_at(Vec3::ZERO, Vec3::Y),
        ..default()
    });
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup_camera)
        .run();
}

¿Qué es una cámara en realidad?

Esta pregunta es muy interesante. Cuando añadimos una cámara a nuestro proyecto Bevy, ¿qué estamos haciendo exactamente? Pues en esencia estamos agregando una matriz 4x4 a un sitio, que más adelante podremos usar. Simplemente eso. Esta matriz al ser multiplicada por un vértice (que será una matriz 4x1), nos dará la posición real del vértice en la pantalla.

Ahora ya podríamos ver algo, pero nos falta ese algo que ver. En los motores actuales los "objetos" se suelen dividir como mínimo en 2 componentes (si tenemos animaciones habrá más). Lo primero es el mesh y lo segundo es el material.

Podemos pensar en el mesh como datos puros, de la forma del objeto. Así pues en el mesh tendremos los vértices del objeto tridimensional, en qué orden se dibujan, vectores de normales y de UV, … En general, todo ello referido a vértices.

Por otro lado, el material vendría a ser los programas que se ejecutan en la GPU (los shaders) más cierta configuración sobre ellos. Pero sin referirnos a los vértices individualmente.

En Bevy es bastante sencillo crear mesh de cero. Para empezar, vamos a empezar con un cuadrado. Lo primero que hay que decir es que las GPUs modernas solo saben dibujar triángulos. Existen varias formas de dibujar, pero nosotros usaremos TriangleList, que es una técnica muy fácil de entender. A continuación deberemos especificar los vértices de nuestro cuadrado. En el mundo 3D se suelen usar números decimales. Debemos espeficar nuestros vértices en base a un cero relativo al objeto en sí. Si luego en esa escena 3D el objeto debe aparecer en otro sitio, se hará una transformación (multiplicación de matrices) independiente.

El sistema de coordenadas depende del motor. Bevy sigue la regla de la mano derecha, con la Y apuntando arriba. En la siguiente imagen se ve de forma clara y comparado con otros motores.

Posteriormente hay que indicar los índices. Como usamos TriangleList, deberemos de añadir tripletas, donde especificamos qué vértices corresponden a cada triángulo. Es decir, si tenemos 0,1,2, el triángulo estará formado por los vértices 0, 1 y 2, y se tomarán sus datos de sus correspondientes buffers (de posición, de normal, …) El orden importa, ya que podemos dibujar triángulos al revés, esos no se verían.

Ahora sí, al lío. Como hemos dicho, el mesh se compone de buffers de datos. Existen en Bevy varios "atributos" o buffers predefinidos. Nosotros de momento usaremos solo el de POSITION. Para el material vamos a añadir un material básico de un color. Luego lo editaremos.

use bevy::prelude::*;
use bevy::render::mesh::Indices;
use bevy::render::render_resource::PrimitiveTopology;

fn setup_quad(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<StandardMaterial>>) {
    let mesh = Mesh::new(PrimitiveTopology::TriangleList)
        .with_inserted_attribute(
            Mesh::ATTRIBUTE_POSITION,
            vec![
                [0.5, -0.5, 0.0],
                [-0.5, 0.5, 0.0],
                [-0.5, -0.5, 0.0],
                [0.5, 0.5, 0.0]
            ])
        .with_indices(Some(Indices::U32(vec![
            2,0,1,
            0,3,1,
        ])));

    let mesh_handle = meshes.add(mesh);
    let material_handle = materials.add(StandardMaterial::from(Color::RED));

    commands.spawn(MaterialMeshBundle {
        mesh: mesh_handle,
        material: material_handle,
        ..default()
    });
}

fn setup_camera(mut commands: Commands) {
    commands.spawn(Camera3dBundle {
        transform: Transform::from_xyz(0.0, 0.0, 2.0).looking_at(Vec3::ZERO, Vec3::Y),
        ..default()
    });
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, (setup_camera, setup_quad))
        .run();
}

Con esto podemos ver ya algo en pantalla.

Custom Materials en Bevy

Ya podemos entrar de lleno a los materiales. Como hemos dicho antes los materiales van a controlar como se dibuja el mesh, mediante shaders (programas que se ejecutan en la GPU) y algunas variables que no dependen de los vértices. Para empezar hemos usado un material que ya viene en Bevy, muy potente, el StandardMaterial. Para que nosotros podamos toquetear a nivel de WGSL y WebGPU, tendremos que implementar un Custom Material.

Hablemos un poco más de los shaders. Aunque actualmente existe un tercer tipo de shader (geometry shader), en esencia hay dos tipos de shader. Y estos dos son obligatorios. El primero de ellos es el Vertex Shader. Este shader se ejecutará para cada vértice que se ha mandado dibujar. Su misión principal es convertir la posición de cada vértice del objetivo, recordemos, relativa al centro del objeto, a una posición de la vista final, la llamada clip_position. Para hacer esto tendrá que intervenir la posición del vértice, la transformación del objeto en la escena y la transformación de la cámara.

Realmente podremos hacer muchas más cosas pero esa es la idea principal.

En segundo, lugar tenemos el Fragment Shader. Este se ejecuta después que los vertex shader. Entre el vertex shader y el fragment shader tiene lugar un proceso llamado rasterización. El proceso consiste en, una vez sabemos donde van los vértices de nuestros triángulos, calcular qué píxeles exactos en pantalla conforman el triángulo entero. Para cada uno de los píxeles que hay que pintar, se ejecuta el fragment shader. La tarea esencial del fragment shader es decirle al pixel en particular, qué color debe salir en pantalla.

Para añadir el Custom Material deberemos modificar un par de cosas:

use bevy::prelude::*;
use bevy::render::mesh::Indices;
use bevy::render::render_resource::PrimitiveTopology;
use bevy::render::render_resource::ShaderRef;
use bevy::render::render_resource::AsBindGroup;

#[derive(AsBindGroup, Debug, Clone, Asset, TypePath)]
struct CustomMaterial {}

impl Material for CustomMaterial {
    fn vertex_shader() -> ShaderRef {
        "shaders/custom.wgsl".into()
    }
    fn fragment_shader() -> ShaderRef {
        "shaders/custom.wgsl".into()
    }
}

fn setup_quad(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<CustomMaterial>>) {
    let mesh = Mesh::new(PrimitiveTopology::TriangleList)
        .with_inserted_attribute(
            Mesh::ATTRIBUTE_POSITION,
            vec![
                [0.5, -0.5, 0.0],
                [-0.5, 0.5, 0.0],
                [-0.5, -0.5, 0.0],
                [0.5, 0.5, 0.0]
            ])
        .with_indices(Some(Indices::U32(vec![
            2,0,1,
            0,3,1,
        ])));

    let mesh_handle = meshes.add(mesh);
    let material_handle = materials.add(CustomMaterial {});

    commands.spawn(MaterialMeshBundle {
        mesh: mesh_handle,
        material: material_handle,
        ..default()
    });
}

fn setup_camera(mut commands: Commands) {
    commands.spawn(Camera3dBundle {
        transform: Transform::from_xyz(0.0, 0.0, 2.0).looking_at(Vec3::ZERO, Vec3::Y),
        ..default()
    });
}

fn main() {
    App::new()
        .add_plugins((DefaultPlugins, MaterialPlugin::<CustomMaterial>::default()))
        .add_systems(Startup, (setup_camera, setup_quad))
        .run();
}

Y deberemos añadir un fichero en assets/shaders/custom.wgsl.

Este fichero para empezar puede ser así:

@vertex
fn vertex(@location(0) position: vec3<f32>) -> @builtin(position) vec4<f32> {
  return vec4(position, 1.0);
}

@fragment
fn fragment() -> @location(0) vec4<f32> {
  return vec4(1.0, 0.0, 0.0, 1.0);
}

El vertex shader simplemente devuelve la posición y el fragment shader devuelve el color rojo en RGBA.

Deberíamos ver algo así:

Seguramente te preguntarás por qué ahora sale deformado. La respuesta es que ahora no hemos hecho ninguna transformación, la posición del vértice a pasado a ser la clip_position. Pero el StandardMaterial hacía transformaciones teniendo en cuenta la cámara, por ello preservaba la proporción.

WGSL

WGSL es el lenguaje de shaders estandarizado por W3C para ser usado con la API de WebGPU. Ninguna tarjeta gráfica ejecuta WebGPU o WGSL de forma nativa sino que se implementa por encima de las APIs reales del sistema. Estas pueden ser Vulkan, DirectX, Metal, OpenGL, … WGSL en ese sentido es un mínimo denominador común, se trata de un lenguaje diseñado para que pueda funcionar a través de cualquier tipo de API gráfica.

WGSL toma bastante inspiración en su sintaxis de Rust, aunque sigue siendo un lenguaje bastante peculiar como iremos viendo. Una de las características de WGSL (y de otros lenguajes similares como GLSL en OpenGL) es que permiten multiplicar matrices directamente. Además, el tema de las variables puede resultar algo confuso, ya que las "importantes" tendremos que anotarlas, para de alguna manera, relacionarlas con los datos del mesh o de la API gráfica.

Para empezar, algunas variables las podemos relacionar con un builtin, eso es, una variable que siempre está disponible en WebGPU. El listado de builtins y lo que significa varía dependiendo del tipo de shader y si es una variable de salida o de entrada. He aquí una pequeña tabla de equivalencias entre WGSL y GLSL.

Los location hacen referencia a datos de buffers. En los vertex shader, a la entrada, hacen referencia a datos del mesh. Los que hemos indicado con atributos, como la posición. En los fragment shader, a la salida, hacen referencia a los datos que el motor quiera obtener. Esto se puede configurar en Bevy, y en qué orden queremos que vayan, pero los ajustes por defecto son suficientemente buenos para no tocarlos.

Además veremos groups y bindings, pero eso más adelante.

Vertex Shader

Como hemos dicho el vertex shader, se ejecuta para cada vértice, toma los datos del mesh y según el modo de dibujo, se ejecuta para ciertos vértices. Lo principal en este proceso es devolver un clip_position.

Para ayudarnos a hacer las transformaciones, Bevy dispone de funciones predefinidas para WGSL que interactúan con otros elementos del motor, como la cámara. Añadiendo un import, podremos acceder a la matriz de transformación que necesitamos y que tiene la cámara 3D que añadimos.

Con el nuevo vertex shader, nuestro cuadrado vuelve a tener las proporciones correctas:

#import bevy_pbr::mesh_view_bindings as view_bindings

@vertex
fn vertex(@location(0) position: vec3<f32>) -> @builtin(position) vec4<f32> {
  return view_bindings::view.view_proj * vec4(position, 1.0);
}

@fragment
fn fragment() -> @location(0) vec4<f32> {
  return vec4(1.0, 0.0, 0.0, 1.0);
}

Lo gracioso del vertex shader, es que, realmente, podemos deformar lo que queramos los vértices. Incluso podemos no hacer caso al atributo de posición e inventarnos una regla matemática para generar los vértices. Para acceder a varios datos a la vez en un vertex shader, podemos usar structs. Además en este ejemplo veremos los arrays:

#import bevy_pbr::mesh_view_bindings as view_bindings

struct VertexInput {
  @location(0) position: vec3<f32>,
  @builtin(vertex_index) vertex_index: u32,
}

@vertex
fn vertex(in: VertexInput) -> @builtin(position) vec4<f32> {
  var figure = array<vec2<f32>, 3>(vec2(0.0, 0.5), vec2(0.0, 0.0), vec2(0.5, 0.0));  
  let top: u32 = u32(3); 
  let position = figure[in.vertex_index % top];
  return view_bindings::view.view_proj * vec4(position, 0.0, 1.0);
}

@fragment
fn fragment() -> @location(0) vec4<f32> {
  return vec4(1.0, 0.0, 0.0, 1.0);
}

Nótese que no usamos la posición en ningún momento, solamente según el índice del vértice, obtenemos un vértice para construir un triángulo. Las variables declaradas con var son mutables y las declaradas con let son inmutables. Para acceder a un array mediante una constante calculada "así" es necesario que el array sea mutable.

Evidentemente esta no es la forma óptima de mostrar un triángulo en pantalla, pero creo que es ilustrativo para ver como funciona un vertex shader. Dentro de un vertex shader están permitidas las estructuras de control más típicas de un lenguaje imperativo como bucles (while, for, loop), condicionales (if, switch, select) y llamadas a otras funciones.

Fragment Shader

Una vez se procesan los vertex shader, la GPU rasteriza y genera unos píxeles que debemos colorear. Hasta ahora lo hemos coloreado de rojo. Aprovechamos aquí para introducir otro tipo de variables. Si recordáis, en el primer ejemplo usando StandardMaterial, podíamos pasarle el color en la CPU al material. ¿Cómo podemos hacer esto en nuestro material?

En el lado de Bevy vamos a añadir un campo nuevo a la estructura CustomMaterial, en este caso un campo llamado color de tipo Color. Tendremos que anotarlo con #[uniform(0)]. Estas variables llamadas uniform son aquellas que se mantienen constantes en GPU. Indicamos 0 pues el la primera variable de este tipo que vamos a pasar.

En WGSL hay algo más de código. Primero tenemos que identificar al grupo donde se encuentra el dato. En este caso es el grupo 1. Dentro del grupo 1, este dato era el uniform 0, así que hacemos binding al 0. Declaramos la variable con el tipo adecuado al código Rust y ya la podemos usar en nuestro fragment shader.

#[derive(AsBindGroup, Debug, Clone, Asset, TypePath)]
struct CustomMaterial {
    #[uniform(0)]
    color: Color,
}
#import bevy_pbr::mesh_view_bindings as view_bindings

@group(1) @binding(0) var<uniform> color: vec4<f32>;

struct VertexInput {
  @location(0) position: vec3<f32>,
  @builtin(vertex_index) vertex_index: u32,
}

@vertex
fn vertex(in: VertexInput) -> @builtin(position) vec4<f32> {
  var figure = array<vec2<f32>, 3>(vec2(0.0, 0.5), vec2(0.0, 0.0), vec2(0.5, 0.0));  
  let top: u32 = u32(3); 
  let position = figure[in.vertex_index % top];
  return view_bindings::view.view_proj * vec4(position, 0.0, 1.0);
}

@fragment
fn fragment() -> @location(0) vec4<f32> {
  return color;
}

Ahora si desde Rust seleccionamos otro color, este se mostrará.

Podemos experimentar también con patrones en WGSL. Para ello podemos valernos de la propiedad builtin position, que en la entrada de un fragment shader representa coordenadas de la pantalla.

En este ejemplo, ocupamos toda la pantalla con nuestro vertex shader y posteriormente dibujamos líneas blancas sobre fondo negro:

struct VertexInput {
  @location(0) position: vec3<f32>,
  @builtin(vertex_index) vertex_index: u32,
}

@vertex
fn vertex(in: VertexInput) -> @builtin(position) vec4<f32> {
  return vec4(in.position * 2.0,1.0);
}

struct FragmentInput {
  @builtin(position) frag_coord: vec4<f32>,
}

@fragment
fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
  var color: vec4<f32>;
  if(in.frag_coord.x % 16.0 < 1.0) {
    color = vec4(1.0, 1.0, 1.0, 1.0);
  } else {
    color = vec4(0.0, 0.0, 0.0, 1.0);
  }

  return color;
}

Con una pequeña variación obtendríamos una grid isométrica. Cabe recordar que el fragment shader solo se aplica al trozo de pantalla rasterizado.

@fragment
fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
  let gridA = ((in.frag_coord.x + in.frag_coord.y * 2.0) % 64.0) < 1.0;
  let gridB = abs((in.frag_coord.x - in.frag_coord.y * 2.0) % 64.0) < 1.0;
  var color: vec4<f32>;
  if(gridA || gridB) {
    color = vec4(1.0, 1.0, 1.0, 1.0);
  } else {
    color = vec4(0.0, 0.0, 0.0, 1.0);
  }

  return color;
}

Una propiedad muy interesante de los shaders es que desde el vertex shader podemos asignar propiedades a una variable de salida, adicional a la posición. Posteriormente podremos acceder a ella desde desde el fragment shader con una propiedad interesante: será interpolada. Esto quiere decir que si en un vértice pongo un 1.0 y en otro 0.0 para esa variable, las llamadas a fragment shaders intermedios irán recibiendo valores de un espectro: 0.8, 0.56, 0.33, … Probemos con ATTRIBUTE_COLOR, otro atributo predefinido en Bevy.

    .with_inserted_attribute(
        Mesh::ATTRIBUTE_COLOR,
        vec![
            [1.0, 0.0, 0.0, 1.0],
            [0.0, 1.0, 0.0, 1.0],
            [0.0, 0.0, 1.0, 1.0],
            [1.0, 1.0, 1.0, 1.0]
        ])
struct VertexInput {
  @location(0) position: vec3<f32>,
  @location(5) color: vec4<f32>,
  @builtin(vertex_index) vertex_index: u32,
}

struct VertexOutput {
  @builtin(position) clip_position: vec4<f32>,
  @location(0) color: vec4<f32>,
}

@vertex
fn vertex(in: VertexInput) -> VertexOutput {
  var out: VertexOutput;
  out.clip_position = view_bindings::view.view_proj * vec4(in.position,1.0);
  out.color = in.color;
  return out;
}

struct FragmentInput {
  @builtin(position) frag_coord: vec4<f32>,
  @location(0) color: vec4<f32>,
}

@fragment
fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {  
  return in.color;
}

Por último, vamos a ver como se podrían cargar texturas a nuestros shaders. Para ello deberemos añadir acceso a las texturas mediante nuestro material. Idealmente se usa una textura, y un sampler, que se encarga de poder hacer reducciones de tamaño de la textura.

Una vez lo tenemos, podemos llamar a la función textureSample. La función necesitará unas coordenadas UV. Estas coordenadas vendrán también en el mesh y las definiremos como (0.0, 0.0) el vértice que corresponda a la parte superior izquierda y (1.0, 1.0) la parte inferior derecha. El interpolado se encargará del resto.

#[derive(AsBindGroup, Debug, Clone, Asset, TypePath)]
struct CustomMaterial {
    #[uniform(0)]
    color: Color,
    #[texture(1)]
    #[sampler(2)]
    image: Handle<Image>,
}

...

fn setup_quad(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<CustomMaterial>>, mut server: Res<AssetServer>) {
    let mesh = Mesh::new(PrimitiveTopology::TriangleList)
        .with_inserted_attribute(
            Mesh::ATTRIBUTE_POSITION,
            vec![
                [0.5, -0.5, 0.0],
                [-0.5, 0.5, 0.0],
                [-0.5, -0.5, 0.0],
                [0.5, 0.5, 0.0]
            ])
        .with_inserted_attribute(
            Mesh::ATTRIBUTE_UV_0,
            vec![
                [1.0, 1.0],
                [0.0, 0.0],
                [0.0, 1.0],
                [1.0, 0.0],
            ])
        .with_inserted_attribute(
            Mesh::ATTRIBUTE_COLOR,
            vec![
                [1.0, 0.0, 0.0, 1.0],
                [0.0, 1.0, 0.0, 1.0],
                [0.0, 0.0, 1.0, 1.0],
                [1.0, 1.0, 1.0, 1.0]
            ])
        .with_indices(Some(Indices::U32(vec![
            2,0,1,
            0,3,1,
        ])));

    let mesh_handle = meshes.add(mesh);
    let image_handle: Handle<Image> = server.load("tren.png");
    let material_handle = materials.add(CustomMaterial { color: Color::BLUE, image: image_handle});

Los shaders quedarían así:

#import bevy_pbr::mesh_view_bindings as view_bindings

@group(1) @binding(0) var<uniform> color: vec4<f32>;
@group(1) @binding(1) var texture: texture_2d<f32>;
@group(1) @binding(2) var samp: sampler;

struct VertexInput {
  @location(0) position: vec3<f32>,
  @location(2) uv: vec2<f32>,
  @location(5) color: vec4<f32>,
  @builtin(vertex_index) vertex_index: u32,
}

struct VertexOutput {
  @builtin(position) clip_position: vec4<f32>,
  @location(0) color: vec4<f32>,
  @location(1) uv: vec2<f32>,
}

@vertex
fn vertex(in: VertexInput) -> VertexOutput {
  var out: VertexOutput;
  out.clip_position = view_bindings::view.view_proj * vec4(in.position,1.0);
  out.uv = in.uv;
  out.color = in.color;
  return out;
}

struct FragmentInput {
  @builtin(position) frag_coord: vec4<f32>,
  @location(0) color: vec4<f32>,
  @location(1) uv: vec2<f32>,
}

@fragment
fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {  
  return textureSample(texture, samp, in.uv) * in.color;
}

Y el resultado será el siguiente:

]]>
https://blog.adrianistan.eu/diversion-shaders-wgsl Thu, 18 Jan 2024 22:17:23 +0000
Vídeo de mi charla de Strand https://blog.adrianistan.eu/video-charla-strand https://blog.adrianistan.eu/video-charla-strand Hace unos días di una charla hablando de Strand, un lenguaje de programación del paradigma lógico y que actualmente está en desuso y muy poca gente conoce. El motivo de la charla fue revisar la historia de este lenguaje y cómo consigue que podamos escribir programas paralelos sin apenas darnos cuenta, ya que no incluye ninguna sentencia de fork/spawn o similar. Espero que os guste.

]]>
https://blog.adrianistan.eu/video-charla-strand Mon, 18 Dec 2023 19:57:00 +0000
Introducción a Apache Spark con datos de la Fórmula 1 https://blog.adrianistan.eu/introduccion-apache-spark https://blog.adrianistan.eu/introduccion-apache-spark Este post es una adaptación del taller que di en el VallaTech Summit 2023 organizado por el Google Developers Group de Valladolid

Apache Spark es un motor de computación distribuido en cluster diseñado para manejar grandes cantidades de datos. Es opensource y está programado en Scala, aunque se puede usar desde Java, Python y R también.Existe una versión comercial, con características adicionales, llamada Databricks.

¿Cuándo necesitamos usar Apache Spark? Cuando necesitamos realizar transformaciones a unos datos y

  • Los datos son demasiado grandes, ocupan demasiado espacio, …
  • Las transformaciones no necesitan ser “real time” (hay otras herramientas específicas si buscamos baja latencia).
  • El cómputo se beneficia de la paralelización

En general: procesamiento de datos (ETL), ciertos tipos de machine learning (ML), reportes, analíticas, etc. De forma habitual trabajaremos con un clúster de Spark, al que se le van mandando jobs. Los jobs, si están programados usando las APIs de Spark, aprovecharán la potencia del clúster completo de forma eficiente.

¿Cómo consigue Spark funcionar?

Hay tres pilares sobre los cuáles Spark es eficiente:

MapReduce: En Spark hay dos tipos de operaciones fundamentales:

  • Map: Se aplica una función para cada dato, que devuelve otro dato. Es fácil ver que es paralelizable.
  • Reduce: Se aplica una función que N datos, los combina en un dato único. Aquí para que sea paralelizable idealmente nuestra operación de reducción deberá ser asociative y conmutativa. Es decir, no importe el orden en el que se van haciendo las reducciones.

Los Map y Reduce se distribuyen por el clúster. Spark se encarga de enviar los datos de forma transparente entre los nodos.

RDD: Inmutabilidad y pereza Los datos se cargan en un RDD. Sobre un RDD podemos hacer operaciones pero los RDD en sí son inmutables. Se nos genera otro RDD con el cambio. ¿Con el cambio? Realmente no, se almacena el cambio que hay que hacer pero son perezosos. Hasta que no necesitemos los datos, no se realizará la operación.

HDFS Aunque Spark funciona con muchas fuentes de datos, es habitual encontrarlo HDFS, un sistema de archivos (Hadoop FileSystem) optimizado para data lakes. Este sistema de archivos está diseñado para exponerse en red y que los nodos puedan leer y escribir independientemente del resto de los nodos, logrando así una paralelización a nivel de I/O.

Datos de la Fórmula 1

Vamos a usar datos de la Fórmula 1 para probar las cosas más esenciales de Spark. Los datos están cogidos de este repositorio de Kaggle, aunque también los tengo subidos a GitHub

Races are won at the track. Championships are won at the factory. Mercedes (2019)

spark-shell

La primera forma que tenemos de usar Spark (previamente descomprimido de la página de Spark) es mediante el Spark-Shell. Línea a línea podemos ir ejecutando código (en Scala 2).

Ejecutamos bin/spark-shell y una vez cargado podemos leer un fichero de datos, en este caso un CSV con los circuitos de la Fórmula 1 de toda su historia.


Welcome to
      ____              __
     / __/__  ___ _____/ /__
    _\ \/ _ \/ _ `/ __/  '_/
   /___/ .__/\_,_/_/ /_/\_\   version 3.5.0
      /_/
         
Using Scala version 2.12.18 (OpenJDK 64-Bit Server VM, Java 21)
Type in expressions to have them evaluated.
Type :help for more information.

scala> 23/11/27 22:43:34 WARN GarbageCollectionMetrics: To enable non-built-in garbage collector(s) List(G1 Concurrent GC), users should configure it(them) to spark.eventLog.gcMetrics.youngGenerationGarbageCollectors or spark.eventLog.gcMetrics.oldGenerationGarbageCollectors


scala> df.show()
+---------+--------------+--------------------+------------+---------+--------+---------+---+--------------------+
|circuitId|    circuitRef|                name|    location|  country|     lat|      lng|alt|                 url|
+---------+--------------+--------------------+------------+---------+--------+---------+---+--------------------+
|        1|   albert_park|Albert Park Grand...|   Melbourne|Australia|-37.8497|  144.968| 10|http://en.wikiped...|
|        2|        sepang|Sepang Internatio...|Kuala Lumpur| Malaysia| 2.76083|  101.738| 18|http://en.wikiped...|
|        3|       bahrain|Bahrain Internati...|      Sakhir|  Bahrain| 26.0325|  50.5106|  7|http://en.wikiped...|
|        4|     catalunya|Circuit de Barcel...|    Montmeló|    Spain|   41.57|  2.26111|109|http://en.wikiped...|
|        5|      istanbul|       Istanbul Park|    Istanbul|   Turkey| 40.9517|   29.405|130|http://en.wikiped...|
|        6|        monaco|   Circuit de Monaco| Monte-Carlo|   Monaco| 43.7347|  7.42056|  7|http://en.wikiped...|
|        7|    villeneuve|Circuit Gilles Vi...|    Montreal|   Canada|    45.5| -73.5228| 13|http://en.wikiped...|
|        8|   magny_cours|Circuit de Nevers...| Magny Cours|   France| 46.8642|  3.16361|228|http://en.wikiped...|
|        9|   silverstone| Silverstone Circuit| Silverstone|       UK| 52.0786| -1.01694|153|http://en.wikiped...|
|       10|hockenheimring|      Hockenheimring|  Hockenheim|  Germany| 49.3278|  8.56583|103|http://en.wikiped...|
|       11|   hungaroring|         Hungaroring|    Budapest|  Hungary| 47.5789|  19.2486|264|http://en.wikiped...|
|       12|      valencia|Valencia Street C...|    Valencia|    Spain| 39.4589|-0.331667|  4|http://en.wikiped...|
|       13|           spa|Circuit de Spa-Fr...|         Spa|  Belgium| 50.4372|  5.97139|401|http://en.wikiped...|
|       14|         monza|Autodromo Naziona...|       Monza|    Italy| 45.6156|  9.28111|162|http://en.wikiped...|
|       15|    marina_bay|Marina Bay Street...|  Marina Bay|Singapore|  1.2914|  103.864| 18|http://en.wikiped...|
|       16|          fuji|       Fuji Speedway|       Oyama|    Japan| 35.3717|  138.927|583|http://en.wikiped...|
|       17|      shanghai|Shanghai Internat...|    Shanghai|    China| 31.3389|   121.22|  5|http://en.wikiped...|
|       18|    interlagos|Autódromo José Ca...|   São Paulo|   Brazil|-23.7036| -46.6997|785|http://en.wikiped...|
|       19|  indianapolis|Indianapolis Moto...|Indianapolis|      USA|  39.795| -86.2347|223|http://en.wikiped...|
|       20|   nurburgring|         Nürburgring|     Nürburg|  Germany| 50.3356|   6.9475|578|http://en.wikiped...|
+---------+--------------+--------------------+------------+---------+--------+---------+---+--------------------+
only showing top 20 rows

Spark SQL y DataFrames

La estructura de datos fundamental de Spark es el RDD. Sin embargo, para la mayoría de tareas es de demasiado bajo nivel. Spark ofrece una API de DataFrames, que nos permite hacer operaciones trabajando con filas y columnas. La API es similar a SQL pero por debajo usa todas las facilidades de Spark para que el cómputo sea distribuido en el clúster de forma óptima.

Para referirnos a una columna usaremos $"columna", col("columna") o df("columna").

Algunos ejemplos de cosas que permite la API de Spark SQL.

  • Generar dataframe nuevo seleccionando ciertas columnas: df.select($“name”, $”country”).show()
  • Filtrar filas for valor de una columna: df.select($”name”).where($”country” === “Spain”).show()
  • Agrupar y contar: df.groupBy($”country”).count().show()
  • Ordenar: df.orderBy(desc($”name”)).show()
  • Joins: df.join(races, "circuitId", "inner")

Desafío 1

Obtener una tabla con la temporada y el piloto que ganó esa temporada en el circuito de Mónaco.

Lo primero será abrir los dataframes de los datos que necesitamos:


val circuits = spark.read.format(“csv”).option(“header”, true).load(“data/circuits.csv”)
val races = spark.read.format(“csv”).option(“header”, true).load(“data/races.csv”)
val drivers = spark.read.format(“csv”).option(“header”, true).load(“data/drivers.csv”)
val results = spark.read.format(“csv”).option(“header”, true).load(“data/results.csv”)

Dejamos solo las carreras que tuvieron lugar en Mónaco


val racesInMonaco = races.join(circuits, "circuitId", "inner").where(circuits("name") === "Circuit de Monaco")

Obtenemos los ganadores de Mónaco


val monacoWinners = racesInMonaco.join(results, "raceId", "inner").where(results("position") === 1)

Obtenemos nombre, apellido y años de la victoria, ordenamos de forma descendente:


monacoWinners.join(drivers, "driverId", "inner").select(drivers("forename"), drivers("surname"), races("year")).orderBy(desc("year"))

Particiones

Para distribuir el trabajo, Spark debe saber cómo repartir los datos dentro de los nodos. Para ello usa el concepto de particiones. Tomará un punto de referencia (como el valor de una columna) para saber si cierto dato tiene que ir en una partición o en otra. Los nodos procesan particiones enteras. Si tu dataset tiene 4 particiones, podrás usar 4 nodos en paralelo.

En un DataFrame podemos reparticionar usando el método repartition. Podemos indicarle el número de particiones que queremos y/o la columna sobre la que hacer la partición Idealmente se debe escoger una columna donde la mayoría de operaciones de un nodo tengan todos el mismo valor

Agregaciones

Las agregaciones o reducciones son la principal diferencia entre una base de datos convencional. Las BBDD relacionales suelen estar optimizadas para OLTP. Las agregaciones (como GROUP BY) se soportan pero no son eficientes. Spark está diseñado de modo OLAP, de modo que las agregaciones son operaciones muy eficientes. Para agrupar en Spark usamos groupBy(). Dentro indicamos las columnas por las que agrupamos, seguido de agg y una función de agregación. Ejemplo:

df.groupBy("name").agg(sum("pedidos") as "pedidos_total")

Desafío 2

¿Cuántas vueltas rápidas tiene cada piloto?


 val fastestLapDriversSum = results
      .where(results("rank") === 1)
      .groupBy(results("driverId"))
      .agg(sum("rank") as "fastest_laps")
      .join(drivers, "driverId", "inner")
      .select("forename", "surname", "fastest_laps")
      .orderBy(desc("fastest_laps"))

Despliegue

Spark Shell es muy bonito pero salvo para hacer pruebas, es poco práctico Podemos generar un JAR para enviar a Spark. Mínimo dos archivos: build.sbt y src/main/scala/Formula1.scala

El build.sbt es tan simple como esto:


name := "Formula 1 Spark"
version := "1.0"
scalaVersion := "2.12.18"
libraryDependencies += "org.apache.spark" %% "spark-sql" % "3.5.0"

El otro fichero, donde irán nuestros algoritmos, deberá seguir la siguiente estructura.


import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions._

object Formula1 {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder.appName("Formula 1").getOrCreate()
    // hacer cosas con el objeto spark
    spark.stop()
  }
}

Compilaríamos con:

sbt package

Y lo mandaríamos al clúster de Spark con spark-submit. En este caso para ejecutar el JAR en un clúster local.

bin/spark-submit --class "Formula1" --master local[4] FICHERO.jar

map / withColumn / when

Con map podemos cambiar por completo cada fila dentro de un DF. Podemos ejecutar cualquier código Scala y obtendremos de vuelta un RDD, no un DF. Así que deberemos transformarlo de vuelta si queremos.

circuits.map(row => (row.getString(0), row.getString(2).toUpperCase)).toDF(“circuitId”, “name”)

Con withColumn podemos agregar /sustituir una columna. Las funciones tienen que ser funciones de Spark o UDFs (más adelante)


circuits.withColumn(“name_uppercase”, upper(col(“name”)))

Una función de Spark muy interesante con withColumn es when.


circuits.withColumn(“in_spain”, when(col(“country”) === “Spain”, true).otherwise(false))

UDF y UDAF

Spark SQL contiene una gran cantidad de funciones. Pero a veces nosotros queremos definir las nuestras. Se pueden programar en Scala y que sean accesibles tanto desde Scala como desde SQL. UDF si operan sobre columnas y UDAF si opera sobre agregaciones. Aunque existe ya la función upper, hagamos como si no existiese para el siguiente ejemplo


def upperCase(str: String): String = str.toUpperCase
val upperCaseUDF = udf(upperCase _)
circuits.select(upperCaseUDF(col("name")).as("name"))

Spark ML

Se trata de una librería que implementa algoritmos de Machine Learning sobre Spark. Hay que tener en cuenta que Spark no accede a las GPUs, por lo que ciertos tipos de ML no son adecuados en Spark (redes neuronales por ejemplo). Pero con otros algoritmos su uso es ideal.

  • Algoritmos de clústering
  • Regresiones, SVM, Random Forest, Bayes, …
  • Algoritmos de recomendación (ALS, …)
  • Extracción y transformaciones de features

Desafío 3

De los carreras de 2022. ¿Cuál fue el número de paradas promedio?


    val racesIn2022 = races.filter(races("year") === 2022)
    val pitStopsRaces2022 = racesIn2022.join(pitStops, "raceId", "inner")
    val pitStopsPerDriver = pitStopsRaces2022
      .groupBy(races("name"), col("driverId"))
      .agg(max("stop") as "stops")
    val pitStopsAvgPerRace = pitStopsPerDriver
      .groupBy("name")
      .agg(avg("stops"))

SQL

Esta API se parece mucho a SQL. Tanto que si nos gusta, podemos escribir las queries en, ¡SQL!


df.createOrReplaceTempView(“circuits”)
spark.sql(“SELECT name, country FROM circuits”).show()

Desafío 4

¿Cuántas horas ha corrido cada piloto de F1?


    lapTimes.createOrReplaceTempView("lap_times")
    drivers.createOrReplaceTempView("drivers")

    def millisecondsToHours(n: Long): Long = {
      n / 3600000
    }

    val millisecondsToHoursUDF = udf(millisecondsToHours _)
    spark.udf.register("msToHours", millisecondsToHoursUDF)

    val driversTime = spark.sql(
      """
        |SELECT forename, surname, msToHours(sum(milliseconds)) AS time
        |FROM lap_times
        |JOIN drivers ON lap_times.driverId = drivers.driverId
        |GROUP BY drivers.forename, drivers.surname
        |ORDER BY time DESC
        |""".stripMargin)
]]>
https://blog.adrianistan.eu/introduccion-apache-spark Mon, 27 Nov 2023 22:23:07 +0000
Sustituyendo Google Photos por mi propia solución local https://blog.adrianistan.eu/sustituyendo-google-photos-solucion-local https://blog.adrianistan.eu/sustituyendo-google-photos-solucion-local Uno de los productos de Google que más he usado ha sido Google Photos. Se trata de un servicio donde podemos subir imágenes y vídeos personales, que se sincroniza con nuestro dispositivo. No obstante, cada vez intento depender menos de servicios de Google y encima, con Photos, me estoy quedando sin espacio. Así que vamos a construir una alternativa.

Syncthing

Una de las aplicaciones más populares para sincronizar entre ordenadores es Syncthing. Se trata de un programa opensource con numerosas aplicaciones (Windows, Linux CLI, Android, MacOS, ...) que permite configurar sincronizar los archivos sin tener que hacer demasiadas configuraciones avanzadas de red. Es capaz de sincronizar archivos incluso entre dispositivos dentro de una NAT, a través de su sistema P2P.

Primero instalamos Syncthing en el ordenador (en mi caso, Arch Linux) y lo activamos


$ pacman -S syncthing
$ systemctl start syncthing@aarroyoc
$ systemctl enable syncthing@aarroyoc

Esto nos va a levantar Syncthing en nuestro sistema de forma permanente y la interfaz web de Syncthing, necesaria para configurarlo. Podemos acceder a ella desde nuestro navegador: http://127.0.0.1:8384.

Vemos tres secciones. En la primera tenemos un listado de carpetas que se van a sincronizar. Vamos a agregar una carpeta para las imágenes de la cámara de mi teléfono Android. La meteré dentro de Imágenes. Recordamos el ID que hemos metido.

En el apartado Avanzado podemos ajustar algunos detalles interesantes. Yo lo voy a dejar todo por defecto salvo el Tipo de Carpeta que en este dispositivo lo voy a poner a "Solo Recibir".

Vamos ahora al dispositivo Android, nos descargamos la aplicación de Syncthing disponible tanto en Google Play como en F-Droid. Veremos algo similar a esto:

En la app web, vamos al lado derecho "Este dispositivo" y hacemos click sobre el pequeño código para mostrar el código entero y un QR. Desde la app de Android lo escaneamos. En la app web se nos pedirá confirmación y ya tenemos enlazados los dispositivos.

En la app de Android creamos la carpeta seleccionando la ubicación de la carpeta de nuestra cámara. En tipo de carpeta dejamos "Solo enviar". Lo más importante es que el ID sea el mismo que el de la carpeta en nuestro otro dispositivo, en este caso puse android-camera

Una vez hecho esto deberemos aprobar el cambio en la app web. Una vez aprobado, ¡empezará el proceso de sincronización!

Ahora podríamos usar Syncthing para otras cosas pero como el objetivo era solo Google Photos. Ahí lo tenemos.

Ahora vamos a añadir un sistema de copia de seguridad en un disco duro externo.

Preparar el disco con Btrfs

Preparando el disco duro

El disco duro ya lo usaba para copias de seguridad, así que tenía datos. Usaba el sistema de archivos XFS. Para este nuevo uso, quería experimentar con Btrfs, así que tocaba formatear.

Lo primero, para saber si nuestro kernel Linux soporta Btrfs es ejecutar


$ cat /proc/filesystems

Y ver si aparece btrfs en el listado.

Lo segundo fue hacer una copia de los datos que ya tenía en el disco. Como sí que tenía espacio, hice una copia con rsync de los datos del HDD a otro disco.


$ rsync -ah --progress /run/media/aarroyoc/Almacen Almacen

Esto conserva todos los permisos y muestra una barra de progreso durante la copia

Ahora desmontamos el HDD. Vamos además a ejecutar fdisk para asegurarnos que el nombre del disco que vamos a formatear es el correcto. Un error aquí podría ser terrible.


$ umount /run/media/aarroyoc/Almacen
$ lsblk -p
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
/dev/sda      8:0    0 931,5G  0 disk 
├─/dev/sda1   8:1    0   260M  0 part /boot
├─/dev/sda2   8:2    0    16M  0 part 
├─/dev/sda3   8:3    0 139,7G  0 part 
├─/dev/sda4   8:4    0    25G  0 part 
├─/dev/sda5   8:5    0  1000M  0 part 
└─/dev/sda6   8:6    0 765,6G  0 part /
/dev/sdb      8:16   0   3,6T  0 disk 
└─/dev/sdb1   8:17   0   3,6T  0 part 
/dev/sr0     11:0    1  1024M  0 rom  
$ fdisk -l /dev/sdb
Disco /dev/sdb: 3,64 TiB, 4000752599040 bytes, 7813969920 sectores
Modelo de disco: Elements 25A1   
Unidades: sectores de 1 * 512 = 512 bytes
Tamaño de sector (lógico/físico): 512 bytes / 4096 bytes
Tamaño de E/S (mínimo/óptimo): 4096 bytes / 4096 bytes
Tipo de etiqueta de disco: gpt
Identificador del disco: 964D5ABA-35C4-495F-94EC-26699CB99391

Disposit.  Comienzo      Final   Sectores Tamaño Tipo
/dev/sdb1      2048 7813967871 7813965824   3,6T Sistema de ficheros de Linux

Finalmente ejecutamos el comando que creará la partición.


$ mkfs.btrfs -f -L Almacen /dev/sdb1

Por defecto en Btrfs va a usar un tamaño de nodo para metadatos de 16K. Valores pequeños aumentan la fragmentación del disco pero las operaciones son más baratas en uso de memoria. Valores altos, propiedades inversas. También vamos a tener Copy-On-Write y checksums. En principio, no lo vamos a desactivar.

Otra opción muy interesante de Btrfs es la compresión automática. Btrfs soporta varios algoritmos: LZO, ZLIB y ZSTD. En Btrfs esta compresión no aplica al disco entero, sino que se puede ir configurando a demanda. Para ello, cada vez que montemos un disco con mount, podemos especificar con que algoritmo de compresión queremos que trabajen las escrituras nuevas.


$ mount -o compress=zstd /dev/sdb1 /mnt/

Si ya teníamos datos, podemos aplicar una recompresión durante la desfragmentación.


$ btrfs filesystem defragment -r -v -czstd /mnt/

Sí, Btrs puede requerir desfragmentar, aunque existen opciones para que se vaya haciendo automáticamente. Si queremos forzarlo, sin tener en cuenta la compresión:


$ btrfs filesystem defragment -r /mnt/

Otras características de Btrfs son los sistemas para encontrar errores (scrub), el soporte RAID (en este caso no aplica) y los snapshots y subvolúmenes. Muy interesante y os recomiendo leer sobre ello.

Finalmente volvemos a copiar de vuelta los datos:


$ cd Almacen
$ rsync -ah --progress * /mnt/

Backups con Borg

Ahora vamos a hacer backups sobre nuestras fotos usando Borg. Los backups creados usando Borg están encriptados y son eficientes, ya que van comprimidos y se deduplicacan de forma inteligente. Voy a hacer un backup de toda la carpeta Imágenes, para hacer backup no solo de las imágenes del teléfono sino de otras.

Borg se encuentra en los repos de la mayoría de distros Linux. En Arch Linux es tan sencillo como:


$ pacman -S borg

Una vez lo tengamos instalado, lo primero será crear un repo de backups en el disco duro externo. Lo voy a ubicar en "/mnt/imagenes" y tendrá una contraseña.


$ borg init --encryption=repokey /mnt/imagenes

Ya podemos crear el backup de toda la carpeta Imágenes del sistema (que recordemos, incluye la carpeta Android Camera que tiene las que sacamos del teléfono)


$ borg create --stats --progress /mnt/imagenes::backup-2023-05-28 ~/Imágenes

El comando nos mostrará al acabar unas estadísticas del backup. La primera vez que hagamos un backup tardará bastante, las siguientes ocasiones, el sistema será inteligente y solo añadirá los cambios respecto a backups anteriores, siendo mucho más rápido.

En cualquier momento podemos listar los backups guardados con borg list.


$ borg list /mnt/imagenes

Podríamos usar extract para extraer el contenido de un backup entero, o de un fichero únicamente. Con delete podemos eliminar backups que ya no queramos. Y con compact podemos optimizar el espacio que usan los backups.

Conclusión

Con todo esto ya tenemos nuestro sustituto a Google Photos, controlado al 100% por nosotros, sin depender de Google. Espero que este post os haya servido de utilidad. ¡Hasta la próxima!

]]>
https://blog.adrianistan.eu/sustituyendo-google-photos-solucion-local Sun, 28 May 2023 11:53:53 +0000
Just, un sustituto de Make polivalente https://blog.adrianistan.eu/just-sustituto-make-polivalente https://blog.adrianistan.eu/just-sustituto-make-polivalente Make es una herramienta común en los entornos UNIX. Se trata de una herramienta originalmente diseñada como un sistema de construcción de programas, principalmente en C, aunque lo suficientemente flexible para adaptarse a cualquier otro entorno. Y ese es uno de sus problemas, aunque sea muy poderoso, está pensado principalmente para construir programas y existen multitud de variantes (no es lo mismo GNU Make que BSD Make). Muchas veces no queremos eso, si no un command runner, donde tenemos comandos con dependencias entre ellos. Aunque Make soporta esto mediante targets PHONY, Just está diseñado desde el principio para esto.

Justfile

Si en Make el punto de entrada es un Makefile, en Just es un Justfile. Cada recipe tiene un nombre y se compone de N comandos. Los comandos se imprimen por pantalla antes de ser ejecutados salvo que se inicien con @. También podemos poner @ delante del nombre del recipe para aplicar a todos los comandos. En sistemas UNIX los comandos son comandos de SH por defecto aunque es posible cambiar a otros lenguajes como Python y JavaScript. En Windows salvo que tengamos algún tipo de Bash instalado seguramente tengamos que recurrir a PowerShell.

Los recipes se pueden listar con just -l y ejecutar con just NOMBRE_TARGET. Si no especificamos ningún recipe se ejecutará el primero del fichero.


hello:
	echo "Hello"
bye:
	@echo "Bye"

Con ese fichero justfile, las siguientes interacciones tienen lugar


aarroyoc@adrianistan ~/d/b/just (master)> just -l
Available recipes:
    bye
    hello
aarroyoc@adrianistan ~/d/b/just (master)> just
echo "Hello"
Hello
aarroyoc@adrianistan ~/d/b/just (master)> just hello
echo "Hello"
Hello
aarroyoc@adrianistan ~/d/b/just (master)> just bye
Bye

Cada recipe puede tener dependencia en uno o más recipes.


hello: world
	echo "Hello"
bye:
	@echo "Bye"
world:
	echo "World"

---

aarroyoc@adrianistan ~/d/b/just (master)> just hello
echo "World"
World
echo "Hello"
Hello

Podemos crear alias dentro de un fichero justfile.


alias h := hello

hello: world
	echo "Hello"
bye:
	@echo "Bye"
@world:
	echo "World"

---

aarroyoc@adrianistan ~/d/b/just (master) [1]> just -l
Available recipes:
    bye
    hello
    h     # alias for `hello`
    world
aarroyoc@adrianistan ~/d/b/just (master)> just h
World
echo "Hello"
Hello

Los recipes pueden tener argumentos. Y podemos definir variables.


name := "Adrián"

@say word:
	echo {{ word }}
        echo {{ name }}
---
aarroyoc@adrianistan ~/d/b/just (master)> just say Hola
Hola
Adrián

Los comentarios (con #) encima de un recipe son comentarios de documentación que aparecen en just -l.


name := "Adrián"

# Write a text and add author
@say word:
	echo {{ word }}
	echo {{ name }}

---

aarroyoc@adrianistan ~/d/b/just (master)> just -l
Available recipes:
    say word # Write a text and add author

Just soporta cargar variables desde ficheros .env. Para ello hay que añadir la directiva: set dotenv-load. Por ejemplo:


DATABASE_PORT=1337

---

set dotenv-load
name := "Adrián"

# Write a text and add author
@say word:
	echo {{ word }}
	echo {{ name }}
	echo $DATABASE_PORT

---

aarroyoc@adrianistan ~/d/b/just (master)> just say Hola
Hola
Adrián
1337

Por defecto en Just un comando fallido hace fallar el recipe. Si queremos que no ocurra, podemos añadir un - delante del comando. Las variables en Just soportan el operador / para trabajar con paths.


file := "a" / "b"

read-file:
	-cat {{ file }}
	echo "Done!"

---

aarroyoc@adrianistan ~/d/b/just (master)> just
cat a/b
cat: a/b: No existe el fichero o el directorio
echo "Done!"
Done!

Just viene equipado con una serie de funciones predefinidas detalladas en su manual. Una de las más interesantes son env_var y env_var_or_default.


home := env_var("HOME")
timezone := env_var_or_default("TIMEZONE", "UTC")

foo:
	echo {{ home }}
	echo {{ timezone }}

---

aarroyoc@adrianistan ~/d/b/just (master)> just
echo /home/aarroyoc
/home/aarroyoc
echo UTC
UTC

Los recipes pueden tener atributos que modifican su funcionamiento. Existen [linux], [macos], [unix], [windows], ... para que esa recipe solo se ejecute en un determinado sistema operativo.

Podemos guardar el resultado de un comando en una variable.

code>
system := `uname -r`

[linux]
foo:
	echo "Linux "{{ system }}

---

aarroyoc@adrianistan ~/d/b/just (master)> just
echo "Linux "6.2.12-arch1-1
Linux 6.2.12-arch1-1

Just soporta condicionales de tipo if/else. Así mismo dentro de un if podemos comprobar mediante Regex. Podemos lanzar errores con la función error.


distro_id := `lsb_release -i`
package_manager := if distro_id =~ "Arch" { "pacman" } else { error("System not supported") }

[linux]
@foo:
	echo "Package manager: "{{ package_manager }}

---

aarroyoc@adrianistan ~/d/b/just (master)> just
Package manager: pacman

Las variables que creamos en Just se pueden sobreescribir desde la invocación externa.


year := "2023"
month := "1"

show:
	cal {{ month }} {{ year }}
---
aarroyoc@adrianistan ~/d/b/just (master)> just
cal 1 2023
     enero 2023     
lu ma mi ju vi sá do
                   1
 2  3  4  5  6  7  8
 9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31               
aarroyoc@adrianistan ~/d/b/just (master)> just year=1990
cal 1 1990
     enero 1990     
lu ma mi ju vi sá do
 1  2  3  4  5  6  7
 8  9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31   

Podemos exportar variables para los comandos que se van a ejecutar con Just con export (aplica a todo el fichero) o con $ (aplica solo a la recipe).


export ENVVAR := "hello"

default:
	echo $ENVVAR

override $ENVVAR="bye":
    echo $ENVVAR

---

aarroyoc@adrianistan ~/d/b/just (master)> just
echo $ENVVAR
hello
aarroyoc@adrianistan ~/d/b/just (master)> just override
echo $ENVVAR
bye

Finalmente acabamos con un ejemplo del manual donde podemos ver como ejecutar comandos en diferentes lenguajes.


polyglot: python js perl sh ruby nu

python:
  #!/usr/bin/env python3
  print('Hello from python!')

js:
  #!/usr/bin/env node
  console.log('Greetings from JavaScript!')

perl:
  #!/usr/bin/env perl
  print "Larry Wall says Hi!\n";

sh:
  #!/usr/bin/env sh
  hello='Yo'
  echo "$hello from a shell script!"

nu:
  #!/usr/bin/env nu
  let hello = 'Hola'
  echo $"($hello) from a nushell script!"

ruby:
  #!/usr/bin/env ruby
  puts "Hello from ruby!"

---

$ just polyglot
Hello from python!
Greetings from JavaScript!
Larry Wall says Hi!
Yo from a shell script!
Hola from a nushell script!
Hello from ruby!

¡Podemos incluso ejecutar Emacs Lisp!


emacs:
  #!/usr/bin/env -S emacs --script
  (princ "Hello World from Emacs Lisp\n")

---

aarroyoc@adrianistan ~/d/b/just (master)> just emacs
Hello World from Emacs Lisp

¿Y tú? ¿Qué opinas de Just? ¿Crees que es una herramienta interesante?

]]>
https://blog.adrianistan.eu/just-sustituto-make-polivalente Sun, 23 Apr 2023 10:37:00 +0000
"¿Las inteligencias artificiales tienen derechos?" transcripción de Ernesto Castro https://blog.adrianistan.eu/transcripcion-las-inteligencias-artificiales-tienen-derechos-ernesto-castro https://blog.adrianistan.eu/transcripcion-las-inteligencias-artificiales-tienen-derechos-ernesto-castro Hace poco vi esta interesante charla filosófica sobre la Inteligencia Artificial impartida por Ernesto Castro y compartida con un vídeo en YouTube. Me pareció interesante transcribir su contenido usando precisamente una IA. En este caso Whisper de OpenAI. También comparto el vídeo original. He hecho algunos retoques sobre todo para que sea más legible. Al final comparto el código Python que he usado.

¿Las inteligencias artificiales tienen derechos?

Pues muchas gracias Alberto por la amable presentación, muchas gracias a todos ustedes por estar aquí, es un placer estar aquí en la Universidad de Castilla y La Mancha dando esta conferencia que se titula Hacia una ética virtual, ¿Tienen derechos los algoritmos? Quisiera para empezar que se fijaran en este vídeo que les estoy aquí proyectando, esta es una instalación que se estrenó en la Bienal de Venecia en el año 2016 y consiste en un robot que intenta desesperadamente limpiar el suelo de la jaula en la que está encerrado, el título de la pieza es No puedo echarme una mano, I can't help myself, de Sun Yuan y Peng Yu, dos artistas chinos que querían plantear una reflexión sobre la inteligencia artificial, sobre la creación artificial y sobre la relación, en fin, entre la ética, la informática. Como ven se trata de, pues eso, literalmente lo que les he dicho, un robot que intenta de manera desesperada limpiar un suelo que está manchado de sangre y que inevitablemente se vuelve a manchar una y otra vez, en un contexto, como ven, donde hay personas que pasean, otros que hacen fotos, gente que está mirando en otras direcciones, mientras él poco a poco se va desesperando al ver que no puede terminar de limpiar ese suelo siempre ensuciado.

Me parece que es una buena ilustración, una buena imagen para empezar esta clase. Como ven, el robot no hace solamente movimientos funcionales, sino que, en fin, se recrea, a veces se felicita por la limpieza que acaba de hacer, en ocasiones se desespera, como hemos dicho, digamos que imitando la conducta también celebratoria o desesperada del ser humano. Me parece que es una buena ilustración, insisto, para empezar esta conferencia, que tiene como objetivo evaluar los límites de lo que se conoce como la ética algorítmica o la ética virtual, es decir, aquella ética que se preocupa por los dilemas morales surgidos en torno al desarrollo de la inteligencia artificial. Aquí tenemos una inteligencia artificial que desarrolla una tarea fútil, que, en fin, está condenada al fracaso, que es incluso como una figura casi mitológica, una especie de sísifo de silicio que una y otra vez limpia un suelo que se vuelve a manchar. Como ya digo, a mí me parece que es una ilustración muy interesante de la conferencia que vamos a plantear. Podríamos incluso plantear la pregunta inicial de si nosotros consideramos que este es un paciente moral o no, es decir, si este tipo de agente que, como ven, se felicita a sí mismo por haber limpiado determinada parcela de ese suelo es o no es un paciente moral, con independencia si lo consideramos un agente racional, con su sentido del deber y del derecho, desde luego tiene una serie de funciones integradas que él desempeña mejor o peor.

A mí, desde luego, me parece que es muy interesante, desde un punto de vista estético, el hecho de que el robot esté enjaulado y, por lo tanto, planteé la cuestión de la barrera o del límite de demarcación entre lo humano y lo artificial. Desde un punto de vista estético, el hecho de que lo que está limpiando constantemente del suelo sea sangre, pues también…, y esta imagen final, ¿no?, esta imagen final de las personas circulando en torno a él y alimentando, a su vez, otros algoritmos, como son los de las redes sociales, donde se postean decenas de fotos de este algoritmo, a su vez, sufriente, intentando limpiar el suelo, me parecía que le daba, ya digo, un carácter redondo al planteamiento del problema. A saber, ¿tienen derecho los algoritmos?

Esta puede parecer una pregunta un poco absurda y, de hecho, la mayor parte de las personas lo consideran así. Sin embargo, planteado en relación a este ejemplo concreto, puede enriquecerse un poco más. Desde un punto de vista estético, como digo, no es baladí que lo que esté limpiándose sea sangre tirada en el suelo. Se puede, incluso –ya digo–, hacer comparaciones con mitos como el de Sísifo, incluso plantear a qué se dedicarán exactamente los robots cuando hereden la tierra de los seres humanos. Quizás a intentar limpiar los crímenes que condujeron a su misma producción. El robot, además, tiene una suerte de concepto pulcro de limpieza que nos recuerda también a la neurosis de muchos filósofos morales que intentan arreglar el mundo, que intentan limpiar esa sangre que, inevitablemente, ya se ha derramado. El hecho –ya digo– de que esté encerrado y que, en última instancia, esté haciendo el trabajo que va a ser robotizado en los próximos años, los trabajos de así llamados de los cuidados, de la limpieza, pues también pone el problema en su justo foco.

No solamente el tema de los derechos de los algoritmos o de los robots, sino también los derechos de los seres humanos, que van a ser reemplazados por esos algoritmos y esos robots. La pregunta de si tienen los algoritmos derechos, pues seguramente se la planteó…, se la han planteado multitud de particulares en su casa, pero en el ámbito filosófico quien lo formuló, por primera vez, de una manera explícita fue Brian Tomásic, autor de un célebre blog utilitarista antiespecista titulado «Ensayos para reducir el sufrimiento» o «Sobre la reducción del sufrimiento». En ese blog y, posteriormente, en la página web para papers, sobre todo académicos, Archive.org, Brian Tomásic, en el 2014, publicó un artículo titulado «¿Importan moralmente los agentes artificiales de aprendizaje por refuerzo?». Con esta expresión tan larga, «agentes artificiales de aprendizaje por refuerzo», Brian Tomásic se refería básicamente a los algoritmos que, en buena medida, aprenden de su entorno por medio del ensayo del error, por medio del refuerzo condicionado a la manera del perro de Paulov o la paloma de Skinner. Esa filosofía y psicología conductista, que tan despreciada o tan vilipendiada está en el siglo XXI, no obstante, es la base de muchos de los condicionamientos adquiridos no solamente por las inteligencias artificiales, sino por las inteligencias naturales que, por ejemplo, operan en las redes sociales. Como es sabido, estas están construidas sobre modelos de condicionamiento de la conducta muy similares a los del perro de Paulov o la paloma de Skinner.

Los agentes artificiales de aprendizaje por refuerzo, los algoritmos, en última instancia, aprenden como aprendemos nosotros, por medio de la prueba y el error, por medio de acciones que son o bien recompensadas o bien castigadas. Una forma de recompensa y de castigo que no tiene una concreción fenomenológica como la del placer o del sufrimiento que experimentamos nosotros, pero que funcionalmente cumple el mismo papel. La inteligencia artificial, al igual que la inteligencia natural, huye del dolor y busca el placer. Lo que se plantea, habrá en Tomás y que, en el contexto de la filosofía antiespecista, es si debemos ampliar nuestro marco moral más allá de lo que ya está ampliado para estos autores. Les insisto que Brian Tomásic forma parte de una comunidad filosófica antiespecista que considera que los animales son pacientes morales, son sujetos de derechos o, por lo menos, de tratamiento moral, con independencia si creemos en los derechos naturales o no, porque sufren y tienen experiencias de placer o de dolor. Es precisamente esa nociocepción, esa capacidad de experimentar el placer y el sufrimiento lo que concedería, a juicio de muchos antiespecistas, derechos a los animales. Esta es una posición que en filosofía moral se califica habitualmente como sensocentrista, a saber, allí donde hay sensibilidad hay derechos morales o, por lo menos, trato moral. Frente a esa posición sensocentrista, por supuesto, hay otras alternativas. La más conocida en la tradición filosófica podríamos llamarlo el raciocentrismo, es decir, la idea de que no hay derechos ni trato moral más allá de la racionalidad específicamente humana.

Contra esa teoría, los antiespecistas tradicionalmente han expuesto el llamado argumento de los casos límite, es decir, dentro de nuestra comunidad moral realmente existente en el presente hay muchos pacientes morales que no son agentes racionales, pues, empezando por los mismos niños, que tienen la misma o menor capacidad racional que determinados simios o determinados mamíferos de altas capacidades cognitivas. Si la cuestión es planteada por los antiespecistas, es muy sencilla, no existe ningún atributo definitorio al ser humano que no esté en otros seres vivos, animales, en menor o mayor grado. El problema que plantea la inteligencia artificial es si hemos de ampliar nuestra comunidad moral no solamente hacia los agentes racionales o sensibles del ámbito natural, sino también hacia los que están por venir o los que ya están presentes de la comunidad artificial. Pues bien, el argumento de Brian Tomasi, que es que el antiespecismo debería ir más allá del sensocentrismo, no debería tan solo reclamar derechos o trato moral para los agentes y seres sensibles, sino también para los algoritmos. Pues los algoritmos están condicionados y se relacionan con su entorno con estímulos de recompensa y castigo similares a los de la, por ejemplo, dopamina en las redes sociales que experimentamos todos nosotros.

Esto ha sido luego posteriormente expandido, se ha montado todo un movimiento, un movimiento que pueden ustedes consultar en internet en la página web petrol.org, podemos incluso ir ahora mismo a ella, los Aprendedores por Refuerzo, que es la página web principal donde están todas las informaciones, preguntas habituales respondidas sobre este movimiento, que ya tiene seis o siete años de edad y que tiene toda una serie de bibliografías al respecto. Entre ellas, la que más le recomendaría sería este artículo que tienen aquí, «Una defensa de los derechos de las inteligencias artificiales», escrito por Erik Svitsgebel y Mara Garza. Y el argumento es este que les he expuesto, básicamente. «Nosotros no somos otra cosa –dicen estos autores– que algoritmos implantados o implementados en hardware biológico. Es una forma de especismo o de racismo virtual o digital el considerar que ciertos algoritmos implantados en un hardware biológico tienen más derecho o más peso moral que algoritmos implementados en silicio». No podemos presenciar el sufrimiento de los algoritmos, pero este existe y puede que no sea relevante o un problema, desde luego, político de primer orden, pero es algo que los filósofos deberían plantearse. Frente a aquellos que dicen que esto son chorradas, pues la filosofía siempre ha ido por delante, por supuesto, de la agenda política.

Y mucho antes de que cualquier político se planteara la posibilidad de legislar el uso de los animales, por ejemplo, dentro de las farmacéuticas o de toda la industria que requiere de la experimentación con animales, pues ya había filósofos que se preguntaban sobre el trato moral o no de estos agentes o sujetos. ¿Cuál es mi posición un poco acerca de esto? Y perdón por darles ya la respuesta, pero vamos a ir directamente a ello, porque no tiene más tutías. El argumento es muy sencillo. El movimiento antiespecista debería ampliar su comunidad moral para incluir a los algoritmos que, aunque no experimenten sufrimiento en los mismos términos fenomenológicos que nosotros, pues a través de los mecanismos como los de la dopamina, etcétera, se relacionan con su entorno por medio de estímulos y refuerzos muy parecidos a los castigos y recompensas psicológicos que nosotros obtenemos cuando conseguimos o fracasamos en nuestros proyectos. Este es, ya digo, el argumento y la propuesta.

Mi posición es bastante negativa o, digamos, crítica con esta teoría. Me parece que es fundamental en el ámbito ético. O sea, yo soy sesocentrista, creo que la experiencia fenomenológica del placer y del displacer es fundamental, incluso hablaría en términos escolásticos diciendo que el placer y el displacer es un atributo trascendental en la ética, es decir, que allí donde no hay placer o displacer no podemos hablar de trato ético, puede haber un trato ecológico, por ejemplo, pues evidentemente los árboles que están plantados detrás suya tienen una serie de intereses biológicos, tienen un conatus espinocista, un deseo de permanecer objetivamente en el ser y, por supuesto, que al talarlos o al cerrar sus ramas estoy de alguna forma interviniendo en sus intereses. Pero esto no es una cuestión ética, yo no tengo por qué tener un trato ético respecto de este tipo de entes, porque no hay una experiencia fenomenológica del placer y del displacer. Cuando yo le cierro una rama a un árbol, este ni sufre ni padece. Y lo mismo sucede con los algoritmos, los algoritmos pueden aprender de su entorno por medio del castigo, entre comillas, o de la recompensa, también entre comillas, pero ahí no hay ningún tipo de experiencia que pudiéramos asociar trascendentalmente con el campo de la ética. En segundo lugar, hay un argumento pragmático, también un argumento esencialista, a ver, si tratásemos éticamente a los algoritmos, estos dejarían de cumplir su función específica, que es, por medio de redes neuronales, aprender de su entorno y, en principio, hacernos la vida más eficiente, más funcional a nosotros. Y aquí…, esto es un tema que no solamente es un argumento pragmatista o utilitarista, sino que va al corazón de la teoría que está detrás de buena parte de este antiespecismo.

Quizás ustedes conozcan vagamente el debate entre consecuencialistas y la escuela de ontológica en el campo de la ética. Muy rápidamente expuesto, podríamos decir que hay dos grandes familias de filósofos morales, aquellos que consideran que las acciones se justifican en base a los principios –esa es la posición principalmente representada por Kant, quien sostiene en una famosa polémica con Benjamin Constant que uno debe decir la verdad, aunque ello conduzca a la muerte de inocentes–, frente a aquel que cree que los actos morales se justifican por los principios, el principio de «nunca mentirás» o «nunca robarás», hay otra serie de filósofos, que se llaman a sí mismos utilitaristas o consecuencialistas, que consideran que los actos morales se justifican por las consecuencias. Entonces, hay ciertas ocasiones en las que mentir no solamente está permitido, sino que es obligado moralmente, por ejemplo, cuando un asesino te pregunta dónde se esconde su víctima. Casi todo el movimiento antiespecista ha sostenido posiciones de carácter consecuencialista y más en concreto hedonista. Esta es una posición o ética que se hereda desde John Stuart Mill y Jeremy Bentham, una serie de filósofos utilitaristas de finales del siglo XVIII, quienes, considerando justamente que la moral se justifica por las consecuencias, se preguntaron cuáles son esas consecuencias. Es decir, a qué tipo de sujetos debería afectar un acto moral para que este sea contemplado como acto moral y no meramente económico o ecológico, etcétera. Y la conclusión a la que llegaron está en un texto célebre de Jeremy Bentham, que para muchos es el comienzo de la lucha filosófica real en favor de los derechos animales, es que ese sujeto de «derechos» –entre comillas, porque para Jeremy Bentham los derechos son estupideces sobre zancos–, ese sujeto de trato moral es aquel paciente que experimenta placer y displacer. Lo dice en un célebre texto respondiendo a las objeciones a la Revolución francesa. En el momento de la Revolución francesa, pues muchos conservadores decían cómo se va a acometer la insensatez de conceder derechos a la clase trabajadora, lo próximo será concederle derechos a las mujeres. Se consideraba una reducción al absurdo decir si concedes derechos a los trabajadores en algún momento se los vas a tener que conceder también a las mujeres. Jeremy Bentham fue más allá y dijo que habrá una época en la que lo importante no sea si uno es racional o irracional, hombre o mujer, rico o pobre, sino si puede padecer, si puede experimentar placer y displacer. Ese es un argumento que expone ahí, en ese famoso texto Jeremy Bentham, y que se hereda en toda la tradición antiespecista.

Esta tradición, que es una tradición hedonista, es una tradición utilitarista, que considera que el bien y el mal se reducen básicamente al placer y al displacer, es una tradición que tiene muchos problemas con la idea de disciplinar y de sacrificio, la idea de que uno prefiera puntualmente obtener, digamos, padecer, tener cierto displacer para obtener un objetivo a largo plazo. Como ya hemos dicho, si no fueran disciplinados por medio de recompensas y castigos, los algoritmos no cumplirían su función específica. Igual que si no hubiera experimentación con animales, por mínima y controlada moralmente que sea, no habría desarrollo tecnológico y científico. Es la pregunta que podemos plantearnos y es si el saber es un fin en sí mismo y cuántas víctimas puede aplastar a su paso. Es una pregunta que se plantea, por supuesto, en el Fausto, si el deseo de conocimiento y de dominio del mundo, digamos, que legitima este tipo de atropellos a pacientes que inevitablemente se consideran violados como víctimas. Entonces, esta es la principal enseñanza, la principal lección ética que yo os traigo de este debate, a saber que el hedonismo, aunque es una posición filosófica de partida bastante razonable, que considera, insisto, que allí donde no hay placer y displacer no hay ética, no obstante queda muy corta para comprender las razones éticas profundas que nos llevan en ocasiones a realizar ciertos sacrificios, es decir, a experimentar cierto displacer por mor de un fin superior. Reducir el sufrimiento no puede ser la única causa ética en el mundo, por mucho que Brian Tomásic lo cree así.

Es más, los seres humanos, podríamos decir, siguiendo a Mendevirán, creen su propia existencia y la existencia del mundo externo porque el mundo externo les presenta ciertas resistencias, y la experiencia de estar vivo y de ser un sujeto diferenciado del mundo consiste justamente en esforzarse y que el mundo se te resista a tus esfuerzos. Y esto es lo que tendría que decir, digamos, a modo de inicio, pero, bueno, como se me quedaba en muy poquita cosa y creo que la cuestión de la ética algorítmica puede dar más de sí, quería también decirles alguna serie de palabras más acerca, en sentido más amplio, de los debates que ha habido en inteligencia artificial desde un punto de vista moral.

Entonces, lo primero que quisiera decir es lanzar una crítica a la moralización de todos los problemas prácticos en el presente. Bueno, estamos ahora mismo en una lucha dentro de la comunidad filosófica a favor del mantenimiento de una asignatura de ética en la ESO. Como si ustedes me han escuchado hablar sobre esta de particular, sabrán que mi posición es la siguiente. No basta con defender la asignatura de ética en la ESO, sino que hay que ir a la ofensiva reclamando la necesidad de la filosofía en su integridad en la educación secundaria. Es decir, que la filosofía no puede quedar reducida a una alternativa a la religión que moraliza y les dice a los chavales de 12, 13 hasta 15 años qué es lo que deben hacer, cómo se deben comportar. La ética en buena medida ha sido utilizada dentro del entramado institucional y empresarial en el que nos encontramos como una fachada, como un pretexto más.

Igual que existe el pink washing, que es el uso de las causas feministas para darle un lavado de cara a proyectos empresariales que no tienen nada que ver con los derechos de las mujeres, e igual que existe el green washing, que es el lavado de cara a las empresas, pretendiendo ser más ecologistas de lo que son, también podríamos llamar que hay un ethic washing, un lavado ético de cara que hace que las empresas se presenten como muy responsables, muy respetables, porque cumplen una serie de funciones sociales, cuando en realidad lo interesante sería estudiar el comportamiento de dichas empresas y de dichas instituciones desde el punto de vista político y económico. Desde un punto de vista político, en los últimos documentos de la Unión Europea se ha abogado por lo que ellos llaman una inteligencia artificial legal, ética y técnicamente sólida, lo cual se describe en siete requisitos. Debe estar supervisada por los seres humanos, debe ser sólida técnicamente, debe garantizar la privacidad y la gobernanza de datos, debe ser transparente, debe ser equitativa, debe garantizar el bienestar y la responsabilidad.

Bien, de estos siete objetivos, supervisión humana, solidez técnica, privacidad, gobernanza de datos, transparencia, equidad, bienestar y responsabilidad, en realidad el único valor que ha sido contemplado como digno de ser respetado en los ordenamientos y en los códigos éticos acerca de los algoritmos en las redes sociales es el asunto de la privacidad, que a mí me parece un tema muy interesante de estudiar filosóficamente. ¿En qué consiste exactamente el valor de la privacidad?

Bueno, el valor de la privacidad es un valor evidentemente moderno, asociado al desarrollo del sujeto principalmente protestante. Es en la ética calvinista, donde se entiende que los individuos tienen una intimidad con Dios, que en su foro interno conectan directamente con el supremo y que no requieren de confesores, como en la vieja religión católica, sino que ellos mismos, a través de la sola escritura, pueden acceder a la inspiración divina. La privacidad es el derecho a ser dejado solo, el hecho de que uno tiene un espacio privado en el que los demás no penetran y no profundizan. Lo más interesante es que, al mismo tiempo que se aboga por la privacidad, son los mismos agentes, los mismos usuarios de las redes sociales los que, cuando se les deja la libertad de publicitar su privacidad, lo hacen sin ningún tipo de cuidado.

Más que hablar de la importancia de la intimidad, sería interesante estudiar lo que Lacan llamaba la extremidad, es decir, cómo la forma superficial que tenemos de conducirnos dice mucho más de nosotros que esa presunta intimidad que nos guardamos íntimamente. Es decir, que un individuo es mucho más real por su conducta y su comportamiento respecto a los demás que por lo que en su intimidad y en su foro interno cree ser. El asunto es que es evidente que el modelo social e institucional de las redes sociales es el modo de la sociedad de control y de vigilancia. Ya lo dijeron una serie de autores, entre ellos Michel Foucault y Gilles Deleuze, que se estaba produciendo en los años setenta, ochenta, una transición desde lo que ellos llamaban las sociedades disciplinarias a las sociedades de control.

Brevemente explicado, las sociedades disciplinarias son aquellas que pretenden internalizar en el sujeto, en la intimidad, en la privacidad del sujeto, una serie de disciplinas, una serie de conductas que en principio deberían conducirle al bienestar, a la felicidad, a la buena vida. El modelo de la sociedad disciplinaria son una serie de instituciones, como la cárcel, la escuela y la familia. Con la desintegración de estas instituciones no se produce una eliminación de los mecanismos de control. Al contrario, precisamente como el sujeto no está disciplinado internamente debe ser controlado externamente. Por decirlo con una célebre cita de Chesterton, cada cura que quitas vale por diez policías. Una sociedad que va perdiendo mecanismos de disciplina internos, como son la religión, va requiriendo cada vez más de sistemas de control policial o parapolicial. Y, en el fondo, todo el desarrollo de la cultura de la cancelación, los indignados, los ofendiditos, todo este asunto de, en fin, las polémicas, la polarización social e ideológica en las redes sociales, no sino otra forma de esta sociedad de control que para Michel Foucault tenía su máxima encarnación en la idea del panóptico. El panóptico es una idea que está expuesta justamente por el padre del utilitarismo, por Jeremy Bensham, quien pensó que la mejor cárcel era una cárcel en la que los propios encarcelados se vigilaran a sí mismos ante la expectativa de un gran hermano, de un gran ojo central que les estuviera mirando constantemente. No es el hecho de ser vigilado, sino la posibilidad, la potencialidad, la virtualidad de esa vigilancia lo que obliga a la autodisciplina. Es una autodisciplina que ya no está, insisto, internalizada por instituciones como la escuela o la familia, la iglesia o el ejército o la cárcel, sino que se difumina a través de mecanismos blandos de poder, como el de las redes sociales, donde se demuestra, en términos foucaultianos, que el poder no es una cuestión de relación jerárquica de dominados y dominantes, sino que es una cuestión mucho más rizomática, mucho más distribuida.

Entonces, lo que es evidente es que estamos en un entorno tecnoptimista, que considera que a los problemas económicos y sociales del presente se les va a dar una solución tecnológica, que basta con esperar a que los grandes gurús de Silicon Valley o de donde sea se saquen algo de la manga para hacer frente a las enfermedades mentales, el cambio climático e incluso la desigualdad de género. Es una ideología libertaria que piensa eso, justamente, que a mayor libertad de emprendizaje en internet, pues mayor bienestar general. Lo que hemos visto es que la mayor parte de las empresas, sobre todo las cinco grandes –Gafam, Google, Apple, Facebook, Amazon y Microsoft– han estado usando este libertarismo de internet para probar impunemente sus productos sobre los consumidores, haciendo sociología de las buenas. La verdadera sociología ya no se hace, por supuesto, en los departamentos de sociología, la hacen las redes sociales con sus grupos de control, diferenciando claramente a estos vamos a darles esto, a estos vamos a darles lo otro.

Lo interesante no es que se esté violando la privacidad, porque, como dijo Gustavo Bueno de una manera muy certera en una intervención que tuvo en la primera edición de Gran Hermano, la privacidad es un valor protestante –esto es importante señalarlo–, es un valor ficticio asociado a la idea de la comunicación interna con Dios. A Gafam no le interesa tu privacidad, le interesa la forma en la que públicamente te relacionas con otros y cómo esa extremidad expresa tus próximos patrones de conducta, no solamente los tuyos, sino los de personas exactamente como tú. No le interesa realmente a Facebook o a Twitter si te estás acostando con fulano o con mengana, le interesa ver cómo personas que se están acostando con fulanos y menganas beta-operatorios en otros sitios van a poder consumir tal o cual producto. El diseño de interfaces, como ustedes saben, lo que hace es primar la facilidad obligando, por lo tanto, a una cierta opacidad sobre los algoritmos que por debajo operan.

Quizás en este desarrollo de la industria y de la tecnología, de la manipulación del comportamiento en línea y fuera de línea, un año crucial a este respecto fue el año 2016, un año en el que hubo varios escándalos relacionados a las redes sociales, muy señaladamente la victoria de Donald Trump, busteada en las redes sociales por Cambridge Analytica, una empresa que se encargó básicamente de dirigir los votos de un sector de la población a los que le iban suministrando periódicamente informaciones farsas. Lo interesante fue cómo, a partir de las modificaciones del algoritmo, posteriores al escándalo de Cambridge Analytica, ha habido toda una transformación del ecosistema de los medios de comunicación en el presente que ha llevado, por ejemplo, a la desaparición del periodismo viral. Muchos de ustedes se preguntarán qué fue de BuzzFeed, a la manera casi de los poetas de Ubisoft. ¿Qué fue de BuzzFeed? ¿Qué fue de VICE? ¿Qué fue de Playground? ¿Qué fue de estos medios de comunicación que en época estaban copando las redes sociales? Cayeron con Cambridge Analytica. Después del cambio del algoritmo, simplemente desaparecieron. Entonces, este es el asunto principal, como digo.

Aunque se hable de que se va a promover el bienestar, la equidad, la presencia humana y la transparencia, etcétera, en el fondo todo lo que se califica como ética de datos está principalmente preocupada por si esos datos y esos algoritmos van a ser transparentes o no para ciertos expertos o ciertos agentes dentro de determinadas redes sociales. Podríamos hablar incluso de un exceso de datos. Esto se ha visto muy bien, por ejemplo, en el nuevo tipo de biografías que se están escribiendo acerca de aquellos autores que vivieron la época de Internet. Estoy pensando principalmente en las biografías que han salido recientemente acerca de Susan Sontag. Susan Sontag, como muchos sabrán, es una crítica cultural estadounidense, que murió de cáncer, si no estoy mal informado, y que vivió los primeros años del uso de Internet. Con lo cual, de ella, al igual que de muchos intelectuales que están desarrollando su vida en el siglo xxi, tenemos no poca información, sino demasiada información.

De Susan Sontag se mantiene y se hizo incluso una exposición al respecto. El historial de sus búsquedas en Internet. Sabemos exactamente... ¿qué páginas porno consultaba Susan Sontag?, ¿a qué horas respondía a los correos?, ¿qué tipo de productos compraba en el supermercado a distancia?, etcétera, etcétera, etcétera. Y el problema ya no es, insisto, que tengamos poca información acerca de los demás, sino que tenemos un exceso de información que da en muchas ocasiones correlaciones espurias. Si ustedes no conocen la página web, se la recomiendo. De hecho, vamos a ponerla ya directamente. Correlaciones espurias es una página web muy divertida que lo que hace es exponer una serie de correlaciones espurias producidas justamente por los metadatos, esta idea de que podremos finalmente prescindir de las teorías y de los mecanismos explicativos de carácter causal, porque bastará con subir todos los datos a algún tipo de algoritmo y él nos dará la respuesta de lo que debamos hacer en el futuro o de cuáles son las fuerzas causales que mueven el presente.

Pues bien, aquí ven, por ejemplo, que existe una correlación de 99 % entre el número de suicidios por ahorcamiento en Estados Unidos y el presupuesto invertido en ciencia. Claro, por supuesto, esto está creado ad hoc, pero fíjense aquí, el número de películas protagonizadas por Nicolas Cage tiene una correlación del 66 % con el número de personas que se han ahogado en piscinas. Y así, sucesivamente, el número de personas que han muerto envueltas en su cama, en sus bedsheets, en sus sábanas, tiene una correlación de 94 % con la cantidad de queso consumido por año en Estados Unidos. Y así, sucesivamente, el número de margarina consumida con la tasa de divorcios en Maine, etcétera, etcétera, etcétera. A lo que asistimos en el presente, una vez que muchos intelectuales han cejado de la gran teoría y se han entregado a las manos del big data, es a este tipo de correlaciones espurias, a un exceso de datos que conduce a esta algocracia donde los algoritmos parecen haber tomado el poder. Muchos pensarán «pero no todo está tan mal, siempre y cuando no lleguemos a la situación de minority report». Y es cierto que no hemos llegado a una situación donde haya, en fin, detenciones policiales en previsión de crímenes que todavía no se han cometido. O quizás estamos en una suerte de minority report distinto. Muchos de ustedes se acordarán de esa película apocalíptica, Cyberpunk hasta cierto punto, en la que se detenía a personas antes de que cometieran determinado crimen porque se pensaba que iban a cometerlo. Se predecía con una exactitud total que iban a cometer ese crimen. Pero en realidad estamos ante un minority report suave bajo la forma del levantamiento o el cuestionamiento de la presunción de inocencia en las redes sociales.

Todo el debate que ha habido en la última década acerca de la cultura de la cancelación, de la postcensura y de los pajilleros de la indignación no hace sino ponernos ante la cara que esta sociedad de control ya empieza a juzgar preventivamente a sus individuos. Hay muchos, en fin, muchos miedos a la sustitución de los seres humanos en ciertos campos. Sobre todo, hay un miedo a la deshumanización y a la robotización en el campo sexual y en el campo de los llamados cuidados. Un futuro distópico en el que una sociedad deshumanizada tan solo copularía con prostitutas robotizadas o en la que todos estaríamos envejecidos, completamente arrugados, siendo alimentados por robots a nuestro servicio. Este miedo a la sustitución de los seres humanos por los robots en el ámbito de los cuidados, tanto sexuales como médicos, podría llevarnos a plantear una versión diferente del famoso experimento de la habitación china.

Muchos de ustedes conocerán este famoso experimento planteado por John Sheldon, que para muchos es la refutación del programa de la inteligencia artificial, explicado sencillamente y para toda la familia. El proyecto fuerte de la inteligencia artificial es aquel proyecto que considera que la inteligencia humana puede ser replicada artificialmente por algoritmos, por robots, por máquinas, y que cualquier algoritmo, cualquier robot, cualquier máquina que se comporte exactamente como un ser humano, en términos conductistas, es idéntica a un ser humano. Si es indistinguible de un ser humano, entonces es humana. El experimento de la habitación china planteado por John Sheldon lo que viene a exponer es cómo incluso en un algoritmo capaz de replicar exactamente nuestra conducta faltaría el elemento de la conciencia, el elemento cualitativo del darse cuenta de las cosas que tan solo podemos tener los seres conscientes. Es un hard problem, un problema muy difícil, como lo llaman filosoficamente, cuál es la relación de supervivencia, de reducción, de emanación, de emergencia entre la conciencia y el cerebro. Imagínense, dice John Sheldon, una habitación china, una habitación donde hay un casillero de entrada y un casillero de salida. En su interior, hay un señor que no sabe nada de chino, pero dispone de un diccionario por medio del cual es capaz de, en fin, responder a cualquier tipo de mensaje que le aparezca en chino. Si tú le metes un mensaje en chino por una de las ranuras, él te va a responder con otro mensaje por el otro lado. Pues bien, así es como funcionan los algoritmos, tienen un input y tienen un output, y tienen entre medias una función que traduce determinados inputs en determinados outputs. A determinada información, determinada respuesta. Pero no hay ningún ejercicio de comprensión. La pregunta que se plantea es si este señor que no sabe nada de chino, que tan solo tiene un diccionario que le permite traducir determinados kanji o hanzu a determinados hanzu, entiende o no entiende chino. La respuesta intuitiva es que no entiende chino, simplemente para él es un cuento chino lo que está traduciendo de un lado para otro. Pues del mismo modo podríamos pensar…

Imaginemos un contexto, insisto, en el que nosotros ya no tenemos a nuestro alrededor seres humanos que nos cuiden, sino que somos pacientes médicos y sexuales de un conjunto de robots a nuestro servicio. ¿Esos robots o algoritmos pueden preocuparse de nosotros? ¿Pueden inquietarse? ¿Tienen la sorge de la cabalada Heidegger en ser y tiempo? La respuesta tradicional sería que no, que al igual que el hombre que está en la caja china o en la habitación china tiene la apariencia de preocupación, pero en realidad no se preocupan, tan solo son capaces de ocuparse de lo que está sucediendo en el presente.

Y vamos ya al asunto que quizás más les preocupa, que es la amenaza de la sustitución de los puestos de trabajo actuales por algoritmos y por, en fin, robots y máquinas de todo tipo. La teoría tradicional de carácter liberal es que, efectivamente, cuando se produce una ganancia de productividad por medio de la incorporación de bienes de capital, pues el número de trabajadores por planta se reduce. Ahora bien, si la economía general crece lo bastante como para que estas ganancias de productividad se traduzcan en incrementos de la demanda, entonces más individuos consumirán determinado producto y, por tanto, aunque por unidad producida haya menos trabajadores, en global la economía y el sector crecerán. Para producir un solo clavo ya no se necesitarán sesenta personas, sino mejor tan solo media persona, pero como se producirán muchos más clavos y se consumirán mucho más clavos, en el conjunto global habrá mucho más trabajadores dentro del sector. Esta es la teoría tradicional neoclásica que se suele exponer y se suele defender y que más o menos cubre, efectivamente, el desarrollo del capitalismo. Los incrementos de productividad vía incorporación maquinaria no conducen a un desempleo general, sino que van aumentando la riqueza de la economía, por lo tanto la demanda y, por lo tanto, la oferta también de ciertos bienes. Esto es lo que es válido para el ámbito de la automatización clásica, sustituir a un trabajador de la FIAT o de la FIAT por un robot que cumple su misma función.

Podríamos incluso decir que, gracias a la tecnificación y a la mecanización, desaparecen los trabajos más inhumanos o peor pagados y, por lo tanto, incluso se está haciendo un servicio humanitario al privarle a ciertas personas de ciertos trabajos, obligándoles a reorientarse hacia sectores donde se requiera más capital humano y donde, en principio, estarían salarios más elevados. Sin embargo, con la automatización digital nos encontramos con un escenario totalmente distinto, pues, a diferencia de lo que sucedía con las máquinas físicas, los autómatas digitales son muy fáciles de replicar y duplicar, no requieren de una inmersión en capital tan grande. Los efectos, por lo tanto, de la automatización y de la robotización generalizada en el ámbito ya no solamente del sector primario y secundario, sino también del terciario, del ámbito de los cuidados, en fin, de los servicios en sentido amplio, parece que conduce hacia un escenario mucho más ciberpunk y distópico, donde todos estaremos en paro tarde o temprano. Aparentemente, nos dirigimos hacia una sociedad en la que aproximadamente el 50 % de los trabajos actuales serán simplemente sustituidos por máquinas, y habrá, como existe en el día de hoy ya, muy poquitos trabajos técnicamente cualificados. y altamente pagados, y servicios poco cualificados, altamente demandados, pero con salarios literalmente de mierda. Mientras que la calificación intermedia, la posibilidad de trabajar en una fábrica o en una oficina con una cualificación intermedia y con un salario digno, poco a poco se va eliminando.

Nos encontramos, por lo tanto, ante dos alternativas o disyuntivas, por decirlo con la clásica disyunción marxista, socialismo o barbarie, o bien una era de ocio, como la que predecía Keynes para el año 2030, prediciendo una tasa de crecimiento del 1 %, Keynes sostenía que para el año 2030 habría un nivel tal de riqueza que los seres humanos no tendrían por qué seguir trabajando. Pues bien, ya en el presente hemos alcanzado la riqueza que Keynes predijo para el año 2030. Sin embargo, trabajamos, consumimos y estamos obligados a darle a la ruedecita de la máquina capitalista no menos, sino más que en el pasado. Sin embargo, hay todavía tecnotópicos, como Yuval Noah Harari, el autor de Deumodeus, que cree que estamos a las puertas prácticamente de la inmortalización de la especie humana. Después de, en fin, haber conquistado todo el globo, el capitalismo ya tiene como objetivo acabar con el hambre, con las enfermedades, con la guerra e incluso inmortalizarnos en una suerte de dicha eterna. Frente a esta visión tecnoptimista, evidentemente hay que señalar que la economía digital es una economía en la que el ganador se lo lleva todo, el que primero llega primero se queda con todo, donde los activos son intangibles y la economía está perfectamente financializada y puede haber ganancias no solamente sin crear empleo, sino destruyendo incluso empleo. Aquí la categoría que se vuelve más válida es la de Karl Marx cuando hablaba del ejército industrial de reserva. Ahora podríamos hablar incluso de un ejército algorítmico de reserva o un ejército virtual o digital de reserva, que sirve como un grupo, en fin, de consumidores de productos digitales, basura y también, por supuesto, de un público cautivo, listo para ser llamado a través de la propaganda en las redes sociales, que gracias a estar parado presiona la baja sobre los salarios realmente existentes. Lejos de ser un elemento inexplicable por la teoría marxista, el paro es un elemento estructural que el capitalismo produce como, insisto, un ejército industrial de reserva o algorítmico de reserva que presiona sobre los salarios a la baja.

Sin embargo, frente a las visiones –insisto– más optimistas de que estamos ante la era del ocio y que dentro de poco podremos dejar de trabajar, me parece a mí que aquí hay una cuestión ontológica que se relaciona un poco con lo dicho al comienzo de la exposición. El ser humano no puede nunca dejar de trabajar y aquí sería importante diferenciar entre empleo y trabajo. El empleo es lo que, por ejemplo, estoy haciendo yo ahora mismo, pero el trabajo es algo que hacemos constantemente, incluso en términos termodinámicos uno puede dejar de trabajar, uno puede dejar de gastar energía. Hay trabajo cuando estamos cuidando a nuestra familia, hay trabajo incluso cuando estamos durmiendo. Una cosa es el empleo como el trabajo remunerado y otra cosa es el trabajo a secas, que uno no puede dejar de realizar. El ser humano, al igual que el resto de seres que viven y se esfuerzan espinocistamente en sobrevivir y en mantenerse en el ser, no puede dejar de trabajar, no puede dejar de esforzarse y de intentar superar las resistencias que le presenta el mundo. Es inevitable el sufrimiento, el dolor, la disciplina, sin la cual –insisto– ni siquiera existiría el concepto del sujeto y de diferenciado de un mundo allá afuera. Y del mismo modo que el empleo no es igual al trabajo, la remuneración no es lo mismo que el salario. Muchas de las economías digitales actuales, de hecho, se sostienen sobre formas de remuneración no salarial, como es toda la remuneración por vía de visibilidad, prestigio, fama, etcétera.

Por último, quisiera exponerle una serie de cuestiones éticas para divertirles un poco, porque no quería dejar de hablar de Elon Musk y del proyecto, en fin, de eliminar la pobreza y, por supuesto, volver automáticos nuestros coches. Esto es algo que él lleva prometiendo como un profeta en el desierto varios años sin que termine de realizarse. La propuesta es una propuesta que todos ustedes conocen, ¿no? Pues los vehículos serán autónomos y ello, lejos de mejorar el tráfico, lo va a empeorar todavía más, porque la gente consumirá más este tipo de Teslas o de coches automáticos, obligando incluso a cavar túneles debajo de la tierra, muchos más de lo que existen en el presente. Lo más interesante, que yo no tenía ni idea de esto, de las propuestas de Elon Musk es que han replanteado en el ámbito del desarrollo industrial el viejo problema ético del tranvía.

El problema del tranvía es un problema clásico de la filosofía ética y que está engarzada, más o menos, con las famosas tres leyes de la robótica formuladas por Isaac Asimov. Asimov, el célebre autor de ciencia ficción, tiene un texto en el que expone que todo desarrollo robótico racional y moralmente comprometido deberá seguir tres leyes. La primera es que un robot no puede dañar a un ser humano o no puede tampoco permitir que un ser humano sea dañado. La segunda ley es que un robot debe obedecer las órdenes que leen los seres humanos, salvo en el caso de que viole la primera ley. La primera ley es que nunca dañarás a un ser humano. Y la tercera ley es que un robot debe protegerse a sí mismo, siempre y cuando no incumpla la segunda ley, siempre obedecerás a un ser humano, y la primera, nunca dañarás a un ser humano. Lo que a continuación, después de exponer estas tres célebres leyes de la robótica, Isaac Asimov, en su cuento Yo, robot, expone precisamente las contradicciones que surgen, los conflictos que surgen inevitablemente entre estas tres leyes. Igual que hay tres campos de valoración moral, la ética, la moral y la política, que inevitablemente van a entrar en conflicto, un robot con tres leyes como son estas, aunque estén organizadas de manera vertical y de manera claramente jerárquica, inevitablemente va a enfrentarse a ciertos dilemas morales, como, por ejemplo, este dilema que tienen aquí en pantalla. Imaginemos que… Les he puesto la ilustración tradicional del tranvía, pero pueden ustedes imaginarse perfectamente un Tesla navegando por uno de los túneles que quiere cavar Elon Musk en Los Ángeles.

Bien, un Tesla desbocado está a punto de atropellar a cinco personas, ustedes se encuentran al lado de una palanca y pueden desviar el Tesla hacia otra vía, donde en vez de haber cinco personas tan solo hay una. Que levante la mano, ¿cuántos de ustedes accionarían la palanca? Es decir, ¿salvarían a cinco personas redirigiendo el tranvía o el Tesla hacia la vía en la que tan solo hay una persona? ¿Cuántas personas accionarían la palanca? Vale, la mayoría. El ejemplo sigue, el ejemplo del tranvía sigue. Imaginemos que, en vez de estar junto a una palanca, pues uno se encontrase en lo alto de un puente y delante suya hubiera un hombre muy obeso, tan obeso que detendría el tranvía desbocado, sin frenos, o el Tesla en el túnel de Los Ángeles. ¿Cuántos de ustedes empujaría al gordo sobre el puente para evitar que esas cinco personas fueran atropelladas? Aquí hay una persona que empujaría al gordo, se llama tal cual el ejemplo, el Fat Man, ¿no? Entonces, el hombre gordo. ¿La justificación de esta discrepancia acaso no es lo mismo lo que está pasando aquí que lo que está pasando aquí? ¿Acaso, en última instancia, el resultado es que muere una persona y se salvan cinco?

Sí, podrían decir ustedes, pero en el fondo, como son tomistas y son escolásticos, ustedes creen, aunque no lo sepan, en la doctrina de doble efecto. La doctrina de doble efecto es que un acto moral puede tener dos consecuencias o más distintas y que lo importante no son las consecuencias laterales de la acción, sino la consecuencia intencionada y causalmente producida. Hay una diferencia, dice santo Tomás, y dice con él buena parte de la tradición escolástica y buena parte también de la tradición analítica, hay una diferencia entre hacer y dejar hacer. No es lo mismo permitir que otra persona mate a un niño que pegarle tú directamente el tiro. No es lo mismo desviar un tren y permitir oblicuamente, como un accidente, como algo no intencionado ni buscado, que muera una persona en otro carril, que tú mismo ser el que empujas a la persona en el carril. Las personas que no han levantado la mano en ninguno de los ejemplos, pues probablemente tengan una visión más, qué sé yo, determinista de la vida, estos cinco tipos están condenados a morir, yo me voy a lavar las manos, no voy ni a accionar la palanca ni a empujar al hombre obeso sobre las vías. Como digo, el argumento es un argumento que no es consecuencialista, porque la consecuencia de la acción es la misma en ambos casos, sino que se sostiene en un principio más elemental. Es decir, cuando uno acciona una palanca, la intención primera, el efecto deseado y buscado es salvar a cinco personas, mientras que la muerte de esta persona que está en la otra vía no es un mero instrumento, tú no estás privándole de su dignidad, sino que simplemente es una consecuencia lateral indeseada, igual que es una consecuencia lateral indeseada que, para terminar con la Segunda Guerra Mundial, pues hubiera que bombardear a Japón con las bombas atómicas produciendo todos los muertos. Los muertos no eran ni un instrumento ni tampoco el objetivo buscado por los militares estadounidenses. el objetivo era acabar con la Segunda Guerra Mundial. Mientras que en el segundo caso ya no puede uno decir que simplemente la intención primera era salvar a las personas. Pues como medio necesario para llegar a ese fin está el empujar y privar dignidad a la persona. Aquí sí se está utilizando a una persona como instrumento para salvar a otras cinco, mientras que en el primer ejemplo, en el ejemplo puro del tranvía, es simplemente una consecuencia lateral indeseada, la diferencia entre un medio necesario y una consecuencia lateral indeseada.

Pero aquí ya llega el tercer caso. El tercer caso es el caso del loop. Esto está mal dibujado porque en realidad la persona que debería estar en la vía menos transitada o menos popular debería ser el hombre gordo. Entonces, esta es una combinación de los dos previos. Usted está al lado de una palanca que puede desviar un tranvía hacia una vía paralela que conecta en una forma de loop con la vía principal, no obstante la persona que está ahí atada es una persona obesa que detendrá el tranvía. ¿Cuántos de ustedes piensan que el que está en este lado de la vía es el hombre gordo, es decir, que no va a volver y a matar a los seis por completo? ¿Cuántos de ustedes, expuestos en esta situación, apoyaría la palanca, que levante la mano? Otra vez todos. Pero se fijan que aquí ya no uno puede agarrarse a la explicación tomista de los dos efectos o del uso no instrumental del efecto colateral indeseado, porque aquí, al desviar el tranvía, el individuo no está simplemente en una vía colateral, sino que está siendo el medio necesario para la detención del tranvía o del Tesla desbocado en un túnel de Los Ángeles. Bueno, pues estos son los tipos… Aunque parezca que esto no tiene nada que ver con Elon Musk y Tesla, la cuestión del desarrollo moral de los coches automáticos tiene que ver con esto. Expuesto en la situación de tener que atropellar a cinco personas o a una, cómo implementar una serie, en fin, de criterios morales en la conducción automática de estos coches para no conducir a un atropello generalizado. Y ya con esto podemos ir cerrando, junto con la idea de una automación mecanizada o totalmente privada de responsabilidad humana.

También hay muchos debates acerca del armamento autónomo, que le quita responsabilidad a los ejércitos, que convierte la ejecución sumaria por medio de drones en algo prácticamente ludológico, relacionado con los videojuegos. Pero quizás lo más interesante sería ya acercarnos al final para hablar de la singularidad. La singularidad es un término que utilizan muchos tecnooptimistas para referirse a aquel momento en que la inteligencia artificial se desarrolla hasta el punto de que supere nuestras capacidades de producción de nuevas inteligencias. Si dentro del conjunto de actividades que hacemos inteligentemente también se encuentra la producción de inteligencias artificiales, se plantean muchos tecnooptimistas, habrá algún momento en que la inteligencia artificial sea lo bastante lista como para mejorarse a sí misma. A partir de ese momento, dicen muchos, el ser humano habría producido su último gran invento. Ya no sería necesario ningún tipo de inventiva humana, pues la propia inteligencia artificial se mejoraría a sí mismo exponencialmente cada vez más. Ese sería el verdadero momento en el que surgiría una superinteligencia que se apoderaría del mundo, una máquina ultrainteligente capaz de diseñarse a sí misma cada vez mejor. Aquí hay dos visiones, por supuesto. La visión optimista, que dice que esto nos va a dar, insisto, la era del ocio, de la vida buena, ya serán las máquinas las que se produzcan a sí mismas y las que se mejoren a sí mismas y se arreglan a sí mismas, y las visiones, por supuesto, ciberpunk o pesimistas, que consideran que esto va a ser la extinción de la especie humana.

Yo simplemente quisiera constatarles que todo esto es futurismo del malo, igual que el que existe en Regreso al futuro, del año 1985. Si recuerdan ustedes, en esa película se predice que para el año 2015 habrá coches voladores y patinetes que no son capaces de ir por encima del agua. En la revista del MIT, del Instituto de Tecnología de Massachusetts, como motivo del cumplimiento de esta predicción fallida en el año 2015, Bush Aldrin, uno de los astronautas que pisó la Luna, hizo unas declaraciones muy sonadas, cuyo titular fue el siguiente. «Me prometieron colonias en Marte y, a cambio, tengo Facebook». Esto es para que se fijen ustedes de la veraspersimilitud que se puede proyectar sobre estas predicciones a futuro. Les recuerdo que en los años ochenta se pensaba que ya en esta década habría coches voladores y patinetes y que habría colonias en Marte. Lejos de estar en la época de mayor desarrollo tecnológico, estamos en una época de rendimientos decrecientes. No hubo mayor transformación tecnológica que la que tuvo lugar entre los años 1880 y 1920. En esos cuarenta años una persona pasó de tener una esperanza de vida de cuarenta años a casi ochenta gracias a la inserción de la penicilina, del UVC, la aparición de tecnologías como el coche, el avión, la radio, el cine, etcétera. Sin embargo, ¿cuáles han sido las tecnologías introducidas desde los años ochenta hasta ahora, en este mismo periodo de cuarenta años? Yo tan solo sería capaz de decirles una, justamente Internet. Pero, claro, ante la expectativa de tener colonias en Marte, que lo que tengamos sea realmente Twitter y Facebook, pues pone todo este tipo de expectativas de superinteligencias y de singularidades a la altura del betún. Estamos hablando de que estos tipos hablan de descargar nuestras conciencias en Internet, mientras que la medida tecnológicamente más puntera para frenar una pandemia global ha sido decirle a la gente que se lave las manos y se ponga un trozo de papel delante de la boca.

Este es el contexto en el que nos encontramos, donde los gurús nos hablan de que vamos a ser iguales a los entes de silicio, mientras las medidas que se utilizan para solucionar los problemas del presente siguen siendo tan rudimentarias como en el siglo XVI o en el XVII. Entonces, esto es lo que yo terminaría diciéndoles. Que no se preocupen por esto, o sea, ya saben cómo es la filosofía, ¿no? Después de una hora charlando yo simplemente les diría «esto no vale nada». Entonces, sin más dilación, les doy la palabra. Muchas gracias.

Código


from pathlib import Path
import os

import openai
import ffmpeg
from yt_dlp import YoutubeDL

openai.api_key = "[OPENAI-API-KEY]"

def download_talk(url: str):
    opts = {
        "format": "m4a/bestaudio/best",
        "postprocessors": [{
            "key": "FFmpegExtractAudio",
            "preferredcodec": "m4a",
            }]
    }
    with YoutubeDL(opts) as ydl:
        ydl.download([url])

def segment_talk(filename: str):
    ffmpeg.input(filename).output("output_%03d.m4a", f="segment", segment_time="600").run()

def transcript_talk(glob: str):
    path = Path(".")
    for filename in sorted(path.glob(glob)):
        with open(filename, "rb") as f:
            print(filename)
            transcript = openai.Audio.transcribe("whisper-1", f, language="es")["text"]
        with open("transcript.txt", "a") as f:
            f.write(transcript)
            f.write("\n")
    
def main():
    download_talk("https://www.youtube.com/watch?v=0_8hsdF_Vd8")
    segment_talk("Las inteligencias artificiales tienen derechos.m4a")
    transcript_talk("output_*.m4a")

if __name__ == "__main__":
    main()
]]>
https://blog.adrianistan.eu/transcripcion-las-inteligencias-artificiales-tienen-derechos-ernesto-castro Sat, 22 Apr 2023 18:21:04 +0000
BQN: programación basada en arrays https://blog.adrianistan.eu/bqn-programacion-basada-arrays https://blog.adrianistan.eu/bqn-programacion-basada-arrays Hoy vamos a ver un lenguaje de programación reciente pero con raíces en los años 60. Si has leído el título, se trata de BQN. Un lenguaje orientado a arrays. Dirás: "pero Adrián, prácticamente todos los lenguajes tienen arrays" y es cierto. Pero este lenguaje lo lleva a otro nivel. Además el lenguaje destaca por usar multitud de símbolos fuera del estándar ASCII, lo que dificulta un poco el aprendizaje. A mí me gusta ver BQN como una calculadora hipervitaminada.

Historia

BQN es un lenguaje que desciende de APL. APL (A Programming Language) fue un lenguaje diseñado por Kenneth Iverson en 1962 para IBM. La idea era basarse en la notación matemática. De esta forma APL tiene una sintaxis especialmente breve y tersa y tiene muchas facilidades para trabajar con matrices.

APL también usaba símbolos fuera de ASCII (hay que tener en cuenta que la primera versión de ASCII es ya en 1963, un año más tarde) y era necesario usar teclados especiales. Con el tiempo surgió una variante llamada J que, entre otras cosas, no usa símbolos fuera de ASCII. Otro descendiente notable de APL es K. K es un lenguaje de código cerrado usado con cierto éxito en distintos campos como las finanzas o la Fórmula 1 (de hecho la empresa de K patrocina al equipo Alpine). K se suele usar de forma conjunta a la base de datos kdb.

Por último, BQN es un intento de revitalizar APL, mejorando los aspectos más "anticuados" pero manteniendo la esencia. Además la principal implementación es open source y con un rendimiento prometedor

Primeros pasos

A diferencia de otros lenguajes, aquí vamos a tener que empezar por como escribir los caracteres. Esta tabla te servirá de referencia

Si instalas CBQN al ejecutar el comando bqn tendrás acceso a un REPL donde podrás escribir los caracteres especiales introduciendo la barra invertida seguida de el caracter a la izquierda de la siguiente tabla. Es decir, si quiero introducir el círculo pequeño debería escribir \ y después la letra j minúscula.

Existen plugins para diferentes editores (yo uso el de Emacs) que permiten escribir de la misma forma los programas más largos.

En la tabla se ve que no todos los caracteres tienen los mismos colores, esto es porque son cosas diferentes en el lenguaje. Los verdes son funciones, los amarillos son 2-modificadores, los morados son 1-modificadores y lo negro son sujetos. Lo veremos a su debido tiempo.

Vamos ahora a escribir el código más simple que podemos hacer. Usar la función × para multiplicar números.


    3 × 4
12

Bastante simple. En verdad, la función × soporta un uso más simple, con un solo número.


   × ¯1
¯1
   × 3
1

En este caso en vez de hacer la multiplicación, ejecuta la función signo. Esto ocurre porque en BQN las funciones pueden ser monádicas o diádicas. Es decir, con un argumento o con dos. Solo existen esas dos opciones. Y muchos símbolos ejecutan cosas bastantes diferentes dependiendo de si son llamadas de forma monádica o diádica.

Otro ejemplo, √ de forma monádica será la raíz cuadrada de un número. De forma diádica será la raíz N de un número.


   √25 
5
   3√25 
2.924017738212866

Como veis, tampoco es necesario dejar un espacio entre función y argumentos.

Orden de evaluación

El orden de evaluación de las funciones es algo que normalmente se da por sentado en la mayoría de lenguajes. Sin embargo en BQN merece la pena detenerse puesto que las funciones aritméticas no cumplen las reglas matemáticas tradicionales. En BQN todas las funciones se tratan de la misma forma y se evalúan de derecha a izquierda. Podemos introducir paréntesis para cambiar el orden de evaluación.


   2×2+2
8
   (2×2)+2
6

Modificadores

Los modificadores afectan al comportamiento de las funciones. Por ejemplo, existe un 1-modificador, llamado ˜ que tiene dos funciones. Aplicada a una función monádica, repite el argumento de la parte derecha en la parte izquierda y llama a la función diádica correspondiente. Aplicada a una función diádica, cambia el orden de los argumentos. Veamos algunos ejemplos:


   ט5
25
   0 - 40
¯40
   0-˜40
40

En el primer caso el modificador repite el número 5 al lado izquierdo de modo que se hace la operación 5x5. En el segundo y tercer caso vemos el caso de los argumentos intercambiados.

Veamos un ejemplo de 2-modificador. Podemos usar el círculo pequeño para aplicar composición de funciones. Es decir, se va a ejecutar primero la función diádica de la derecha, con dos argumentos y posteriormente el resultado se pasa a una función monádica de la izquierda.


   2 -∘× 5
¯10

En este caso primero se realiza 5*2 y luego se convierte en negativo el resultado.

Arrays

Hemos dicho que BQN era un lenguaje orientado a arrays pero todavía no los hemos presentado. Vamos a empezar con arrays unidimensionales, llamados listas.

Existen dos sintaxis para listas:


   ⟨1, 2, 3⟩
⟨ 1 2 3 ⟩
   1‿2‿3
⟨ 1 2 3 ⟩

Las listas funcionan como los números para la mayoría de funciones.


   ⟨1, 2, 3⟩ × 2
⟨ 2 4 6 ⟩
   ⟨1,2,3⟩ × ⟨1,2,3⟩
⟨ 1 4 9 ⟩

Vamos a introducir algunas funciones específicas para trabajar con listas: ⋈ ∾ ⌽

⋈ de forma monádica crea una lista con un elemento, con dos crea una lista de elementos.

∾ une dos listas

⌽ con un argumento invierte la lista y con dos argumentos rota la lista N posiciones


   ⋈ 1
⟨ 1 ⟩
   ⋈ 42
⟨ 42 ⟩
   12 ⋈ 42
⟨ 12 42 ⟩
   1‿2 ∾ 3‿4
⟨ 1 2 3 4 ⟩
   ⌽ 1‿2‿3‿4
⟨ 4 3 2 1 ⟩
   2 ⌽ 1‿2‿3‿4
⟨ 3 4 1 2 ⟩

Existen modificadores específicos para listas: ¨ aplica la función a cada elemento. Similar a un map de programación funcional. ´ aplica la función de forma iterativa para obtener un único valor. En ese sentido es similar a un foldl o reduce de programación funcional.


   ⌽ ¨ "abcd"‿"ABCDEF"‿"01"
⟨ "dcba" "FEDCBA" "10" ⟩
   +´ ⟨50, 60, 70⟩
180
   ⋈¨ 1‿2‿3
⟨ ⟨ 1 ⟩ ⟨ 2 ⟩ ⟨ 3 ⟩ ⟩
   "ABC" ⋈¨ "abc"
⟨ "Aa" "Bb" "Cc" ⟩

Programación tácita

Un estilo de programación que BQN promueve es la programación tácita. La idea es programar con el menor número posible de variables (que de hecho no hemos explicado). Para que esto pueda funcionar tenemos que añadir más modificadores que permitan expresar más patrones, los combinadores.

El primer combinador ya lo hemos usado, el círculo pequeño. Ahora veremos el círculo grande, ○. La diferencia entre el círculo pequeño y el grande es que el pequeño aplica la función diádica con dos argumentos y el resultado se pasa a la función monádica mientras que el grande aplica una función monádica a los argumentos y luego aplica una función diádica al resultado.

Por ejemplo, ≠ como función monádica devuelve la longitud de la lista y = como función monádica si son iguales. Para comparar si dos listas tienen la misma longitud:


   1‿2‿3 =○≠ 1‿2‿3
1
   1‿2‿3 =○≠ 1‿2‿3‿4
0

Además tenemos los combinadores Before y After. Estos aplican una función de preprocesado hacia tanto al argumento izquierdo y al argumento derecho.

En esta imagen vemos los combinadores más importantes y como funcionan.

Variables

Las variables se declaran con el operador ←. Las variables según la nomenclatura con la que se llama pueden actuar como sujetos, funciones, 1-modificares y 2-modificadores.


   msg ← "Hola"
"Hola"
   msg ∾ " Mundo"
"Hola Mundo"
   Msg
"Hola"

Las variables se pueden modificar con ↩.


   msg ← "Hola"
"Hola"
   msg ∾⟜"Mundo"↩
"HolaMundo"

Ejemplo real

Vamos a ver como resolver el ejercicio del día 1 del Advent of Code de 2022. Lo primero que se nos pide es entre cada bloque de número, hacer las sumas de los bloques y obtener el máximo. La segunda parte es coger los 3 máximos y sumarlo. Primero veamos el código que he programado:


data ← •Import "sample.bqn"
solution1 ← ⊑∨+´¨ data
solution2 ← +´3⊸↑∨+´¨ data
•Show solution1 ⋈ solution2

Me he tomado la licencia de adaptar el fichero original para que ya esté directamente con listas sin tener que procesar el texto en BQN.


⟨
⟨
1000,
2000,
3000,
⟩,⟨
4000,
⟩,⟨
5000,
6000,
⟩,⟨
7000,
8000,
9000,
⟩,⟨
10000,
⟩
⟩

Hay algunas funciones extra que no he comentado. ⊑ en modo monádico coge el primer elemento de la lista. ∨ ordena la lista. ↑ coge los N primeros elementos de una lista. De esta forma, de forma super breve, hemos resuelto el problema.

Con esto concluimos esta introducción a un lenguaje completamente diferente, BQN. Nos hemos dejado muchas cosas en el tintero. Yo mismo me sigo considerando novato en este paradigma que espero que os haya despertado la curiosidad.

]]>
https://blog.adrianistan.eu/bqn-programacion-basada-arrays Sun, 16 Apr 2023 15:13:11 +0000
Usando NixOS como servidor en una Raspberry Pi https://blog.adrianistan.eu/usando-nixos-servidor-raspberry https://blog.adrianistan.eu/usando-nixos-servidor-raspberry El otro día me llegó una VisionFive 2, una de las placas RISC-V con mejor relación calidad/precio del mercado. Sin embargo, antes de entrar en ello. Voy a volver con la Raspbbery Pi 3B un momento y es que, el otro día se corrompió la tarjeta microSD. Es algo muy habitual en estos dispositivos y siempre tengo backups y tarjetas de repuesto. Pero en esta ocasión quería probar algo nuevo. En vez de usar Debian dije, ¿y si uso NixOS?

¿Qué es NixOS?

Hace ya 3 años le dediqué un artículo a Nix. En aquel artículo instalaba Nix en mi distro de aquella época, Debian, y mostraba partes del lenguaje Nix, de crear paquetes y usar entornos Nix. Sin embargo, esta experiencia de Nix, aunque útil para usar paquetes más modernos en distros más estables, no es la ideal. Donde Nix verdaderamente brilla es en NixOS. NixOS es una distribución Linux que usa para su configuración el lenguaje Nix. Prácticamente toda la configuración del sistema, incluídos los paquetes que se instalan claro, está escrito en Nix.

Todas las ventajas de Nix como rollbacks o la configuración declarativa disponibles a un sistema operativo entero. ¡Veamos como funciona!

Raspberry Pi 3B

La Raspberry Pi 3B tiene un procesador ARM de 64 bits (AArch64) y está soportada oficialmente por NixOS. Otros modelos de Raspberry Pi no tienen la misma suerte, recomiendo revisar la wiki de NixOS.

Lo primero que deberemos hacer es descargar una imagen desde Hydra de una compilación de NixOS para AArch64.

Una vez descargada, la descomprimimos y la grabamos en una tarjeta SD de por lo menos 16 GB.


(sudo) dd if=nixos-sd-image-22.11.2979.47c00341629-aarch64-linux.img of=/dev/sdb bs=4M status=progress

Revisar antes de ejecutar dd que efectivamente la tarjeta microSD está en /dev/sdb o si no reemplazar por la ruta que sea.

Con esto ya podremos encender nuestra Raspberry Pi. Primero arrancará U-Boot y después NixOS hará algunas configuraciones iniciales como expandir la partición para usar toda la tarjeta microSD. Esta imagen de NixOS ejecuta un servidor SSH por defecto y tiene dos usuarios creados: root y nixos, sin contraseña los dos. Lo ideal es ejecutar los primeros pasos con teclado. Lo primero será configurar las contraseñas de los usuarios root y nixos.

Para ello entramos como root y ejecutamos los siguientes comandos.


passwd
passwd nixos

Que empiece la magia Nix

Hasta ahora todo es muy parecido a otros sistemas operativos en ARM. A partir de ahora vamos a ver lo que hace NixOS diferente y especial.

Lo primer es generar la configuración inicial de NixOS. Para ello usaremos el siguiente comando:


(sudo) nixos-generate-config

Este comando nos generará varios ficheros en /etc/nixos. Esta es la carpeta más importante de NixOS ya que toda la config del sistema residirá ahí. El fichero principal, y que nos habrá creado el comando es /etc/nixos/configuration.nix.

En mi caso me generó este fichero:


# Edit this configuration file to define what should be installed on
# your system.  Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running ‘nixos-help’).

{ config, pkgs, ... }:

{
  imports =
    [ # Include the results of the hardware scan.
      ./hardware-configuration.nix
    ];

  # Use the extlinux boot loader. (NixOS wants to enable GRUB by default)
  boot.loader.grub.enable = false;
  # Enables the generation of /boot/extlinux/extlinux.conf
  boot.loader.generic-extlinux-compatible.enable = true;

  networking.hostName = "nixos"; # Define your hostname.
  # Pick only one of the below networking options.
  # networking.wireless.enable = true;  # Enables wireless support via wpa_supplicant.
  # networking.networkmanager.enable = true;  # Easiest to use and most distros use this by default.

  # Set your time zone.
  time.timeZone = "Europe/Amsterdam";

  # Configure network proxy if necessary
  # networking.proxy.default = "http://user:password@proxy:port/";
  # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";

  # Select internationalisation properties.
  # i18n.defaultLocale = "en_US.UTF-8";
  # console = {
  #   font = "Lat2-Terminus16";
  #   keyMap = "us";
  #   useXkbConfig = true; # use xkbOptions in tty.
  # };

  # Enable the X11 windowing system.
  # services.xserver.enable = true;


  

  # Configure keymap in X11
  # services.xserver.layout = "us";
  # services.xserver.xkbOptions = {
  #   "eurosign:e";
  #   "caps:escape" # map caps to escape.
  # };

  # Enable CUPS to print documents.
  # services.printing.enable = true;

  # Enable sound.
  # sound.enable = true;
  # hardware.pulseaudio.enable = true;

  # Enable touchpad support (enabled default in most desktopManager).
  # services.xserver.libinput.enable = true;

  # List packages installed in system profile. To search, run:
  # $ nix search wget
  environment.systemPackages = with pkgs; [
    vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
    wget
  ];

  # Some programs need SUID wrappers, can be configured further or are
  # started in user sessions.
  # programs.mtr.enable = true;
  # programs.gnupg.agent = {
  #   enable = true;
  #   enableSSHSupport = true;
  # };

  # List services that you want to enable:

  # Enable the OpenSSH daemon.
  services.openssh.enable = true;

  # Open ports in the firewall.
  # networking.firewall.allowedTCPPorts = [ ... ];
  # networking.firewall.allowedUDPPorts = [ ... ];
  # Or disable the firewall altogether.
  # networking.firewall.enable = false;

  # Copy the NixOS configuration file and link it from the resulting system
  # (/run/current-system/configuration.nix). This is useful in case you
  # accidentally delete configuration.nix.
  # system.copySystemConfiguration = true;

  # This value determines the NixOS release from which the default
  # settings for stateful data, like file locations and database versions
  # on your system were taken. It‘s perfectly fine and recommended to leave
  # this value at the release version of the first install of this system.
  # Before changing this value read the documentation for this option
  # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
  system.stateVersion = "22.11"; # Did you read the comment?

}

Ajustando la red

Lo primero que hice fue ajustar la red. En mi caso quería IPv4 por Ethernet con una IP estática (192.168.1.200). Además cambié la zona horaria a Madrid. Así que añadí/modifiqué estas líneas:


networking.hostName = "raspberry"; # Define your hostname.
  # Pick only one of the below networking options.
  # networking.wireless.enable = true;  # Enables wireless support via wpa_supplicant.
  networking.networkmanager.enable = true;  # Easiest to use and most distros use this by default.

  networking.interfaces.eth0.ipv4.addresses = [ {
    address = "192.168.1.200";
    prefixLength = 24;
  } ];
  networking.defaultGateway = "192.168.1.1";
  networking.nameservers = [ "192.168.1.1" ];

Por defecto hay un firewall configurado, le abrí los puertos 80, 443 y 22 (aunque el mero hecho de tener services.openssh.enable a true ya abre el 22


  # Enable the OpenSSH daemon.
  services.openssh.enable = true;

  # Open ports in the firewall.
  networking.firewall.allowedTCPPorts = [ 80 443 22 ];
  # networking.firewall.allowedUDPPorts = [ ... ];
  # Or disable the firewall altogether.
  # networking.firewall.enable = false;

Usuarios y paquetes

Luego quise crearme un usuario personal, llamado aarroyoc, que estuviese en los grupos "wheel" y "docker" y con algunos paquetes exclusivos para él como git y docker-compose.


users.users.aarroyoc = {
    isNormalUser = true;
    extraGroups = [ "wheel" "docker" ]; # Enable ‘sudo’ for the user.
    packages = with pkgs; [
      git
      docker-compose
    ];
  };

Si queremos añadir paquetes para todo el sistema, deberíamos añadirlos en la lista de la propiedad environment.systemPackages

.

Nginx, Docker y SSL

Aplicaciones como Nginx, Docker o Let's Encrypt tienen sus propios módulos de configuración en NixOS que nos hacen la vida más fácil. En ese mismo fichero podemos añadir las siguientes líneas para activar Docker, levantar Nginx de proxy con un VirtualHost y configurarlo con SSL mediante Let's Encrypt


  # Docker
  virtualisation.docker.enable = true;

  # ACME
  security.acme.acceptTerms = true;
  security.acme.defaults.email = "VALID_EMAIL";

  # Nginx
  services.nginx = {
    enable = true;
    recommendedProxySettings = true;
    recommendedTlsSettings = true;

    virtualHosts."stats.adrianistan.eu" = {
      enableACME = true;
      forceSSL = true;
      locations."/" = {
        proxyPass = "http://127.0.0.1:8945";
      };
    };
  };

Aplicando la configuración

Ahora es hora de aplicar la configuración. Nix tiene comprobaciones y en muchos casos nos avisará no solo de errores de sintaxis en el fichero de configuración sino de configuraciones incorrectas entre sí.

El comando mágico para aplicar la configuración es:


(sudo) nixos-rebuild switch

Este comando construye la configuración nueva de NixOS y cambia a ella, aunque puede no reiniciar todos los servicios de forma correcta. Existen otras opciones como nixos-rebuild boot para efectuar el cambio en el siguiente reinicio.

Por supuesto, al tener la configuración centralizada, podemos copiarla, guardárnosla como backup y recrear un sistema idéntico en otro ordenador. Si usamos módulos tal y como hace NixOS por defecto con la configuración específica del hardware de la placa, podemos tener configuraciones compartidas entre sistemas solo en los ficheros que nos interese y además quedará todo muy modular.

Por último, para conseguir actualizaciones podemos ejecutar el siguiente comando, que actualizará los paquetes del sistema (pero no los que los usuarios hayan creado en sus nix-env y nix-shell).


(sudo) nixos-rebuild switch --upgrade

Por lo demás, el resto de cosas que teníamos en Nix como los comandos nix-shell, nix-env y nix, ¡siguen funcionando!. NixOS ofrece muchísimas opciones de configuración que solo estoy descubriendo a partir de ahora.

Quizá en el futuro experimentaré con pasar los servicios de Docker y Docker Compose a soluciones 100% basadas en Nix.

]]>
https://blog.adrianistan.eu/usando-nixos-servidor-raspberry Mon, 6 Mar 2023 19:09:03 +0000
Strand, un lenguaje extremadamente paralelo https://blog.adrianistan.eu/strand-lenguaje-extremadamente-paralelo https://blog.adrianistan.eu/strand-lenguaje-extremadamente-paralelo Strand es un lenguaje de programación lógico diseñado en 1988 por Ian Foster y Stephen Taylor. Se trata de un lenguaje diseñado desde para ser extremadamente paralelo. El lenguaje no tuvo mucho éxito, su página de Wikipedia en inglés es enana y muy poca gente lo conoce. No obstante, los autores de Strand llegaron a publicar un libro, Strand: New concepts in Parallel Programming. Además, gracias al trabajo de Felix L. Winkelmann, existe una implementación open source de Strand. En este post veremos algunos conceptos fundamentales de Strand.

La sintaxis de Strand es similar a la de Prolog. De hecho, a primera vista podríamos confundir los lenguajes. Los números siguen reglas similares a Prolog, las variables empiezan por mayúscula y existen variables anónimas con _. En Strand no hay diferencia entre atom y string y las comillas simples y las dobles son equivalentes. Por último, las listas en Strand funcionan de forma similar a Prolog, no obstante las estructuras de Prolog aquí son tuplas, un conjunto de N términos de tamaño fijo entre llaves. No obstante, mediante azúcar sintáctico es posible obtener la representación que tienen las estructuras en Prolog.


house(77, bedrooms(3), Owner) % Equivale a
{house, 77, {beedrooms, 3}, Owner}

Los procesos

Hasta aquí todo es básicamente Prolog. El elemento diferencial de Strand son los procesos. La ejecución de un programa Strand consiste en la ejecución de N procesos. Los procesos pueden ejecutar tres tipos de acciones: terminar, cambiar estado y fork. Cuando un proceso acaba, esa parte de la computación finaliza. Cuando cambia de estado, el mismo proceso busca otra definición de proceso para continuar la computación. Cuando un proceso hace fork, se crea otro proceso que seguirá la computación en paralelo empezando con una definición de proceso inicial. Strand llama definición de proceso al conjunto de reglas con el mismo nombre y aridad (o sea, lo que es un predicado en Prolog).


main :-
    a,
    b.

a :- true.
b :- true.

Imaginemos el programa anterior. Empezamos a ejecutar el programa por con un proceso que ejecutará main. El proceso main hará dos cosas, primero hará un fork para crear un nuevo proceso y le pedirá ejecutar b. Después cambiará de estado él y ejecutará a. Es decir, a y b se ejecutarán de forma concurrente. En este caso a y b terminarán y acabarán y main acabará también.

Los procesos, como veremos, se pueden comunicar a través de las variables.

Las reglas, a priori pueden parecer similares a las de Prolog, pero hay que hacer un pequeño inciso. Strand no tiene backtracking. Pero conserva el no determinismo. ¿Cómo es esto posible? La filosofía de Prolog respecto al no determinismo podríamos resumirla en No sé que camino tomar mientras que en Strand es No me importa que camino tomar.

Ante una definición de proceso consistente en más de una regla, Strand cogerá cualquiera de ellas forma arbitraria. Pero como esta información puede ser insuficiente para asegurar que es un camino que nos interesa tomar, se añaden las guardas. Las guardas son comprobaciones extra que se comprueban antes de elegir de forma definitiva esa regla como la regla por la que a de seguir la computación. Las guardas se separan del cuerpo de la regla por el caracter | y en Strand las guardas solo pueden contener operadores predefinidos por el lenguaje. Mientras estemos en una guarda, todavía el camino a otras reglas dentro de la definición de proceso están abiertas. Una vez todos los chequeos pasan, la elección es definitiva. A esto se le llama commited choice.


max(X, Y, Z) :- X > Y | Z := X.
max(X, Y, Z) :- X =< Y | Z := Y.

Esta pequeña definición de proceso, max/3, nos permite calcular el máximo entre X e Y, dejando el resultado en Z. A diferencia de Prolog, en Strand al no haber backtracking, debemos usar guardas para saber que regla es la buena para poder continuar en ella la computación. Además, vemos un operador nuevo. Se trata de :=. En Strand esto se llama asignación. Y es que sí, Strand también elimina el concepto de unificación de Prolog. En su lugar tendremos matching y asignaciones. La asignación nos permite asignar un valor a una variable. En muchos casos es similar a la unificación pero es menos flexible. No obstante en la implementación de FLENG sí podremos usar la unificación, ya que no es estrictamente 100% Strand.

Veamos otro programa Strand.


-initialization(main).

max(X, Y, Z) :- X > Y | Z := X.
max(X, Y, Z) :- X < Y | Z := Y.

area(square, D, V) :- V is D * D.
area(circle, D, V) :- V is 3.1416 * D * D.
area(T, _, V) :- T =\= square, T =\= circle | V := 0.

largest(O1, D1, O2, D2, A) :-
    area(O1, D1, A1),
    area(O2, D2, A2),
    max(A1, A2, A).

main :-
    largest(square, 50, circle, 30, A),
    writeln(A).

Este programa devuelve el área mayor entre un cuadrado de lado 50 y un círculo de radio 30. Y lo hace de forma paralela. Lo primero que quiero destacar es el pequeño en max/3. Ahora si X == Y, podría cualquier regla indistintamente, no está definido (pero en este caso es válido, nos da igual). Por esto mismo, también en la tercera regla de area tenemos que confirmar que la forma no es la de cuadrado o círculo, ya que Strand podría elegir cualquiera esa regla antes. Vemos también el uso del operador is para la aritmética.

Este programa también es concurrente. El proceso que ejecuta largest cambia de estado para ejecutar la primera llamada a area y hace fork para ejecutar la segunda. Las áreas se ejecutan en paralelo. La llamada a max también es paralela, pero hay un problema, hace referencia a variables cuyo valor tiene que calcular area. Strand es inteligente y sabe que no puede continuar, así que max espera hasta que las variables necesarias para las evaluar las guardas tengan un valor, y así poder continuar. De este modo, hemos sincronizado el programa usando variables.

Productores y consumidores en Strand

Sabemos que una variable que sin valor y del cuál necesitamos valor hace esperar el proceso. La variable puede tomar valor en otro proceso mediante la asignación. Vamos a ver ahora algunos patrones que podemos usar en Strand aprovechando estas técnicas. La más básica de todas es tener productores y consumidores. Básicamente tendremos procesos que generarán valores, a su ritmo, y procesos que irán consumiendo esos valores.

La idea fundamental para implementar estos patrones es pensar en listas de variables, en vez de variables a secas.

Vamos a implementar un productor que genera 10 veces el número 5.


-initialization(main).

produce(N, Vals) :-
    N > 0 | Vals := [5|Vals1], N1 is N - 1, produce(N1, Vals1).
produce(0, Vals) :- Vals := [].

Hay dos reglas para produce. Una se dispara cuando N es 0 y simplemente asigna la lista de valores a la lista vacía, imposibilitando mandar más mensajes. Por otro lado, tenemos el caso de N > 0. Ahí, usamos la sintaxis de lista [Head|Tail] para asignar el primer valor de la lista Vals a 5 y obtener una variable con el resto de la lista. Esta variable, la pasaremos de forma recursiva a produce de nuevo. Al finalizar la ejecución del proceso, habrá mandado N mensajes, todos ellos con valor 5.


consumer(Vals, Sum) :-
    consumer_(Vals, 0, Sum).

consumer_([Val|Vals], Sum0, Sum) :-
    Sum1 is Sum0 + Val,
    consumer_(Vals, Sum1, Sum).
consumer_([], Sum0, Sum) :-
    Sum0 := Sum.

Por su lado el consumidor va a sumar los mensajes que le vayan llegando. En la cabeza de la regla vamos a hacer match del primer valor con el resto. Esto hará que Strand espere hasta que pueda garantizar si puede tomar la primera o la segunda regla. Haremos la suma y posteriormente volveremos a pasar la cola de la lista para volver a esperar que valor toma, si otra vez [Head|Tail] o si por el contrario, acabamos con la lista vacía y ya no hay más mensajes que procesar.

Por último, podemos juntar todo y ver como funciona correctamente.


main :-
    produce(10, Vals),
    consumer(Vals, Sum),
    fmt:format("Suma: ~q\n", [Sum]).

¿Y si en vez de enviar un número mandásemos una estructura para poder enviar mensajes de vuelta desde el consumidor? Pues no habría ningún problema, Strand nos permite hacer este tipo de patrones también. Veamos como podríamos enviar de vuelta la suma hasta el momento.


-initialization(main).

produce(N, Vals) :-
    N > 0 |
    Vals := [{5, CurrentSum}|Vals1],
    N1 is N - 1,
    fmt:format("Current Sum: ~d\n", [CurrentSum]),
    produce(N1, Vals1).
    
produce(0, Vals) :- Vals := [].

consumer(Vals, Sum) :-
    consumer_(Vals, 0, Sum).

consumer_([{Val, CurrentSum}|Vals], Sum0, Sum) :-
    Sum1 is Sum0 + Val,
    CurrentSum := Sum1,
    consumer_(Vals, Sum1, Sum).
consumer_([], Sum0, Sum) :-
    Sum0 := Sum.

main :-
    produce(10, Vals),
    consumer(Vals, Sum),
    fmt:format("Suma: ~q\n", [Sum]).

Como se puede ver el programa se ejecuta correctamente, ningún mensaje se pierde, aunque por pantalla aparecen desordenados. Es obvio ya que no hemos insertado ninguna sincronización.

Blackboard

Cuando hablamos de concurrencia en otros lenguajes una de las primeras cosas es como compartir estructuras de datos entre procesos. En Strand también es posible mediante el patrón Blackboard. La idea es que la estructura de datos pertenece en sí a un proceso, que va a ir procesando mensajes de diferentes usuarios que quieren leer o escribir en la estructura de datos. Veamos un ejemplo de un programa que tendrá varios escritores y varios lectores. Los escritores añadirán números y los lectores pueden leer la lista entera.


-initialization(main).

main :-
    writer(10, S1),
    writer(20, S2),
    reader(1, S3),
    reader(2, S4),
    merger([merge(S1), merge(S2), merge(S3), merge(S4)], M),
    manager(M).

writer(N, S) :-
    S := [write(N)|S1],
    writer(N, S1).

reader(N, S) :-
    S := [read(List)|S1],
    fmt:format("Reader: ~d\tList: ~q\n", [N, List]),
    reader(N, S1).

manager(M) :- manager(M, []).

manager([read(BB)|M], L) :- BB := L, manager(M, L).
manager([write(E)|M], L) :- manager(M, [E|L]).
manager([], _).

Este programa se ejecutaría indefinidamente y los dos escritores irían escribiendo mientras los dos lectores irían leyendo la lista e imprimiendo por pantalla. Todo de forma atómica ya que el proceso manager se encargará de ir procesando los mensajes de forma individual. Mención especial a merger/2, que usamos para combinar varios streams de variables en una sola.

Conclusión

Espero que esta breve e incompleta introducción a Strand os haya despertado la curiosidad de este lenguaje, bastante desconocido. Nos dejamos en el tintero muchas cosas, como más patrones o el uso de diferentes ordenadores para un mismo programa (Strand permitía hacer clústeres).

]]>
https://blog.adrianistan.eu/strand-lenguaje-extremadamente-paralelo Sun, 15 Jan 2023 17:33:06 +0000
El camino a Prolog: parser y juntarlo todo (parte III) https://blog.adrianistan.eu/camino-a-prolog-repl https://blog.adrianistan.eu/camino-a-prolog-repl Ya tenemos un sistema que es capaz de probar cosas mediante backtracking interactivo pero no podemos pasarle ficheros ni escribir queries de forma normal. Lo primero que tendremos que hacer será un parser de la sintaxis de Prolog a nuestras estructuras de datos y después lo juntaremos todo en un programa ejecutable. Veremos como resolver el problema de la cebra.

Parsear

Para parsear voy a usar la librería nom ya que es muy sencilla de usar. Consiste en ir tragándonos parte del input mientras devolvemos la parte de input que queda y lo que hemos parseado.

Lo primero que voy a añadir es como parsear un átomo. Hay Un átomo puede ser de tres tipos en nuestro sistema. Por un lado cualquier cosa que empiece por minúsculas. Por otro lado cualquier cosa entre comillas simples. Por otro lado, añadiremos [] como nil, un átomo que usaremos para las listas.


fn term_atom(input: &str) -> IResult<&str, Term> {
    alt((term_atom_default, term_atom_quoted, term_atom_nil))(input)
}

fn term_atom_default(input: &str) -> IResult<&str, Term> {
    let (input, first) = anychar(input)?;
    if !first.is_ascii_lowercase() {
	return Err(Err::Error(Error::new(input, ErrorKind::Char)));
    }
    let (input, atom) = alphanumeric0(input)?;

    Ok((input, Term::Atom(format!("{}{}", first, atom))))
}

fn term_atom_quoted(input: &str) -> IResult<&str, Term> {
    let (input, atom) = delimited(char('\''), is_not("'"), char('\''))(input)?;

    Ok((input, Term::Atom(atom.to_string())))
}

fn term_atom_nil(input: &str) -> IResult<&str, Term> {
    let (input, _) = tag("[]")(input)?;

    Ok((input, Term::Atom("[]".into())))
}

Las variables serán simplemente texto que empieza por mayúsculas


fn term_var(input: &str) -> IResult<&str, Term> {
    let (input, first) = anychar(input)?;
    if !first.is_ascii_uppercase() {
	return Err(Err::Error(Error::new(input, ErrorKind::Char)));
    }
    let (input, var) = alphanumeric0(input)?;

    Ok((input, Term::Var(format!("{}{}", first, var))))
}

Las estructuras vamos a soportar las normales (átomo (entrecomillado o no) + lista de argumentos)) y azucar sintáctico sobre listas que es habitual en Prolog.


fn term_str(input: &str) -> IResult<&str, Term> {
    alt((term_str_default, term_str_quoted, term_str_list, term_str_head_tail))(input)
}

fn term_str_default(input: &str) -> IResult<&str, Term> {
    let (input, first) = anychar(input)?;
    if !first.is_ascii_lowercase() {
	return Err(Err::Error(Error::new(input, ErrorKind::Char)));
    }
    let (input, atom) = alphanumeric0(input)?;
    
    let (input, _) = char('(')(input)?;

    let (input, args) = separated_list1(spaced_comma, alt((term_str, term_var, term_atom)))(input)?;
    
    let (input, _) = char(')')(input)?;

    Ok((input, Term::Str(format!("{}{}", first, atom), args)))
}

fn term_str_quoted(input: &str) -> IResult<&str, Term> {
    let (input, atom) = delimited(char('\''), is_not("'"), char('\''))(input)?;
    let (input, _) = char('(')(input)?;

    let (input, args) = separated_list1(spaced_comma, alt((term_str, term_var, term_atom)))(input)?;
    
    let (input, _) = char(')')(input)?;

    Ok((input, Term::Str(atom.to_string(), args)))
}

fn term_str_list(input: &str) -> IResult<&str, Term> {
    let (input, _) = char('[')(input)?;
    let (input, elements) = separated_list1(spaced_comma, alt((term_str, term_var, term_atom)))(input)?;
    let (input, _) = char(']')(input)?;

    let list = build_list(elements.into());

    Ok((input, list))
}

fn build_list(mut elements: VecDeque) -> Term {
    if let Some(element) = elements.pop_front() {
	Term::Str(".".into(), vec![element, build_list(elements)])
    } else {
	Term::Atom("[]".into())
    }
}

fn term_str_head_tail(input: &str) -> IResult<&str, Term> {
    let (input, _) = char('[')(input)?;
    let (input, head) = alt((term_str, term_var, term_atom))(input)?;
    let (input, _) = char('|')(input)?;
    let (input, tail) = alt((term_str, term_var, term_atom))(input)?;
    let (input, _) = char(']')(input)?;

    Ok((input, Term::Str(".".into(), vec![head, tail])))
}

Por último, un fichero estará compuesto de Clauses, ya sean facts (sin cuerpo) o rules (con cuerpo).


pub fn file(input: &str) -> IResult<&str, Vec> {
    let (input, clauses) = separated_list0(multispace1, clause)(input)?;

    Ok((input, clauses))
}

fn clause(input: &str) -> IResult<&str, Clause> {
    let (input, clause) = alt((clause_fact, clause_rule))(input)?;

    Ok((input, clause))
}

fn clause_fact(input: &str) -> IResult<&str, Clause> {
    let (input, head) = alt((term_str, term_atom))(input)?;
    let (input, _) = char('.')(input)?;

    Ok((input, Clause { head, body: vec![] }))
}

fn clause_rule(input: &str) -> IResult<&str, Clause> {
    let (input, head) = alt((term_str, term_atom))(input)?;
    let (input, _) = many1(char(' '))(input)?;
    let (input, _) = tag(":-")(input)?;
    let (input, _) = many1(char(' '))(input)?;
    let (input, body) = clause_body(input)?;

    Ok((input, Clause { head, body }))
}

pub fn clause_body(input: &str) -> IResult<&str, Vec> {
    let (input, goals) = separated_list1(spaced_comma, alt((term_str, term_atom)))(input)?;
    let (input, _) = char('.')(input)?;

    Ok((input, goals))
}

fn spaced_comma(input: &str) -> IResult<&str, ()> {
    let (input, _) = many0(char(' '))(input)?;
    let (input, _) = char(',')(input)?;
    let (input, _) = many0(char(' '))(input)?;

    Ok((input, ()))
}

Con eso tendríamos el parseado.

Juntándolo todo

Vamos a juntarlo todo para hacer un programa. Este programa lo que hará será leer de un archivo (o no) para rellenar la base de datos. Después iniciaría un REPL (Read-Eval-Print-Loop). En el REPL se lee de teclado la query (será un clause_body se nuestro parser), obtenemos las variables, añadimos el Term de backtracking interactivo al final y llamamos a prove_all.


fn main() {
    println!("Esgueva Prolog 0.1.0 - Adrián Arroyo Calle 2022");
    let args: Vec = env::args().collect();

    match args.len() {
	2 => {
	    if args[1] == "-h" {
		print_help();
	    } else {
		repl(file_to_database(&args[1]))
	    }
	}
	1 => repl(Database::new()),
	_ => print_help()
    }

}

fn print_help() {
    println!("Usage: esgueva [PROLOG FILE]\tStart Esgueva top-level optionally loading a file");
    println!("       esgueva -h\t\tShow help");
}

fn file_to_database(file: &str) -> Database {
    let mut db = Database::new();
    let contents = fs::read_to_string(file).expect("File must exist");

    if let Ok((_, clauses)) = parser::file(&contents) {
	for clause in clauses {
	    db.add_clause(clause);
	}
    } else {
	eprintln!("Error loading file: {}", file);
    }
    
    db
}

fn repl(database: Database) {
    loop {
	print!("?- ");
	io::stdout().flush().unwrap();
	let mut input = String::new();
	io::stdin().read_line(&mut input).unwrap();
	if let Ok((_, mut goals)) = parser::clause_body(&input) {
	    let vars_in_goals = prover::find_variables_in_goals(&goals);
	    goals.push(Term::Atom("__backtracking?".into()));
	    prover::prove_all(goals.into(), Some(HashMap::new()), &database, &vars_in_goals);
	    println!("false.");
	} else {
	    eprintln!("Can't parse query!");
	}
    }
}

Finalmente podemos ejecutarlo y ¡hacer pruebas!

El problema de la Cebra

El problema de la cebra vimos como resolverlo con clp(Z) en otro post. También podemos resolverlo en nuestro nuevo mini Prolog.

No voy a explicar el problema de nuevo. El programa que lo resuelve es este:


member(X, [X|Xs]).
member(X, [Y|Xs]) :- member(X, Xs).

nextto(X, Y, List) :- iright(X, Y, List).
nextto(X, Y, List) :- iright(Y, X, List).

iright(Left, Right, [Left|[Right|Xs]]).
iright(Left, Right, [X|Xs]) :- iright(Left, Right, Xs).

eq(X, X).


zebra(H, W, Z) :- eq(H, [house(norwegian, X1, X2, X3, X4), X5, house(X6, X7, X8, milk, X9), X10, X11]), member(house(englishman, A1, A2, A3, red), H), member(house(spaniard, dog, B1, B2, B3), H), member(house(C1, C2, C3, coffee, green), H), member(house(ukrainian, D1, D2, tea, D3), H), iright(house(E1, E2, E3, E4, ivory), house(F1, F2, F3, F4, green), H), member(house(G1, snails, winston, G2, G3), H), member(house(H1, H2, kools, H3, yellow), H), nextto(house(I1, I2, chesterfield, I3, I4), house(J1, fox, J2, J3, J4), H), nextto(house(K1, K2, kools, K3, K4), house(L1, horse, L2, L3, L4), H), member(house(M1, M2, luckystrike, orangejuice, M3), H), member(house(japanese, N1, parliaments, N2, N3), H), nextto(house(norwegian, O1, O2, O3, O4), house(P1, P2, P3, P4, blue), H), member(house(W, R1, R2, water, R3), H), member(house(Z, zebra, S1, S2, S3), H).

Genera la solución correcta

Con esto termino la serie. Alguno se preguntará si desde aquí se podría seguir construyendo un sistema Prolog más avanzado. La respuesta es que sí pero no es lo habitual. Llegado el momento se considera otra forma de implementar Prolog basado en nificación destructiva y en máquinas virtuales como WAM, ZIP o TOAM. En cualquier caso, el código de este sistema está disponible en GitHub: Esgueva Prolog

]]>
https://blog.adrianistan.eu/camino-a-prolog-repl Mon, 2 Jan 2023 14:45:51 +0000
El camino a Prolog: obtener soluciones y no determinismo (parte II) https://blog.adrianistan.eu/camino-a-prolog-backtracking https://blog.adrianistan.eu/camino-a-prolog-backtracking En el artículo anterior dejamos un algoritmo de unificación escrito en Rust. Ahora, y siguiendo también el libro de Peter Norvig, debemos implementar algo que pruebe las reglas que tenemos definidas para la query que insertemos.

Database

Lo primero que vamos a implementar es una base de datos donde almacenamos todos nuestros predicados. En esencia, la base de datos será un HashMap donde la clave es Predicate (nombre + aridad) y el contenido un listado de Clause. Cada Clause tiene que tener un Term como cabeza de la regla y opcionalmente un listado de Terms que serán la parte del cuerpo. Dentro de ese listado se tienen que cumplir todo, es decir, es un AND. Para implementar los OR, simplemente usaremos las N cláusulas.

Con este código podemos implementar una base de datos que permite añadir Clauses (y las almacena en el Predicate correcto), obtiene todas las Clauses de un Predicate y permite borrados.


use std::collections::HashMap;

use crate::term::Term;

#[derive(PartialEq, Debug)]
pub struct Clause {
    pub head: Term,
    pub body: Vec,
}

#[derive(PartialEq, Eq, Hash, Clone)]
pub struct Predicate {
    pub name: String,
    arity: usize,
}
impl Predicate {
    pub fn from_clause(clause: &Clause) -> Option<Predicate> {
	Self::from_term(&clause.head)
    }

    pub fn from_term(term: &Term) -> Option<Predicate> {
        match term.clone() {
	    Term::Str(f, args) => Some(Predicate {
		name: f.clone(),
		arity: args.len(),
	    }),
	    Term::Atom(f) => Some(Predicate {
		name: f.clone(),
		arity: 0,
	    }),
	    _ => None
	}
    }
}

pub struct Database {
    data: HashMap>
}

impl Database {
    pub fn new() -> Self {
	Database {
	    data: HashMap::new()
	}
    }

    pub fn add_clause(&mut self, clause: Clause) {
	if let Some(predicate_key) = Predicate::from_clause(&clause) {
            {

		self.data.entry(predicate_key.clone()).or_insert(Vec::new());
	    }
	    {
		let predicate = self.data.get_mut(&predicate_key).unwrap();
		predicate.push(clause);
	    }
	}
    }

    pub fn get_clauses(&self, predicate: &Predicate) -> Option<&Vec<Clause>> {
	self.data.get(predicate)
    }

    pub fn clear_all(&mut self) {
	self.data = HashMap::new();
    }

    pub fn clear_predicate(&mut self, predicate: &Predicate) {
	self.data.remove(predicate);
    }
}

Ejecutar código

Vamos a añadir una función llamada prove que servirá para probar un Term. ¿Cómo se prueba un Term? Se obtiene el predicado al que hace referencia y sobre las cláusulas que tiene el predicado, probamos si alguna de las cabezas de esas cláusulas unifica con el Term. Eso nos dará unos bindings, parte de la solución. Si la cláusula que ha unificado no tiene términos en el cuerpo, no hay que hacer nada más, esa sería una solución que prueba Term. Pero si sí lo tiene, llamaremos a prove_all para probar la conjunción de los términos del cuerpo.

En esta primera versión analizamos todas las posibles ramas, es decir, si hay varias cláusulas que unifican, estas generarán dos bindings diferentes y a su vez, prove_all puede devolver N bindings.


fn prove(goal: Term, bindings: Bindings, database: &Database) -> Option<Vec<Bindings>> {
    if let Some(predicate) = Predicate::from_term(&goal) {
	if let Some(clauses) = database.get_clauses(&predicate) {
	    let mut solutions = Vec::new();
	    for clause in clauses {
		let renamed_clause = rename_variables(&clause);
		let bindings = unify(goal.clone(), renamed_clause.head, bindings.clone(), false);
		if bindings.is_none() {
		    // do nothing
		} else if renamed_clause.body.len() == 0 {
		    solutions.push(bindings);
		} else {
		    if let Some(mut all_solutions) = prove_all(renamed_clause.body, vec![bindings], database) {
			solutions.append(&mut all_solutions);
		    }
		}
	    }
	    if solutions.len() == 0 {
		None
	    } else {
		Some(solutions)
	    }
	} else {
	    None
	}
    } else {
	None
    }
}

Un paso que no hemos mencionado, pero es importante, es el renombrado de variables. Y es que un término y otro pueden tener los mismos nombres de variables, pero en este paso, deberían ser consideradas variables diferentes. La función rename_variables nos permite generar una Clause nueva con nombres de variables aleatorios (en este caso mediante UUID, aunque lo ideal es un contador sin más).


fn rename_variables(clause: &Clause) -> Clause {
    let mut bindings = HashMap::new();
    Clause {
	head: rename_term(&clause.head, &mut bindings),
	body: clause.body.iter().map(|term| rename_term(term, &mut bindings)).collect(),
    }
}

fn rename_term(term: &Term, bindings: &mut HashMap<String, String>) -> Term {
    match term {
	Term::Atom(f) => Term::Atom(f.clone()),
	Term::Var(var) => {
	    if let Some(subst) = bindings.get(var) {
		Term::Var(subst.clone())
	    } else {
		let id = Uuid::now_v1(&[1, 2, 3, 4, 5, 6]).to_string();
		bindings.insert(var.clone(), id.clone());
		Term::Var(id)
	    }
	},
	Term::Str(f, args) => {
	    Term::Str(f.clone(), args.iter().map(|arg| rename_term(arg, bindings)).collect())
	}
    }
}

¿Y como será prove_all? Sencillamente vamos term por term, intentando probarlo con un listado de bindings que tenemos en ese momento, así hasta que ya no queden terms por probar.


fn prove_all(mut goals: Vec<Term>, bindings: Vec<Bindings>, database: &Database) -> Option<Vec<Bindings>> {
    if let Some(goal) = goals.pop() {
	let mut solutions = Vec::new();
	for binding in bindings {
	    if let Some(mut goal_solutions) = prove(goal.clone(), binding, database) {
		solutions.append(&mut goal_solutions);
	    }
	}
	if solutions.len() == 0 {
	    None
	} else {
	    prove_all(goals, solutions, database)
	}
    } else {
	Some(bindings)
    }
}

Con esto, dado una query (que será una conjunción de Term) llamaremos a prove_all y obtendremos todas las soluciones. No obstante no serán fáciles de leer ya que lo que vamos a devolver es un listado de sustituciones. Un sistema Prolog real, para facilitar la interpretación de las respuestas, da las soluciones en base al valor que deben adoptar las variables originales de la query, incluso resolviendo de forma recursiva las variables.

Así pues necesitamos dos funciones extra: find_variables_in_goals va a obtener un listado de las variables que aparecen en la query original y subst_bindings aplicará las sustituciones de forma recursiva.


fn subst_bindings(bindings: Bindings, term: Term) -> Term {
    let bindings = bindings.expect("Only can be called when bindings are OK");
    match term {
	Term::Atom(x) => Term::Atom(x.clone()),
	Term::Var(ref x) => {
	    if let Some(value) = bindings.get(x) {
		subst_bindings(Some(bindings.clone()), value.clone())
	    } else {
		Term::Var(x.clone())
	    }
	}
	Term::Str(f, args) => {
	    Term::Str(f.clone(), args.iter().map(|t| subst_bindings(Some(bindings.clone()), t.clone())).collect())
	}
    }
}

pub fn find_variables_in_goals(goals: &Vec<Term>) -> HashSet<String> {
    let mut vars = HashSet::new();
    for goal in goals {
	match goal {
	    Term::Var(var) => {
		vars.insert(var.clone());
	    },
	    Term::Atom(_) => (),
	    Term::Str(_, args) => vars.extend(find_variables_in_goals(&args))
	}
    }
    vars
}

Una vez ya tenemos esto, podemos hacer una función top_level_prove que admita una query e imprima por pantalla los resultados.


fn top_level_prove(goals: Vec<Term>, database: &Database) {
    let vars_in_goals = find_variables_in_goals(&goals);
    let solutions = prove_all(goals, vec![Some(HashMap::new())], database);

    if let Some(solutions) = solutions {
	let mut output = Vec::new();
	for solution in solutions {
	    let mut line = Vec::new();
	    for var in &vars_in_goals {
		line.push(format!("{} = {}", var, subst_bindings(solution.clone(), Term::Var(var.clone()))));
	    }
	    output.push(line.join(","));
	}
	println!("{}", output.join(";\n"));
    } else {
	println!("false.")
    }
}

Backtracking

Esto funciona pero Prolog no funciona así. Prolog no analiza todas las ramificaciones de query a no ser que se le pida expresamente. Por defecto, Prolog va a intentar solo una solución, hasta que no pueda seguir con ella. En ese caso, vuelve atrás y toma otro camino. Al llegar a una solución, se pregunta al usuario si quiere más soluciones o no.

Una forma se solucionarlo es 1) hacer que prove y prove_all solo devuelvan una solución, aunque se quedan en memoria al resto de posibilidades y 2) introducir en todas las queries un term especial al final (solo se llegará cuando el resto de goals ya han sido probados). Este term provocará una pregunta al usuario que dependiendo de su respuesta lo haremos que se pruebe (y acabaremos) o que no se pruebe y el sistema tenga que probar otras alternativas.


fn prove(goal: Term, bindings: Bindings, database: &Database, other_goals: VecDeque<Term>, vars_in_goals: &HashSet<String>) -> Bindings {
    if let Some(predicate) = Predicate::from_term(&goal) {
	if &predicate.name == "__backtracking?" {
	    let mut line = Vec::new();
	    for var in vars_in_goals {
		line.push(format!("{} = {}", var, subst_bindings(bindings.clone(), Term::Var(var.clone()))));
	    }
	    if line.len() == 0 {
		println!("true");
	    } else {
                println!("{}", line.join(","));
	    }
	    if ask_confirm() {
		None
	    } else {
		bindings
	    }
	} else {
	    if let Some(clauses) = database.get_clauses(&predicate) {
		for clause in clauses {
		    let renamed_clause = rename_variables(&clause);
		    let bindings = unify(goal.clone(), renamed_clause.head, bindings.clone(), false);
		    if bindings.is_none() {
			// do nothing
		    } else {
			let mut goals = VecDeque::from(renamed_clause.body.clone());
			goals.append(&mut other_goals.clone());
			let new_bindings = prove_all(goals, bindings, database, vars_in_goals);
			if new_bindings.is_some() {
			    return new_bindings;
			}
		    }
		}
		None
	    } else {
		None
	    }
	}
    } else {
	None
    }
}

pub fn prove_all(mut goals: VecDeque<Term>, bindings: Bindings, database: &Database, vars_in_goals: &HashSet<String>) -> Bindings {
    if let Some(goal) = goals.pop_front() {
	prove(goal, bindings, database, goals, vars_in_goals)
    } else {
	bindings
    }
}

fn ask_confirm() -> bool {
        let mut input = String::new();
        std::io::stdin().read_line(&mut input).unwrap();
        match input.chars().nth(0).unwrap() {
            ';' => return true,
            _ => return false,
        }
}

Con esto ya tendremos un sistema similar a Prolog. Pero nos ha faltado algo muy importante. ¡Leer la sintaxis! En el siguiente post veremos como leer la sintaxis simplificada de Prolog y como juntar todo. Además resolveremos el puzzle de la Zebra con nuestro nuevo sistema.

]]>
https://blog.adrianistan.eu/camino-a-prolog-backtracking Mon, 2 Jan 2023 13:36:13 +0000
El camino a Prolog: unificación (parte I) https://blog.adrianistan.eu/camino-a-prolog-unificacion https://blog.adrianistan.eu/camino-a-prolog-unificacion Ho, ho, ho. Durante estos días festivos de Navidad voy a escribir unos posts de una serie llamada El camino a Prolog donde vamos a tratar de construir un sistema Prolog de juguete. Mi intención no es construir un sistema Prolog real y usable, sino un sistema de juguete para que se pueda entender como funcionan por debajo muchas cosas. Esto me ayudará a mí a entender mejor como funcionan sistemas como Scryer Prolog por debajo. Sin más dilación empecemos con la primera parte.

¿Qué hace Prolog distinto a otros lenguajes

Existen muchos lenguajes de programación y existe mucha documentación sobre como implementarlos. No obstante, la mayoría de libros y webs se centran en implementar lenguajes imperativos. También existe documentación sobre lenguajes funcionales, que en los últimos años han recuperado popularidad. Sin embargo, Prolog es un lenguaje lógico y eso tiene desafíos únicos. Precisamente estos desafíos provienen de sus cualidad únicas.

Cuando me pidan que describa Prolog, lo primero que se me viene a la cabeza son dos conceptos: unificación y no determinismo. Ambos requieren de la introducción del concepto de variables lógicas. Las variables lógicas que pueden tomar un valor. Mención especial al pueden, ya que una variable puede estar todavía sin valor. Una vez adquieren un valor, lo mantienen de forma permanente. Piensa en ellas como las variables matemáticas de álgebra, donde no se permite mutabilidad y además pueden tener un valor desconocido en cierto punto del cómputo.

Un algoritmo de unificación

Vamos a atacar el primer problema que hace a Prolog diferente de otros lenguajes. Tenemos que implementar la unificación, la cuál nos va a permitir tener variables lógicas. Las variables lógicas las escribiremos con su primera letra en mayúsculas.

¿Qué quiere decir que dos expresiones unifiquen? Quiere decir que existe una sustitución de variables que permite hacer iguales las dos expresiones. Por ejemplo:


f(X, b) = f(a, Y).

Las dos expresiones a los dos lados del igual no son iguales, pero unifican, ya que es posible que X tome el valor a e Y tomar el valor b y de ese modo sí serían iguales.

Existen varias formas de hacerlo aunque muchos libros contenían un bug en su implementación. Para nuestro algoritmo vamos a implementar el código de Peter Norvig en Paradigms of AI Programming: Case Studies in Common Lisp.

Primero vamos a implementar el caso más obvias. Las cosas idénticas a nivel fundamental deben unificar. Es decir un átomo y otro átomo igual unifican. Y una variable con ella misma también unifica directamente. En este caso no se genera ninguna sustitución de variable. Si dos átomos no son iguales, no pueden unificar.

Vamos a empezar escribiendo en Rust nuestro algoritmo. Por un lado, vamos a tener un Sum Type llamado Term. Los Term van a poder ser de tres tipos: átomos, variables y estructuras. Las estructuras no son más que un átomo seguido de un listado de Terms. Podrían ser incluso otras estructuras. De esta forma se pueden representar datos complejos en Prolog.

Con esa información podemos tener algo parecido a esto.


#[derive(Debug, Clone)]
enum Term {
    Atom(String),
    Var(String),
    Str(String, Vec),
}

impl PartialEq for Term {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Term::Atom(x), Term::Atom(y)) => x == y,
            (Term::Var(x), Term::Var(y)) => x == y,
            _ => false
        }
    }
}

Para el algoritmo de unificación vamos a ir modificando un diccionario con las variables y sus sustituciones. Este diccionario estará envuelto por Option, ya que solo tendrá sentido si la unificación es posible. Si no es posible devolveremos None. Otra idea presente dentro de la función unify es que sea posible llamarla de forma recursiva.


type Bindings = Option>;

fn unify(x: Term, y: Term, bindings: Bindings) -> Bindings {
    if x == y {
        bindings
    } else {
        None
    }
}

Con esto ya tenemos el primer subcaso de la unificación resuelto. Ahora tocan las variables.

Unificando variables

En el caso de que el parámetro x sea una variable, debería poder unificar con y, sea cuál sea el valor de y. Ya que x tomará el valor concreto de y. Podría ser un átomo, una variable o una estructura. Lo que haremos será almacenar en el diccionario que para la variable x se sustituye por lo que tenga y. Podría existir el caso de que x ya apareciese en los bindings (hasta que no tengamos implementadas las estructuras no pero hay que irlo teniendo en cuenta). En ese caso, deberemos probar que lo que tenemos almacenado unifica con el nuevo valor con el que tratamos de unificar la variable. Y todo esto se aplica también en el caso de que la variable sea y en vez de x.


fn unify(x: Term, y: Term, bindings: Bindings) -> Bindings {
    if x == y {
        bindings
    } else if let Term::Var(var) = x {
        unify_variable(var, y, bindings)
    } else if let Term::Var(var) = y {
        unify_variable(var, x, bindings)
    } else {
        None
    }
}

#[inline]
fn unify_variable(var: String, y: Term, bindings: Bindings) -> Bindings {
    let mut bindings = bindings?;
    match bindings.get(&var) {
        Some(value) => unify(value.clone(), y, Some(bindings)),
        None => {
            bindings.insert(var, y.clone());
            Some(bindings)
        }
    }
}

Manejando estructuras

Ahora vamos a hacer que nuestro algoritmo de unificación maneje estructuras, de modo que el algoritmo funcione en unificaciones mucho más realistas, como el del ejemplo inicial.


fn unify(x: Term, y: Term, bindings: Bindings) -> Bindings {
    if x == y {
        bindings
    } else if let Term::Var(var) = x {
        unify_variable(var, y, bindings)
    } else if let Term::Var(var) = y {
        unify_variable(var, x, bindings)
    } else if let (Term::Str(f_x, args_x), Term::Str(f_y, args_y)) = (x, y) {
        if f_x == f_y && args_x.len() == args_y.len() {
            zip(args_x, args_y).fold(bindings, |acc_bindings, (t_x, t_y)| {
                unify(t_x, t_y, acc_bindings)
            })
        } else {
            None
        }
    } else {
        None
    }
}

Se comprueba que tanto x como y son estructuras, de la misma aridad y con el functor (el átomo inicial) iguales. Posteriormente vamos haciendo unificaciones de cada Term dentro de la estructura. Podemos añadir un test que pruebe la unificación de f(X, b) = f(a, Y).


#[test]
fn unify_str() {
    let x = Term::Str("f".into(), vec![Term::Var("X".into()), Term::Atom("b".into())]);
    let y = Term::Str("f".into(), vec![Term::Atom("a".into()), Term::Var("Y".into())]);
    let bindings = unify(x, y, Some(HashMap::new()));
    let mut expected = HashMap::new();
    expected.insert("X".into(), Term::Atom("a".into()));
    expected.insert("Y".into(), Term::Atom("b".into()));    
    assert_eq!(Some(expected), bindings);
}

Y funcionará correctamente. No obstante, si pasamos algunos ejemplos más específicos, podemos hacer que falle. La unificación de f(X, Y) = f(Y, X) es correcta pero genera dos bondings: X -> Y y Y -> X. Y de hecho si añadiésemos más términos que tuviesen que unificar con esas variables de nuevo, entraríamos en un bucle infinito. La solución es modificar unify_variable para, cuando tenemos una variable ya en nuestros bindings, que se unifique sobre el valor al que apunta y no sobre la propia variable.


#[inline]
fn unify_variable(var: String, y: Term, bindings: Bindings) -> Bindings {
    let mut bindings = bindings?;
    match bindings.get(&var) {
        Some(value) => unify(value.clone(), y, Some(bindings)),
        None => {
            if let Term::Var(ref y_var) = y {
                if let Some(y_val) = bindings.get(y_var) {
                    return unify(Term::Var(var), y_val.clone(), Some(bindings))
                }
            }
            bindings.insert(var, y.clone());
            Some(bindings)
        }
    }
}

Ya tendríamos un algoritmo de unificación similar al de un sistema Prolog. No obstante, esto no sería unificación propiamente dicha, ya que podemos crear términos cíclicos. Por ejemplo: X = f(X) unificaría con X = f(X). Pero esto sería incorrecto. No obstante, tradicionalmente Prolog ha permitido esto. Opcionalmente se puede introducir un occurs check, una comprobación, que añade coste al cómputo pero que evita que estas unificaciones sucedan con éxito.

Occurs check

Si queremos introducir el occurs check simplemente tendremos que añadir una condición, a comprobar antes de añadir un binding nuevo y es si esa variable aparece en el otro lado, de forma recursiva si es necesario.


#[inline]
fn unify_variable(var: String, y: Term, bindings: Bindings, occurs_check_flag: bool) -> Bindings {
    let mut bindings = bindings?;
    match bindings.get(&var) {
        Some(value) => unify(value.clone(), y, Some(bindings), occurs_check_flag),
        None => {
            if let Term::Var(ref y_var) = y {
                if let Some(y_val) = bindings.get(y_var) {
                    return unify(Term::Var(var), y_val.clone(), Some(bindings), occurs_check_flag)
                }
            }

            occurs_check(var.clone(), y.clone(), Some(bindings), occurs_check_flag).and_then(|mut bindings| {
                bindings.insert(var, y.clone());
                Some(bindings)
            })
	   
        }
    }
}

#[inline]
fn occurs_check(var: String, y: Term, bindings: Bindings, occurs_check_flag: bool) -> Bindings {
    if !occurs_check_flag {
        bindings
    } else if Term::Var(var.clone()) == y {
        None
    } else if let Term::Var(y_var) = y {
        match bindings.clone()?.get(&y_var) {
            Some(y_val) => {
                return occurs_check(var, y_val.clone(), bindings, occurs_check_flag);
	    }
            None => bindings
        }
    } else if let Term::Str(_, args) = y {
        args.iter().fold(bindings, |acc_bindings, t_y| {
            occurs_check(var.clone(), t_y.clone(), acc_bindings, occurs_check_flag)
        })
    } else {
        bindings
    }
}

Conclusión

Con esto ya tendríamos un algoritmo de unificación completo en Rust para nuestro sistema Prolog de juguete.

]]>
https://blog.adrianistan.eu/camino-a-prolog-unificacion Tue, 27 Dec 2022 20:05:23 +0000
Optimizar una mudanza (o un proyecto de software) en Prolog https://blog.adrianistan.eu/optimizar-mudanza-proyecto-software-prolog https://blog.adrianistan.eu/optimizar-mudanza-proyecto-software-prolog Seguimos viendo aplicaciones puzzles en Prolog, ayudándonos de clpz y hoy nos toca uno con multitud de aplicaciones prácticas. El problema de la mudanza consiste en que hay que mover unas piezas de mobiliario (tareas). Algunas de estas necesitan varias personas para poder moverse y se tarda un tiempo en moverlas. Además, hay algunos muebles que tienen que moverse antes que otros debido a su disposición en la casa. Si sois 4 amigos, ¿cuál es el tiempo mínimo que podéis tardar en mover todo?

A continuación, veamos una tabla detallada con los objetos a mover, el número de personas que se necesitan, cuánto se tarda y si hay alguna restricción adicional.

MueblePersonasTiempoNotas
Piano330
Cama320Tiene que moverse antes que el piano
Mesa215
TV215Tiene que moverse antes que la mesa
Silla 1110
Silla 2110
Silla 3110
Silla 4110
Estantería 1215Tiene que moverse antes que la cama
Estantería 2215Tiene que moverse antes que la mesa

Podemos empezar viendo cuáles van a ser las variables de nuestro problema. En este tipo de problemas, una buena selección de variables suele ser el tiempo de inicio y el de finalización de cada tarea. El dominio de esta variable la podemos poner entre 0 (tiempo de inicio de la mudanza) y el resultado de sumar todos los tiempos. Ese sería el peor caso, donde todas las tareas se tendrían que hacer de forma secuencial.

Para realizar una asignación de tareas con un recurso finito (en este caso las personas), de modo que las tareas puedan hacerse sin cortes y en ningún instante de tiempo haya más uso de recursos que el límite, clpz nos ofrece una constraint específica llamada cumulative/2. A esta constraint se le debe pasar una lista de tareas, así como el límite de recursos finitos.


moving(Vars) :-
    Vars = [StartPiano, EndPiano, StartBed, EndBed, StartTable, EndTable, StartTV, EndTV,
	    StartChair1, StartChair2, StartChair3, StartChair4, EndChair1, EndChair2, EndChair3, EndChair4,
	    StartShelf1, StartShelf2, EndShelf1, EndShelf2],
    Vars ins 0..150,
    % Tasks
    Tasks = [
	task(StartPiano, 30, EndPiano, 3, _),
	task(StartBed, 20, EndBed, 3, _),
	task(StartTable, 15, EndTable, 2, _),
	task(StartTV, 15, EndTV, 2, _),
	task(StartChair1, 10, EndChair1, 1, _),
	task(StartChair2, 10, EndChair2, 1, _),
	task(StartChair3, 10, EndChair3, 1, _),
	task(StartChair4, 10, EndChair4, 1, _),
	task(StartShelf1, 15, EndShelf1, 2, _),
	task(StartShelf2, 15, EndShelf2, 2, _)
    ],
    % Cumulative constraint
    cumulative(Tasks, [limit(4)]).

No obstante, esto solo nos da una asignación de las tareas sin tener en cuenta las restricciones de dependencia. Afortunadamente, las podemos expresar como restricciones normales de clpz, ya que tenemos los tiempos. Lógicamente, si la TV tiene que moverse antes que la mesa, entonces el tiempo de finalización de la tarea de la TV debe ser menor que el tiempo de inicio de la mesa.


    % Must be moved before
    EndBed #< StartPiano,
    EndTV #< StartTable,
    EndShelf1 #< StartBed,
    EndShelf2 #< StartTable,

Con esto tendríamos una asignación válida, pero no óptima. Para conseguir un resultado óptimo deberemos minimizar sobre el máximo de los tiempos de finalización. Para ello, calculamos con un predicado recursivo el tiempo de finalización máximo y esa variable se la pasamos a labeling/2 con el objetivo de minimizar. El resultado final es el siguiente:


:- use_module(library(clpz)).

moving(Vars, EndTime) :-
    Vars = [StartPiano, EndPiano, StartBed, EndBed, StartTable, EndTable, StartTV, EndTV,
	    StartChair1, StartChair2, StartChair3, StartChair4, EndChair1, EndChair2, EndChair3, EndChair4,
	    StartShelf1, StartShelf2, EndShelf1, EndShelf2],
    Vars ins 0..100,
    % Tasks
    Tasks = [
	task(StartPiano, 30, EndPiano, 3, _),
	task(StartBed, 20, EndBed, 3, _),
	task(StartTable, 15, EndTable, 2, _),
	task(StartTV, 15, EndTV, 2, _),
	task(StartChair1, 10, EndChair1, 1, _),
	task(StartChair2, 10, EndChair2, 1, _),
	task(StartChair3, 10, EndChair3, 1, _),
	task(StartChair4, 10, EndChair4, 1, _),
	task(StartShelf1, 15, EndShelf1, 2, _),
	task(StartShelf2, 15, EndShelf2, 2, _)
    ],
    % Must be moved before
    EndBed #< StartPiano,
    EndTV #< StartTable,
    EndShelf1 #< StartBed,
    EndShelf2 #< StartTable,

    % EndTime
    end_time(EndTime, [EndPiano, EndBed, EndTable, EndTV, EndChair1, EndChair2, EndChair3, EndChair4, EndShelf1, EndShelf2]),
    % Cumulative constraint
    cumulative(Tasks, [limit(4)]),

    % Find solution
    labeling([ff, min(EndTime)], Vars).

end_time(EndTime, [EndTime]).
end_time(EndTime, [X|Xs]) :-
    end_time(EndTime0, Xs),
    EndTime #= max(X, EndTime0).

run :-
    moving([StartPiano, EndPiano, StartBed, EndBed, StartTable, EndTable, StartTV, EndTV,
	    StartChair1, StartChair2, StartChair3, StartChair4, EndChair1, EndChair2, EndChair3, EndChair4,
	    StartShelf1, StartShelf2, EndShelf1, EndShelf2], EndTime),
    format("Optimal time: ~d~n", [EndTime]),
    format("Piano   ~d  ~d~n",[StartPiano, EndPiano]),
    format("Bed     ~d  ~d~n",[StartBed, EndBed]),
    format("Table   ~d  ~d~n",[StartTable, EndTable]),
    format("TV      ~d  ~d~n",[StartTV, EndTV]),
    format("Chair 1 ~d  ~d~n",[StartChair1, EndChair1]),
    format("Chair 2 ~d  ~d~n",[StartChair2, EndChair2]),
    format("Chair 3 ~d  ~d~n",[StartChair3, EndChair3]),
    format("Chair 4 ~d  ~d~n",[StartChair4, EndChair4]),
    format("Shelf 1 ~d  ~d~n",[StartShelf1, EndShelf1]),
    format("Shelf 2 ~d  ~d~n",[StartShelf2, EndShelf2]).

¡Con esto podemos hacer la mudanza de forma óptima! Y quién dice mudanza, dice la planificación de un proyecto de software por ejemplo. La constraint cumulative es verdaderamente potente.

]]>
https://blog.adrianistan.eu/optimizar-mudanza-proyecto-software-prolog Tue, 22 Nov 2022 20:27:13 +0000
Comando wc en Prolog https://blog.adrianistan.eu/wc-en-prolog https://blog.adrianistan.eu/wc-en-prolog Existe un programa dentro de los sistemas Unix llamado wc. Son las iniciales de "word counter". Este programa en cuestión toma un fichero de texto y cuenta líneas, palabras y bytes/caracteres. Vamos a intentar hacer un programa similar pero en Prolog, buscando la pureza, no la velocidad.

Vamos a implementar una máquina de estados, usando como referencia la máquina de estados de este artículo de Robert Graham.

Lo primero será leer el archivo mediante phrase_from_file/2. Este predicado nos permite aplicar una DCG a la vez que se va leyendo el archivo (no hay que leer el fichero entero para ir empezando con la ejecución). Esta es de las formas más eficientes de leer ficheros en Scryer Prolog.


:- use_module(library(format)).
:- use_module(library(lists)).
:- use_module(library(pio)).

:- initialization(run).

run :-
    phrase_from_file(state(was_space, 1, 0, 0, Lines, Words, Chars), "quijote.txt"),
    format("~d ~d ~d~n", [Lines, Words, Chars]),
    halt.

La DCG básicamente va a leer caracter a caracter, comprobando de qué tipo es, actualizando los valores de líneas, palabras y caracteres (los valores iniciales son 1, 0 y 0 respectivamente).

Los tipos de caracteres que podemos leer los definiremos en este predicado llamado type/2, y podrán ser de tres tipos: space, newline y word. El primer argumento será el caracter, el segundo el tipo.


type(X, space) :- member(X, " \t").
type('\n', newline).
type(X, word) :- \+ member(X, " \t\n").

Ahora definimos las transiciones de estado en un predicado state/3. El primer argumento será el estado actual, después el tipo de caracter encontrado, y por último, el estado al que se pasa.


state(_, space, was_space).
state(_, newline, new_line).
state(was_space, word, new_word).
state(new_line, word, new_word).
state(new_word, word, was_word).
state(was_word, word, was_word).

¡Ya lo tenemos casi todo! Ahora falta definir la DCG que va a ir leyendo los caracteres. También la he llamado state, aunque su aridad es mucho mayor. El primer argumento es el estado que implementa, seguido de las líneas, palabras y caracteres antes de entrar al estado, y al finalizar la lectura.

Importante no olvidar la última definición, que aplica cuando ya no hay más que leer, y que reporta los resultados.


state(was_space, Lines0, Words0, Chars0, Lines, Words, Chars) -->
    [X],
    {
	type(X, Type),
	state(was_space, Type, NextState),
	Chars1 is Chars0 + 1
    },
    state(NextState, Lines0, Words0, Chars1, Lines, Words, Chars).

state(new_line, Lines0, Words0, Chars0, Lines, Words, Chars) -->
    [X],
    {
	type(X, Type),
	state(new_line, Type, NextState),
	Lines1 is Lines0 + 1,
	Chars1 is Chars0 + 1
    },
    state(NextState, Lines1, Words0, Chars1, Lines, Words, Chars).
	
state(new_word, Lines0, Words0, Chars0, Lines, Words, Chars) -->
    [X],
    {
	type(X, Type),
	state(new_word, Type, NextState),
	Words1 is Words0 + 1,
	Chars1 is Chars0 + 1
    },
    state(NextState, Lines0, Words1, Chars1, Lines, Words, Chars).

state(was_word, Lines0, Words0, Chars0, Lines, Words, Chars) -->
    [X],
    {
	type(X, Type),
	state(was_word, Type, NextState),
	Chars1 is Chars0 + 1
    },
    state(NextState, Lines0, Words0, Chars1, Lines, Words, Chars).

state(_, L, W, C, L, W, C) --> [].

Con esto ya tenemos, de forma muy limpia, wc en Prolog. Nótese que el fichero de lectura está hardcodeado (es este Quijote). Esto lo he hecho para no despistar con esa parte, que todavía puede mejorar en Scryer Prolog.

Y, ¿cómo es el rendimiento? Pues realmente es malo, para qué engañarnos. Es varios órdenes de magnitud más lento. Pero claro, hay que tener en cuenta que a día de hoy Scryer Prolog es 100% interpretado. También, en algunas definiciones estamos añadiendo choicepoints innecesarios (por ejemplo en type/2). Quitándolos obtenemos un programa el doble de rápido pero ya no es tan elegante.

]]>
https://blog.adrianistan.eu/wc-en-prolog Sat, 19 Nov 2022 16:45:44 +0000
El acertijo de la cebra https://blog.adrianistan.eu/acertijo-cebra https://blog.adrianistan.eu/acertijo-cebra Hoy vamos a resolver un acertijo bastante famoso, el de la cebra, atribuido a Einstein y a Lewis Carroll. Seguramente no haya sido inventado por ninguno de los dos, pero sigue siendo muy interesante.

El puzzle dice lo siguiente:

  • Cinco hombres de cinco nacionalidades diferentes viven en las primeras cinco casas de una calle. Tienen cinco profesiones diferentes y cada uno tiene un animal y una bebida favorita diferentes. Cada casa está pintada de un color diferente.
  • El inglés vive en la casa roja.
  • El español tiene un perro.
  • El japonés es pintor.
  • El italiano bebe té.
  • El noruego vive en la primera casa por la izquierda.
  • El dueño de la casa verde bebe café.
  • La casa verde está a la derecha de la casa blanca.
  • El escultor cultiva caracoles.
  • El diplomático vive en la casa amarilla.
  • La leche se bebe en la casa de enmedio.
  • La casa del noruego está al lado de la casa azul.
  • El violinista bebe zumo de frutas.
  • El zorro está en una casa al lado de la del doctor.
  • El caballo está en una casa al lado de la del diplomático.

¿Quién tiene una cebra? ¿Quién bebe agua?

Resolviéndolo usando clpz

Usando clpz podemos resolver este acertijo de forma muy clara y concisa. El truco consista en asignar un número a cada casa, en orden, siendo el 1 la casa más a la izquierda y 5 la casa a la derecha. Sobre eso, creamos variables para cada una de las nacionalidad, profesiones, bebidas, etc que podrán adquirir un valor entre 1 y 5. Por supuesto, dentro de cada categoría, no se pueden repetir los números, porque cada uno está asignado a una casa diferente.


    Vars = [
	% nationalities
	English, Spanish, Japanese, Norwegian, Italian,
	% professiones
	Painter, Doctor, Diplomat, Violinist, Sculptor,
	% beverages
	Tea, Coffee, Juice, Milk, Water,
	% colors
	Red, Green, White, Yellow, Blue,
	% animals
	Dog, Snails, Fox, Horse, Zebra],
    Vars ins 1..5,

    all_different([English, Spanish, Japanese, Norwegian, Italian]),
    all_different([Painter, Doctor, Diplomat, Violinist, Sculptor]),
    all_different([Tea, Coffee, Juice, Milk, Water]),
    all_different([Red, Green, White, Yellow, Blue]),
    all_different([Dog, Snails, Fox, Horse, Zebra]),

A continuación codificamos las reglas. Como asignamos el valor en base a la posición de la casa, una casa a la derecha de otra es simplemente sumar 1. Decir que una casa está al lado de otra, significa que la diferencia absoluta entre 2 variables tiene que ser 1.


    English #= Red,
    Spanish #= Dog,
    Japanese #= Painter,
    Italian #= Tea,
    Norwegian #= 1,
    Green #= Coffee,
    Green #= White + 1,
    Sculptor #= Snails,
    Diplomat #= Yellow,
    Milk #= 3,
    abs(Norwegian-Blue) #= 1,
    Violinist #= Juice,
    abs(Fox-Doctor) #= 1,
    abs(Horse-Diplomat) #= 1,

Si juntamos todo, nos dará el siguiente programa, ya bonito para ver el resultado.


:- use_module(library(clpz)).
:- use_module(library(lists)).
:- use_module(library(format)).

run :-
    zebra(Vars),
    append([English,Spanish,Japanese,Norwegian,Italian|_], [Zebra], Vars),
    Nats = [English-english, Spanish-spanish, Japanese-japanese, Norwegian-norwegian, Italian-italian],
    member(Zebra-Nat, Nats),
    format("The ~a has the zebra~n", [Nat]),
    nth0(14, Vars, Water),
    member(Water-Nat1, Nats),
    format("The ~a has the water~n", [Nat1]).
    

zebra(Vars) :-
    Vars = [
	% nationalities
	English, Spanish, Japanese, Norwegian, Italian,
	% professiones
	Painter, Doctor, Diplomat, Violinist, Sculptor,
	% beverages
	Tea, Coffee, Juice, Milk, Water,
	% colors
	Red, Green, White, Yellow, Blue,
	% animals
	Dog, Snails, Fox, Horse, Zebra],
    Vars ins 1..5,

    all_different([English, Spanish, Japanese, Norwegian, Italian]),
    all_different([Painter, Doctor, Diplomat, Violinist, Sculptor]),
    all_different([Tea, Coffee, Juice, Milk, Water]),
    all_different([Red, Green, White, Yellow, Blue]),
    all_different([Dog, Snails, Fox, Horse, Zebra]),

    English #= Red,
    Spanish #= Dog,
    Japanese #= Painter,
    Italian #= Tea,
    Norwegian #= 1,
    Green #= Coffee,
    Green #= White + 1,
    Sculptor #= Snails,
    Diplomat #= Yellow,
    Milk #= 3,
    abs(Norwegian-Blue) #= 1,
    Violinist #= Juice,
    abs(Fox-Doctor) #= 1,
    abs(Horse-Diplomat) #= 1,
    
    label(Vars).

El resultado es que la cebra la tenía el japonés y el noruego es el que bebe agua.

]]>
https://blog.adrianistan.eu/acertijo-cebra Sun, 6 Nov 2022 17:10:59 +0000
CLIPS: el motor de reglas de la NASA https://blog.adrianistan.eu/clips-motor-reglas-nasa https://blog.adrianistan.eu/clips-motor-reglas-nasa CLIPS, no confundir con LISP, es un sistema diseñado para la creación de sistemas expertos y otros programas que requieran de una solución heurística. Fue programado por la NASA entre 1985 y 1996, año en que pasó a dominio público. Se trata de un lenguaje dentro de la familia de la programación lógica, al igual que Prolog, miniKanren, Mercury o Logtalk. La diferencia fundamental es que todos los anteriores se pueden categorizar como backward chaining mientras que CLIPS es forward chaining. Además, veremos que a nivel de sintaxis y operación, hay algunas diferencias.

Esencia de CLIPS

El concepto más importante de CLIPS son las reglas o rules. Las reglas se añaden al programa CLIPS, bien mediante archivo o mediante el REPL. Posteriormente se definen hechos o facts. Estos facts pueden hacer disparar una o más reglas, que se ejecutarán cuando ejecutemos el proceso de resolución de CLIPS. Estas reglas a su vez pueden añadir a su vez más facts a la base de datos. Así hasta que ya no queden reglas que disparar. Además de reglas y facts, CLIPS dispone de funciones, métodos, objetos y bastantes más cosas. Pero esto es lo principal.

La diferencia principal respecto a Prolog y los sistemas backward chaining es que en Prolog estamos tratando de probar un hecho yendo hacia atrás, reconstruyendo los pasos hasta llegar a ver si hay algo que hace que cuadre y lo que hayamos puesto sea verdad o sea verdad mediante alguna sustitución de variable. En CLIPS, empezamos con unos hechos y simplemente se van derivando todos los hechos que pueden salir con la aplicación de las reglas, aunque algún hecho en particular no nos interese.

Tipos de datos en CLIPS

CLIPS dispone de 8 tipos de datos básicos: integer, float, string, symbol, external-address, fact-address, instance-name e instance-address. Estos últimos son únicos de CLIPS y los veremos más adelante. Los números integer y float funcionan de forma similar a otros lenguajes. Los strings van siempre con comillas dobles y los symbol sin similares a los atom de Prolog.

REPL y aritmética

Uno de los modos fundamentales de usar CLIPS es mediante su REPL. Aquí podremos escribir código CLIPS y ver el resultado inmediato.

Al principio he dicho que no hay que confundir CLIPS con LISP (del que existe una implementación llamada CLISP). Esta confusión puede crecer ya que el código CLIPS se parece mucho al código LISP, con sus paréntesis y sus s-expresiones. La idea de las s-expresiones es que las cosas que están entre paréntesis se evalúan teniendo en cuenta el primer elemento como función y el resto como parámetros de esta función.

Así, (+ 5 10) significa: llama a la función + con los argumentos 5 y 10. El resultado de evaluar ese código es 15.

Para salir del REPL, usaremos (exit)

Definiendo facts

Los facts en CLIPS se componen de un nombre seguido de N argumentos. Existen dos tipos de facts: ordenados y desordenados. En los ordenados, a cada argumento solo le podemos hacer referencia por su orden dentro del argumento. Mientras que los desordenados podemos referirnos a cada argumento por un nombre.


// ordenado
(point 3 4)
// desordenado
(point (x 3) (y 4))

Intentaremos usar facts desordenados salvo en casos muy concretos. Para poder crear facts desordenados necesitamos usar deftemplate, y definir la estructura del fact. Además podemos indicar valores por defecto, tipo, valores admitidos, etc. Una cosa que tendremos que indicar sí o sí es si nuestro campo es slot o multislot. slot es cuando solo admitiremos un valor por regla, mientras que en el multislot admitimos una conjunto de valores en ese campo dentro de la misma regla. Podemos usar (facts) para consultar los facts actuales.


CLIPS>   
(deftemplate point
  (slot x)        
  (slot y (type INTEGER))
  (slot id (default-dynamic (gensym*))))  
CLIPS> (assert (point (x 5) (y 10)) 

Otro ejemplo:


CLIPS>
(deftemplate person
  (slot name
    (type STRING))
  (slot age
    (type INTEGER)
    (default 30))
  (slot blood-type
    (type SYMBOL)
    (allowed-values a b ab '0'))           
  (multislot brothers)))
CLIPS> (assert (person (name "Homer Simpson") (blood-type ab) (brothers herb)))

CLIPS> (assert (person (name "Marge Simpson") (age 29) (blood-type '0') (brothers paty selma)))

CLIPS> (facts)
f-1     (person (name "Homer Simpson") (age 30) (blood-type ab) (brothers herb))
f-2     (person (name "Marge Simpson") (age 29) (blood-type '0') (brothers paty selma))
For a total of 2 facts.

Cuando invocamos (reset), los facts, parte del estado de CLIPS se limpia. Una forma de tener facts siempre predefinidos es usar deffacts. De este modos, ciertos facts se cargarán siempre al resetear al estado.


(deffacts simpsons-family "Simpsons Family"
  (person (name "Homer Simpson") (blood-type ab) (brothers herb)) 
  (person (name "Bart Simpson") (age 12) (blood-type ab) (brothers lisa maggie))
  (person (name "Marge Simpson") (age 29) (blood-type '0') (brothers paty selma)))

Reglas

Como hemos explicado antes. Las reglas son la esencia de CLIPS. Estan reglas verifican si ciertos hechos se cumplen, y en su caso, ejecutar algo.

Las reglas se definen con defrule. En primera instancia, definimos un nombre de regla, después ponemos como debe ser los hechos que disparan la regla. Se trata de un pattern matching, donde podemos poner variables para capturar datos o ignorar ciertos aspectos de la regla.

Una vez tengamos las reglas, podemos consultar la agenda, para ver si alguna regla puede aplicarse. En las reglas podemos usar variables, que empiezan con ? normalmente.

También podremos ejecutar las reglas, con (run).


(deftemplate person
  (slot name)
  (multislot parents))

(deffacts village "Village of Imaginary"
  (person (name "Marta") (parents "Julio" "María"))
  (person (name "María") (parents "Emilio" "Sacarina"))
  (person (name "Luis") (parents "Alfonso" "Julia"))
  (person (name "Alfonso") (parents "Bernardo" "Milagros"))
  (person (name "Juan Carlos") (parents "Bernardo" "Milagros")))

(defrule say-hello
  (person (name ?name))
=>
  (println "Hola: " ?name "!"))

Ejecutamos (reset) y ya podemos consultar la agenda de activaciones. Ejecutamos con (run)


CLIPS> (agenda)
CLIPS> (reset)
CLIPS> (agenda)
0      say-hello: f-5
0      say-hello: f-4
0      say-hello: f-3
0      say-hello: f-2
0      say-hello: f-1
For a total of 5 activations.
CLIPS> (run)
Hola: Juan Carlos!
Hola: Alfonso!
Hola: Luis!
Hola: María!
Hola: Marta!

Una regla más interesante, será por ejemplo, sacar un listado de personas que son padres:


(defrule is-parent
  (person (name ?name))
  (person (parents $? ?name $?))
=>
  (assert (parent ?name)))

Aquí vemos como podemos añadir varios facts para disparar la regla. Además vemos el operador, similar a ?, salvo que $? capturar 0 o más valores.

Además vemos en este ejemplo como podemos añadir facts nuevos a la base de datos, que a su vez podrían disparar más reglas.

Con esta regla, una vez ejecutado, tendríamos dos facts nuevos:


(parent "María")
(parent "Alfonso")

Que son las únicas personas listadas que sabemos que son padres.

También podemos sacar los hermanos.


(defrule brother
  (person (name ?a) (parents $?parents))
  (person (name ?b&:(neq ?a ?b)) (parents $?parents))
=>
  (assert (brothers ?a ?b)))

En este caso, para asegurarnos que no estamos cogiendo la misma persona las dos veces, añadimos una restricción de que ?b debe ser diferente a ?a. Esto es mediante la sintaxis de VARIABLE&:(test). Esta otra sería equivalente, sería mediante una pseudoregla llamada test.


(defrule brother2
  (person (name ?a) (parents $?parents))
  (person (name ?b) (parents $?parents))
  (test (neq ?a ?b))
=>
  (assert (brothers ?a ?b)))

Aun así, veremos si ejecutamos esto, que los dos únicos hermanos que hay generan dos facts con esta regla, una donde uno va antes que el otro y la contraria.

Algunas cosas útiles que nos pueden venir bien son las funciones bind (para asignar un valor a una variable) y read, que nos permite leer de teclado. También podemos comparar el valor que aparece en una regla con la evaluación de una función con =(). Juntando esto, podemos construir el siguiente ejemplo, sacado directamente de la documentación oficial:


CLIPS> (clear)
CLIPS>
(defrule ask-question
  =>
  (bind ?length (random 1 10))
  (bind ?width (random 1 10))
  (println "A rectangle has length " ?length " and width " ?width)
  (print "What is the area of this rectangle? ")
  (assert (response ?length ?width (read))))
CLIPS>
(defrule correct-area
  (response ?length ?width =(* ?length ?width))
  =>
  (println "You are correct!"))
CLIPS>
(defrule incorrect-area
  (response ?length ?width ~=(* ?length ?width))
  =>
  (println "You are incorrect!"))
CLIPS> (run)
A rectangle has length 8 and width 10
What is the area of this rectangle? 80
You are correct!
CLIPS> (reset)
CLIPS> (run)
A rectangle has length 4 and width 4
What is the area of this rectangle? 8
You are incorrect!
CLIPS>

Por último, una cosa que nos puede resultar interesante es como eliminar facts de la base de datos. Para ello usaremos el operador <- para obtener un objeto tipo fact. Estos objetos se pueden usar para comparar entre ellos o para usarlos en retract y eliminarlos de la base de datos. Un ejemplo muy tonto, también sacado de la documentación oficial:


CLIPS> (clear)
CLIPS>
(defrule print-grocery-list
  ?f <- (grocery-list $?items)
  =>
  (retract ?f)
  (println "groceries: " (implode$ ?items)))
CLIPS> (assert (grocery-list milk eggs cheese))

CLIPS> (run)
groceries: milk eggs cheese
CLIPS> (facts)
CLIPS>

Funciones

Brevemente comentaremos que en CLIPS podemos crear nuestras propias funciones. No voy a explicar en detalle la sintaxis, pero será similar para la gente que haya usado LISP.


CLIPS> (clear)
CLIPS>
(deffunction factorial (?a)
  (if (or (not (integerp ?a)) (< ?a 0))
  then
    (println "Factorial Error!")
  else
    (if (= ?a 0)
    then 1
    else (* ?a (factorial (- ?a 1))))))
CLIPS> (factorial 1)
1
CLIPS> (factorial 2)
2
CLIPS> (factorial 3)
6
CLIPS>

Conclusión

CLIPS es un lenguaje muy completo, que no se puede explicar en un simple post, pero espero que os haya descubierto otra herramienta dentro de la programación funcional. No hemos visto muchas de las funciones predefinidas, funciones genéricas, el sistema de clases, forall, y otros detalles. La documentación oficial es muy extensa y con muchos ejemplos. También existen varios libros como el clásico Expert Systems: Principles and Programming o el nuevo Adventures in Rule-based Programming. ]]> https://blog.adrianistan.eu/clips-motor-reglas-nasa Thu, 27 Oct 2022 20:53:50 +0000 Un paseo por el ensamblador de RISC-V https://blog.adrianistan.eu/paseo-ensamblador-risc-v-64 https://blog.adrianistan.eu/paseo-ensamblador-risc-v-64 En este blog ya hablamos hace tiempo de ensamblador, en concreto de MIPS. Sin embargo, esta arquitectura está en decadencia. Si bien ha habido algún intento por revivirla, parece claro que el futuro no pasa por MIPS. En cambio, una nueva arquitectura ha surgido, y es tremendamente similar a MIPS: se trata de RISC-V.


Mi Mango Pi ejecutando Arch Linux RISC-V 64

¿Qué es RISC-V y por qué es importante?

RISC-V (risc-five) se trata de una arquitectura tipo RISC diseñada por la Universidad de California en Berkeley en 2010. Se trata de una arquitectura load-store, es decir, las instrucciones se dividen entre instrucciones para acceder a memoria e instrucciones para todo lo demás y que solo operan con registros. A diferencia de otras, tiene un enfoque abierto desde el primer momento, pudiendo cualquier fabricante crear procesadores basados en RISC-V sin pagar regalías. Se trata de una arquitectura con un núcleo pequeño pero extensible por parte de los fabricantes. De hecho, el core de RISC-V 64 bits usa solamente 15 instrucciones y no incluye ni siquiera multipliaciones de enteros. Las instrucciones todas tienen un tamaño de 32 bits mientras que el espacio de direcciones es de 64 bits (con variantes de 32 bits y 128 bits).

Poco a poco, han ido apareciendo fabricantes que han diseñado procesadores RISC-V, cada vez más útiles e interesantes, algunos de la mano de empresas creadas específicamente para esto como SiFive o StarFive. En particular, yo dispongo de una pequeña placa Mango Pi MQ-PRO, con el procesador Allwinner D1.

Además, el software se ha ido adaptando y hoy en día, no hay problema en ejecutar sistemas Linux bajo estos procesadores, más allá de temas de drivers o de boot, que al igual que en ARM, no está estandarizado.

Con estos ingredientes, ¿nos acercamos a ver como es su ensamblador para entender mejor su funcionamiento?

32 registros de 64 bits

Al igual que MIPS, RISC-V dispone de 32 registros, en este caso de 64 bits. Los podemos nombrar como r0, r1, ... hasta r31. Dentro de estos registros, el r0 o zero, va a ser un registro donde no podremos escribir ya que siempre contendrá el valor 0. Los demás son idénticos aunque existe una convención. En la imagen vienen representados los nombres por convención de los registros. Los marcados en amarillo se preservan entre llamadas.

Esto quiere decir que usaremos los s0..s11 para datos que queramos conservar, t0..t6 para datos temporales, a0..a7 para comunicarnos entre llamadas y ra/sp/gp/tp para sus casos de uso designados. El contador de programa no es accesible.

Cuatro tipos de instrucciones

En su versión más fundamental existen 4 tipos de instrucciones, dependiendo de como se organizan internamente los datos de cada instrucción. Como hemos dicho, todas las instrucciones son de 32 bits, pero existen extensiones que permiten usar instrucciones más pequeñas para dispositivos embebidos.

Operaciones básicas

La operación más sencilla puede ser la suma de enteros. Para ello se usa la instrucción add. El primer registro casi siempre es el registro de destino de la operación, mientras que los demás son fuente.


// a = b + c
// sería
add a, b, c
// con registros
add s0, s1, s2

RISC-V también permite introducir inmediatos en las instrucciones, es decir, constantes, pero se usa otra instrucción, normalmente con el sufijo i


// a = b + 1
// estando a en s0 y b en s1, sería
addi s0, s1, 1

Podemos usar add y el registro zero para implementar un move


// a = b
// estando a en s0 y b en s1
add s0, zero, s1

He aquí un listado de las instrucciones que existen de este tipo:

Branch y jump

Para ser capaces de tomar decisiones en un ordenador tenemos que tener alguna forma de ejecutar código de forma condicional. Esa es la magia de los ordenadores. Las operaciones de branch nos permiten evaluar una condición y saltar si se cumple. A nivel de arquitectura existen solamente unas pocas instrucciones de branch que se complementan con pseudoinstrucciones (instrucciones que no existen a nivel de procesador pero sí para el ensamblador y que traducirá en una o más instrucciones reales).


int x = 5;
do{
    x--;
}while(x!=0);

// siendo x s0

.section .text
main:
    addi s0, zero, 5
loop:
    addi s0, s0, -1
    bne s0, zero, loop

También podemos hacer saltos, guardando la dirección original de la que venimos en el registro ra. No obstante, aquí recomiendo usar las pseudoinstrucciones de call y j que veremos más adelante.

Cargar y guardar en memoria

Aquí viene el otro tipo diferente de instrucciones de RISC-V, en las que accederemos a direcciones de memoria. En general hay dos tipos, las load y las store, unas cargan y otras guardan y las dos llevan el mismo orden: primero registro destino/origen, segundo registro con la dirección de memoria y un posible offset.

Para definir datos en memoria usamos la parte de .data. Allí podemos definir zonas de memoria e inicializarlas. Por ejemplo, con .string podemos inicializar una cadena de caracteres en memoria. Con .word inicializamos uno o varios números de 32 bits (4 bytes), con .dword uno o varios números de 64 bits (8 bytes), etc...

Posteriormente, podemos cargarlo con ld si es un dword, lw si es un word, lh si es un half, lb si es un byte y los correspondientes sd, sw, sh y sb. Además podemos introducir un offset como ya se ha dicho.


int* x = [1,2,3,4,5];
int suma = 0;
for(int i =0;i<4;i++){
    suma = suma + x[i]
}

.data
x: .dword 1,2,3,4,5
.text
main:
	add s0, zero, zero
	addi t9, zero, 5
	la t0, x
bucle:
	ld t1, 0(t0)
	add s0, s0, t1
	addi t0, t0, 8 # cada valor son 8 bytes (64 bits), para ir al siguiente número sumamos 8
	addi t9, t9, -1
	bne zero, t9, bucle

Siendo la una pseudoinstrucción que nos permite cargar una dirección de memoria en un registro. En la sintaxis de ld se ha dejado el offset a 0. Si el offset fuese de 8, accederíamos al siguiente elemento del array, etc.

Fibonacci en RISC-V 64 sobre Linux

Finalmente para acabar, vamos a comentar un código que calcula los 12 primeros números de la secuencia de Fibonacci y los imprime por pantalla, en un sistema Linux. Para ello se hacen llamadas al sistema (ecall) y llamadas a la API de C.


.section .data
fibs: .double 0,0,0,0,0,0,0,0,0,0,0,0
size: .word 12
space: .string " "
head: .string "The Fibonacci numbers are:\n"
printf_str: .string "%d\n"

.section .text
.global main
main:
    la s0, fibs # cargar direccion de fibs
    la s5, size # cargar direccion de size
    lw s5, 0(s5) # cargar el valor de size
    li s2, 1 # cargar 1 como primer numero de fibonacci
    sd s2, 0(s0) # guardar el primer número de fibonacci (1)
    sd s2, 8(s0) # guardar el segundo número de fibonacci (1)
    addi s1, s5, -2 # iteramos sobre size - 2
loop:
    ld s3, 0(s0) # cargamos el numero N -2 
    ld s4, 8(s0) # cargamos el numero N - 1
    add s2, s3, s4 # calculamos N
    sd s2, 16(s0) # almacenamos N
    addi s0, s0, 8 # movemos el puntero al siguiente numero
    addi s1, s1, -1 # decrementamos el loop
    bgtz s1, loop # loop
    call print # imprimimos la secuencia
    j exit # salimos

print:
    mv s11, ra # guardamos la direccion de origen de la llamada
    la s0, fibs # cargamos la direccion de fibs
    la s1, size # cargamos la direccion de size
    lw s1, 0(s1) # cargamos el valor de size
    la a0, head # cargamos la direccion de head en a0 (lo usa printf)
    call printf # llamamos a printf
out:
    la a0, printf_str # cargamos la direccion printf_str en a0
    ld a1, 0(s0) # cargamos el numero N en a1 (para printf)
    call printf # llamamos a printf
    addi s0, s0, 8 # aumentamos el puntero 8 para ir al siguiente numero
    addi s1, s1, -1 # decrementamos el loop
    bgt s1, zero, out # loop
    jr s11 # volvemos a la direccion original de la llamada

# salir usando la syscall de Linux de exit
exit:
    li a7, 93
    li a0, 0
    ecall

Se puede compilar con:


gcc -o fib fib.s -static

Y al ejecutarse nos imprime los números correspondientes.

Las pseudoinstrucciones más comunes son las siguientes:

Y con esto ya entenderíamos las bases principales del ensamblador de RISC-V. Evidentemente esto es solo el comienzo ya que hay muchas extensiones ya definidas, pero si quieres saciar tu curiosidad es suficiente.

Me gustaría agradecer a Erik Engheim, autor de esta cheatsheet de RISC-V, uno de los recursos más sencillos para entender las bases de RISC-V.

]]>
https://blog.adrianistan.eu/paseo-ensamblador-risc-v-64 Tue, 23 Aug 2022 21:03:36 +0000
Planificar una inmersión SCUBA con Prolog https://blog.adrianistan.eu/planificar-scuba-prolog https://blog.adrianistan.eu/planificar-scuba-prolog El año pasado, un día anodino, vi por casualidad un cartel en la escuela de Ingeniería Informática. Hablaba de un curso de buceo, como los buceadores de las películas, con su botella, su regulador y pasarse bajo el agua un montón de tiempo. El nombre técnico de este tipo de buceo es SCUBA, que viene de Self-Contained Underwater Breathing Apparatus. Decidí apuntarme y este año y tras unos cursos y unas cuantas inmersiones más, llega el primer artículo de buceo al blog. Pero no nos vamos a alejar mucho de la temática habitual, sino que veremos como podemos usar Prolog para planificar nuestras inmersiones.

¿Cómo funcionará el planificador?

Antes de entrar y añadir qué factores vamos a tener en cuenta vamos a centrarnos en lo básico del problema. ¿Qué es hacer una planificación de buceo? Un plan es una lista de profundidades a las que vamos a estar en un determinado momento. Así por ejemplo empezaremos a 0 metros de profundidad (superficie) en el minuto 0. En el minuto 2, hemos bajado a 10 metros de profundidad. Nos mantenemos a esa profundidad 20 minutos y finalmente, ascendemos a la superficie en el minuto 24.

Lo que queremos comprobar es si ese plan es válido o requiere cambios. Prolog nos ayudará. Aunque el plan siempre deberá revisarse y/o comentar por humanos con información adicional (corrientes, visibilidad, etc)

Lo primero será definir un predicado plan/1 que toma una lista de puntos de la inmersión. Este predicado comprobará que el primer elemento es en superficie y que el último también lo es. Además comprobaremos que los tiempos estén alineados y que las variables estan dentro del rango. El tiempo estará entre 0 y 100 y la profundidad entre 0 y 30 (límite más o menos frecuente en buceo recreativo, aunque según la titulación podría ser 18 o 40). Para establecer este dominio de la variable, usamos between. ¿Podríamos haber usado clpz? Para esta parte sí, pero luego meteremos números decimales dentro de los cálculos y ahí ya dejaría de funcionar. clpz funciona cuando todas las variables son números enteros.


:- use_module(library(between)).

% s(Time, Depth)
plan(Plan) :-
    Plan = [X|Xs],
    X = s(0, 0),
    plan(X, Xs).

plan(S, []) :-
    S = s(_, D),
    D = 0.
plan(S0, [S|Ss]) :-
    S0 = s(T0, D0),
    S = s(T, D),
    between(0, 100, T),
    between(0, 100, T0),
    between(0, 30, D),
    between(0, 30, D0),
    T > T0,
    plan(S, Ss).

Con este código probamos nuestro plan.


?- plan([s(0, 0), s(2, 10), s(22, 10), s(30, 0)]).
   true.
?- plan([s(0, 0), s(2, 10), s(22, 10), s(30, 2)]).
   false.
?- plan([s(0, 0), s(2, 10), s(22, 10), s(30, 0)]).
   true.
?- plan([s(0, 2), s(2, 10), s(22, 10), s(30, 0)]).
   false.
?- plan([s(0, 0), s(2, 10), s(22, 10), s(30, 0)]).
   true.

Como veis, algunos planos son viables mientras que otros no. No obstante, hay todavía más cosas que añadir.

Consumo de aire

Quizá el problema más evidente que vemos cuando nos planteamos explorar el mar es cómo respirar. Los humanos no tenemos branquias, no podemos respirar bajo el agua de forma natural. Ante esto hay varias soluciones, cada una dará lugar a tipos de buceo diferentes. Desde aguantar la respiración (apnea), llevar un tubo y bucear a poca profundidad (snorkel), estar conectado a una campana, ... En SCUBA, el aire (aire, no oxígeno), se lleva en una botella (botella, no bombona) a muy alta presión. Esta botella se conecta a un aparato llamado regulador. Los reguladores actuales normalmente disponen de dos etapas, una primera que se conecta a la botella y devuelve aire a 8-9 bares. La segunda toma ese aire y lo transforman a la presión correcta para que nuestros pulmones puedan procesar el aire correctamente. ¿Cuál es esa presión?

La presión de aire a la que tiene que transformar el aire la segunda etapa es la presión que haya en el exterior sobre el cuerpo humano. Cuando estamos en superficie, la presión atmosférica es de 1 bar (aprox). Bajo el agua, a esa presión deberemos sumar 1 bar por cada 10 metros de profundidad.

Las botellas son de diferentes tamaños y materiales. Las más comunes en Europa son de 12L y de 15L de acero. No obstante, el aluminio es necesario si metemos gases diferentes (más adelante). Estas botellas suelen llenarse a 200 bar. Una botella de 12L a 200 bar contiene 2400L de aire a presión a nivel de mar. (Volumen de Aire = Volumen de Botella * Presión, por la Ley de Boyle).

De normal, un ser humano respira unos 20 L por minuto. Al ser bajo el agua, estos litros en realidad son más, ya que tienen que salir 20L a la presión de la profundidad. Por tanto en un minuto, los litros de aire consumidos serán 20*Presión de la profundidad. Para saber la presión perdida, volvemos a la ley de Boyle, la diferencia de presión será la diferencia de volumen de aire dividido el volumen de la botella.

En el caso de que estemos calculando el consumo entre dos puntos, a diferente profundidad, podemos hacer una aproximación a la profundidad media sin apenas diferencias.


% Calculates the pressure difference between two times, probably at different depth
pressure_dif(DifTime, D0, D, DifGas) :-
    breathing_rate(BR),
    bottle_size(BL),
    DifVolume is BR*DifTime*(((D0+D) / 20)+1), % breathing rate * time * air pressure
    DifGas is DifVolume / BL. % Air volume = Bottle volume * bottle pressure

% Breathing Rate (20 L/m) to (70 L/m)
breathing_rate(20).
% Bottle Sizes (10, 12, 15, 18)
bottle_size(12).

Ahora hay que modificar el plan para añadir el consumo de aire, que será una variable entre 0 y 200. Además al salir, hay que asegurarse una reserva, de mínimo 50 bares.


plan(Plan) :-
    Plan = [X|Xs],
    X = s(0, 0, _),
    plan(X, Xs).

plan(S, []) :-
    S = s(T, D, G),
    D = 0.
plan(S0, [S|Ss]) :-
    S0 = s(T0, D0, G0),
    S = s(T, D, G),
    between(0, 100, T),
    between(0, 100, T0),
    between(0, 30, D),
    between(0, 30, D0),
    between(50, 200, G0),
    T > T0,
    DifTime is T - T0,
    pressure_dif(DifTime, D0, D, GasConsumption),
    G is G0 - round(GasConsumption),
    between(50, 200, G),
    plan(S, Ss).

Con esto podemos probar si vamos a tener aire suficiente en ciertos planes. Además, en cada punto podemos saber el aire que tenemos, dejando una variable. Dejar el tamaño de botella y la breathing rate configurables quedan como ejercicio para el lector.


?- plan([s(0, 0, 200), s(2, 10, _), s(22, 10, _), s(30, 0, G0)]).
   G0 = 108.
?- plan([s(0, 0, 200), s(2, 10, _), s(50, 10, _), s(70, 0, _)]).
   false.

Enfermedad descompresiva

Antiguamente, se sabía que si los buzos se quedaban mucho tiempo bajo el agua tenían una enfermedad y podían morir. No se sabía muy bien por qué, ya que no era por ahogamiento.

Hoy en día se sabe que se produce debido a la absorción del nitrógeno por parte de nuestros tejidos. Aquí intervienen la ley de Henry y la de Dalton, pero no me quiero meter mucho. Realmente el problema no está en absorber ese nitrógeno extra sino liberarlo. Este nitrógeno se irá liberando solo al ir ascendiendo, en forma de microburbujas. Pero si ascendemos muy rápido o si hemos capturado mucho nitrógeno, estas microburbujas se fusionarán entre ellas y serán burbujas grandes que pueden bloquear arterias y venas. Ese es el verdadero peligro.

Realmente no existe forma 100% garantizada de que a un buzo no le suceda ya que no podemos medir como está siendo la expulsión de burbujas de su organismo, pero sí que podemos prevenirlo: ascendiendo lentamente y teniendo unos límites de tiempo bajo el agua.

Lo primero es relativamente sencillo. Dependiendo de la agencia, las normas varían, pero vamos a poner que no se puede ascender más de 9 metros por minuto. Añadiendo este código a nuestro predicado plan, podemos hacer la comprobación.


    DifDepth is D0 - D,
    % Max SAFE speed to ascend is 9/min SSI or 18/min PADI
    (
	DifDepth > 0 ->
	DifDepth / DifTime =< 9
    ;   true
    ),

Y listo. Podemos ver como si ascendemos muy rápido, nos rechazará el plan.


?- plan([s(0, 0, 200), s(2, 10, _), s(7, 30, _), s(8, 0, _)])
   false.

Para no excedernos el tiempo en el agua hay varios sistemas. Actualmente existen los ordenadores de buceo, que usan un algoritmo como el RGBM (Modelo de Gradiente Reducido de la Burbuja, algoritmo cerrado y con muchas variaciones), Buhlmann (ZH-L16A, ZH-L16B, ...), VPM, Haldane,... Con estos ordenadores buceas y al tener profundímetro, saben exactamente la profundidad a la que te encuentras en cada momento y ajustan de ese modo el tiempo máximo que te queda de forma segura. Existe otro método, las tablas de buceo, como esta de PADI, que nos indican de forma sencilla el tiempo máximo pero con la contra de que son más restrictivas (suelen dar menos tiempo) y hay que llevarlas en la cabeza cuando se bucea. Estas tablas también se han desarrollado mediante algoritmos, en concreto las de PADI usan DSAT.

Una aproximación inicial puede ser intentar utilizar alguna de estas tablas en nuestro programa. Las tablas funcionan cogiendo la profundidad máxima que alcanzamos y dándonos un tiempo máximo de inmersión. Además se pueden utilizar para combinar varias inmersiones entre sí, ya que al salir del agua no nos liberaremos del nitrógeno mágicamente, sino que seguirá habiendo mucho dentro de nuestro cuerpo y deberemos ser más conservadores.

Para ello, después de comprobar nuestro plan, vamos a hacer una comprobación extra a través del predicado deco_time. Este predicado toma la profunidad máxima de la inmersión (usando foldl) y el tiempo final. Comprueba que no nos pasamos de tiempo según la profunidad máxima que hayamos alcanzado. Hay muchos valores en la tabla que no vienen. Cuando esto pasa, tanto en el caso de la profundidad o del tiempo, miramos un valor siempre mayor. En el caso del tiempo ponemos un tope para no introducir un bucle infinito.

Además, nos saca el grupo de presión por el que salimos. Esto es importante si vamos a hacer más de una inmersión en un día. Por simplificar, no lo voy a introducir, ya que habría que usar el resto de tablas, añadir descansos, etc.


plan(Plan) :-
    Plan = [X|Xs],
    X = s(0, 0, _),
    plan(X, Xs),
    deco_time(Plan).

deco_time(Plan) :-
    foldl(plan_max_depth, Plan, 0, MaxDepth),
    append(_, [s(T,_,_)], Plan),
    padi_depth(MaxDepth, _PG, T).

Y la parte de la tabla...


% padi(MaxDepth, PressureGroup, Time)
padi(10, a, 10).
padi(10, b, 20).
padi(10, c, 26).
padi(10, d, 30).
padi(10, e, 34).
padi(10, f, 37).
padi(10, g, 41).
padi(10, h, 45).
padi(10, i, 50).
padi(10, j, 54).
padi(10, k, 59).
padi(10, l, 64).
padi(10, m, 70).
padi(10, n, 75).
padi(10, o, 82).
padi(10, p, 88).
padi(10, q, 95).
padi(10, r, 104).
padi(10, s, 112).
padi(10, t, 122).
padi(10, u, 133).
padi(10, v, 145).
padi(10, w, 160).
padi(10, x, 178).
padi(10, y, 199).
padi(10, z, 219).
padi(12, a, 9).
padi(12, b, 17).
padi(12, c, 23).
padi(12, d, 26).
padi(12, e, 29).
padi(12, f, 32).
padi(12, g, 35).
padi(12, h, 38).
padi(12, i, 42).
padi(12, j, 45).
padi(12, k, 49).
padi(12, l, 53).
padi(12, m, 57).
padi(12, n, 62).
padi(12, o, 66).
padi(12, p, 71).
padi(12, q, 76).
padi(12, r, 82).
padi(12, s, 88).
padi(12, t, 94).
padi(12, u, 101).
padi(12, v, 108).
padi(12, w, 116).
padi(12, x, 125).
padi(12, y, 134).
padi(12, z, 147).
padi(14, a, 8).
padi(14, b, 15).
padi(14, c, 19).
padi(14, d, 22).
padi(14, e, 24).
padi(14, f, 27).
padi(14, g, 29).
padi(14, h, 32).
padi(14, i, 35).
padi(14, j, 37).
padi(14, k, 40).
padi(14, l, 43).
padi(14, m, 47).
padi(14, n, 50).
padi(14, o, 53).
padi(14, p, 57).
padi(14, q, 61).
padi(14, r, 64).
padi(14, s, 68).
padi(14, t, 73).
padi(14, u, 77).
padi(14, v, 82).
padi(14, w, 87).
padi(14, x, 92).
padi(14, y, 98).
padi(16, a, 7).
padi(16, b, 13).
padi(16, c, 17).
padi(16, d, 19).
padi(16, e, 21).
padi(16, f, 23).
padi(16, g, 25).
padi(16, h, 27).
padi(16, i, 29).
padi(16, j, 32).
padi(16, k, 34).
padi(16, l, 37).
padi(16, m, 39).
padi(16, n, 42).
padi(16, o, 45).
padi(16, p, 48).
padi(16, q, 50).
padi(16, r, 53).
padi(16, s, 56).
padi(16, t, 60).
padi(16, u, 63).
padi(16, v, 67).
padi(16, w, 70).
padi(16, x, 72).
padi(18, a, 6).
padi(18, b, 11).
padi(18, c, 15).
padi(18, d, 16).
padi(18, e, 18).
padi(18, f, 20).
padi(18, g, 22).
padi(18, h, 24).
padi(18, i, 26).
padi(18, j, 28).
padi(18, k, 30).
padi(18, l, 32).
padi(18, m, 34).
padi(18, n, 36).
padi(18, o, 39).
padi(18, p, 41).
padi(18, q, 43).
padi(18, r, 46).
padi(18, s, 48).
padi(18, t, 51).
padi(18, u, 53).
padi(18, v, 55).
padi(18, w, 56).
padi(20, a, 6).
padi(20, b, 10).
padi(20, c, 13).
padi(20, d, 15).
padi(20, e, 16).
padi(20, f, 18).
padi(20, g, 20).
padi(20, h, 21).
padi(20, i, 23).
padi(20, j, 25).
padi(20, k, 26).
padi(20, l, 28).
padi(20, m, 30).
padi(20, n, 32).
padi(20, o, 34).
padi(20, p, 36).
padi(20, q, 38).
padi(20, r, 40).
padi(20, s, 42).
padi(20, t, 44).
padi(20, u, 45).
padi(22, a, 5).
padi(22, b, 9).
padi(22, c, 12).
padi(22, d, 13).
padi(22, e, 15).
padi(22, f, 16).
padi(22, g, 18).
padi(22, h, 19).
padi(22, i, 21).
padi(22, j, 22).
padi(22, k, 24).
padi(22, l, 25).
padi(22, m, 27).
padi(22, n, 29).
padi(22, o, 30).
padi(22, p, 32).
padi(22, q, 34).
padi(22, r, 36).
padi(22, s, 37).
padi(25, a, 4).
padi(25, b, 8).
padi(25, c, 10).
padi(25, d, 11).
padi(25, e, 13).
padi(25, f, 14).
padi(25, g, 15).
padi(25, h, 17).
padi(25, i, 18).
padi(25, j, 19).
padi(25, k, 21).
padi(25, l, 22).
padi(25, m, 23).
padi(25, n, 25).
padi(25, o, 26).
padi(25, p, 28).
padi(25, q, 29).
padi(30, a, 3).
padi(30, b, 6).
padi(30, c, 8).
padi(30, d, 9).
padi(30, e, 10).
padi(30, f, 11).
padi(30, g, 12).
padi(30, h, 13).
padi(30, i, 14).
padi(30, j, 15).
padi(30, k, 16).
padi(30, l, 17).
padi(30, m, 18).
padi(30, n, 19).
padi(30, o, 20).
padi_depth(D, PG, T) :-
    T < 219,
    (
	padi(D, _, _) ->
	(
	    padi(D, PG, T) ->
	    true
	;   (
	    T1 is T + 1,
	    padi_depth(D, PG, T1)
	)
	)
    ;
	(
	    D1 is D+1,
	    padi_depth(D1, PG, T)
	)
    ).

Parada de seguridad

La parada de seguridad es una parada de 3 minutos que se hace entre 6 y 4 metros, antes de finalizar la inmersión. Aunque siguiendo estas tablas no deberíamos nunca tener necesidad de parada de seguridad, sigue siendo muy recomendable, ya que prevenimos más si cabe la enfermedad descompresiva.

Vamos a añadir otro check para ver si en el plan aparece una parada de seguridad. Para ello usaremos en este caso DCGs.


plan(Plan) :-
    Plan = [X|Xs],
    X = s(0, 0, _),
    plan(X, Xs),
    deco_time(Plan),
    phrase(safe_stop, Plan).

safe_stop -->
    ... ,
    [s(T0, D0, _),s(T1, D1, _), _],
    {
	T1 - T0 >= 3,
	between(4, 6, D0),
	between(4, 6, D1)
    }.

safe_stop es una DCG donde cogemos los estados penúltimo y antepenúltimo y comprobamos que entre ellos hay mínimo 3 minutos y que las profundidades están entre 4 y 6 metros.

Más cosas

Con esto ya tendríamos un validador de inmersiones simples de buceo. También nos sirve para generarlas, aunque ya no será inmediato. ¿Qué cosas faltarían? Faltaría poder hacer varias inmersiones en un mismo día, usar otros gases además del aire (Nitrox principalmente) y contar con un algoritmo de descompresión más flexible que las tablas. Pero como esto ya haría el post infumable, decido cortar por aquí. Espero que os haya gustado esta mezcla de buceo y Prolog.

]]>
https://blog.adrianistan.eu/planificar-scuba-prolog Sat, 20 Aug 2022 21:50:43 +0000
Usando el lenguaje mejor pagado del mundo: Clojure https://blog.adrianistan.eu/usando-lenguaje-mejor-pagado-clojure https://blog.adrianistan.eu/usando-lenguaje-mejor-pagado-clojure Hace unas semanas salieron los resultados de la encuesta de StackOverflow. Se trata de una encuesta que puede rellenar cualquier programador, sea cuál sea su nivel (estudiante, junior, etc) y de ahí se pueden intentar sacar algunas conclusiones. Una de ellas es que el lenguaje más amado es Rust, mientras que los más odiados son el trío calavera de COBOL, Matlab y VBA.

No obstante, vivimos en un mundo capitalista y por ello, una pregunta muy interesante es, ¿con cuál cobraré más? La respuesta real sería compleja, ya que existen muchas empresas, con diferentes modelos de negocio, experiencia tanto técnica como en el dominio, saber moverse, país de trabajo, ... Pero StackOverflow, simplificando todo a un lenguaje de programación nos da dos respuestas claras a nivel mundial: Clojure ($106.644) y Erlang ( $103.000 ) (y ya, a más diferencia, F#).

Erlang ya lo trataremos en otro momento. Así que vamos a probar Clojure y ver si podemos empezar a amasar billetes :)

Lo primero es ver qué tipo de lenguaje es Clojure. Se trata de un lenguaje funcional de la familia Lisp, especialmente diseñado para ser interoperable con Java y demás lenguajes de la JVM. Es dinámico y prioriza un estilo de programación basado en el REPL. Además de Java, existe una variante llamada ClojureScript que compila a JavaScript.

Hola Mundo

Habrá que empezar un Hola Mundo como manda la tradición.

. Creamos una carpeta para el proyecto, a continuación creamos una carpeta src y dentro metemos un fichero llamado hello.clj. Su contenido es el siguiente:


(ns hello)

(defn run [opts]
  (println "Hello world!"))

Básicamente estamos creando una función run que llama a println. Se trata de un lenguaje Lisp claramente, donde en cada paréntesis lo primero es la función y después van los argumentos. A parte de paréntesis tenemos otros símbolos como corchetes. Esto es porque ciertos tipos de datos como los vectores usan otros caracteres. Clojure es famoso por sus estructuras de datos inmutables, pero eficientes, así como la simpleza en la sintaxis de estas (listas con paréntesis, vectores con corchetes, maps con llaves, ...).

Lo podemos ejecutar con:


clj -X hello/run

También podríamos haber generado un JAR como cualquier otro lenguaje de la JVM.

Una calculadora RPN

El ejemplo que he elegido para ver si me empiezan a llover billetes es una pequeña calculadora en notación polaca inversa (RPN). Soportará solo las operaciones más básicas (suma, resta, multiplicación y división).

Para ello vamos a coger una línea de entrada, vamos a dividir la línea por espacios y cada "palabra" la vamos a intentar reconocer, ya sea si es símbolo o si es un número. Para esto último hacemos la función parse-token


(defn parse-token [word]
  (cond
    (= word "+") :sum
    (= word "-") :sub
    (= word "*") :mul
    (= word "/") :div
    :else (try
            (Integer/parseInt word)
	    (catch Exception e (println (str "Input " word " ignored!")) nil))))

En esta función tenemos un argumento y un cond (similar a Switch pero con una expresión diferente en cada línea. Para los símbolos devolvemos un átomo que representa cada símbolo. Para los números llamamos a la clase de Java Integer y en concreton al método estático parseInt. Como este método puede devolver una excepción, la capturamos. Si el usuario introduce algo que no sabemos lo que es, se lo avisaremos y devolveremos nil.


(defn exec-token [stack token]
  (if (number? token)
    (conj stack token)
    (let [len (count stack)]
      (if (> len 1)
        (let [a (nth stack (- len 1))
	      b (nth stack (- len 2))
	      stack1 (pop (pop stack))]
          (conj stack1
            (case token
              :sum (+ a b)
	      :sub (- a b)
	      :mul (* a b)
	      :div (/ a b))))
        (do
	  (println (str "Not enough numbers in the stack to apply " token))
	  stack)))))

La siguiente función que vamos a escribir es la que ejecuta un token. Esta función toma un stack inicial y un token, y devuelve un nuevo stack. Si el token es un número, simplemente se añade al stack. Si es un símbolo se comprueba que haya suficientes números en el stack para la operación, se obtienen los números y se aplican las operaciones según el átomo. Usamos case como un cond simplificado. conj nos permite añadir un elemento al final de un vector. pop nos elimina el último elemento de un vector. nth nos permite acceder a un elemento por su posición del vector. Al ser vectores y no listas, esta operación sí es rápida (en otros Lisp con listas por defecto, no sería tan sencillo).


(defn calc-step [stack]
  (print "=>")
  (flush)
  (if-let [line (read-line)]
    (let [words (str/split line #" ")
          tokens (filter some? (map parse-token words))
          new-stack (reduce exec-token stack tokens)]
      (println new-stack)
      new-stack)
    (System/exit 0)))


(defn run [opts]
  (println "Welcome to Clojure RPN")
  (loop [stack []]
    (recur (calc-step stack))))

Las últimas dos funciones son las que controlan el flujo. Una función run que se ejecuta en bucle indefinidamente (usamos loop y recur para optimizar la recursión) y una función calc-step que muestra la línea de pedir datos, lee los datos, si no hay datos se sale, y si los hay, obtiene las palabras, las tokeniza, filtra los nulos y finalmente ejecuta los tokens uno a uno para obtener un nuevo stack (lo hacemos con reduce), el cuál imprimimos y devolvemos.


aarroyoc@adrianistan ~/d/b/clojure-rpn (master)> clj -X main/run
Welcome to Clojure RPN
=>12 13 + 2 *
[50]
=>12
[50 12]
=>/
[6/25]
=>1
[6/25 1]
=>+
[31/25]
=>hola
Input hola ignored!
[31/25]
=>hola 12 13
Input hola ignored!
[31/25 12 13]
=>+ +
[656/25]
=>

Con esto, hemos hecho un vistazo rápido por Clojure. No hemos podido entrar en las capacidades de concurrencia y STM que dispone el lenguaje, aunque si veo interés se puede buscar un ejemplo donde se vean ambos usos.

]]>
https://blog.adrianistan.eu/usando-lenguaje-mejor-pagado-clojure Sun, 14 Aug 2022 12:34:39 +0000
Resolviendo el problema de las 3 jarras usando automaton y clpz https://blog.adrianistan.eu/resolviendo-3-jarras-jugs-automaton-clpz https://blog.adrianistan.eu/resolviendo-3-jarras-jugs-automaton-clpz El problema de las 3 jarras (Three jugs) consiste en 3 jarras de agua, de 3L, 5L y 8L. Las dos primeras están vacías y la de 8L está llena. El objetivo es que, echando el agua de unas jarras a otras, consigamos tener en alguna jarra 4L exactos. Este problema lo vamos a resolver en Prolog intentando usar una técnica más avanzada de la librería clpz, automaton. Los usos básicos de clpz ya los vimos en otro post.

.

¿Qué nos ofrece automaton?

Los predicados automaton (automaton/3 y automaton/8) se usan en problemas de scheduling para obtener el orden de las tareas a realizar si estas pueden describirse como un autómata finito determinista (AFD, en inglés las siglas son DFA). Es decir, si describimos el problema como un AFD, podremos pasárselo a clpz para que encuentre la forma más corta de resolver realizar las tareas.

Veamos un ejemplo muy sencillo. Nosotros podemos definir un AFD para analizar la expresión regular "0*1*0*". Es decir, N ceros, seguido de N unos, seguido de N ceros. El diagrama de estados sería el siguiente:


Diagrama sacado del libro Constraint Solving and Planning with Picat

Nosotros empezamos en el estado S1. Si leemos un cero, seguimos en S1. Si leemos un uno, pasamos a S2. En S2 si recibimos un uno seguimos en S2. Si recibimos un cero pasamos a S3. Si recibimos un 1 es un error. Si recibimos un cero, seguimos en S3 hasta acabar.

¿Cómo pasamos esto a automaton? En primer lugar, vamos a usar el predicado automaton/3 que es más sencillo. El otro contiene más opciones y se puede usar en automátas más complejos. El predicado tiene 3 argumentos: lista de entradas, estados especiales (entradas y salidas; source y sink) y finalmente un listado de arcos entre estados tomando una acción. Podemos usarlo para comprobar si una lista cumple el autómata.


?- Vs = [0,0,1,1,1,0], automaton(Vs, [source(s1), sink(s1), sink(s2), sink(s3)], [arc(s1,0,s1), arc(s1,1,s2),arc(s2,1,s2),arc(s2,0,s3),arc(s3,0,s3)]).

Pero como estamos en Prolog, también podemos preguntar qué listas de longitud N se pueden construir. Para ello necesitamos llamar a label/1, ya que estamos en clpz. Un ejemplo con N=6


?- length(Vs, 6), automaton(Vs, [source(s1), sink(s1), sink(s2), sink(s3)], [arc(s1,0,s1), arc(s1,1,s2),arc(s2,1,s2),arc(s2,0,s3),arc(s3,0,s3)]), label(Vs).

El problema de las 3 jarras

El principal problema de resolver problemas mediante su traslación a DFA, es que muchas veces se requieren muchos pasos para transformar los datos a un DFA correcto.

En este caso lo que voy a hacer es buscar una representación programática del estado. Como son 3 jarras, usaré un guión para separar entre los litros que contiene cada jarra en este momento. Es decir, el término 1-2-5 representará al estado en que la primera jarra tiene 1L, la segunda tiene 2L y la tercera tiene 5L. Con esta representación el estado inicial será 0-0-8 y solo hay uno.

Sacar los estados finales ya es más complicado. Para ello primero voy a generar todos los posibles estados que se pueden hacer con 3 jarras y 8L. De esos filtraré en los que alguna jarra contiene 4L exactos.


states(States) :-
    [A,B,C] ins 0..8,
    A+B+C #= 8,
    findall(A-B-C, label([A,B,C]), States).

end_states(States, EndStates) :-
    findall(sink(S), (
		member(S, States),
		S = A-B-C,
		(A = 4; B = 4; C = 4)
	    ), EndStates).

Ahora toca la parte maś compleja que es definir los arcos. Evidentemente no los vamos a escribir a mano, sino que vamos a hacer reglas. Como tenemos ya todos los estados de 8L, vamos a ir filtrando los arcos que van entre dos estados posibles diferentes con una acción. Las acciones las vamos a numerar del 1 al 6, ya que automaton requiere que estas acciones sean números enteros.

De un estado se puede pasar a otro a través de un arco haciendo una acción si tiene sentido el movimiento de litros que hacemos (llenamos la jarra hasta el tope o hasta que nos quedamos sin agua en el origen). Usamos aritmética de clpz.


arcs(States, Arcs) :-
    findall(arc(S0, Action, S1),(
		member(S0, States),
		member(S1, States),
		S0 \= S1,
		arc(S0, Action, S1)
	    ), Arcs).

% Arcs
% Pour from A to B
arc(A-B-C, 1, X-Y-Z) :-
    Dif #= min(A, 5-B),
    X #= A - Dif,
    Y #= B + Dif,
    Z = C.
% Pour from A to C
arc(A-B-C, 2, X-Y-Z) :-
    Dif #= min(A, 8-C),
    X #= A - Dif,
    Y = B,
    Z #= C + Dif.
% Pour from B to C
arc(A-B-C, 3, X-Y-Z) :-
    Dif #= min(B, 8-C),
    X = A,
    Y #= B - Dif,
    Z #= C + Dif.
% Pour from B to A
arc(A-B-C, 4, X-Y-Z) :-
    Dif #= min(B, 3-A),
    X #= A + Dif,
    Y #= B - Dif,
    Z = C.
% Pour from C to A
arc(A-B-C, 5, X-Y-Z) :-
    Dif #= min(C, 3-A),
    X #= A + Dif,
    Y = B,
    Z #= C - Dif.
% Pour from C to B
arc(A-B-C, 6, X-Y-Z) :-
    Dif #= min(C, 5-B),
    X = A,
    Y #= B + Dif,
    Z #= C - Dif.

¡Ya tenemos todas las piezas! Solo hace falta juntarlas para resolver el problema.


:- use_module(library(lists)).
:- use_module(library(clpz)).

solve(Vs) :-
    states(States),
    end_states(States, EndStates),
    arcs(States, Arcs),
    length(Vs, _),
    automaton(Vs, [source(0-0-8)|EndStates], Arcs),
    label(Vs).

% ?- solve(Vs).
%   Vs = [6,4,2,4,6,4]
%;  Vs = [5,1,5,1,3,1,5]
%;  Vs = [6,4,2,4,6,4,2]
%;  Vs = [5,1,5,1,3,1,5,1]
%;  Vs = [5,1,6,4,2,4,6,4]
%;  Vs = [5,2,6,4,2,4,6,4]
%; ...

La solución más corta parece ser [6,4,2,4,6,4]. Con esto ya tendríamos el problema resuelto, pero es cierto que es complicado de interpretar. Vamos a añadir un predicado para explicar la solución.


print(Xs) :- print(Xs, 0-0-8).
print([], _).
print([X|Xs], S) :-
    arc(S, X, S1),
    S = A-B-C,
    S1 = D-E-F,
    format("Going from (~d,~d,~d) to (~d, ~d, ~d)\n", [A,B,C,D,E,F]),
    print(Xs, S1).

Con esto ya tenemos resuelto el problema de las 3 jarras.

]]>
https://blog.adrianistan.eu/resolviendo-3-jarras-jugs-automaton-clpz Sun, 19 Jun 2022 16:27:42 +0000
El famoso problema del 15-puzzle en Prolog https://blog.adrianistan.eu/famoso-problema-15-puzzle https://blog.adrianistan.eu/famoso-problema-15-puzzle Uno de los problemas clásicos dentro del campo de la Inteligencia Artificial simbólica es el 15-puzzle. Es exactamente igual que el 8-puzzle, pero con un tablero más grande. La idea es que tenemos un tablero cuadrado NxN (en el caso del 15-puzzle, N=4; en el 8-puzzle, N=3) donde hay piezas desordenadas y tenemos que ordenarlas para que se siga la secuencia numérica en orden. Realmente hay una pieza que falta y que usamos de hueco para intercambiar posiciones. Las piezas se pueden mover arriba, abajo, a la izquierda o a la derecha pero solo si la casilla a donde vamos es donde está el hueco (y el hueco pasa a donde estaba la pieza antes).

Este problema se puede resolver de distintas formas. Nos servirá para probar diferentes técnicas de resolución en Prolog.

Tablero que vamos a resolver (resolución óptima en 34 movimientos):

13954
15618
10211
143712

Solución por backtracking

Ya que Prolog implementa backtracking por nosotros de forma transparente, esta solución es la más natural y sencilla que podemos hacer en Prolog. La idea es partir de un estado inicial (en este caso va a ser el de la foto) y proveer predicados de movimientos (izquierda, derecha, arriba, abajo) entre un estado y otro que solo se puedan dar si el movimiento es posible dada las reglas. Vamos almacenando el historial de movimientos válidos en una lista hasta llegar al tablero resuelto. El propio backtracking de Prolog nos irá descartando los movimientos que aunque fueron válidos no nos llevaban a la solución final.

Nuestra primera versión será esto:


medium_state(table(13,9,5,4,15,6,1,8,void,10,2,11,14,3,7,12)).
end_state(table(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,void)).


solve(S0, H, H) :-
    end_state(S0).
solve(S0, H0, H) :-
    move(M, S0, S),
    solve(S, [M|H0], H).

move(left, table(X1,void,X3,X4,X5,X6,X7,X8,X9,X10,X11,X12,X13,X14,X15,X16), table(void,X1,X3,X4,X5,X6,X7,X8,X9,X10,X11,X12,X13,X14,X15,X16)).
move(left, table(X1,X2,void,X4,X5,X6,X7,X8,X9,X10,X11,X12,X13,X14,X15,X16), table(X1,void,X2,X4,X5,X6,X7,X8,X9,X10,X11,X12,X13,X14,X15,X16)).
move(left, table(X1,X2,X3,void,X5,X6,X7,X8,X9,X10,X11,X12,X13,X14,X15,X16), table(X1,X2,void,X3,X5,X6,X7,X8,X9,X10,X11,X12,X13,X14,X15,X16)).
move(left, table(X1,X2,X3,X4,X5,void,X7,X8,X9,X10,X11,X12,X13,X14,X15,X16), table(X1,X2,X3,X4,void,X5,X7,X8,X9,X10,X11,X12,X13,X14,X15,X16)).
move(left, table(X1,X2,X3,X4,X5,X6,void,X8,X9,X10,X11,X12,X13,X14,X15,X16), table(X1,X2,X3,X4,X5,void,X6,X8,X9,X10,X11,X12,X13,X14,X15,X16)).
move(left, table(X1,X2,X3,X4,X5,X6,X7,void,X9,X10,X11,X12,X13,X14,X15,X16), table(X1,X2,X3,X4,X5,X6,void,X7,X9,X10,X11,X12,X13,X14,X15,X16)).
move(left, table(X1,X2,X3,X4,X5,X6,X7,X8,X9,void,X11,X12,X13,X14,X15,X16), table(X1,X2,X3,X4,X5,X6,X7,X8,void,X9,X11,X12,X13,X14,X15,X16)).
move(left, table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,void,X12,X13,X14,X15,X16), table(X1,X2,X3,X4,X5,X6,X7,X8,X9,void,X10,X12,X13,X14,X15,X16)).
move(left, table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,void,X13,X14,X15,X16), table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,void,X11,X13,X14,X15,X16)).
move(left, table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,X12,X13,void,X15,X16), table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,X12,void,X13,X15,X16)).
move(left, table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,X12,X13,X14,void,X16), table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,X12,X13,void,X14,X16)).
move(left, table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,X12,X13,X14,X15,void), table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,X12,X13,X14,void,X15)).
    
move(right, S0, S) :-
    move(left, S, S0).

move(up, table(X1,X2,X3,X4,void,X6,X7,X8,X9,X10,X11,X12,X13,X14,X15,X16), table(void,X2,X3,X4,X1,X6,X7,X8,X9,X10,X11,X12,X13,X14,X15,X16)).
move(up, table(X1,X2,X3,X4,X5,void,X7,X8,X9,X10,X11,X12,X13,X14,X15,X16), table(X1,void,X3,X4,X5,X2,X7,X8,X9,X10,X11,X12,X13,X14,X15,X16)).
move(up, table(X1,X2,X3,X4,X5,X6,void,X8,X9,X10,X11,X12,X13,X14,X15,X16), table(X1,X2,void,X4,X5,X6,X3,X8,X9,X10,X11,X12,X13,X14,X15,X16)).
move(up, table(X1,X2,X3,X4,X5,X6,X7,void,X9,X10,X11,X12,X13,X14,X15,X16), table(X1,X2,X3,void,X5,X6,X7,X4,X9,X10,X11,X12,X13,X14,X15,X16)).
move(up, table(X1,X2,X3,X4,X5,X6,X7,X8,void,X10,X11,X12,X13,X14,X15,X16), table(X1,X2,X3,X4,void,X6,X7,X8,X5,X10,X11,X12,X13,X14,X15,X16)).
move(up, table(X1,X2,X3,X4,X5,X6,X7,X8,X9,void,X11,X12,X13,X14,X15,X16), table(X1,X2,X3,X4,X5,void,X7,X8,X9,X6,X11,X12,X13,X14,X15,X16)).
move(up, table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,void,X12,X13,X14,X15,X16), table(X1,X2,X3,X4,X5,X6,void,X8,X9,X10,X7,X12,X13,X14,X15,X16)).
move(up, table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,void,X13,X14,X15,X16), table(X1,X2,X3,X4,X5,X6,X7,void,X9,X10,X11,X8,X13,X14,X15,X16)).
move(up, table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,X12,void,X14,X15,X16), table(X1,X2,X3,X4,X5,X6,X7,X8,void,X10,X11,X12,X9,X14,X15,X16)).
move(up, table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,X12,X13,void,X15,X16), table(X1,X2,X3,X4,X5,X6,X7,X8,X9,void,X11,X12,X13,X10,X15,X16)).
move(up, table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,X12,X13,X14,void,X16), table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,void,X12,X13,X14,X11,X16)).
move(up, table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,X12,X13,X14,X15,void), table(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,void,X13,X14,X15,X12)).

move(down, S0, S) :-
    move(up, S, S0).

La mayor parte del código es definir los movimientos posibles (aprovechamos que left/right y up/down son inversos para reducir código). El predicado solve tiene dos reglas. En una, se comprueba si ya hemos llegado al estado final. En la otra, se hace un movimiento y se vuelve a llamar a solve. El movimiento realizado se guarda en una lista que usamos de historial.

Este código podría funcionar en ciertos problemas pero en este puzzle no, ya que es muy fácil caer en bucles de movimientos de los que no se puede salir. Es por ello que hay que modificar solve para introducir un set de estados ya visitados. Si volvemos a llegar a un estado ya visitado, no aceptamos el movimiento y Prolog por backtracking intentará buscar otra solución (y deshacerá el historial, ...)


solve(S0, _, H, H) :-
    end_state(S0).
solve(S0, Visited, H0, H) :-
    move(M, S0, S),
    \+ member(S, Visited),
    solve(S, [S|Visited], [M|H0], H).

% ?- medium_state(S), solve(S, [], [], H).

Este código ya sí, generará una solución. Pero tiene dos problemas: la solución tardará mucho en calcularse y el camino que obtengamos en el historial no será óptimo probablemente. ¿Por qué? Porque Prolog por defecto tiene una estrategia de búsqueda que es Depth First Search. Es decir, ante una disyuntiva de qué camino tomar, elige el que añade más pasos sobre el estado donde se encuentra. Esta opción se eligió porque es muy eficiente en uso de memoria, pero es muy mala en CPU y en este problema no garantiza optimalidad, aunque sí encontrar una solución (si la solución existe y evitamos bucles infinitos).

Breadth First Search

Un algoritmo alternativo es Breadth First Search. En este caso, tendremos que modificar el predicado solve nuevamente, pero el resto puede ser igual. En este caso, vamos a ir guardando los estados que visitar en una lista, de modo que los primeros estados que vayamos descubriendo son los primeros que vamos a ir analizando. Solo cuando hayamos acabado con los estados de N movimientos, pasará a los de N+1 movimientos. En este caso concreto, la solución sí será óptima. Sin embargo, ahora el historial y el set de visitados deben arrastrarse en cada estado, lo que aumenta el uso de memoria.


solve([state(S0,_,H)|_], H) :-
    end_state(S0).
solve([state(S0, Visited, H0)|States], H) :-
    findall(state(S, [S0|Visited], [M|H0]), (move(M, S0, S),\+ member(S, Visited)), NewStates),
    append(States, NewStates, AllStates),
    solve(AllStates, H).

% ?- medium_state(S), solve([state(S, [], [])], H).

Con este nuevo solve, vamos añadiendo los estados que tenemos que visitar a una lista, añadiéndolos al final y siempre que no estén repetidos dentro del set de visitados de cada estado. Además se guarda el histórico de como se ha llegado a cada estado. Con ciertas instancias del puzzle, este método puede ser ya bastante rápido, sobre todo si sabemos que se necesitan pocos movimientos para acabar. Pero en otros casos puede tardar bastante todavía y encima, gasta mucha más memoria. Por suerte, hay otros algoritmos.

Algoritmo A*

Tanto DFS como BFS son algoritmos de búsqueda no informados. Eso quiere decir que van explorando las posibles soluciones sin ninguna "pista" que les indique por donde es mejor ir. A* es un algoritmo de búsqueda informado. En este caso, calculamos una heurística, que es una pista de por donde tiene que seguir la búsqueda, acortando el número de nodos a comprobar (idealmente). Si queremos obtener un resultado óptimo al finalizar el algoritmo deberemos usar heurísticas admisibles. Una heurística se considera admisible si no estima de más el costo de una acción futura.

Una heurística muy popular es calcular la distancia de Manhattan de cada elemento respecto a donde debería estar. Es admisible y no supone mucha carga calcularla. Otras heurísticas interesantes son la Walking Distance y los patrones precalculados


solve([_-state(S0,H,_)|_], _, H) :-
    end_state(S0).
solve([_-state(S0, H0, N0)|States], Visited, H) :-
    findall(F-state(S, [M|H0], N), (
        move(M, S0, S),
        \+ member(_-state(S,_,_), States),
        \+ member(S, Visited),
        h_value(S, HVal),
        N is N0+1,
        F is HVal + N
    ), NewStates),
    append(States, NewStates, AllStates),!,
    keysort(AllStates, OrderedAllStates),
    solve(OrderedAllStates, [S0|Visited], H).

h_value(S, H) :-
    S =.. [_|Ls],
    maplist(distance(Ls), Ls, Ds),
    sum_list(Ds, H).

distance(_, void, 0) :- !.
distance(Ls, E, D) :-
    nth0(N, Ls, E),
    X0 is N // 4,
    Y0 is N mod 4,
    X1 is (E-1) // 4,
    Y1 is (E-1) mod 4,
    D is abs(X1-X0)+abs(Y1-Y0).

% ?- medium_state(S), solve([0-state(S, [], 0)], [], H).

Con este código implementamos A* en Prolog. La lista de estados a visitar (o abiertos) es lo primero que se le pasa a solve. Cada estado tiene primero su valor de F para que el predicado estándar keysort/2 pueda ordenar los estados según F. Después cada estado tiene su valor, su histórico y su número N de coste hasta llegar al estado. Es necesario para calcular en los nuevos estados el nuevo F. Para cada estado nuevo, que no esté en la lista, ni se haya visitado, se calcula el valor de la heurística H (distancia de Manhattan). Se calcula F con la suma del nuevo valor N (en este caso el coste siempre es 1, todos los movimientos cuestan igual) y el valor de H. Posteriormente se añade a la lista y se reordenan.

Algunos tiempos por aquí en mi máquina, sin ser muy exhaustivo:

AlgoritmoSistema PrologTiempo¿Solución óptima?
DFSSWI Prolog 8.4.2> 5 min (parado)N/A
BFSScryer Prolog 0.9.0Se queda sin memoriaN/A
BFSSWI Prolog 8.4.2Se queda sin memoriaN/A
A* - ManhattanScryer Prolog 0.9.061.792s
A* - ManhattanSWI Prolog 8.4.28.825s
A* - ManhattanCiao 1.20.04.82s
A* - ManhattanGNU Prolog 1.5.02.959s
]]>
https://blog.adrianistan.eu/famoso-problema-15-puzzle Sat, 11 Jun 2022 22:59:38 +0000
Mercury: cuando Prolog y Haskell tuvieron un bebé https://blog.adrianistan.eu/mercury-prolog-haskell-bebe https://blog.adrianistan.eu/mercury-prolog-haskell-bebe Mercury es un lenguaje de programación muy poco conocido pero muy interesante. De forma corta podríamos describirlo como el hijo que tendrían Prolog y Haskell. Se trata de un lenguaje que combina los paradigmas lógico y funcional en uno solo. Fue desarrollado por la Universidad de Melbourne en Australia aunque ahora sobrevive como proyecto opensource. Su objetivo era la creación de software grande, robusto y eficiente. La sintaxis es tremendamente parecida a Prolog, por lo que aunque haya algunas modificaciones, es relativamente fácil escribir en él si ya conocemos Prolog. Mercury tiene tipado fuerte y estático y genera ejecutables nativos. Es algo más puro que Prolog ya que gestiona mejor los side effects y es probablemente el lenguaje lógico más rápido que existe a día de hoy.

Para empezar con Mercury, una opción es descargar la última versión estable (formato DEB) desde la página oficial aunque muchas distros Linux ya han empaquetado Mercury. También soporta Windows y otros sistemas operativos pero hay que compilar desde cero, lo cuál puede ser un poco complicado de primeras.

Hola mundo en Mercury

La primera diferencia con Prolog la vamos a tener nada más ponernos a programar. No existe un shell donde introducir queries, sino que vamos a necesitar un fichero y compilarlo.

En un fichero llamado hola.m podemos escribir lo siguiente:


:- module hola.
:- interface.

:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.
main(!IO) :-
    write_string("¡Hola Mercury!\n", !IO).

Si conoces Prolog, verás que la sintaxis es muy similar pero hay bastantes cosas nuevas. No voy a explicar las cosas que son iguales que Prolog, para eso mejor ve al tutorial de Prolog. Lo primero es indicar el módulo en el que estamos. En Mercury todos los programas están organizados en módulos, normalmente con el mismo nombre que el fichero. Cada módulo se compone de dos secciones, una interfaz y una implementación. La interfaz es lo que el módulo deja ver al resto del programa, y la implementación es completamente privada.

En Mercury es necesario declarar los predicados que luego vamos a definir, indicando los tipos y modos. En este caso, para definir main, necesitamos acceder a tipos que están el módulo io, así que lo importamos antes. Los tipos del predicado main (que se ejecuta por defecto) son io::di e io::uo. Sin entrar mucho en detalles, el primero significa destructive input y el segundo, unique output. Con is det declaramos el predicado como determinista (siempre tiene solución y es única).

En la implementación vemos que aceptamos un único argumento (no eran dos?) y llamamos a write_string, que imprime un string y le tenemos que pasar el argumento de IO también. IO es una variable ya que, al igual que en Prolog, en Mercury las variables se distinguen porque empiezan por mayúsculas.


main(IO, IO1) :-
    write_string("¡Hola Mercury!\n", IO, IO1)

Y ahí ya se ve claramente como los argumentos coinciden con lo que declaramos en la interfaz. Estas variables de estado se pueden encadenar y el azúcar sintáctico también nos ayuda a hacer refactorizaciones más sencillas en código con variables de estado encadenadas.

Para compilar, ejecutamos mmc


mmc hola.m

Y ya tenemos un ejecutable nativo llamado "hola" que podemos ejecutar.

Funciones

Aparte de predicados, Mercury también tiene funciones, algo de lo que carece Prolog. Veamos este ejemplo para calcular la sucesión de fibonacci:


:- module fib.
:- interface.

:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.

:- import_module int.

:- func fibonacci(int) = int.

fibonacci(N) =
    ( if (N = 0;N = 1) then
        1
    else
        fibonacci(N-1) + fibonacci(N-2)
    ).

main(!IO) :-
    F = fibonacci(5),
    io.write_int(F, !IO),
    io.nl(!IO).

En este ejemplo, declaramos dentro de la implementación la función fibonacci, que toma un int y devuelve un int. Para usar int tenemos que importar el módulo, como solo lo necesitamos en la implementación, no lo importamos en la interfaz. Después la función se define con otra sintaxis. Aquí podemos ver los if-then-else (aunque la sintaxis de Prolog ( -> ; ) también es admitida). Más diferencias que podemos ver respecto a Prolog es que las expresiones aritméticas se evalúan directamente.

Por último, en main, llamamos a la función fibonacci e imprimimos el resultado. Aquí usamos el nombre cualificado de los predicados, con io delante. Es opcional en muchos casos pero altamente recomendable ponerlo.

Tipos de datos

Vamos a entrar más en detalle en los tipos de datos de Mercury, que toman una fuerte inspiración de Haskell.

Lo primero que hay que tener en cuenta es que en Mercury, todos las variables van a ser inmutables. Sin embargo, a diferencia de Prolog, el compilador puede inferir lugares donde aplicar mutabilidad internamente para mejorar el rendimiento.

Los tipos más básicos son int, float, string y char. Además contamos con tuplas y listas.

Las tuplas son un número de elementos prefijados de tipo diferente, por ejemplo, {5, "hola"} es una tupla de tipo {int, string}

Las listas en Mercury son colecciones de N elementos, todos ellos del mismo tipo.[3,4,5,6] es de tipo list(int). Las listas se pueden deconstruir como en Prolog usando la barra vertical. Si L = [1,2,3,4] y L = [X|Xs], entonces X es igual a 1 y Xs a [2,3,4].

Tipos suma

Mercury soporta tipos suma. Los más básicos son simples enumeraciones.


:- type color ---> red ; green ; yellow ; blue ; black ; white ; purple.

En este caso crearíamos el tipo color con esos posibles valores. Pero cada enumeración puede llevar asociado algún dato extra.


:- type font ---> serif(color) ; sans_serif ; mono.

En este caso los tipos de fuente pueden ser Serif, Sans-Serif y Mono, pero en el caso de la Serif, lleva asociado un color.

Estos campos extra pueden tener un nombre, para acceder a ellos más fácilmente mediante ^. De este modo tendríamos lo que en otros lenguajes se llama records o estructuras.


:- type mail ---> mail( from :: string, to :: string, msg :: string).

% mas adelante
...
ToAddress = Mail^to,
...

Con := podemos crear un nuevo record igual al anterior pero cambiando el valor de un campo.


:- type mail ---> mail( from :: string, to :: string, msg :: string).

...
    Mail = mail("a@example.org", "b@example.org", "Test message"),
    Mail1 = (Mail^to := "c@example.org"),
    (if Mail1^to = "b@example.org" then
        io.write_string("Mail to B\n", !IO)
    else
        io.write_string("Mail NOT to B\n", !IO)
    ),
...

En este caso se ejecutaría la parte de "Mail NOT to B".

Tipos polimórficos

Mercury soporta polimorfismo a nivel de tipo. Es posible crear estructuras de datos genéricas para un tipo de dato T. El ejemplo más archiconocido es la lista, que en Mercury podríamos definirla así.


:- type list(T) ---> [] ; [T | list(T)].

Tipos equivalentes

En Mercury podemos crear tipos idénticos a otros, pero con distinto nombre para mejorar la legibilidad.


:- type money == int.
:- type radius == float.

Modos en Mercury

Un elemento vital para Mercury es la declaración de modos de los predicados. En parte ya lo hemos visto, ya que al declarar los predicados estábamos indicando los modos a la vez, pero en realidad puede separarse en dos directivas diferentes. Los modos indican a Mercury la "dirección" del código así como las posibles soluciones.

Partamos de un ejemplo sencillo, un predicado (no función) de suma, donde pasamos los dos sumandos mediante una tupla.


:- pred suma_tuple({int, int}::in, int::out) is det.

suma_tuple({X, Y}, Z) :-
    Z = X + Y.

En la línea de declaración hemos combinado los tipos y los modos. Esto se puede separar. Será obligatorio separar si queremos que un predicado se compile con más de un modo.


:- pred suma_tuple({int, int}, int).
:- mode suma_tuple(in, out) is det.
suma_tuple({X, Y}, Z) :-
    Z = X + Y.

En la declaración de modo indicamos que variables son de entrada y cuáles de salida. Además definimos el número de soluciones. Los posibles valores son: det (=1), semidet (<=1), multi(>= 1), nondet (>=0) y failure (=0).


:- pred phone(string, string).
:- mode phone(in, out) is semidet.
phone("123456789", "Benito").
phone("245678901", "Mar").

En este ejemplo phone va a tener un modo, donde proveemos un número y nos devuelve el nombre. Es semidet ya que puede que no encuentre el teléfono (no hay solución) o que sí. Pero no podemos devolver más de una solución (podríamos si lo declaramos como nondet).

Con este modo podemos hacer estas llamadas:


    (if phone("123456789", _Name) then
        io.write_string("Phone found\n", !IO)
    else
        true
    ),

Sin embargo, no podemos hacer lo inverso.


    (if phone(_Phone, "Benito") then
        io.write_string("Phone found\n", !IO)
    else
        true
    ),

Para que compile deberemos agregar este otro modo:


:- mode phone(out, in) is semidet.

Existen más tipos de modo pero no serán tan habituales.

Ejemplo más grande

He aquí un ejemplo de como se resolvería el problema 1 del Advent of Code de 2021 en Mercury (ambas partes).


:- module aoc2021day1.
:- interface.

:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.

:- import_module int.
:- import_module list.
:- import_module string.

:- pred load_data(string::in, list(string)::out, io::di, io::uo) is det.

load_data(Filename, ReadData, !IO) :-
    io.read_named_file_as_lines(Filename, OpenResult, !IO),
    (if OpenResult = ok(Data) then
        ReadData = Data
     else
        ReadData = []
    ).

:- pred solve(list(int)::in, int::out) is det.
solve([], 0).
solve([_], 0).
solve([X,Y|Xs], N) :-
    solve([Y|Xs], N0),
    (if X < Y then
        N = N0 + 1
    else
        N = N0
    ).

:- pred slide_window(list(int)::in, list(int)::out) is det.
slide_window([], []).
slide_window([_], []).
slide_window([_,_], []).
slide_window([X,Y,Z|Xs], Ys) :-
    slide_window([Y,Z|Xs], Ys0),
    N = X + Y + Z,
    Ys = [N|Ys0].

:- pred solve2(list(int)::in, int::out) is det.
solve2(Data, N) :-
    slide_window(Data, Slides),
    solve(Slides, N).

main(!IO) :-
    load_data("input", ReadData, !IO),
    (if list.map(string.to_int, ReadData, Data) then
        solve(Data, N),
	io.format("Solution 1: %d\n", [i(N)], !IO),
	solve2(Data, M),
	io.format("Solution 2: %d\n", [i(M)], !IO)
    else
        io.write_string("Invalid file\n", !IO)
    ).

Typeclasses

En Mercury podemos crear typeclasses de forma muy similar a Haskell. También son muy parecidas a las traits de Rust. Con typeclasses podemos restringir los argumentos de un predicado/función a tipos que implementen la typeclass que nos interesa.


:- typeclass shape(T) where [].

:- type point ---> point(float, float).
:- type rectangle ---> rectangle(point, float, float).
:- type circle ---> circle(point, float).

:- instance shape(rectangle) where [].
:- instance shape(circle) where [].

En este ejemplo, creamos una typeclass llamada shape, sin más requisitos y creamos 3 tipos: point, rectangle y circle. De estos, dos van a ser instancias de la typeclass shape: rectangle y circle.

Posteriormente podremos definir predicados donde solo admitamos tipos que implementen shape, pero nos da igual si son rectangle o circle, de la siguiente forma.


:- pred shapes_only(T::in) is det <= shape(T).

Sin embargo, la verdadera utilidad es que las typeclasses impongan ciertos predicados que deben implementarse para poder ser instancia de algo. En nuestro ejemplo de shape, podemos poner calcular el área. Es algo que en todas las figuras puede hacerse, pero el método es diferente si es un rectángulo o si es un círculo.

Para ello, declaramos predicados y modos en la typeclass:


:- typeclass shape(T) where [
    pred get_area(T, float),
    mode get_area(in, out) is det
].

Al instanciar, podemos añadir el código directamente o hacer referencia a un predicado externo.


:- instance shape(rectangle) where [
    (get_area(Rect, Area) :-
        Rect = rectangle(_Centre, Width, Height),
	Area = Width * Height
    )
].
:- instance shape(circle) where [
   pred(get_area/2) is circle_area
].

:- pred circle_area(circle::in, float::out) is det.
circle_area(circle(_Centre, Radius), Area) :-
    Area = math.pi * Radius * Radius.

Un ejemplo completo de uso de typeclasses a continuación:


:- module shape.
:- interface.

:- import_module io.
:- pred main(io::di, io::uo) is det.

:- implementation.

:- import_module float.
:- import_module list.
:- import_module math.
:- import_module string.

:- typeclass shape(T) where [
    pred get_area(T, float),
    mode get_area(in, out) is det
].

:- type point ---> point(float, float).
:- type rectangle ---> rectangle(point, float, float).
:- type circle ---> circle(point, float).

:- instance shape(rectangle) where [
    (get_area(Rect, Area) :-
        Rect = rectangle(_Centre, Width, Height),
	Area = Width * Height
    )
].
:- instance shape(circle) where [
   pred(get_area/2) is circle_area
].

:- pred circle_area(circle::in, float::out) is det.
circle_area(circle(_Centre, Radius), Area) :-
    Area = math.pi * Radius * Radius.

:- pred print_area(T::in, io::di, io::uo) is det <= shape(T).
print_area(Shape, !IO) :-
    get_area(Shape, Area),
    io.format("Area of shape: %f\n", [f(Area)], !IO).

main(!IO) :-
    print_area(rectangle(point(1.0, 2.0), 30.0, 10.0), !IO),
    print_area(circle(point(1.0, 2.0), 10.0), !IO).

Con esto ya conoceríamos las principales mejoras de Mercury respecto a Prolog y damos por acabado el tutorial. ¿Qué te parece Mercury? ¿Prefieres la brevedad de Prolog o la explicitud de Mercury?

]]>
https://blog.adrianistan.eu/mercury-prolog-haskell-bebe Sun, 17 Apr 2022 10:40:02 +0000
Teletexto #012 https://blog.adrianistan.eu/teletexto-012 https://blog.adrianistan.eu/teletexto-012 Ya iba siendo hora de otro teletexto, ¿no creéis? Pues sí, ha pasado ya un tiempo y mi lista de enlace ha crecido enormemente. Vamos con algunos de ellos.

Empezamos fuerte hablando de APL, uno de los lenguajes de programación que inventaron un paradigma, en concreto el de los Array Languages. Famoso por su código ultrabreve lleno de símbolos matemáticos (originalmente hacía falta un teclado especial para programar en él), este lenguaje de los años 60 tiene cosas que merecen la pena aprender. Es lo que explica Why APL is a language worth knowing. Si has usado R o Numpy, algunas cosas te sonarán, pero la sintaxis de APL es quizá la mejor para este tipo de programación, incluso a día de hoy.

En la Unión Europea se está trabajando en un nuevo reglamento que puede cambiar bastantes cosas del mundo digital, se trata de la Digital Markets Act. Una de los mayores cambios viene en que se exigirá a ciertos actores dentro de las aplicaciones de mensajerías interoperabilidad. ¿Cómo funcionará esto? En Matrix han hecho una FAQ para entenderlo mejor. Spoiler: no implicará que todos usen la misma API ni que se pasen a un estándar XMPP/Matrix.

Alguna vez he tratado de hacer un juego de mesa en ordenador. Y siempre está la parte de hacerlo multijugador online. Esta parte es compleja y se puede abordar desde diferentes estrategias. En este post, Longelwind nos explica las estrategias más comunes que pueden hacerse para implementar juegos por turnos online.

Hace ya bastante tiempo los programadores asumimos que los GOTO eran herramientas con más malos usos que buenos y su uso se redujo drásticamente, dando origen a la programación estructurada. Sin embargo, para tratar con concurrencia seguimos usando GOTO constántemente, el ejemplo más famoso es el GO del lenguaje Go. Una de las alternativas es la concurrencia estructurada que implementan Trio en Python o las corrutinas de Kotlin.

Los modas en diseño van y vienen. Lo que hace 10 años era feo ahora vuelve a considerarse. Aunque como siempre, hay ligeros detalles que lo modernizan. Algo así pasa con el claymorphism, el nuevo estilo para apps y webs que crece con fuerza. En Smashing Magazine nos hablan de como hemos llegado hasta aquí, que elementos lo distinguen de otros estilos y como lo podemos aplicar en nuestros diseños.

Hay muchas licencias dentro del mundo del software. Algunas privativas, otras de software libre, virales, ... Pero también las hay malas. Tan rematadamente malas que alguien las ha recopilado en un mismo sitio. Licencias que no deberías usar nunca. Algunas son irónicas, otras no.

Daniel Jakots quería tener una dirección IPv6 pero su ISP no se la asignaba. Sin embargo, pensó, pues me asigno una y la anuncio por toda la red, ¡como si fuese mi propio ISP! En su post cuenta lo que hay que hacer para lograrlo.

En este blog ya he hablado de Z3 y vimos como podíamos resolver un sudoku con este demostrador de teoremas. Otra aplicación, bastante práctica, es comprobar que los permisos RBAC funcionan como queremos.

Cuando he usado Gradle, siempre me ha parecido muy complejo. Sin embargo, gracias a este tutorial de fundamentales de Gradle, he podido descubrir como funciona por debajo y como su "magia" no es tal y en realidad, se trata de uno de los task runners más potentes y flexibles.

Se habla mucho de oligopolios tecnológicos. Un sector donde está dominado por ellos son las búsquedas. Las alternativas principales a Google en occidente son Bing de Microsoft y DuckDuckGo, pero este último tiene un problema... No usa su propio índice, depende de Bing también. En este artículo repasan todos los motores de búsqueda 100% independientes con su propio índice y la calidad de sus resultados. ¡Muchos no los conocía!

Un nuevo tipo de base de datos está surgiendo con el Machine Learning. Se trata de las bases de datos vectoriales, especializadas en almacenar vectores y matrices. Un ejemplo es Milvus. He aquí una introducción a ellas.

¿Qué inspiró la creación de Clojure? En esta presentación, ya clásica, Rich Hickey explica como Clojure era la solución a los problemas que veía en la programación.

frawk es un lenguaje muy similar a AWK implementado en Rust. No es 100% compatible a posta, para mejorar en ciertos aspectos pero la idea es muy similar. ¿Logrará reemplazar a AWK?

ichiban/prolog es una implementación de ISO Prolog hecha en Go preparada para ser empotrado en los programas. The only reasonable scripting engine.

Finalmente, he descubierto hace poco una de las pruebas más duras del ciclismo de montaña/gravel. Se trata de la Tour Divide, una carrera sin asistencia desde Canadá hasta México (4500km aproximadamente) y donde no solo hay que pedalear sino sobrevivir en la naturaleza. El récord está en 13 días y 22 horas, ¿alguien lo mejora?

Me despido con esta canción: Airport Lady de Toshiki Kadomatsu. Todavía me quedan muchos enlaces pero esa la dejaremos para la edición 12+1.

]]>
https://blog.adrianistan.eu/teletexto-012 Sun, 3 Apr 2022 15:37:54 +0000
Código Morse en Prolog https://blog.adrianistan.eu/codigo-morse-prolog https://blog.adrianistan.eu/codigo-morse-prolog El otro día me encontré por Twitter con el reto semanal de Brais Moure (@MoureDev). Se trataba de un programa que permitiese pasar texto a código morse y viceversa. Decidí resolver el problema en Prolog, mi lenguaje lógico favorito, y he aquí una explicación para entender la solución. Usaré Scryer Prolog ya que es el más cómodo para trabajar con strings.

La definición del reto es esta:

Crea un programa que sea capaz de transformar texto natural a código morse y viceversa.
- Debe detectar automáticamente de qué tipo se trata y realizar la conversión.
- En morse se soporta raya "—", punto ".", un espacio " " entre letras o símbolos y dos espacios entre palabras " ".
- El alfabeto morse soportado será el mostrado en https://es.wikipedia.org/wiki/Código_morse.

El primer paso será definir unos predicados que contengan el caracter y su equivalente en Morse. Hay que tener en cuenta que el alfabeto en el que nos basamos considera "ch" como letra independiente y por tanto la deberemos añadir también. Esto de "ch" también nos hará tener que meter una regla extra más adelante.


morse("a", ".-").
morse("b", "-...").
morse("c", "-.-.").
morse("ch", "----").
morse("d", "-..").
morse("e", ".").
morse("f", "..-.").
morse("g", "--.").
morse("h", "....").
morse("i", "..").
morse("j", ".---").
morse("k", "-.-").
morse("l", ".-..").
morse("m", "--").
morse("n", "-.").
morse("ñ", "--.--").
morse("o", "---").
morse("p", ".--.").
morse("q", "--.-").
morse("r", ".-.").
morse("s", "...").
morse("t", "-").
morse("u", "..-").
morse("v", "...-").
morse("w", ".--").
morse("x", "-..-").
morse("y", "-.--").
morse("z", "--..").
morse("0", "-----").
morse("1", ".----").
morse("2", "..---").
morse("3", "...--").
morse("4", "....-").
morse("5", ".....").
morse("6", "-....").
morse("7", "--...").
morse("8", "---..").
morse("9", "----.").
morse(".", ".-.-.-").
morse(",", "--..--").
morse("?", "..--..").
morse("\"", ".-..-.").
morse("/", "-..-.").

Una vez tengamos hecho esto vamos a usar una DCG para realizar la transformación caracter a caracter. La regla básica es la siguiente:


morse_string(Morse) -->
    {
	morse([X], M0),
        append_morse(M0, M1, Morse)
    },
    [X],
    morse_string(M1).

morse_string([]) --> [].

Viene a decir que probemos valores de morse de 1 caracter de longitud. Probamos a ver si la entrada del texto es justo ese caracter (si no, reintentamos con otro valor) y el código Morse de traducción lo juntamos con los de los siguientes caracteres. Así hasta llegar al final cuando no hay más caracteres que procesar y devolvemos la lista vacía (string vacío).

El predicado append_morse podría considerase un append normal salvo porque añade un espacio entre medias. De este modo, cumplimos el requisito de que los caracteres Morse van separados por un espacio. Cuando juntamos con un string vacío, no añadimos ese espacio.


append_morse(M0, M1, Morse) :-
    if_(M1 = "", append(M0, M1, Morse), append(M0, [' '|M1], Morse)).

if_ forma parte de la librería reif de Scryer Prolog. Se trata de un condicional lógico puro (a diferencia de ->) y nos permite que la condición aplique aunque las variables no estén instanciadas.

Para el caso del espacio y de dos caracteres, tendremos que añadir otras dos reglas de morse_string.


morse_string(Morse) -->
    {
	morse([X,Y], M0),
	append_morse(M0, M1, Morse)
    },
    [X,Y],
    morse_string(M1).

morse_string(Morse) -->
    {
	append(" ", M0, Morse)
    },
    " ",
    morse_string(M0).

Y ya estaría. Finalmente añadimos el predicado para aplicar la DCG:


text_morse(Text, Morse) :-
    once(phrase(morse_string(Morse), Text)).

La ventaja de haberlo definido la DCG tal y como la hemos hecho es que ¡podemos aplicar la conversión en ambos sentidos! Tanto de Morse a Texto como de Texto a Morse, la transformación se hace correctamente, solo tenemos que usar text_morse en el sentido correcto. Podemos solventar este problema e intentar realizar la transformación en ambos sentidos de forma automática. Esto sería el equivalente a "Debe detectar automáticamente de qué tipo se trata y realizar la conversión.".


solve(Input, Output) :-
    text_morse(Output, Input) ; text_morse(Input, Output).

El código completo en el repo de blog-ejemplos

]]>
https://blog.adrianistan.eu/codigo-morse-prolog Fri, 11 Mar 2022 10:42:46 +0000
Logtalk, Prolog orientado a objetos https://blog.adrianistan.eu/logtalk-prolog-orientado-objetos https://blog.adrianistan.eu/logtalk-prolog-orientado-objetos Que en este blog se habla de Prolog es algo que se da por hecho. Hoy sin embargo, vamos a hablar de un lenguaje quizá más desconocido que extiende Prolog. Se trata de Logtalk y de forma resumida podríamos decir que es Prolog + Orientación a Objetos. Pero no cualquier tipo de orientación a objetos. Orientación a objetos declarativa. Logtalk nos permite encapsular y reutilizar código de forma mucho más efectiva que en Prolog, no perdiendo nada de lo que ya podemos hacer en Prolog.

Logtalk nació en 1998 debido a los problemas de Prolog al escribir aplicaciones grandes. Diseñado por Paulo Moura, se trata de acercar Smalltalk y Prolog. De este modo el elemento fundamental de Logtalk serán los objetos, no las clases. A través de objetos podremos hacer clases si es lo que deseamos, o podemos usarlos como prototipos. Logtalk se ejecuta por encima de un sistema Prolog, sobre todo aquellos que implementen el estándar ISO Prolog. Actualmente Logtalk soporta: B-Prolog, Ciao Prolog, CxProlog, ECLiPSe, GNU Prolog, JIProlog, LVM, Quintus Prolog, Scryer Prolog, SICStus Prolog, SWI Prolog, Tau Prolog, Trealla Prolog, XSB y YAP. El código 100% Logtalk debería ser portable entre estos sistemas. En este artículo usaré Scryer Prolog. Podemos instalar Logtalk desde logtalk.org

En Logtalk existen tres tipos de entidades: objetos, protocolos y categorías.

Objetos

El elemento fundamental de Logtalk es el objeto. Veremos que casi todo en Logtalk son objetos. Estos objetos no tienen estado ya que son puramente declarativos. Con los objetos podemos construir jerarquías tipo prototipo (ideal para objetos únicos) y tipo clase (ideal para objetos de los que se harán muchos).


:- object(python).

    :- public(say_creator/0).
    say_creator :-
        write('Created by: Guido van Rossum'),nl.

:- end_object.

Este código crea un objeto llamado python con un predicado o método público, llamado say_creator con aridad 0. El predicado en sí lo que hace es imprimir por pantalla un texto. Lo vamos a guardar en un fichero llamado langs.lgt (lgt es la extensión de Logtalk)

Vamos a ejecutarlo. Para ello, lanzamos el comando de Logtalk de nuestro Prolog, en mi caso, es scryerlgt. Si usas SWI Prolog, es swilgt. Si todo va bien, verás una terminal similar a la de Prolog. Una vez allí, cargamos el fichero con logtalk_load.


?- logtalk_load('langs.lgt').

Si todo va bien, ahora podremos mandar un mensaje al objeto python.


?- python::say_creator.

Si no existe predicado en el objeto o si la visibilidad del método no lo permite, el envío falla. Se podría decir mucho de los mensajes como que emiten eventos, se puede hacer broadcasting, etc pero esto es más avanzado.

Los objetos pueden: implementar protocolos, importar categorías, extender otros objetos, instanciar un objeto (clase) y especializar otro objeto (clase). Las jerarquías de objetos se pueden realizar mediante extensiones (prototipos) o mediante instancias y especializaciones (clases).

Por ejemplo, un objeto que extienda otro se haría así:


:- object(animal).

    :- public(run/0).
    run :-
        write('Animal is running'),nl.

:- end_object.

:- object(dog, extends(animal)).

    :- public(sleep/0).
    sleep :-
        write('Dog is sleeping'),nl.
:- end_object.

Si cargamos el fichero, veremos que el objeto animal tiene solo el método run, mientras que dog tiene el método run y sleep.

Aquí podemos introducir también las otras dos formas de mandar un mensaje. Con :: podemos mandar un mensaje al mismo objeto en el que estamos. Y con ^^ podemos mandar un mensaje al superobjeto (es decir, del que estamos extendiendo).


:- object(animal).

    :- public(run/0).
    run :-
        write('Animal is running'),nl.

:- end_object.

:- object(dog, extends(animal)).

    :- public(run/0).
    run :-
        write('Dog is running'),nl,
        ^^run.

    :- public(sleep/0).
    sleep :-
        write('Dog is sleeping'),nl.

    :- public(run_and_sleep/0).
    run_and_sleep :-
        ::run,
        ::sleep.
:- end_object.

Al ejecutar dog::run_and_sleep/0 obtendremos:


Dog is running
Animal is running
Dog is sleeping

Adicionalmente podemos usar self/1 para obtener una referencia al objeto actual. run_and_sleep/0 puede escribirse también así:


    run_and_sleep :-
        self(Self),
        Self::run,
        Self::sleep.

Crear objetos de forma dinámica

Si has programado en otros lenguaje OOP quizá te esté extrañando que todos los objetos existan desde el principio en Logtalk. Afortunadamente, esto era porque no habíamos llegado todavía a la parte donde se crean de forma dinámica. Esto se hace mediante el predicado create_object/4.

El primer argumento será la variable o el nombre del objeto. Seguidamente irán las relaciones. En tercer lugar las directivas (ahora las vemos pero básicamente public, private, info,...) y en cuarto lugar las cláusulas.

Por ejemplo, estos dos códigos son equivalentes:


?- create_object(foo, [extends(bar)], [public(foo/1)], [foo(1), foo(2)]).

%%%%

:- object(foo, extends(bar)).

    :- dynamic.

    :- public(foo/1).
    foo(1).
    foo(2).

:- end_object.

Lo normal es dejar la creación de objetos dinámicos en un objeto prototito o en una metaclase.

Los objetos dinámicos se pueden borrar con abolish_object/1.

Objetos paramétricos

Existen unos objetos algo diferentes, los objetos paramétricos. Son objetos cuyo identificador no es un átomo sino un término compuesto de Prolog. De este modo los propios objetos cargan con información en su definición de nombre.


:- object(ruby(_Creator)).
    
    :- public(say_creator/0).
    say_creator :-
        write('Created by: '),
        parameter(1, Creator),
        write(Creator),nl.
        
:- end_object.

Y lo tendremos que ejecutar de esta forma: ruby('Matz')::say_creator.. Los objetos paramétricos también pueden usar this/1 y la sintaxis de doble underscore (_Creator_). Pero como siempre, para estas cosas extra, lo recomendable es mirar el Logtalk Handbook.

Protocolos

Lo siguiente dentro de Logtalk son los protocolos. Los protocolos permiten separar la interfaz de la implementación. Los protocolos solo incluyen las declaraciones de los predicados, pero no su lógica.


:- protocol(langp).

    :- public(say_creator/0).

:- end_protocol.

:- object(python, implements(langp)).

    say_creator :-
        write('Created by: Guido van Rossum'),nl.

:- end_object.

Ahora definimos un protocolo llamado langp, que el objeto python implementa. Ahora ya no necesitamos incluir la directiva de visibilidad en el objeto ya que esa forma parte de la interfaz al exterior del objeto.

Categorías

Las categorías son unidades de código reutilizables. Podemos pensar en ellos como partes de un objeto que se pueden añadir a otros objetos.


:- category(reverse_creator(_Creator_)).

    :- uses(list, [reverse/2]).

    :- public(reverse_creator/1).
    reverse_creator(Reversed) :-
        reverse(_Creator_, Reversed).

:- end_category.

:- object(ruby(CreatorParam), implements(langp), imports(reverse_creator(CreatorParam))).
    
    say_creator :-
        write('Created by: '),
        parameter(1, Creator),
        write(Creator),nl.
        
:- end_object.

En este ejemplo, vamos a tener una categoría (paramétrica) y vamos a importarlo desde nuestro objeto paramétrico. Así podemos ver como los objetos paramétricos se pueden pasar los parámetros entre sí. Además, en el ejemplo se usa la directiva use, que nos permite "importar" ciertos predicados de algunos objetos (en este caso del objeto list) para no tener que escribir la sintaxis larga de mandar mensaje.

Para que esto funcione, vamos a tener que cargar la librería con el objeto list en Logtalk primero.


?- logtalk_load(types(loader)).
    ....
?- logtalk_load('langs.lgt').
    ....
?- ruby("Matz")::reverse_creator(X).
    X = "ztaM".

Esto nos lleva a la pregunta de, ¿hay alguna forma de organizar los proyectos en Logtalk?

Estructura proyecto Logtalk

Por norma general un proyecto Logtalk consta de archivos lgt. Hay dos especiales, loader.lgt y tester.lgt.

loader.lgt deberá contener todo lo necesario para cargar la aplicación, normalmente cargar todos los ficheros y librerías en el orden correcto.


:- initialization((
    logtalk_load(sets(loader)),
    logtalk_load(meta(loader)),
    logtalk_load(app)
)).

Este es un ejemplo de fichero loader, que carga las librerías sets, meta y el fichero app.lgt.

El fichero tester.lgt es igual, pero deberá incluir una llamada a ejecutar los tests de lgtUnit. lgtUnit es el framework de testing de Logtalk, muy interesante pero del que hablaré en otro artículo.

Lambdas y más

Logtalk incluye predicados lambda, es decir, predicados definidos sobre un metapredicado.


?- meta::map([X, Y]>>(Y is 2*X), [1,2,3], Ys).
   Ys = [2,4,6].

En este caso, el predicado lambda es equivalente al predicado:


times2(X, Y) :- Y is 2 * X.

Pero sin tener que haber definido un nombre ni ubicarlo en otro sitio.

Logtalk incluye más funcionalidad aparte de objetos, protocolos y categorías. Eventos, multithreading, type checking, debugger, documentation, tests, etc. Definitivamente, escribir sobre todo ello llevaría un buen rato.

Este artículo solo pretende ser una idea introductoria a Logtalk. Aparte del handbook también puedes ver los ejemplos del repo de GitHub o la web de Learn X in Y minutes

]]>
https://blog.adrianistan.eu/logtalk-prolog-orientado-objetos Thu, 17 Feb 2022 22:41:25 +0000
Alien fingers en Prolog https://blog.adrianistan.eu/alien-fingers-prolog https://blog.adrianistan.eu/alien-fingers-prolog Hace pocos días me encontré con el problema de "Alien fingers" en mi TL de Twitter. Se trata de un sencillo problema, que podemos resolver en Prolog de forma 100% declarativa.

El puzzle, escrito por Chris Maslanka, viene a decir que los aliens usan notación hexadecimal para escribir números. Resulta que el número que escribieron, era de dos dígitos y era muy curioso, porque su valor era el mismo que los dígitos invertidos en notación decimal. ¿Qué número era?.

Para encontrar la respuesta en Prolog, primero vamos a definir la notación hexadecimal, para ello usaremos DCGs.

La DCG en cuestión describirá, para un número N, su representación en hexadecimal con dos dígitos.


:- use_module(library(dcgs)).
:- use_module(library(clpz)).

hex_number(N) -->
    hex(A),
    hex(B),
    {
        N #= B + A*16
    }.

hex(0) --> "0".
hex(1) --> "1".
hex(2) --> "2".
hex(3) --> "3".
hex(4) --> "4".
hex(5) --> "5".
hex(6) --> "6".
hex(7) --> "7".
hex(8) --> "8".
hex(9) --> "9".
hex(10) --> "A".
hex(11) --> "B".
hex(12) --> "C".
hex(13) --> "D".
hex(14) --> "E".
hex(15) --> "F".

Básicamente, hex_number de N describe la lista compuesta de hex(A) y hex(B). hex de N describe a su vez un caracter, siendo N el número que que representa (del 10 al 15 usamos de la A hasta la F). Finalmente calculamos el valor numérico del número representado.

Esta DCG se puede usar en muchos sentidos. Podemos usarla para generar todos los númeroe hexadecimales de 2 dígitos, para parsear un número en notación hexadecimal o para generar la representación hexadecimal de un número.

Para los números decimales es igual pero más sencillo ya que es nuestra notación natural


dec_number(N) -->
    dec(A),
    dec(B),
    {
        N #= B + A*10
    }.

dec(0) --> "0".
dec(1) --> "1".
dec(2) --> "2".
dec(3) --> "3".
dec(4) --> "4".
dec(5) --> "5".
dec(6) --> "6".
dec(7) --> "7".
dec(8) --> "8".
dec(9) --> "9".

Ahora simplemente necesitamos algo para que las representaciones se puedan invertir. En Scryer Prolog, podemos tratar los strings como listas a todos los efectos, es por ello que podemos usar el predicado reverse/2. Sin embargo, como este caso es muy sencillo, podemos usar un predicado casero que solo sirva para invertir listas de dos elementos. Se haría así:


inv([A, B], [B, A]).

Ahora ya podemos resolver el puzzle. Simplemente escribimos el enunciado del problema en Prolog


puzzle(Hex) :-
    phrase(hex_number(N), Hex), % Un número N en notación hexadecimal Hex que...
    inv(Hex, Dec), % Invirtiendo los caracteres ...
    phrase(dec_number(N), Dec). %  es igual al mismo número N pero en notación decimal

¡Y obtenemos los resultados!

Nos da dos soluciones, una trivial de "00" y otra, más interesante de "35". "35" en hexadecimal representa el número 53, que en notación decimal se escribe como "35" pero invertido. ¡Ese es el número que escribieron los aliens!

]]>
https://blog.adrianistan.eu/alien-fingers-prolog Fri, 28 Jan 2022 17:09:42 +0000
Introducción a Z3, el demostrador de teoremas de Microsoft https://blog.adrianistan.eu/introduccion-z3-demostrador-teoremas https://blog.adrianistan.eu/introduccion-z3-demostrador-teoremas Z3 es un demostrador de teoremas open source creado por Microsoft. Una herramienta muy útil para resolver ciertos problemas de alta complejidad algortítmica. Z3 en particular se enfoca en la resolución SMT. En este artículo veremos como podemos usarlo para resolver problemas sencillos y no tan sencillos. Se trata de otro lenguaje dentro del mundo de la programación lógica, como Prolog, pero bastante más específico.

Z3 trabaja con proposiciones. ¿Qué son proposiciones? Son sentencias que pueden ser verdad o mentira. Si una sentencia no puede ser verdad o mentira (es una pregunta, es una falacia, etc) no podrá representarse en Z3.

Para empezar vamos a declarar dos proposiciones, p y q, que existen en nuestro modelo.


(declare-const p Bool)
(declare-const q Bool)

Como veis, se usa una sintaxis basada en S-expresiones como Lisp. Veremos más adelante como esto no es obligatorio.

Una vez tengamos definido la estructura de nuestro modelo, vamos a realizar afirmaciones sobre él. Por ejemplo, diremos que la proposición p es verdad mientras que de q no decimos nada. Por último, añadimos el paso check-sat, para que Z3 compruebe si está situación es factible (no entra en contradicciones).


(declare-const p Bool)
(declare-const q Bool)
(assert p)
(check-sat)

Ejecutamos el fichero con z3 basic.z3 donde basic.z3 es el nombre del fichero y vemos que marca por pantalla la palabra sat. Esto significa que sí, es factible. Podemos añadir (get-model) para que Z3 nos ponga un ejemplo de modelo que satisfaga las proposiciones. En este caso, Z3 devuelve algo así:


(
  (define-fun p () Bool
    true)
  (define-fun q () Bool
    false)
)

Básicamente si p es verdadero (era una condición de partida) y q es falso no se cae en ninguna contradicción y se satisface todo. Bien, este era un ejemplo muy trivial. Vayamos a ejemplos más elaborados.

Demostrando teoremas por reducción al absurdo

Hemos visto que Z3 genera un modelo que es un ejemplo donde se cumplen las propiedades. ¿Cómo podemos demostrar algo entonces de forma general y no con ejemplos? Una forma es mediante reducción al absurdo. Es decir, vamos a pedir a Z3 que intente encontrar un ejemplo de lo contrario a lo que queremos demostrar, si no puede encontrarlo, como las proposiciones solo pueden verdaderas o falsas, lo contrario de lo contrario, es decir, lo que buscábamos originalmente, es verdad.


(declare-const p Bool)
(define-fun conjecture () Bool
    (or p (not p))
)
(assert (not conjecture))
(check-sat)

En este caso, intentamos probar que "P o NO P" siempre es verdad. Para ello negamos nuestra conjetura, es decir decimos que "P o NO P" es falso. Al ejecutarlo, Z3 nos devuelve unsat, es decir, no encuentra ejemplos. Por tanto se demuestra que lo contrario a lo que ha intentado demostrar, es cierto, es decir, "P o NO P" siempre es verdadero.

Variables numéricas y ecuaciones

Z3 no se limita a variables booleanas. También podemos hacer proposiciones con números y sus relaciones entre ellos. Por ejemplo, podemos proponer el siguiente problema a resolver:

Encontrar el valor de X e Y, siendo ambos enteros, X > 2, Y < 10 y X + 2*Y == 7.


(declare-const x Int)
(declare-const y Int)
(assert (> x 2))
(assert (< y 10))
(assert (= (+ x (* 2 y)) 7))
(check-sat)
(get-model)

El resultado de Z3 es un modelo donde X = 7 e Y = 0, que efectivamente cumple todas las condiciones.

La librería de Python

La sintaxis, basada en S-expresiones puede volverse más confusa aquí pero esto tiene truco. La idea es que no programemos directamente en Z3, sino que generemos código Z3 de forma programática o que usemos alguna de las API que hay (existen para Python, Rust, C#, ...).

A partir de ahora usaremos el paquete de Python (z3-solver). Esto nos permitirá usar una sintaxis más sencilla así como usar bucles y funciones para ir contruyendo las proposiciones. El ejemplo anterior ahora queda así:


from z3 import *

x = Int("x")
y = Int("y")
s = Solver()
s.add(x > 2)
s.add(y < 10)
s.add(x + 2 * y == 7)
print(s.check())
model = s.model()
print(f"x={model[x]}")
print(f"y={model[y]}")

Sudoku en Z3

Hace tiempo vimos una forma de resolver el Sudoku en Prolog (no muy eficiente). Este Sudoku se podría resolver mejor con clp(Z), algo de lo que ya he hablado también en el blog con otro ejemplo. Veamos como se resolvería el Sudoku con Z3.

El primer paso es representar el sudoku incompleto. En este caso voy a usar 0 para representar los huecos. A continuación, creamos un Solver de Z3 y creamos una variable por cada hueco y una constante (una variable con valor asignado desde el principio) para las celdas que sí sabemos el valor. Estas variables las dejamos en un array del mismo tamaño que el original, pero ahora estará lleno de variables Z3.


from z3 import *

sudoku = [4,0,0,0,6,0,9,1,0,
         2,0,0,0,0,7,0,5,0,
         0,9,0,8,0,0,0,2,0,
         0,0,1,6,0,9,0,0,2,
         0,8,0,0,0,0,0,6,3,
         0,7,0,0,4,0,0,0,0,
         7,0,3,0,0,8,0,9,0,
         0,0,0,0,3,0,4,0,5,
         0,4,0,9,0,0,6,0,0]

s = Solver()
# generate vars
sudoku_z3 = []
for i,cell in enumerate(sudoku):
    if cell == 0:
        sudoku_z3.append(Int(f"sudoku_{i}"))
    else:
        sudoku_z3.append(IntVal(cell))

Ahora añadimos para cada variable, que el valor de cada celda está entre 1 y 9.


# limits
for var in sudoku_z3:
    s.add(var >= 1)
    s.add(var <= 9)

Ahora usamos Distinct para decir que en cada columna, en cada fila y en cada cuadrado los valores han de ser todos diferentes entre sí.


# rows
for i in range(9):
    row = [sudoku_z3[j] for j in range(9*i, 9*(i+1))]
    s.add(Distinct(*row))

# columns
for i in range(9):
    column = [sudoku_z3[j] for j in range(i, 81, 9)]
    s.add(Distinct(*column))

# square
for i in range(9):
    o = i // 3
    p = i % 3
    square = [
        sudoku_z3[o*27+p*3],
        sudoku_z3[o*27+p*3+1],
        sudoku_z3[o*27+p*3+2],
        sudoku_z3[o*27+p*3+9],
        sudoku_z3[o*27+p*3+10],
        sudoku_z3[o*27+p*3+11],
        sudoku_z3[o*27+p*3+18],
        sudoku_z3[o*27+p*3+19],
        sudoku_z3[o*27+p*3+20]
    ]
    s.add(Distinct(square))

Por último, hacemos el check. Nos dará sat si es un Sudoku resoluble y unsat si no lo es. Y podemos obtener los datos del modelo para pintar el Sudoku resuelto.


print(s.check())

for i, cell in enumerate(sudoku):
    if cell == 0:
        print(f"{s.model()[sudoku_z3[i]]}", end=" ")
    else:
        print(f"{cell}", end=" ")
    if i % 9 == 8:
        print()

Obtenemos la misma solución que con Prolog, así que podemos darlo por bueno.

Z3 se puede usar para resolver muchos problemas bastante más serios que el Sudoku. Con este artículo, vemos otro miembro más dentro de la programación lógica.

]]>
https://blog.adrianistan.eu/introduccion-z3-demostrador-teoremas Wed, 29 Dec 2021 23:28:08 +0000
htmx, o como sería HTML si se tomase el hipertexto en serio https://blog.adrianistan.eu/htmx-html-hipertexto-en-serio https://blog.adrianistan.eu/htmx-html-hipertexto-en-serio Hoy os traigo un framework web con el que últimamente estoy jugando bastante. Se llama htmx y a diferencia de otros frameworks como React o Vue, aquí no escribiremos JavaScript. Escribiremos más HTML siguiendo la filosofía original del hipertexto. ¿Intrigado? Sigue leyendo.

¿Qué es el hipertexto?

Se define hipertexto como una estructura no jerárquica, no secuencial donde nodos se enlazan entre sí a través de enlaces. La web es el ejemplo más claro de hipertexto. Podemos saltar de una página a otra haciendo click en enlaces. Pero en realidad las webs modernas no son documentos 100% atómicos. La mayoría tienen varios componentes. Estos componentes en el desarrollo web tradicional no son nodos dentro de la red de hipertexto. ¿Y si sí lo fueran? Es decir, que las diferentes componentes de una web fuesen un nodo dentro del hipertexto. Si esto lo mezclamos con imágenes y el vídeo (que ya siguen este esquema) entramos de lleno dentro del mundo de la hipermedia.

En el mundo de la hipermedia las APIs no devuelven XML o JSON, sino devuelven HTML que a su vez devuelven más acciones dentro de la aplicación. Un poco como HATEOAS pero donde el humano es el que decide. htmx sigue esta filosofía. De este modo vamos a poder escribir grandes aplicaciones web de forma declarativa dentro del propio HTML. Un enfoque totalmente diferente a React o Angular.

Primeros pasos con htmx

La clave de htmx es poder generar peticiones HTTP desde más sitios. El resultado de esas peticiones, serán en muchos casos documentos HTML parciales, que podemos luego mostrar donde queramos.

Para instalar htmx, lo primero es importar la librería:


<script src="https://unpkg.com/htmx.org@1.6.1"></script>

Para definir el verbo de la petición, añadimos los atributos hx-get, hx-post, hx-put, hx-delete u hx-patch con valor la URL a donde se debe dirigir la petición. Si no indicamos nada más, se asignará el trigger más natural dependiendo del elemento. El trigger es aquella acción que hace que se dispare la petición. Podemos sobreescribirlo con el atributo hx-trigger. Un ejemplo:


   <div hx-post="/mouse_entered" hx-trigger="mouseenter">
      [Here Mouse, Mouse!]
   </div>

Este código hará una llamada POST a /mouse_entered cuando el ratón pase por encima. La respuesta se mostrará en el propio div.

Los triggers a su vez pueden llevar modificadores como limitar a solo una vez la acción, dejar un retraso entre peticiones mínimo, solo hacer la petición si ha habido cambios, etc...

Más cosas que hay que saber sobre htmx

Por defecto htmx escribe la respuesta de la petición dentro del propio elemento. Eso se puede modificar con el atributo hx-target que toma selectores CSS. Además, por defecto se hace un swap de tipo "innerHTML", es decir, se escribe en el interior del HTML. Pero puede hacerse de más formas: outerHTML, afterbegin, beforebegin,...

htmx además admite añadir contenido a las peticiones HTTP, mediante los atributos hx-include y hx-params.

Además de todo esto, htmx se integra con CSS y podemos tener animaciones para los elementos nuevos y spinners de carga de forma muy sencilla.

Un detalle a tener en cuenta es que htmx se va heredando a los elementos hijos. Es decir, si tenemos un atributo htmx en alguna etiqueta, lo más probable es que a los elementos dentro de esa etiqueta también les aplique.

Por último, mencionar que htmx se puede extender mediante JavaScript y mediante HyperScript, un lenguaje del mismo creador de htmx, con la idea de ser muy sencillo de usar justo para estos casos.

La documentación es bastante clara y se puede buscar con facilidad.

ActiveSearch en htmx

Un ejemplo más avanzado para ver htmx es una tabla sobre la que podemos realizar búsquedas. Las búsquedas se realizan en el servidor, que devuelve solo las filas que cumplen las condiciones.


<h3> 
  Search Contacts 
  <span class="htmx-indicator">
    <img src="/img/bars.svg"/> Searching... 
   </span> 
</h3>
<input class="form-control" type="text" 
       name="search" placeholder="Begin Typing To Search Users..." 
       hx-post="/search" 
       hx-trigger="keyup changed delay:500ms" 
       hx-target="#search-results" 
       hx-indicator=".htmx-indicator">

<table class="table">
    <thead>
    <tr>
      <th>First Name</th>
      <th>Last Name</th>
      <th>Email</th>
    </tr>
    </thead>
    <tbody id="search-results">
    </tbody>
</table>

En la foto se ve que ocurre al introducir Venus en la caja de búsqueda. Se realiza una petición POST a /search. Esta envía un payload de tipo application/x-www-form-urlencoded con search="Venus". La petición se lanza cuando dejamos de presionar una tecla en el teclado, el valor ha cambiado y se espera medio segundo antes de enviarla. La respuesta se mostrará en el tbody con ID search-results. Además, mientras carga, se mostrará el GIF de búsqueda que lleva la clase htmx-indicator.

Como véis, de forma muy sencilla podemos tener esa interactividad de las SPA. Adicionalmente esto es muy fácil de implementar en el backend con frameworks minimalistas ya que realmente no usa ninguna tecnología especialmente novedosa por detrás.

Existe muchísimas cosas que se pueden hacer con htmx y un poco de CSS. Podemos cargar diálogos modales así.

Conclusión final

Hemos visto que htmx es tremendamente sencillo pero nos permite realizar muchas cosas para las que la gente usa hoy en día frameworks complejos. Sin embargo, tiene algunas pegas. Este modelo requiere de más coordinación entre el backend y el frontend, ya que ahora no hay API, sino HTML directamente.

Además, en sitios donde la API sea necesaria por otros motivos (aplicaciones móviles) puede significar duplicar parte del trabajo en backend. En cualquier caso, para proyectos pequeños me parece la mejor opción. Además funciona con mucha facilidad con casi cualquier framework web de backend que puedes encontrarte. Y el tema de la autenticación queda completamente manejada por el backend y las cookies de siempre.

¿Vosotros qué pensáis de este framework?

]]>
https://blog.adrianistan.eu/htmx-html-hipertexto-en-serio Tue, 21 Dec 2021 19:58:48 +0000
El problema del Hello World https://blog.adrianistan.eu/problema-hello-world https://blog.adrianistan.eu/problema-hello-world Cuando empezamos en un lenguaje de programación lo más habitual suele ser empezar con la mítica frase de Hello World (Hola Mundo). Sin embargo, hay que tener en cuenta que este pequeño programa tiene algunos inconvenientes si lo queremos usar para comparar entre lenguajes y sus funcionalidades.

El primer Hello World fue escrito por Brian Kernighan en 1973, en el tutorial del lenguaje de programación B. B fue un predecesor de C y cuando se lanzó C al mundo, también se introdujo este ejemplo.


/* Hello World en B */

main() {
  extern a, b, c;
  putchar (a); putchar (b); putchar (c); putchar ('!*n');
}

a 'hell' ;
b 'o, w' ;
c 'orld' ;

El Hello World sirve para varias cosas. En primer lugar, nos permite comprobar que las herramientas necesarias para programar con ese lenguaje están correctamente instaladas (IDE, compilador, intérprete, etc). Además, no deja de ser un programa breve en el que vemos ya resultados. Esto motiva bastante al recién llegado que ve como ya sus acciones tienen repercusiones.

Sin embargo, hay un par de problemas relacionados con el Hello World que me gustaría destacar.

El primero es que, a día de hoy, no sirve apenas para probar una instalación. En muchos lenguajes, puedes hacer el Hello World de forma muy simple (y eso queda muy bien, para qué engañarnos) pero en proyectos reales nunca lo harás así. Por ejemplo, en el mundo Java, lo más habitual es usar Maven o Gradle, pero el Hello World es tan simple que podrás hacerlo directamente con javac. También forma parte del setup tener alguna forma de hacer tests (o al menos así pienso yo). En algunos lenguajes como Rust no hay problema, ya que los tests vienen integrados en el propio lenguaje (por tanto si funciona el Hello World, funcionará el sistema de tests) pero en la mayoría de lenguajes no es así. Hay que configurar el GoogleTest o JUnit de turno. Así pues, un Hello World simplifica demasiado y hacernos creer que nuestro entorno está 100% listo cuando no es así.

En segundo lugar, el Hello World es un ejemplo que opera directamente con la IO (bueno, de hecho solo con la O). Esto es relevante, ya que en ciertos lenguajes, sobre todo funcionales, la IO hay que tratarla con cuidado y no ejemplifica el uso normal del código. Pongamos ejemplos. Haskell es un lenguaje funcional puro. Pero no podemos hacer IO en Haskell sin más, ya que eso no sería puro (estamos generando side-effects sin control). La solución en Haskell es, controlarlos, mediante mónadas. Así, podemos hacer el Hello World pero estamos metiéndonos en un aspecto del lenguaje que no es tan habitual. Tampoco es el ideal para aprender a usar Haskell. Lo mismo sucede en Prolog, donde podemos usar write/1 para tener un código breve, pero es mala práctica y la buena práctica requiere algo más de aprendizaje previo. Incluso en Rust, un lenguaje imperativo, se usa una macro en vez de una función. En este caso no hay mucha diferencia, salvo porque las macros se explican con detalle mucho maś adelante que las funciones. Otro caso extremo es JavaScript. Este lenguaje se puede ejecutar tanto en una web como en un servidor y la forma de mostrar cosas por pantalla es muy diferente en ambos casos (console.log, alert, modificar el DOM, ...)


-- Hello World en Haskell
main :: IO ()
main = do
    putStrLn "Hello World!"

Este punto nos saca otro más amplio. ¿Sirve el Hello World para observar diferencias significativas entre lenguajes? Pues realmente no mucho. La IO es una parte limitada de todo un lenguaje. No se pueden apreciar diferencias reales entre Prolog y Python si solo te quedas en el Hello World simplón y eso que son terriblemente diferentes. Para hacer este tipo de comparativas, Knuth y Pardo diseñaron el algoritmo TPK. Es un algoritmo que no hace nada útil, como el Hello World, pero tiene en cuenta bastantes cosas que cuando se diseñó eran relevantes: arrays, funciones matemáticas, subrutinas, IO, condicionales e iteraciones. Hoy en día quizá el algoritmo TPK esté desfasado ya que hay muchas más cosas a tener en cuenta, ¿cómo maneja la concurrencia? ¿cómo usa la memoria dinámica? ¿el sistema de tipos es fuerte o débil? Quizá en los comentarios se os ocurran mil cosas más.

Como conclusión. Hacer el Hello World no está mal, pero hay que ser consciente de sus limitaciones. Si acabas de llegar a la programación es ilusionante sí, pero ya, no lo uses ni como comprobación de un setup exitoso ni para comparar lenguajes entre sí.

]]>
https://blog.adrianistan.eu/problema-hello-world Mon, 13 Dec 2021 23:05:24 +0000
Herramientas para empezar con Big Data https://blog.adrianistan.eu/herramientas-empezar-big-data https://blog.adrianistan.eu/herramientas-empezar-big-data Una de las disciplinas con más crecimiento en los últimos años dentro de la informática es el Big Data. Sin duda se trata de un mundo relacionado pero a su vez diferente al desarrollo de software puro en el que se ha centrado la enseñanza hasta hace relativamente poco. Si investigamos como empezar con Big Data veremos que hay muchos trabajos concretos, cada uno con habilidades diferentes. No obstante me voy a centrar en las herramientas informáticas más populares y que pueden aplicar a varios de estos puestos.


Panorama Big Data & AI - http://mattturck.com

SQL

Pongo SQL en primer lugar porque es muy importante y mucha gente lo minusvalora. SQL es el lenguaje de consulta más popular y el que soporta las bases de datos más importantes del mundo (Oracle, PostgreSQL, IBM DB2, Microsoft SQL Server, MariaDB, MySQL, ...). Los proyectos de Big Data no todos tienen por qué usar otro tipo de bases de datos más novedosas, eso es algo que depende de la casuística en sí y muchas veces las bases de datos relacionales son la mejor opción. Pero una cosa que hay que tener muy claro es que SQL no es solamente un lenguaje para lecturas y escrituras simples. Es un lenguaje muy completo (y a veces complejo) para realizar consultas. Con la gran ventaja de que muchas veces, el código más legible será el que mayor rendimiento ofrezca.

Es por ello que SQL no se limita solo a bases de datos tradicionales. Spark, ClickHouse y Presto no son bases de datos al uso y también veremos un importante uso de este lenguaje dentro de estas herramientas.

Tampoco SQL es sinónimo de modelo relacional, ya que aunque beba mucho de él, veremos soporte a documentos JSON en las últimas versiones del estándar y a otros paradigmas. Además, un buen control sobre SQL nos permitirá hacer visualizaciones e informes rápidamente.

Apache Spark

Apache Spark es un sistema para procesamientos Big Data. Basado en la idea de MapReduce, Spark se ejecuta en varios nodos que se van repartiendo el trabajo dividiéndolo y rejuntándolo de forma combinada. La ventaja es que si usamos las funciones de Spark para nosotros será algo casi transparente. Spark está escrito en Scala y es una de las formas de escribir flujos de datos en Spark, pero también podemos usar Java, Python y SQL. Además es compatible con el almacenamiento Hadoop.

Apache Spark también incluye una librería de algoritmos de Machine Learning que puede sernos de utilidad, ya que si bien no son lo más avanzado de la técnica, están probados y se entrenan en paralelo.

Python / R

Dentro de los lenguajes para hacer análisis de datos, tenemos que destacan sobremanera: Python y R. Ambos tienen sus ventajas y sus inconvenientes. A mí personalmente me gusta más Python y en general pienso que cualquier persona que haya programado antes se sentirá más cómodo. Por contra, la gente que venga sin conocimientos de programación fuertes o ha estado más cercano a la estadística, encontrará R más de su estilo.

Python tiene la ventaja de ser un lenguaje de propósito general donde es más sencillo encontrar librerías para otras cosas diferentes al procesamiento de datos. También tiene algunas librerías bastante potentes como Pandas, matplotlib, NumPy y Sklearn / TensorFlow (estas últimas si nos interesa el Machine Learning que no siempre será el caso).

Por contra R está mucho más especializado en estadística y procesamiento de datos. Por ejemplo, gran parte de la funcionalidad que ofrece Pandas en Python la tenemos en R de forma nativa. Existen librerías muy potentes como ggplot2, y en general, toda la colección de tidyverse es de muy buena calidad.

En cualquier caso alguno de estos dos lenguajes te será esencial para poder ser productivo.

Tableau / D3

Una parte vital del Big Data es mostrar los datos en visualizaciones que nos permitan tomar acciones. Tableau es una aplicación con mucha solera y una cantidad ingente de opciones. PowerBI es un producto muy similar, ofrecido por Microsoft. D3, por contra, se trata de una librería para manipular gráficos en JavaScript (en el navegador web) de una forma muy conveniente para mostrar datos. D3 es quizá de más bajo nivel, tardando más en hacer ciertas visualizaciones básicas pero es muy flexible y nos permite crear las visualizaciones más impactantes.

¿Hay más?

Por supuesto que hay más. Y habrá más. Es un campo en constante cambio, por lo que incluso para gente que se dedica a ello, el reciclaje es importante con cursos especializados, blogs y conferencias. Para gente que entra de nuevas, una buena opción puede ser un bootcamp de data science que repasará bastantes herramientas de distintos campos y además pondrá todas estas herramientas en su contexto de uso adecuado.

]]>
https://blog.adrianistan.eu/herramientas-empezar-big-data Tue, 30 Nov 2021 23:50:52 +0000
Ruta x ruta x Castilla y León https://blog.adrianistan.eu/rutaxrutaxcyl https://blog.adrianistan.eu/rutaxrutaxcyl Recientemente se anunció que mi proyecto Ruta x Ruta x Castilla y León resultó ganador del V Concurso de Datos Abiertos. Modalidad de Productos y Servicios: premio de estudiantes. El concurso consitía en usar los datos abiertos proporcionados por la Junta de Castilla y León para construir algo interesante. Desde aquí quiero dar las gracias a los organizadores y además quiero invitaros a qué vosotros probéis también la aplicación web (si vivís en Castilla y León claro).

A continuación copio íntegramente la memoria del proyecto, de modo que sirva de documentación de la propia aplicación por si alguien tiene interés.

Objetivo

Ruta x ruta x Castilla y León es una aplicación web para consultar rutas (tanto de senderismo, como de bicicleta, etc) y subir nuestros tracks (grabaciones GPS) de modo que otras personas puedan verlo. La diferencia principal de esta aplicación respecto a otras existentes en el mercado es precisamente el uso de fuentes de datos abiertos para enriquecer la información de la ruta. A través de Ruta x Ruta no solo es posible ver el recorrido de la ruta y una descripción, sino puntos de interés alrededor de ella (monumentos, bares/restaurantes y eventos).

Funcionalidades

Consultar rutas

Desde la página principal de la aplicación podemos ver rutas existentes y realizar búsquedas simples.

Al hacer click sobre una, veremos la ruta sobre una ortofoto o un mapa, marcando el punto de partida y de fin. Además veremos los monumentos, bares y eventos cercanos si los hubiese. Estos puntos de interés se calculan de forma automática al hacer la visualización. Al hacer click sobre ellos veremos un desplegable que nos da información sobre el punto de interés y este se marcará en el mapa. También veremos tracks subidos por los usuarios relacionados con esta ruta.

Consultar un track

Cualquier usuario puede ver los tracks públicos. Estos incluyen un mapa del recorrido, marcando la velocidad (siendo verde rápido y rojo lento), un resumen de algunas métricas y un gráfico de altimetría.

Subir un track

Los usuarios que han iniciado sesión (mediante Google), pueden subir tracks para compartirlos en la red. Además pueden asociarlos a una ruta ya publicada. Se incluye una pequeña previsualización antes de subir el track.

Proponer ruta

Los usuarios que han iniciado sesión pueden proponer rutas nuevas mediante el editor de rutas. Funciona haciendo click sobre el mapa para agregar un nuevo punto, que se une al anterior mediante una línea recta. Podemos eliminar el último punto introducido haciendo click sobre el marcador azul.

Ver perfil de usuario

Es posible ver los tracks subidos por un usuario mediante su enlace a perfil (accesible a los usuarios que han iniciado sesión haciendo click en su nombre). Desde allí se pueden ver todos los tracks subidos, así como borrar los que ya no le interese mostrar al usuario.

Fuentes de Datos Abiertos usadas

El producto Ruta x ruta usa las siguientes fuentes de datos abiertos:

Arquitectura y tecnologías usadas

La aplicación Ruta x Ruta tiene cuatro componentes principales, a saber:

Base de datos. Allí se almacena todo lo relacionado con la aplicación, desde los puntos de interés hasta los tracks de los usuarios. Se trata de una base de datos MongoDB, funcionando sobre el servicio en la nube, MongoDB Atlas. Una de las características más interesantes de esta tecnología es la posibilidad de hacer consultas geográficas.

Backend. El componente “cerebro” que lee y escribe datos en la base de datos y expone una API REST para el frontend. Además gestiona la autenticación (via Google OAuth) y realiza el procesado de los tracks. Está hecho en Kotlin usando el framework Ktor y alojado en la nube Microsoft Azure.

Frontend. La capa visual con la que interactúan los usuarios. Realiza llamadas al backend con las operaciones deseadas. Está hecho en React y JavaScript y alojado en el servicio Netlify.

Ingesta. Una serie de scripts escritos en JavaScript que obtienen los datos de los puntos de interés, los procesan y los guardan en la base de datos.

Ideas futuras

Bien por falta de tiempo o falta de recursos se han decidido descartar varias ideas. Se comentan brevemente.

  • Comentarios en las rutas y tracks. Se ha descartado porque añade la necesidad de moderación humana para evitar spam, mensajes irrespetuosos, etc
  • Imágenes de las rutas y los tracks. Por el mismo motivo se han descartado en esta primera versión. Las imágenes tienen que revisarse de forma humana para asegurarse que no contienen spam, pornografía, etc Además, en este caso habría que añadir un nuevo componente tecnológico, ya que las imágenes deberían estar alojadas separadas de la base de datos.
  • Imagen autogenerada para redes sociales para compartir. Las rutas y tracks podrían generar imágenes automáticas para compartir en Instagram, Twitter, WhatsApp, etc Se ha descartado por tener cierto reto técnico.
  • Meteorología de las rutas en tiempo real. Se consideró añadir una predicción del tiempo justo para la zona que atraviesa la ruta. Sin embargo se ha descartado ya que suponía un reto técnico excesivo para el tiempo del que se disponía.

Vídeo

Conclusiones

En definitiva, se plantea una aplicación web para ver rutas que indican de forma automática los puntos de interés que hay alrededor. Estas rutas son creadas por los usuarios usando el editor interactivo, y previa moderación, se publican para todo el mundo. El uso de datos abiertos abarca dos categorías: mapas/ortofoto y puntos de interés geolocalizados. Además se permite subir tracks GPS a los usuarios. Si bien esta funcionalidad usa una parte más limitada de datos abiertos, se considera interesante para hacer la aplicación más interesante.

]]>
https://blog.adrianistan.eu/rutaxrutaxcyl Mon, 8 Nov 2021 21:57:43 +0000
Teletexto #011 https://blog.adrianistan.eu/teletexto-011 https://blog.adrianistan.eu/teletexto-011 Después de una pausa, retomamos los artículos en el blog con otra edición de Teletexto, la número 11. Ya sabéis que Teletexto es la sección donde dejo enlaces que me han resultado interesantes.

Antes de empezar me gustaría aclarar el tema de la serie de Reinforcement Learning. Quizá en el futuro haga más artículos, pero por ahora está en pausa. No tenía mucho interés y explicar algunas cosas se estaba volviendo tedioso y quizá no estaba todo demasiado claro por querer ser más breve. Aun así, es algo que me ha servido a mí para entender mejor las bases. Dicho esto: empezamos el teletexto.

En este tiempo hemos tenido dos versiones nuevas de dos lenguajes muy importantes: Python 3.10 y Java 17 LTS. Sobre las novedades del primero, revisa este enlace de Real Python. Lo más destacado es la inclusión del match. En esencia es como un switch de otros lenguajes pero con pattern matching. Así que se parece más al match de Rust, al case de Haskell o incluso a la unificación de Prolog. En mi opinión no sé si este cambio era necesario. Es cierto que el pattern matching se está volviendo un obligado dentro de los lenguajes de programación mainstream, pero esta estructura añade cierta complejidad extra a Python. Además, el uso de match es más interesante en lenguajes funcionales, algo a lo que Python no se inclina demasiado. Ya hable de estas cosas en La perlificación de Python

Sobre Java 17, podremos leer en Blog Bitix las novedades más interesantes. Java 17 es la nueva LTS, la anterior era Java 11. Entre estas versiones Java ha ido evolucionando mucho, tomando ideas que implementaron antes Scala y Kotlin. Esto plantea una interesante pregunta. Scala y Kotlin surgieron como "mejoras" de Java. ¿Qué pasa si Java mejora tanto que ya no hay diferencias entre estos lenguajes y Java? ¿Perderán si razón de ser? Quizá Scala, con un enfoque diferente no tenga este problema pero Kotlin sigue un enfoque muy similar a Java. Es cierto que hay ciertas cosas que parece que Java no va a incorporar (sintaxis menos verbosa, null safety, etc) pero es interesante. Quizá por eso Kotlin, aparte de la JVM, quiere expandirse a otros entornos con Kotlin/JS y Kotlin/Native. Y la dependencia de Android en Kotlin es mayor de lo que mucha gente cree (Compose no se puede implementar en Java). Así que quizá sí pueda sobrevivir lo suficiente como para hacerse un hueco no tan ligado a Java.

Hablando de Kotlin, he aquí unos idioms que deberías manejar si vas a programar en Kotlin.

JavaScript es uno de los lenguajes más importantes a día de hoy. Lingua franca de la web, también se ha extendido por backend e IoT. Muchos lenguajes viven con una sola implementación de referencia. No es el caso de JavaScript, uno de los lenguajes con más implementaciones diferentes que existen. Esta web intenta recopilar todas las implementaciones que existen. Spoiler: hay más de 40 implementaciones.

La web Lalal.ai nos permite dividir una canción en pista vocal y pista instrumental usando inteligencia artificial. Una auténtica pasada, ideal para sacar las instrumentales para un karaoke.

Un genio ha cogido Space Cadet (el mítico pinball de Windows XP), lo ha decompilado y lo ha traducido a WebAssembly... ¡Y funciona! Una maravilla poder volver a jugar al mítico juego, que por motivos de tiempo y un bug misterioso se tuvo que eliminar en Windows Vista.

Los videojuegos son software, pero tiene peculiaridades. Este post de 2008 comenta una muy interesante Una parte muy interesante: desarrollar videojuegos de forma incremental es complicado. Sí, se habla mucho de hacer prototipos pero hasta eso tiene límites. ¿Cómo vas a desarrollar incrementalmente el ajedrez se pregunta? No es divertido hasta que no tiene cierta complejidad y figuras diferentes. Personalmente creo que aquí hay dos tipos de escuelas: los que van a ir prototipo a prototipo a partir de alguna mecánica divertida o algo que se les haya ocurrido y los que simplemente juntan muchas piezas hasta que al final, por haber tantas, empieza a ser interesante (este último ejemplo serían SimCity o Los Sims, cuyos prototipos no creo que sean muy entretenidos).

¡Deja de aprender!

Hace unas semanas fue la conferencia Strange Loop en St. Louis. Quizá no sea la más popular, pero para mí es de las favoritas. Siempre hay temas muy interesantes de invitados muy interesantes que se salen de lo convencional. Todavía no he visto todas las charlas de este año. Pero voy a comentar dos que me parecen muy interesantes:

  • "Strange Dreams of Stranger Loops" by Will Byrd Un quine es un programa que al ejecutarse, devuelve el mismo programa. Este tipo de programas parecen meras malabarismos mentales y en algunos casos lo son, pero Will argumenta que por debajo de toda esta maraña debe haber propiedades sobre la computación que todavía desconocemos y que merecería la pena investigar. En la charla usa miniKanren (él es su inventor de hecho)
  • "Outperforming Imperative with Pure Functional Languages" by Richard Feldman. El autor del lenguaje de programación Roc explica por qué la programación funcional siempre ha tenido menos rendimiento que la imperativa y qué está haciendo en su lenguaje para poder reducir esa diferencia sin dejar de ser un lenguaje funcional puro.

Hay una escuela de pensamiento que opina que los tests importantes son los de integración o los E2E. Tienen argumentos muy interesantes que os dejo leer aquí y aquí. Luego existe la otra escuela de pensamiento que dice que de hecho solo con buenos unit tests bastaría. ¿De qué escuela eres?

¿Quiéres ser más productivo programando? En Writing faster hay unos consejos muy interesantes. No todo el mundo es igual, pero al menos introduce algunas ideas que podrías valorar para ser más productivo.

Me despido hoy sin música, pero con dos vídeos que me parecen muy bonitos, sobre un hombre y sus motores artesanales: Motores Patelo. Un ejemplo de verdadera ingeniería.

]]>
https://blog.adrianistan.eu/teletexto-011 Tue, 2 Nov 2021 21:53:41 +0000
Reinforcement Learning (Aprendizaje por refuerzo): DQN, OpenAI Gym, Stable Baselines (parte 4) https://blog.adrianistan.eu/reinforcement-learning-aprendizaje-refuerzo-dqn-openai-gym-stable-baselines-4 https://blog.adrianistan.eu/reinforcement-learning-aprendizaje-refuerzo-dqn-openai-gym-stable-baselines-4 En el post anterior vimos como podíamos aprender las funciones valor y las funciones Q para todos los estados. Sin embargo, en muchos problemas el número de estados reales es muy elevado y es posible que no lleguemos a pasar por ellos las suficientes veces como para poder aprender correctamente. Una solución es aproximar el valor mediante una función. La idea es que esta función, al aproximar, estará generalizando y podrá derivar el valor de la función valor de cada estado de forma más o menos correcta.

Esta función de aproximación puede ser de muchos tipos, aunque normalmente suelen ser redes neuronales. Esto es debido a que tienen varias ventajas, la principal de ellas, es que es diferenciable. Muy útil para el entrenamiento. Usaremos el descenso estocástico del gradiente para optimizar las redes neuronales.

Vector de características

El primer paso para crear una función de aproximación es definir las entradas. Las entradas deberán ser ciertas características del entorno, que permitan describir el estado. Por ejemplo, en un coche autónomo, las características serían por ejemplo velocidad del coche, distancia del sensor delantero al muro, distancia del sensor lateral izquierdo al muro, etc.

Entrenamiento

Para realizar el entrenamiento de una red neuronal usando el descenso estocástico del gradiente necesitaremos un objetivo. De este modo la técnica será similar que en aprendizaje supervisado. Sin embargo, a diferencia de aprendizaje supervisado, aquí no hay valores de la función valor dados de antemano. ¿Cuáles son los objetivos entonces? En este caso podemos usar lo aprendido anteriormente y tomar las estimaciones de Monte Carlo o TD como si fuese lo que tenemos que aprender.

Como nota a mencionar, TD no funcionará bien en todas las ocasiones (si la aproximación tiene que ser no lineal). Existe una variante llamada Gradient TD que corrige este inconveniente.

DQN

Con lo visto antes ya podemos intuir como funciona DQN (Deep Q-Network). Pero hay algunos detalles importantes. Lo primero es que tiene "experience replay". Esto quiere decir que el entrenamiento precalcula varios escenarios y luego, va tomando trozos de ellos y los va introduciendo en el entrenamiento, repitiéndolos a veces incluso. Esto mejora la eficiencia al requerir menos ejecuciones de episodios y mejora la estabilidad. Además tiene "fixed Q-targets". Esto significa que los valores Q que se usan para comparar lo mucho o poco que hay que desviarse respecto al valor real son fijos. Es decir, tendremos unos valores Q dinámicos que será sobre los que guardamos los resultados y otros, que nos sirven para calcular la diferencia, que son fijos y se actualizan cada cierto número de iteraciones. Esto, que parece contraintuitivo, mejora la estabilidad del algoritmo.

Ejemplo real con OpenAI Gym

Vamos a resolver un problema ya más complejo. Será el problema del Cart Pole. Se trata de un péndulo que está en posición vertical y debemos mover el carro de abajo para que no caiga a ninguno de los lados.

Este entorno es un problema clásico y como tal, ya está diseñado por nosotros en diferentes librerías. Nosotros usaremos OpenAI Gym. Se trata de una librería que provee de varios entornos interesantes para poner a prueba nuestros algoritmos, con representación gráfica de los mismos.

Este entorno se caracteriza por tener cuatro entradas: posición del carro, velocidad del carro, ángulo del péndulo y velocidad angular del péndulo. Y dos acciones: acelerar a la izquierda y acelerar a la derecha. Las recompensas son 1 por cada acción tomada. El episodio finaliza cuando el ángulo del péndulo supera los 12 grados, el carro se aleja demasiado del centro y cuando el episodio alcanza los 200 pasos.

Vamos a ver como funciona la interfaz Gym para que luego seamos capaces de crear nuestros propios entornos. Esta se compone de:

  • Una clase que hereda de gym.Env
  • Un constructor donde inicializamos, como mínimo, self.action_space y self.observation_space y opcionalmente self.reward_range y parámetros internos del entorno nuestro. Estas dos primeras deben ser de algún subtipo gym.spaces. Los más normales son Discrete, que es básicamente un valor activado sobre N posibles y Box, que es una matriz de características (podemos especificar la forma para que sea unidimensional, o que tenga más dimensiones), que contiene números. Es muy habitual que las observaciones sean de tipo Box, ya que se miden varias características a la vez, con números decimales y Discrete sea para las acciones ya que tomamos una acción de N posibles. Pero otras combinaciones son posibles y existen más tipos de gym.spaces. Si vamos a introducir características diferentes dentro del mismo Box es muy conveniente normalizar los datos entre -1 y 1. self.reward_range, al contrario, es una tupla en la que se especifica el valor máximo y el mínimo que pueden alcanzar las recompensas.
  • Un método reset, que reinicia el entorno y devuelve una observación inicial. Estas observaciones, deberán devolverse con NumPy si son de tipo Box y coincidir con lo declarado en el constructor en cuanto a forma y tipos.
  • Un método step que toma una acción y devuelve una tupla con cuatro valores: nueva observación, recompensa, un boolean de si el episodio ha acabado ya o no y un diccionario de información extra (no usado por los algoritmos).
  • Opcionalmente pueden llevar instrucciones sobre como renderizar el entorno y así ver en vivo o en vídeo el funcionamiento de los algoritmos. Principalmente se controla a través del método render.

Identifiquemos estas partes dentro del entorno de CartPole-v1. El código fuente está aquí: https://github.com/openai/gym/blob/master/gym/envs/classic_control/cartpole.py


class CartPoleEnv(gym.Env):
    # inicializamos el entorno con parámetros que necesitemos y definimos action_space y observation_space. El primero es de tipo Discrete y el segundo de tipo Box
    def __init__(self):
        self.gravity = 9.8
        self.masscart = 1.0
        self.masspole = 0.1
        self.total_mass = self.masspole + self.masscart
        self.length = 0.5  # actually half the pole's length
        self.polemass_length = self.masspole * self.length
        self.force_mag = 10.0
        self.tau = 0.02  # seconds between state updates
        self.kinematics_integrator = "euler"

        # Angle at which to fail the episode
        self.theta_threshold_radians = 12 * 2 * math.pi / 360
        self.x_threshold = 2.4

        # Angle limit set to 2 * theta_threshold_radians so failing observation
        # is still within bounds.
        high = np.array(
            [
                self.x_threshold * 2,
                np.finfo(np.float32).max,
                self.theta_threshold_radians * 2,
                np.finfo(np.float32).max,
            ],
            dtype=np.float32,
        )

        self.action_space = spaces.Discrete(2)
        self.observation_space = spaces.Box(-high, high, dtype=np.float32)

        self.seed()
        self.viewer = None
        self.state = None

        self.steps_beyond_done = None

    def seed(self, seed=None):
        self.np_random, seed = seeding.np_random(seed)
        return [seed]

    # se ejecuta cuando el algoritmo llama a una acción, calcula la nueva observación (nuevo estado si es totalmente observable), la recompensa y si ha acabado ya o no
    def step(self, action):
        err_msg = "%r (%s) invalid" % (action, type(action))
        assert self.action_space.contains(action), err_msg

        x, x_dot, theta, theta_dot = self.state
        force = self.force_mag if action == 1 else -self.force_mag
        costheta = math.cos(theta)
        sintheta = math.sin(theta)

        # For the interested reader:
        # https://coneural.org/florian/papers/05_cart_pole.pdf
        temp = (
            force + self.polemass_length * theta_dot ** 2 * sintheta
        ) / self.total_mass
        thetaacc = (self.gravity * sintheta - costheta * temp) / (
            self.length * (4.0 / 3.0 - self.masspole * costheta ** 2 / self.total_mass)
        )
        xacc = temp - self.polemass_length * thetaacc * costheta / self.total_mass

        if self.kinematics_integrator == "euler":
            x = x + self.tau * x_dot
            x_dot = x_dot + self.tau * xacc
            theta = theta + self.tau * theta_dot
            theta_dot = theta_dot + self.tau * thetaacc
        else:  # semi-implicit euler
            x_dot = x_dot + self.tau * xacc
            x = x + self.tau * x_dot
            theta_dot = theta_dot + self.tau * thetaacc
            theta = theta + self.tau * theta_dot

        self.state = (x, x_dot, theta, theta_dot)

        done = bool(
            x < -self.x_threshold
            or x > self.x_threshold
            or theta < -self.theta_threshold_radians
            or theta > self.theta_threshold_radians
        )

        if not done:
            reward = 1.0
        elif self.steps_beyond_done is None:
            # Pole just fell!
            self.steps_beyond_done = 0
            reward = 1.0
        else:
            if self.steps_beyond_done == 0:
                logger.warn(
                    "You are calling 'step()' even though this "
                    "environment has already returned done = True. You "
                    "should always call 'reset()' once you receive 'done = "
                    "True' -- any further steps are undefined behavior."
                )
            self.steps_beyond_done += 1
            reward = 0.0

        return np.array(self.state, dtype=np.float32), reward, done, {}

    # resetea el estado interno del CartPole y devuelve la observación
    def reset(self):
        self.state = self.np_random.uniform(low=-0.05, high=0.05, size=(4,))
        self.steps_beyond_done = None
        return np.array(self.state, dtype=np.float32)

En Stable Baselines

Este algoritmo, DQN, es muy popular, y se encuentra implementado en diferentes librerías. Una de las más interesantes es Stable Baselines 3, que intenta ser el sklearn del aprendizaje por refuerzo. Usa PyTorch internamente. Con Stable Baselines 3, debemos proporcionar un entorno que siga la interfaz Gym y ajustar los hiperparámetros. En el caso de DQN los hiperparámetros más importantes son:

  • policy - La política a usar del modelo. Casi siempre MlpPolicy. Si la entrada son imágenes, CnnPolicy.
  • env - El entorno sobre el que vamos a aprender. Debe implementar la interfaz OpenAI Gym
  • learning_rate - Ratio de aprendizaje de la red neuronal
  • buffer_size - El tamaño del buffer que almacenará las transiciones del "experience replay".
  • batch_size - Tamaño del batch que se usa para reentrenar.
  • learning_starts - Cuantos steps debe dar el modelo antes de empezar a aprender la red neuronal.
  • gamma - Gamma, el factor de descuento. En posts anteriores hemos hablado de él.
  • train_freq - Cada cuantos steps se reentrena el modelo.
  • gradient_steps - Cuantos pasos de gradiente se dan al entrenar. Por defecto, 1.
  • target_update_interval - Cada cuantos steps se actualiza el "fixed Q-target".
  • policy_kwargs - Ajustes de la política. En el caso de MlpPolicy podremos ajustar la forma de la red, así como las funciones de activación y más detalles.
  • Más detalles y parámetros aquí

Con estos parámetros podemos hacer un código similar a este:


import gym
from stable_baselines3 import DQN

env = gym.make("CartPole-v1") # también se puede crear la instancia de la clase directamente en vez de usar gym.make

model = DQN(
    "MlpPolicy",
    env,
    learning_rate=1e-3,
    buffer_size=50000,
    learning_starts=10000,
    batch_size=64,
    gamma=0.999,
    gradient_steps=1,
    train_freq=20,
    target_update_interval=2000,
    verbose=1
    )
model.learn(total_timesteps=500_000)
model.save("dqn_cartpole")

Y aquí un pequeño vídeo para comparar. Primero una política aleatoria, en segundo lugar la política aprendida con DQN.

He aquí el código de como cargar el modelo entrenado y visualizarlo.


import gym
from stable_baselines3 import DQN

env = gym.make("CartPole-v1")

model = DQN.load("dqn_cartpole")

obs = env.reset()
for i in range(1000):
    action, _state = model.predict(obs, deterministic=True)
    obs, reward, done, info = env.step(action)
    env.render()
    if done:
        obs = env.reset()
]]>
https://blog.adrianistan.eu/reinforcement-learning-aprendizaje-refuerzo-dqn-openai-gym-stable-baselines-4 Sun, 5 Sep 2021 10:32:37 +0000
Reinforcement Learning (Aprendizaje por refuerzo): Q-Learning (parte 3) https://blog.adrianistan.eu/reinforcement-learning-aprendizaje-refuerzo-q-learning-3 https://blog.adrianistan.eu/reinforcement-learning-aprendizaje-refuerzo-q-learning-3 Ya hemos visto en posts anteriores qué es al aprendizaje por refuerzo y como podemos modelar los problemas de este tipo con un MDP. Ahora veremos varias técnicas para calcular la función valor de cada estado y, con esta información, mejorar la política.

Programación dinámica

El primero de estos métodos se llama programación dinámica. La programación dinámica es una clase de algoritmos muy eficientes que se basan en resolver problemas con dos cualidades: tienen una subestructura óptima y las subestructuras se repiten. Con lo primero nos referimos a que resolver una parte del problema de forma óptima, nos da la parte óptima de esa subparte en el problema grande (o sea, se puede resolver a cachos y luego juntar, que sigue siendo óptimo). En este blog, ya hablé de otro problema que se resolvía con programación dinámica, el problema de la mochila.

Policy Iteration

Una de las formas en las que podemos usar programación dinámica para calcular la función valor es usar Iterative Policy Evaluation. La idea es empezar con un valor todos los estados, y poco a poco irlos refinando hasta que la solución definitiva de acuerdo a la siguiente fórmula, donde k es la iteración.

Pongamos el ejemplo del MDP del artículo anterior e intentémos calcular la función valor.

La política que vamos a evaluar es 50/50 aleatoriamente cuando hay posibilidad de elección entre varias acciones. Gamma es 1. Para ello, podemos usar una herramienta como LibreOffice Calc, donde cada fila es un estado, y cada columna es una evaluación k. Las celdas serán el valor de la función valor, que puede depender de la función valor de otros estados en el k inmediatamente anterior.

En este caso se puede ver como los valores de la función valor de cada estado se estabilizan a las pocas iteraciones. Ese es el valor correcto de la función valor. Una vez tenemos evaluada la política, podemos mejorarla. ¿Cómo? Actuando de forma egoísta respecto a la función valor, más en concreto la función valor Q, que nos da la función valor de cada acción. Realizando estos dos pasos repetidamente podemos realizar un aprendizaje de política buena. ¿Es la mejor? Puede que sí, pero solo con estos pasos no podemos asegurarlo y tendremos que añadir más

Value Iteration

Otra forma dentro de DP para obtener una buena política es Value Iteration. Aquí no existe una política explícita sino que se actúa de forma greedy sobre las funciones valor que se calculan. La fórmula para la función valor en Value Iteration es ligeramente diferente a la de Policy Iteration.

En vez de tomar las acciones en general, se toma siempre la acción de mayor función valor como la que sirva para calcular la función valor de ese estado.

En este ejemplo, los resultados son similares, pero se alcanza la convergencia antes. Este método, a diferencia del anterior, garantiza que la función valor que vamos a obtener es óptima.

Monte Carlo

La programación dinámica tiene técnicas interesantes si conocemos el MDP a priori pero en la mayoría de casos no lo vamos a conocer. Los siguientes métodos, Monte Carlo y Temporal Difference, no usan la estructura del MDP para sus cálculos, simplemente resultados de diferentes ejecuciones.

Monte Carlo se basa en calcular la función valor en base a una media que vamos construyendo poco a poco según vamos realizando ejecuciones. Existen dos tipos: First-Vist and Every-Visit, se diferencian en un pequeño detalle. Vayamos primero con el segundo.

Para evaluar la función valor de un estado, ejecutamos un episodio hasta que acabe. Luego inspeccionamos la historia, cada vez que pasamos por el estado del que estamos calculando el valor, aumentamos el contador N y sumamos el retorno (G) en ese momento. Finalmente dividimos la suma de G entre N y tendremos una aproximación a la función valor para ese estado. Cuando tengamos muchos episodios, la aproximación será más precisa. Si esto solo lo hacemos para la primera vez, tenemos un First-Visit en vez de un Every-Visit.

Temporal Difference

Temporal Difference es muy similar a Monte Carlo, pero con una diferencia fundamental. En vez de usar el retorno real (G), se usa un retorno estimado, basándonos en la recompensa inmediata y la estimación de la función valor del siguiente estado. Esto permite aprender más rápidamente, de secuencias incompletas. Por ejemplo, si vamos en un coche, igual no sabemos si ir por una ruta o por otra es mejor en ese momento (tenemos que esperar al final para saberlo) pero seguro que sabemos que si nos vamos por un barranco vamos a perder mucha recompensa. TD se daría cuenta y en mientras se está ejecutando el episodio, asignaría rápidamente funciones valor bajas a los estados que pensamos que nos llevan al barranco.

Uno de los problemas de TD es que aumenta el bias, aunque reduce la varianza, respecto a Monte Carlo. Por este motivo, si bien TD suele ser más rápido, es mucho más sensible a los valores iniciales de la función valor.

TD tiene diferentes variaciones, dependiendo del número de recompensas reales que vamos a usar en la predicción de G. Si solo usamos la recompensa inmediata, hablaremos de TD(n = 0), si además de esa, usamos una recompensa más del futuro, TD(n = 1), con dos recompensas además de la inmediata, TD(n = 2) y así hasta el infinito. En este caso, no estamos aproximando G, ya que usamos siempre los valores reales. Es por ello que TD(n = infinito) = Monte Carlo.

Sin embargo, como la longitud de los episodios puede ser muy diversa, lo ideal es usar TD(lambda), donde lambda es un valor entre 0 y 1, que ajusta proporcionalmente el número de recompensas que tendremos en cuenta del futuro.

Trazas de elegibilidad

Pongamos el ejemplo de una secuencia donde sonó la campana tres veces, despúes hemos visto una luz y después hemos recibido un shock eléctrico. ¿Qué causó el shock? ¿Fue la luz por ser lo más reciente? ¿O fue la campana, ya que fue más frecuente? ¿Qué debería aprender el agente?

Las trazas de elegibilidad son una medida que nos permite tener en cuenta ambas opciones a la vez. Son usadas en TD(lambda) para calcular los valores reales. No vamos a entrar mucho en detalle de las fórmulas pero si quisiésemos implementar esto en la realidad, las necesitaríamos.

GLIE

Hasta ahora hemos visto como calcular la función valor sin conocer el MDP subyacente de un problema (MonteCarlo y TD), ahora, al igual que con programación dinámica si conocemos el MDP, debemos idear un método para encontrar la política óptima.

Vamos a basarnos en Monte Carlo primeramente y vamos a pensar en hacer una selección greedy sobre la función valor. ¿Es posible? No, porque para ello debemos tener un modelo de MDP. Pero tenemos otra función, la función Q, similar a la función valor pero donde especificamos una acción a tomar. Así pues podemos hacer un algoritmo que evalúe las funciones Q para todas las acciones posibles desde el estado y elegiremos la acción que corresponda a mayor valor de su función Q.

El problema de elegir una acción de forma greedy es que si bien la acción será probablemente buena, seguramente no sea la mejor (recordad, dilema explotación vs exploración del primer post). Es aquí donde entra epsilon-greedy. En esencia es greedy como antes, pero hay una probabilidad no nula de que se elija una acción no mediante el sistema greedy, sino completamente al azar, concretamente la probabilidad es epsilon. Esto mejora las cosas pero no es suficiente todavía para garantizar obtener una solución óptima.

El motivo es que permanentemente vamos a tener una probabilidad de hacer algo aleatorio. Si bien esto nos permite explorar más alternativas, en algún momento será contraproducente. Es por ello que el valor de epsilon debe ir descendiendo progresivamente hasta ser 0. Concretamente podemos decir que epsilon es igual a 1 dividido el número de episodio que estemos procesando. Cuando esto ocurre, tenemos GLIE.

SARSA

Ya tenemos una forma de obtener políticas óptimas basándonos en Monte Carlo. Pero como hemos visto antes, TD(lambda) tiene ciertas ventajas y es más general (Monte Carlo es un caso extremo). Así, pues, ¿qué pasa si usamos la idea de GLIE pero en vez de actualizar Q con G, lo hacemos con la recompensa inmediata y una estimación? Este algoritmo se llama SARSA.

En SARSA también podemos usar lambda, en ese caso deberemos usar trazas de elegibilidad y el pseudocódigo sería algo así:

Q-Learning

Q-Learning es un algoritmo muy similar a SARSA pero con una diferencia clave. Se trata de un algoritmo off-policy. Esto quiere decir que no va a ir siguiendo la propia política que va aprendiendo, sino que va a seguir una política pero va a ir construyendo otra diferente (mejor). En general este tipo de algoritmos son mejores si podemos realizar simulaciones y no nos importa tener más variabilidad durante el entrenamiento.

En Q-Learning la siguiente acción vendrá dada por una política, como por ejemplo, epsilon-greedy, pero a la hora de evaluar el nuevo valor de la función Q, elegiremos la siguiente acción de forma greedy. De este modo, podemos combinar exploración (la política primera explora a veces) pero seguimos construyendo una política óptima.

Un ejemplo práctico

He sido muy teórico, y he eso que he resumido muchas cosas. Igual hasta tal punto que no queda muy claro a veces de lo que hablo, si es así, decídmelo y le daré un repaso al apartado. Pero ha llegado el momento de sacar el código. Vamos a implementar Q-Learning para nuestro entorno, ya conocido de otros episodios. Usaremos Python por simpleza, pero lo podéis adaptar fácilmente a cualquier lenguaje.

Lo primero es codificar este entorno en una función, que tomará el estado y la acción y devolverá una recompensa, el siguiente estado y si hemos acabado o no. Las acciones inválidas tienen una recompensa negativa muy fuerte (-10) para que rápidamente el algoritmo no intente hacerlas. Los estados S1, S2, S3, ... se codifican como 0, 1, 2 y las acciones de forma similar. Es decir, se "resta" 1 al número del dibujo. Esto es para que quede más limpio con las operaciones.


import random

def env(state, action):
    if state == 0:
        if action == 0:
            return -1, 2, False
        if action == 1:
            if random.random() < 0.9:
                return -1, 1, False
            else:
                return -1, 2, False
    if state == 1:
        if action == 2:
            if random.random() < 0.5:
                return -5, 2, False
            else:
                return -5, 3, False
    if state == 2:
        if action == 3:
            return -1, 3, False
        if action == 4:
            return -1, 4, True
    if state == 3:
        if action == 4:
            return -1, 4, True
    
    # invalid action
    return -10, state, False

Ahora viene Q-Learning, del pseudocódigo podemos implementar fácilmente el código real. El valor de Q lo vamos a almacenar en una matriz de estados x acciones. Inicializada todoa unos salvo los estados terminales. La política que guía será epsilon-greedy con epsilon igual a 1/k y las constantes gamma y alpha se ajustan a 0.99 y 0.1 respectivamente.


import random
import numpy as np

def qlearn():
    gamma = 0.99
    alpha = 0.1
    q = np.ones((5, 6))
    q[4,:] = 0 # terminal state
    for k in range(1, 1000):
        s = 0
        epsilon = 1/k
        while True:
            if random.random() < epsilon:
                a = random.randint(0, 5)
            else:
                a = np.argmax(q[s,:])
            reward, next_state, done = env(s, a)
            q[s, a] = q[s, a] + alpha*(reward+gamma*np.max(q[next_state, :])-q[s, a])
            s = next_state
            if done:
                break
    return q

Código disponible en GitHub

Y ya estaría. Ahora podemos llamar a la función qlearn para obtener la matriz que representa la función Q. Nuestra política será actuar de forma greedy con esta tabla. Veamos un ejemplo.

Si estamos en S1, la política nos manda tomar A1. La cual nos lleva a S3. La política nos manda ahora tomar A5, la cual nos lleva a S5 y finaliza el episodio. Este es el recorrido óptimo. Pero desde otros estados de partida, también nos llevaría a tomar las mejores acciones. Y esto sin saber como funciona la función env, de la que solo ve las salidas dadas las entradas.

Y con esto ya estarían los métodos clásicos de Aprendizaje por Refuerzo. Lo siguiente será comentar las técnicas más modernas. No dejéis de consultar la bibliografía (primer post) si queréis profundizar más.

]]>
https://blog.adrianistan.eu/reinforcement-learning-aprendizaje-refuerzo-q-learning-3 Wed, 18 Aug 2021 20:38:19 +0000
Reinforcement Learning (Aprendizaje por refuerzo): MDPs y ecuación de Bellman https://blog.adrianistan.eu/reinforcement-learning-aprendizaje-refuerzo-mdp-bellman-2 https://blog.adrianistan.eu/reinforcement-learning-aprendizaje-refuerzo-mdp-bellman-2 En el anterior post vimos una introducción a algunos conceptos del aprendizaje por refuerzo. Una cosa que se vio, es que los problemas se pueden modelar como MDP. En este post veremos como se hace, ya que nos permitirá visualizar mejor qué es lo que ocurre dentro de un problema de RL y nos da una base matemática para encontrar soluciones.

¿Qué es un MDP?

Un MDP (Markov Decission Process) es un entorno donde todos los estados son Markov que define las transiciones entre estos, recompensas, ... Los estados Markov como ya vimos, son autoexplicativos, y no necesitamos saber por qué estados ha pasado antes porque el propio hecho de estar en ese estado ya contiene esa información.

Para definir un MDP debemos definir los siguientes elementos:

  • Un cojunto finito de estados (S)
  • Un conjunto finito de acciones (A)
  • Una matriz de transición de estados. Dado un estado actual y una acción, nos dirá la probabilidad de ir a cada uno de los otros estados.
  • Una función de recompensa que toma el estado actual y la acción actual.
  • Gamma, el factor de descuento, entre 0 y 1.

Podemos representar un MDP de forma gráfica tal que así. Este MDP nos servirá de ejemplo

El MDP tiene 5 estados (uno de ellos final). En algunos estados podemos tomar varias acciones, en algunos solo una. Además algunas acciones nos llevan de forma directa a otro estado, mientras que en otras hay una probabilidad de ir a un estado u a otro. Cada acción lleva asociada una recompensa. El objetivo es ir de S1 a S5 con la mayor recompensa posible.

Definimos como retorno, Gt, a la suma de las recompensas descontadas desde el instante de tiempo t. ¿Descontadas? Quiere decir, que cada vez valgan menos, en base al valor gamma. El valor gamma sirve para controlar entre la recompensa inmediata y la recompensa futura. Un valor de gamma igual a cero hace que el agente sea miope y analice más allá del primer paso. Un valor de gamma igual a uno hace que el agente tenga en cuenta todo sin priorizar por tiempo. En general, el mejor valor no será ninguno de los dos extremos (0 o 1) sino un valor intermedio.

Una vez tengamos un MDP, podemos asignarle una política. La política nos dice para cada estado, qué acción hay que tomar, con una cierta probabilidad. Cada ejecución de la política sobre el MDP se le llama episodio.

Con un MDP y una política podemos definir su función valor V, para un estado s. Se define como la media de los retornos desde el estado actual.

También podemos definir la función Q, se define como la media de los retornos desde el estado actual tomando la acción a.

Ecuación de Bellman

La forma anterior que hemos visto de expresar la función valor es matemáticamente correcta pero poco práctica. Realizando unas transformaciones podemos convertirla en la ecuación de Bellman.

De forma matricial:

De forma individual:

La ecuación de Bellman es interesante, porque es resoluble, aunque algorítmicamente no es muy interesante. En la práctica solo podemos resolver pequeños MDP. Pero es la base paa

Calculemos los valores de la función valor para algunos estados del MDP de la imagen (asumiremos gamma = 1 y una política 100% aleatoria). El más sencillo es S5. Como la única posibilidad es tomar A6, que nos devuelve a S5 y tiene recompensa 0, v(S5) = 0. Para S4, es también sencillo, ya sabiendo como funciona S5. En este caso solo hay una opción que es tomar A5. Entonces v(S4) = -1 + 1*v(S5) = -1. Ahora pensemos la función valor de S3. Sería v(S3) = 0,5*(-1 + 1*v(S5)) + 0,5*(-1 + 1*v(S4)) = -0,5 - 1 = -1,5 y así sucesivamente.

Se define una función valor óptima para aquella que consigue el valor más elevado, sobre todas las políticas existentes. Al obtener una función valor óptima resolvemos el MDP y sabemos el rendimiento óptimo que podemos lograr.

Resolver este problema no es fácil y no existe una solución genérica. Existen diferentes métodos iterativos que tratan de encontrar esa política que consiga la función valor óptima. Algunos de ellos son: Value Iteration, Policy Iteration, Q-learning y SARSA. Y los veremos en el siguiente post.

]]> https://blog.adrianistan.eu/reinforcement-learning-aprendizaje-refuerzo-mdp-bellman-2 Thu, 5 Aug 2021 22:03:36 +0000 Reinforcement Learning (Aprendizaje por refuerzo): ¿Qué es y cómo funciona? (parte 1) https://blog.adrianistan.eu/reinforcement-learning-aprendizaje-refuerzo-que-es-parte-1 https://blog.adrianistan.eu/reinforcement-learning-aprendizaje-refuerzo-que-es-parte-1 Dentro del Machine Learning existen tres ramas: aprendizaje supervisado, no supervisado y por refuerzo. En esta serie de posts vamos a hacer una introducción a esta última rama, siguiendo el esquema habitual que se usa en libros y cursos pero simplificando ciertas cosas. Este post tendrá un caracter más teórico, pero necesario para poder desarrollar temas más avanzados.

¿Qué es el aprendizaje por refuerzo?

Se trata de una rama de Machine Learning donde un agente recibe recompensas según las acciones que realice. Las recompensas, que configuramos nosotros, tienen que ir encaminadas a que el agente, al intentar conseguir la recompensa máxima, realice lo que nosotros queremos que haga.

Un ejemplo. Pongamos el caso de un coche de carreras que tiene que hacer un circuito. Podemos darle un premio por cada tramo de circuito por el que pasa la primera vez, quitarle puntos si se sale, quitarle puntos si tarda mucho, ... Al final el agente que controla el coche aprenderá a que para obtener la recompensa máxima debe pilotar el coche sin salirse haciendo todo el circuito en el menor tiempo posible.

A parte de las recompensas, existen otras tres diferencias respecto a las otras ramas:

  • El feedback sobre las acciones no es instantáneo, sino que puede retrasarse.
  • El tiempo es una variable que siempre está presente.
  • Las acciones tomadas por el agente varían los datos que vamos a recibir.

Términos y notación

Lo primero será ver los conceptos y notaciones que vamos a usar en este área y que se aplicarán posteriormente a cualquier algoritmo que encontremos.

La recompensa, llamada Rt, es un valor escalar, positivo o negativo, que indica como de bien o mal está el agente en el instante de tiempo t.

El aprendizaje por refuerzo funciona si aceptamos la siguiente hipótesis como válida: Todos los objetivos pueden describirse como la maximización de las recompensas acumuladas esperadas.

Estas recompensas se obtienen realizando acciones, At, es la acción elegida en el instante de tiempo t. Estas acciones se toman de forma secuencial. Las recompensas de las acciones no tienen por qué ser inmediatas. Es muy posible que sacrificar el beneficio inmediato sea mejor en pos de un beneficio mayor a largo plazo.

Para que el agente pueda tomar decisiones informadas, recibe una observación en cada instante de tiempo, Ot. Las observaciones dependen del problema que estemos resolviendo. Por ejemplo, en un juego arcade, las observaciones podrían ser una matriz de píxeles con los colores que tienen en ese momento en la pantalla.

Al conjunto de todos los paquetes Observación-Recompensa-Acción hasta el instante de tiempo t se le denomina historia, Ht. Ht = O1, R1, A1, ..., At-1, Ot, Rt. La información que el agente usa para tomar la siguiente acción se basa en la historia y se le llama estado: St = f(Ht). Aquí hay un detalle a tener en cuenta y es que partiendo del mismo estado y tomando la misma acción, el resultado (observación y recompensa siguientes) puede ser diferente.

Este estado también se le llama a veces, estado del agente. Puede ocurrir que exista otro estado, el estado ambiental o del entorno: Set. Este es el estado que tiene el entorno exterior donde vive el agente. Puede depender de variables que de cara a nuestro problema sean irrelevantes y a veces es inaccesible.

Si en nuestro problema el estado del entorno es accesible, se dice que estamos ante un entorno completamente observable y en este caso, llamaremos estado indistintamente a ambos: St = Set. En caso contrario, hablaremos de un entorno parcialmente observable y habrá distinción, aunque lo importante sigue siendo el estado del agente. La mayoría de problemas en el mundo real son parcialmente observables ya que no podemos observar de forma completa el universo.

En aprendizaje por refuerzo vamos a trabajar con estados Markov. ¿Qué es eso? Son estados que contienen en sí mismos toda la información histórica necesaria. Es decir, una vez estamos en un estado Markov, no necesitamos mirar atrás por qué otros estados hemos pasado para hacer una buena decisión ya que sería redundante, el propio estado, por su definición, ya contiene toda la información para hacer esa decisión. Esto quiere decir que no vamos a necesitar guardar la historia completa, ya que con guardar el estado actual, ya tenemos toda la información. Esto en la práctica significa que cualquier proceso temporal, que dependa de haber pasado antes por otros estados un número N de veces por ejemplo, va a ser representado con un estado diferente para cada valor de N.

El futuro es independiente del pasado dado el presente.

Los entornos completamente observables los podremos modelar como MDP (Markov Decission Process) mientras que los parcialmente observables los modelaremos como POMDP (Partially Observable Markov Decission Process). Gran parte del aprendizaje por refuerzo se basa en evaluar el MDP de un problema.

Componentes internos de un agente

En RL (reinforcement learning) un agente tiene como mínimo una de estas tres cosas (puede tener las tres):

  • Política: ¿Qué debe hacer el agente?
  • Función Valor: ¿Cómo de bueno es un estado?
  • Modelo: ¿Cómo funciona el mundo exterior?

Política

La política de un agente es la función que indica que acción tomar dado un estado (que como recordamos es Markov y es lo único necesario para tomar una buena decisión). Normalmente se denomina 𝜋. At = 𝜋(St). Las políticas pueden ser deterministas o estocásticas.

Los algoritmos que usan solo una política se llaman Policy Based

Función Valor

La función valor nos dice que tan bueno es un estado. Se trata de una predicción de una recompensa futura. De este modo, podemos pensar que algunas acciones son mejores que otras en base al valor de esta función. Esta función puede adoptar muchas formas, pero lo más normal es estimar la función valor, dada una política según las recompensas que iría obteniendo hasta el infinito, y multiplicándolas por gamma (factor de descuento).

Los algoritmos que usan solo la función valor se llaman Value Based. En verdad, toda función valor lleva una política implícita pero si esta polícita ya es explícita e "independiente" de la función valor, estamos hablando de un algoritmo Actor Critic.

A parte de la función v, también existe la función q, que es similar, pero espeficicando una acción inicial.

Modelo

Por último el modelo se compondría de dos partes, una parte que predice el siguiente estado del entorno (y de la misma forma, la observación que tendríamos) y otra que predice la recompensa.

Hoy en día existen muchos algoritmos de RL model-free, es decir, sin modelo. También existen algoritmos model-based pero son menos conocidos. El problema es que muchas veces es muy costoso elaborar este tipo de modelos en la práctica.

Exploración vs Explotación

Un agente se enfrenta a un dilema constantemente: ¿es mejor explotar algo conocido que sé que funciona bien o experimento cosas nuevas para poder descurbir cosas mejores? El agente debería explorar pero tampoco debe perder demasiado las recompensas que ha obtenido. Encontrar el balance adecuado es complicado.

Un ejemplo humano de este dilema sería por ejemplo, un día de cena con tus amigos, ir a un restaurante que ya conoces y que sabes que está bien o probar un restaurante nuevo que quizá sea mejor, pero no lo sabes.

Predicción vs Control

En RL tenemos dos problemas, relacionados pero diferentes. Por un lado, la predicción: evaluar correctamente el desempeño de una política en el futuro. Por otro lado, el control: obtener la mejor política posible para maximizar la recompensa.

Algoritmos clásicos

Existen tres tipos de algoritmos clásicos para RL:

  • Programación dinámica
  • Monte Carlo
  • Temporal Difference (TD)

En el siguiente post analizaremos con más detalle qué significa que podemos modelar un problema de RL como un MDP y qué es la ecuación de Bellman.

Bibliografía

]]>
https://blog.adrianistan.eu/reinforcement-learning-aprendizaje-refuerzo-que-es-parte-1 Wed, 4 Aug 2021 21:55:09 +0000
Soccerdoku en Prolog (usando clpz) https://blog.adrianistan.eu/soccerdoku-prolog-clpz https://blog.adrianistan.eu/soccerdoku-prolog-clpz No soy especialmente futbolero. Sé las reglas y sé más o menos donde anda cada equipo pero no veo partidos. Sin embargo, con motivo de la Eurocopa 2020 vamos a resolver un problema lógico relacionado con el fútbol, el Soccerdoku. Y lo haremos en Prolog usando la librería clpz


Soccerdoku de Rob Eastaway

¿Qué es un Soccerdoku?

Un soccerdoku es un problema como el que tenéis arriba (y ese es el que vamos a resolver). Basícamente consiste en rellenar las típicas tablas resumen de los equipos de una liga. Estas tablas tienen varias columnas:

P - Partidos jugados, W = Partidos ganados, L = Partidos perdidos, D = Partidos empatados, GF = goles a favor, GA = goles en contra, Points = Puntos del equipo. Además los equipos están ordenados en la tabla por puntos (de más a menos).

Los puntos se calculan teniendo en cuenta que un partido ganado son 3 puntos para el ganador y cero para el perdedor, y en caso de empate ambos se llevan 1 punto.

La idea es que estas tablas resumen pueden llegar a ser redundantes y ante una tabla incompleta podemos completar el resto de datos.

Modelando el problema - ¿Qué variables?

En este tipo de problemas lo más importante es la elección de lo que van a ser nuestras variables. Normalmente elegiremos lo más fundamental, aquello que nos permita derivar el resto de datos de la tabla. Así pues nuestras variables van a ser el número de goles de cada equipo en cada partido.

Sabemos que son 4 equipos, pero ¿cuántos partidos han jugado? El enunciado nos dice que han jugado todos contra todos una vez, así que han sido 6 partidos. Con esto ya podemos crear las variables, que serán las siguientes:

Nº Partido Equipo 1 Goles equipo 1 Equipo 2 Goles equipo 2
1 Albion A1 United U1
2 Albion A2 Town T2
3 Albion A3 Rovers R3
4 United U4 Town T4
5 United U5 Rovers R5
6 Town T6 Rovers R6

Nos faltaría una cosa más. ¿Qué valores pueden tener estas variables? El mínimo es 0 (cero goles) ya que no tiene sentido que tomen valores negativos. El máximo es teóricamente infinito pero vemos que ningún equipo tiene más de 4 en Goles a favor (goles que han marcado en todos los partidos, sumados), así que pondremos el máximo a 4.

En Prolog

Voy a usar la librería clpz de Markus Triska. Se puede usar tanto en Scryer Prolog como en SICStus Prolog.

La librería tiene tres partes fundamentales. La primera es la creación de variables, que podemos hacer con ins. Mediante ins creamos las variables, de tipo entero, dentro de un rango. La segunda parte son las restricciones, que añadiremos después, y por último, el etiquetado. El etiquetado es dar un valor concreto a todas las variables en conjunto, de forma que el resultado sea factible. Dicho esto, veamos como se haría:


:- use_module(library(clpz)).
:- use_module(library(format)).

soccerdoku :-
    [A1, A2, A3, U4, U5, T6, U1, T2, R3, T4, R5, R6] ins 0..4,
    label([A1, A2, A3, U4, U5, T6, U1, T2, R3, T4, R5, R6]),
    format("Albion ~d - United ~d~n", [A1, U1]),
    format("Albion ~d - Town ~d~n", [A2, T2]),
    format("Albion ~d - Rovers ~d~n", [A3, R3]),
    format("United ~d - Town ~d~n", [U4, T4]),
    format("United ~d - Rovers ~d~n", [U5, R5]),
    format("Town ~d - Rovers ~d~n", [T6, R6]).

Como véis, inicializamos todas las variables entre 0 y 4 y después buscamos una solución factible (etiquetado). Como no hay restricciones, el resultado será incorrecto.

Añadiendo restricciones

Que todos los partidos hayan sido 0 - 0 es incorrecto, pero ¿por qué? Porque tenemos información extra en la tabla original que nos dice que esos resultados no podían ser compatibles. En este problema existen dos tipos de restricciones: las de puntos y las de goles. Vamos con las de goles que son más sencillas.

Los goles a favor tienen que ser la suma de todos los goles que ha marcado un equipo. En la tabla tenemos los goles a favor de todos los equipos. En resumen, podemos decir que A1+A2+A3 debería ser igual a 4. Con clpz podemos expresar esta igualdad mediante el operador #=.


A1 + A2 + A3 #= 4,
U4 + U5 + U1 #= 4,
T6 + T2 + T4 #= 1,
R3 + R5 + R6 #= 1,

De la misma forma podríamos hacerlo con los goles en contra de los que disponemos el dato. En este caso, será la suma de los goles de los oponentes de un equipo en los respectivos partidos. En el caso de United, será A1 + T4 + R5 #= 5.

Con esto hemos acotado las soluciones mucho, pero sigue habiendo más de una solución posible. Vamos a introducir las restricciones de puntos.

Según el enunciado, en un partido el que gana se lleva 3 puntos, el que pierde 0, y si empatan, ambos se llevan 1. Sabemos que Albion tiene 7 puntos y Rovers tiene 1 punto.

Lo primero es hacer un predicado que según los goles de cada partido, nos de los puntos para los equipos participantes. Se puede hacer de la siguiente forma, usando comparadores de clpz:


match_points(Goals1, Goals2, Points1, Points2) :-
    Goals1 #> Goals2,
    Points1 #= 3,
    Points2 #= 0.

match_points(Goals1, Goals2, Points1, Points2) :-
    Goals1 #< Goals2,
    Points1 #= 0,
    Points2 #= 3.

match_points(Goals1, Goals2, Points1, Points2) :-
    Goals1 #= Goals2,
    Points1 #= 1,
    Points2 #= 1.

Y podemos añadir las restricciones de puntos para Albion y Rovers. Con esto ya obtendremos una solución única, que será la correcta.

Código final

El código final es el siguiente:


:- use_module(library(clpz)).
:- use_module(library(format)).

match_points(Goals1, Goals2, Points1, Points2) :-
    Goals1 #> Goals2,
    Points1 #= 3,
    Points2 #= 0.

match_points(Goals1, Goals2, Points1, Points2) :-
    Goals1 #< Goals2,
    Points1 #= 0,
    Points2 #= 3.

match_points(Goals1, Goals2, Points1, Points2) :-
    Goals1 #= Goals2,
    Points1 #= 1,
    Points2 #= 1.

soccerdoku :-
    [A1, A2, A3, U4, U5, T6, U1, T2, R3, T4, R5, R6] ins 0..4,
    A1 + A2 + A3 #= 4,
    U4 + U5 + U1 #= 4,
    T6 + T2 + T4 #= 1,
    R3 + R5 + R6 #= 1,
    A1 + T4 + R5 #= 5,
    A2 + U4 + R6 #= 2,
    A3 + U5 + T6 #= 3,
    match_points(A1, U1, PA1, _),
    match_points(A2, T2, PA2, _),
    match_points(A3, R3, PA3, PR3),
    match_points(U5, R5, _, PR5),
    match_points(T6, R6, _, PR6),
    PA1 + PA2 + PA3 #= 7,
    PR3 + PR5 + PR6 #= 1,
    label([A1, A2, A3, U4, U5, T6, U1, T2, R3, T4, R5, R6]),
    format("Albion ~d - United ~d~n", [A1, U1]),
    format("Albion ~d - Town ~d~n", [A2, T2]),
    format("Albion ~d - Rovers ~d~n", [A3, R3]),
    format("United ~d - Town ~d~n", [U4, T4]),
    format("United ~d - Rovers ~d~n", [U5, R5]),
    format("Town ~d - Rovers ~d~n", [T6, R6]).

% sustituye los format con esto para tener la salida alineada y bonita
%    Width = 15,
%    format("Albion ~t~d~*+ - United ~t~d~*+~n", [A1, Width, U1, Width]),
%    format("Albion ~t~d~*+ - Town ~t~d~*+~n", [A2, Width, T2, Width]),
%    format("Albion ~t~d~*+ - Rovers ~t~d~*+~n", [A3, Width, R3, Width]),
%    format("United ~t~d~*+ - Town ~t~d~*+~n", [U4, Width, T4, Width]),
%    format("United ~t~d~*+ - Rovers ~t~d~*+~n", [U5, Width, R5, Width]),
%    format("Town ~t~d~*+ - Rovers ~t~d~*+~n", [T6, Width, R6, Width]).

Y el resultado final es:

Con esto ya podemos rellenar el resto de la tabla de manera trivial. Albion tuvo dos victorias y un empate, 0 goles en contra. United tuvo 6 puntos, de dos victorias y una derrota. Town tuvo 2 puntos, de dos empates y una derrota. Y Rovers tiene un punto de un emparte y dos derrotas.

¿Qué te parece el soccerdoku? ¿Conocías las capacidades de la librería clpz? Dejadme vuestra opinión en los comentarios. Espero que os haya gustado este pequeño pasatiempo.

]]>
https://blog.adrianistan.eu/soccerdoku-prolog-clpz Sun, 13 Jun 2021 21:32:16 +0000
Teletexto #010 https://blog.adrianistan.eu/teletexto-010 https://blog.adrianistan.eu/teletexto-010 Bienvenidos a una nueva edición del Teletexto. Esta vez tengo guardadas cosas de hace bastante tiempo, así que, sin más dilación, comencemos.

La última versión "tradicional" de Java fue Java 8. Desde aquel momento, se ha acelerado el ritmo de versiones. Actualmente estamos en Java 16, con vistas a Java 17 en septiembre. En esta web se recogen todos los cambios que ha habido entre Java 8 y Java 16 para que sea más fácil digerirlos.

Europa no está atravesando su mejor momento en cuanto a sus empresas tecnológicas. Bert Hubert lleva trabajando muchos años en el sector de las telecomunicaciones en Europa con PowerDNS y nos da lo que cree que es el motivo por el que Europa se está quedando atrás. De forma resumida: se externaliza demasiado y eso tiene un coste más allá del económico.

¿Os acordáis del vuelo MH370 que desapareció en 2014 en Malasia? Todavía no se ha encontrado el avión (algún trozo sí) y sigue sin haber una explicación oficial satisfactoria. No obstante, este artículo reúne todos los datos conocidos hasta la fecha. El caso parecer apuntar todo a lo mismo, que aunque improbable, parece ser lo único posible a estas alturas.

¿Quieres desarrollar una aplicación pero no eres Jeff Bezos ni Bill Gates? Tranquilo, muchos servicios tienen capas gratuitas (bases de datos, correos electrónicos, servidores, etc). Todo ello se recoge en la lista de Stack on a Budget.

El mundo OVNI en España no tiene nada que envidiarle a los Estados Unidos. Quizá UMMO no es lo más conocido cuando hablamos de seres extraterrestres, pero su historia es tremendamente interesante.

El Efecto Lindy dice que el tiempo de vida que le queda a algo que no se desgasta (ideas, software, etc) es proporcional a su edad actual. Eso quiere decir, por ejemplo, que un lenguaje de programación más antiguo como COBOL o C seguramente viva más tiempo que un lenguaje de programación inventado ayer. Lo mismo pasa con otras cosas como los libros, que seguramente se sigan leyendo mucho más tiempo los que ya se llevan leyendo mucho tiempo que los nuevos bestsellers.

Siguiendo con la misma idea de antes... Las bases de datos relacionales típicas llevan muchos años entre nosotros. Sin embargo hacen asumciones que quizá hoy en día no tengan demasiado sentido.

El switch-true pattern. Un patrón, con críticos y defensores a partes iguales, que se puede usar en JavaScript para expresar cadenas de if-else complejas. Todo se basa en que el case de JavaScript puede evaluar expresiones. ¿Qué opinas al respecto?

¿Qué es Geohash? Es una forma de expresar coordenadas terrestres en base a letras. Según añadimos más letras, la ubicación se vuelve más precisa. Una interesante alternativa a las duplas latitud-longitud. Una idea similar es What 3 Words, que usa tres palabras para identificar cualquier punto de la Tierra, pero el algoritmo es propietario y tiene bastantes desventajas...

La canción de este Teletexto es la versión de Vencidos por Josele Santiago (original de Serrat, basada en un poema de León Felipe).

]]>
https://blog.adrianistan.eu/teletexto-010 Tue, 1 Jun 2021 18:57:34 +0000
Llamar a Rust desde Prolog: swipl-rs https://blog.adrianistan.eu/llamar-rust-prolog-swipl-rs https://blog.adrianistan.eu/llamar-rust-prolog-swipl-rs Prolog es un lenguaje muy interesante pero muchas veces, cuando programamos en él, sentimos que estamos aislados. No existen muchas librerías en Prolog y eso hace que muchas veces se nos quiten las ganas de tener que reimplementar algo en Prolog. Sin embargo, varios entornos Prolog nos ofrecen llamadas nativas (normalmente a C). Algunos de estos sistemas son SWI Prolog o GNU Prolog. En este post vamos a ir un paso más allá, y en vez de usar C para complementar a Prolog, usaremos Rust. Todo ello gracias a swipl-rs.

Creando la librería en Rust con swipl-rs

El primer paso será crear la librería en Rust. Nuestra librería va a ser un wrapper de la librería gráfica Raylib. Usaremos cargo para crear el código inicial:


cargo new --lib raylog

A continuación editamos el fichero Cargo.toml para añadir la siguiente información:


[lib]
crate-type = ["cdylib"]

[dependencies]
swipl = "0.3.0"
raylib-sys = "3.5"

Por un lado definimos la librería de tipo cdylib (librería dinámica) y añadimos las dependencias de swipl-rs y de Raylib. En este caso, usaré raylib-sys, que compila automáticamente Raylib y tiene una API muy parecida a la de C. Esta API es más fácil que usar que la API estilo Rust del crate raylib a secas.

A continuación vamos al fichero lib.rs. Hay dos cosas importantes: la macro predicates y la función install.

La función install, será llamada para hacer el setup de nuestra librería. La macro predicates nos dejará definir predicados, de diversos tipos.


use swipl::prelude::*;
use raylib_sys::*;

predicates! {
    semidet fn init_raylib(_context, width, height, title) {
        let width = width.get::<i64>()? as i32;
        let height = height.get::<i64>()? as i32;
        let title = title.get::<String>()?;
        let title = CString::new(title).unwrap();

        unsafe {
            InitWindow(width, height, title.as_ptr());
        }
        
        Ok(())
    }
}

#[no_mangle]
pub extern "C" fn install() {
    register_init_raylib();
}

En la macro definimos un predicado llamado init_raylib. Es de tipo semidet, es decir, solo genera una (posible) solución, pero también puede fallar. El primer argumento siempre es el contexto, después tenemos los argumentos que le pasaríamos al predicado. Sobre esos argumentos podemos obtener el valor real, haciendo un get, o unificarlos con algún valor si son variables. En este caso, simplemente inicializamos la ventana en Raylib, con los valores que nos dan y devolvemos un Ok, para indicar que el predicado es cierto.

En la función install, hacemos el registro del predicado. La macro genera funciones del estilo register_XXXX para que las añadamos aquí.

Un predicado con fallo

Si has entendido por qué ponemos el Ok, al finalizar la definición del predicado, esto te resultará sencillo. Si queremos que en Prolog un predicado falle, deberemos devoler Err. En este caso hay dos opciones, una excepción de Prolog o un simple fallo. Esto lo podremos usar en el mapeo de WindowShouldClose:


    semidet fn should_close(_context) {
        unsafe {
            if WindowShouldClose() {
                Ok(())
            } else {
                Err(PrologError::Failure)
            }
        }
    }

Unificando con términos sin traslación directa a Rust

En Prolog es muy habitual usar términos que contienen otros términos. Estos se llaman comunmente compuestos o complejos. En Prolog se usan para representar cierta información que va junta, como un struct o un record en otro lenguaje. Pero esto no tiene traducción directa a Rust. Para ello, usaremos la macro term_getable, que nos dejará definir tipos de datos compatibles con la operación get.

En este ejemplo vamos a tener un compuesto de color, que contiene los valores de rojo, verde, azul y alpha de un color. Tiene sentido que vayan juntos. En Prolog diríamos:


Color = color(124, 123, 123, 255)

En swipl-rs podemos realizar la lectura de ese término con la macro así:


struct PrologColor{
    r: u8,
    g: u8,
    b: u8,
    a: u8
}

term_getable!{
    (PrologColor, "color", term) => {
        let r: i64 = attempt_opt(term.get_arg(1)).unwrap_or(None)?;
        let g: i64 = attempt_opt(term.get_arg(2)).unwrap_or(None)?;
        let b: i64 = attempt_opt(term.get_arg(3)).unwrap_or(None)?;
        let a: i64 = attempt_opt(term.get_arg(4)).unwrap_or(None)?;

        Some(PrologColor{
            r: r as u8,
            g: g as u8,
            b: b as u8,
            a: a as u8
        })
    }
}

Y se podría usar así:


    semidet fn clear_background(_context, color) {
        let color: PrologColor = color.get()?;
        unsafe {
            ClearBackground(Color{
                r: color.r,
                g: color.g,
                b: color.b,
                a: color.a
            });
        }
        Ok(())
    }

Con esto y algún predicado más, pero de construcción similar, ya podemos hacer un Hola Mundo.

El programa Prolog

El programa Prolog debe, en primer lugar, cargar la librería dinámica. Después, podremos usar los predicados que hemos hecho para controlar Raylib desde Prolog. Un código podría ser:


:- use_module(library(random)).

init :-
    use_foreign_library('target/debug/libraylog.so'),
    init_raylib(600, 400, "RayLog"),
    loop.

loop :-
    \+ should_close,
    !,
    begin_drawing,
    random_between(128, 255, Red),
    random_between(128, 255, Green),
    random_between(128, 255, Blue),
    clear_background(color(Red, Green, Blue, 255)),
    draw_text("Raylib + Prolog =:= Fun", 12, 12, 20, color(0, 0, 0, 255)),
    end_drawing,
    loop.

loop :-
    halt(0).

Ahora solo habrá que arrancar SWI Prolog con este fichero y ejecutar init.

Esto es solo un ejemplo de lo que nos permite hacer swipl-rs. Pero hay muchas más opciones. Ya no hay excusas para usar la librería que más te gusta desde Prolog.

]]>
https://blog.adrianistan.eu/llamar-rust-prolog-swipl-rs Mon, 31 May 2021 22:08:48 +0000
Buscando en los datos del leak de Facebook con ElasticSearch https://blog.adrianistan.eu/buscando-facebook-leak-elasticsearch https://blog.adrianistan.eu/buscando-facebook-leak-elasticsearch Recientemente hemos conocido la noticia de un leak de datos de Facebook. Principalmente son nombres y números de teléfono. Se trata de una brecha grave, ya que es una fuente perfecta para phising y estafas de este tipo. En este post vamos a ver como cargar estos datos en una base de datos optimizada para búsquedas como ElasticSearch y como hacer algunas consultas. No voy a enlazar los datos originales, ya que es una información peligrosa pero no es muy difícil encontrarlos.

Instalando ElasticSearch y Kibana

El primer paso es instalar ElasticSearch, nuestra base de datos, y Kibana, una interfaz de consulta y administración de ElasticSearch. En mi caso, voy a usar Docker, así que la "instalación" es en realidad un fichero de Docker Compose. Si no sabes nada de Docker Compose te recomiendo este tutorial. El fichero se llamará docker-compose.yml:


version: "3.6"
services:
  elasticsearch:
    image: elasticsearch:7.12.0
    ports:
    - 9200:9200
    - 9300:9300
    environment:
      discovery.type: single-node
    volumes:
      - data:/usr/share/elasticsearch/data
  kibana:
    image: kibana:7.12.0
    ports:
    - 5601:5601
    environment:
      ELASTICSEARCH_HOSTS: http://elasticsearch:9200
    depends_on:
    - elasticsearch

volumes:
  data:

Básicamente lo que indicamos es que queremos un servicio basado en una imagen de ElasticSearch 7.12.0, donde abrimos los puertos 9200 y 9300 y lo configuramos como single-node. Además le añadimos un volumen para almacenar los datos.

Después indicamos que queremos un servicio de Kibana, que abrirá el puerto 5601 y le configuramos el servidor de ElasticSearch al que hemos definido arriba.

Para descargarlo todo, ejecutamos el comando up de docker-compose:


docker-compose up

Ingestión de los datos con Logstash

Ya tenemos ElasticSearch y Kibana levantados. Ahora hace falta ingestar los datos del leak. Para ello usaremos otra herramienta del mundo Elastic, Logstash. Logstash es una herramienta de ingesta potente, que posee varios filtros. Con esta herramienta leeremos los datos de Facebook en formato quasi-csv (está separado por : en vez de , y las comillas funcionan raro) y los dejaremos en nuevo index de ElasticSearch.

Todo esto se define con un fichero de configuración, que será parecido a este:


input {
    file {
        path => "/config/spain.txt"
        start_position => "beginning"
        sincedb_path => "/dev/null"
    }
}
filter {
    mutate {
        gsub => ["message", "\"", ""]
    }
    csv {
        separator => ":"
        columns => ["phone_number", "facebook_id", "name", "surname", "gender", "location1", "location2", "relationship", "work", "field1", "field2", "field3"]
    }
}
output {
    elasticsearch {
        hosts => "http://elasticsearch:9200"
        index => "facebook-spain"
    }
}

Se indica el fichero origen de los datos, en este caso, /config/spain.txt. Se indica que se haga una sustitución de las comillas por espacios, se usa el plugin csv para cargar los datos según las columnas que indiquemos. Y finalmente se indica donde hay que guardar los datos, en este caso en un ElasticSearch, en el index facebook-spain (se crea solo)

Una forma de ejecutar Logstash es también desde Docker Compose, usando otro fichero. Esta ha sido mi opción, pero también podríamos hacerlo desde fuera, solo hay que tener cuidado de configurar el host de ElasticSearch y la ruta al fichero de origen.

Con Docker Compose, habría que añadir este servicio y asegurarse de que tanto el fichero logstash-fb-spain.conf como el spain.txt (los datos en crudo) esten en la misma carpeta:


  logstash:
    image: logstash:7.12.0
    command: logstash -f /config/logstash-fb-spain.conf
    volumes:
    - ./:/config
    depends_on:
    - elasticsearch

Y levanta el servicio con docker-compose up. Este proceso llevará su tiempo. Relájate, a mí me duró un par de horas.

Buscando Kibana

Si accedemos a http://localhost:5601, veremos algo similar a esto:

Se trata de la página de inicio actual de Kibana. Para consultar los datos sobre Kibana hay que crear un Index Pattern. Para hacerlo vamos a Manage. En las opciones buscamos Index Patterns y desde ahí creamos uno. Un Index Pattern es una expresión para grupar varios Index en uno. En nuestro caso solo tenemos un Index (facebook-spain). Podemos crear un Index Pattern con el patrón "facebook*", "facebook-spain*" o "facebook-spain", todos ellos y más variaciones valdrán para seleccionar nuestro Index.

Ya que estamos en los ajustes, podemos ir a Index Managemente y veremos el tamaño de nuestro Index creado con Logstash:

En el caso de spain.txt, son casi 11 millones de registros.

Ahora ya sí podemos ir a la sección Discover dentro del menú lateral izquierdo

Desde aquí podremos hacer consultas más o menos avanzadas usando el lenguaje KQL. Pero no te preocupes, escribir directamente lo que quieres buscar es un tipo de query KQL válido. En la imagen se puede ver una consulta en KQL algo más avanzada:

Con esto ya podemos realizar todo tipo de búsquedas. No obstante, el buscador no será capaz de distinguir mayúsculas de minúsculas y las tildes de las no tildes. Esto se puede definir con analizadores en ElasticSearch pero en este caso concreto, no creo que merezca mucho la pena. De cara a un buscador, por ejemplo, de artículos en un blog, sí sería interesante hacerlo.

Bonus: una app en FastAPI para consultar

Kibana es una mera interfaz sobre ElasticSearch. Nosotros podemos crear nuestras propias aplicaciones que usen la API REST de ElasticSearch para realizar las consultas. El endpoint para hacer búsquedas en ElasticSearch es /_search. Existen diferentes formas de buscar, mediante solo un campo, multi-campo, con y sin boosting, e incluso con KQL. Yo he elegido esta última opción.

Para crear el proyyecto FastAPI uso Poetry, un gestor de paquetes y entornos virtuales de Python.


poetry init
poetry add fastapi aiohttp jinja2 uvicorn
poetry shell

El código de la aplicación es muy simple. Hay dos rutas, una de inicio y otra para realizar una búsqueda en ElasticSearch mediante REST. Ambas usano la misma plantilla HTML para mostrar resultado.


from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
import aiohttp


app = FastAPI()
templates = Jinja2Templates(directory="templates")

session = aiohttp.ClientSession()


@app.get("/")
def index(request: Request):
    return templates.TemplateResponse("search.html", {"request": request, "hits": []})

@app.get("/search")
async def search(request: Request, name: str, surname: str):
    query = {
        "size": 100,
        "query": {
            "query_string": {
                "query": f"(name:{name} AND surname:{surname})",
            }
        }
    }

    async with session.get("http://localhost:9200/_search", json=query) as response:
        result = await response.json()

    return templates.TemplateResponse("search.html", {"request": request, "hits": result["hits"]["hits"]})

La plantilla está disponible aquí. Esta app podemos ejecutarla con el comando

.

uvicorn main:app

¡Y ya podremos hacer búsquedas de forma más amigable!

Todo el código usado aquí, salvo los datos del leak, están en el repositorio de GitHub de ejemplos del blog.

]]>
https://blog.adrianistan.eu/buscando-facebook-leak-elasticsearch Wed, 7 Apr 2021 20:15:14 +0000
Teletexto #009 https://blog.adrianistan.eu/teletexto-009 https://blog.adrianistan.eu/teletexto-009 Primer Teletexto del año. Veamos los enlaces más interesantes que me han llegado desde el último teletexto.

  • En Top 10 Python libraries of 2020 veremos un listado de las 10 mejores librerías para Python creadas en 2020. Algunas de ellas son muy interesantes, échalas un ojo.
  • A día de hoy, seguimos usando sistemas operativos, que conceptualmente, son muy similares a aquellos de los años 70, pero ya han pasado 50 años. ¿Cómo debería ser el sistema operativo del siglo XXI, ahora que ya tenemos más experiencia? tomaka es un usuario activo de la comunidad Rust y ha escrito en un post algunas cosas que él considera necesarias (por ejemplo, eliminar el concepto de "programas", solo tener librerías, tener una shell que sea tipada, direccionamiento basado en contenido, sandboxing,...) en este artículo: The future of operating systems. Él intenta poner en práctica algunas de estas ideas en el sistema operativo redshirt.
  • El testing es muy importante si queremos software de calidad. Sin embargo, cuando programamos código a veces, sin darnos cuenta, nos dificultamos a nosotros mismos la facilidad y calidad del testeo. En esta charla de Google, vemos como programar en estilo OOP de modo que el código que hagamos sea fácil y robusto de testear.
  • Existe un patrón de diseño, capaz de sustituir tanto herencia como composición en OOP llamado ECS (Entity-Component-System). Vendría a ser similar a la composición vía Traits o Interfaces salvo que es dinámico y los sistemas (la lógica) está más desacoplada. Es relativamente popular en videojuegos, pero el famoso motor Godot, no la usa y explican por qué en: Why isn't Godot an ECS-based engine?. Es interesante, aunque os recomiendo echar un vistazo a ECS también (lo tengo en la lista de asuntos a hablar en el blog). En lenguajes como Rust, es de hecho una opción muy buena ya que no tenemos el set de herramientas clásico de la OOP (clases, herencia, etc). Para ello existen varias librerías como hecs o el motor (todavía en progreso) Bevy, que sí usa ECS de forma central.
  • No deberías usar la sintaxis AT&T nunca, bajo ninguna circunstancia. Así de contundente es este artículo que explica porque la sintaxis AT&T es una mala opción (para quién no lo sepa, en ensamblador de x86, existen dos sintaxis populares, la Intel y la AT&T). Lo cierto es que este artículo me generó la duda de ¿por qué esta sintaxis es así? Y hay una respuesta a StackOverflow que examina los motivos históricos. Como siempre, sale a la luz el pasado de UNIX y C, que fueron originalmente sistemas para arquitecturas PDP-11, diferentes a las x86 de Intel.
  • He empezado a colaborar más activamente en Scryer Prolog, el intérprete de Prolog hecho en Rust que aspira a ser rápido y compatible con el estándar ISO. Algunas de mis aportaciones recientes, y que animo a todo el mundo a probar son: el servidor HTTP en Prolog y el generador de UUIDv4 en Prolog.
  • Por último, no quería dejar pasar un debate que se ha formado en las distros respecto a como hay que empaquetar el software programado en lenguajes como Go y Rust. Las técnicas que se venían utilizando con C y C++ están empezando a dar bastantes quebraderos de cabeza. Hay quien opina que debería seguir el modelo actual, ya que es más seguro, lleva funcionando mucho tiempo, etc... Sin embargo, yo me inclino más por el lado de que las distros no pueden tratar de imponer sus modelos a los lenguajes, ya que herramientas como Cargo son multiplataforma, están mucho mejor integradas, añaden funcionalidad específica, mientras que un DEB/RPM no te ofrece realmente nada extra y es válido solo para una parte más reducida de los usuarios. También está el tema de la reproducibilidad. Todo el mundo lo quiere, tanto devs como distros, pero me parece que cada uno lo quiere a un nivel diferente y eso causa conflictos. Esto podría intentar desarrollarlo más en un post si veo que hay interñes. En todo caso: aquí un artículo sobre parte del asunto desde el lado de Debian y aquí alguien que piensa que empaquetar librerías en Rust ahora mismo en las distros no tiene sentido

Para despedirme vamos con una canción de William Hart que encontré hace poco en la radio

]]>
https://blog.adrianistan.eu/teletexto-009 Sun, 28 Feb 2021 17:16:18 +0000
El formato RON: Rusty Object Notation https://blog.adrianistan.eu/formato-ron-rusty-object-notation https://blog.adrianistan.eu/formato-ron-rusty-object-notation Hace poco he tenido la necesidad de refactorizar un código muy repetitivo y con un alto número de constantes. Este es un caso ideal para usar un formato externo, definido por nosotros, y donde vamos a conseguir separar el código "real" de la repetición.

Existen muchos formatos de serialización de datos: JSON, XML y YAML son los más conocidos. JSON en particular es muy usado para comunicar entre procesos (ya sea mediante una API REST o cualquier otro sistema) mientras que YAML es bastante usado como formato para representar configuraciones (Kubernetes, Spring Boot, Docker, ...). Ninguno de estos formatos es idóneo, pero crear un formato de la nada y esperar interoperabilidad es complicado.

Afortunadamente en este caso no necesito interoperabilidad, ya que mi programa iba a ser el único que iba a leer estos ficheros, así que, ¿por qué no probar algo mejor?

Y aquí llegamos a Serde. De esta crate ya hemos hablado en el blog, y es el estándar para serializar/deserializar en Rust. Soporta una gran cantidad de formatos, pero entre ellos hay uno especial, uno diseñado específicamente para trabajar con Rust de la forma más parecida posible a este: RON, Rusty Object Notation.

El objetivo de RON es tener un formato que se parezca mucho a como se expresan los datos dentro de Rust soportando además todas las características de Serde.

Sintaxis de RON

En RON tenemos structs, que son una equivalencia directa de los struct de Rust, con su comprobación de miembros estricta. Usaremos los paréntesis y un nombre opcional de struct. También tendremos mapas, que nos permiten añadir claves dinámicas. Esto es una diferencia respecto a JSON, donde la diferencia entre una claves fija y una clave que es un dato no es posible a simple vista. Para los mapas usaremos llaves y las claves deberán ir entre comillas. Por último también tenemos arrays con corchete. El resto es muy parecido a la sintaxis Rust, misma sintaxis de comentarios, soporte para trailing commas, tuplas, enums, etc.


/* Este es un fichero RON de ejemplo */

[
    // los nombres de los struct son opcionales 
    Place(
        name: "Academia de Caballería",
        position: Position(
            lat: 0.0,
            lon: 0.0,
        ),
        photos: [],
        urls: {
            "facebook": "https://www.facebook.com/Academia"
        }
    ),
    (
        name: "Archivo de Simancas",
        position: (
            lat: 15.0,
            lon: 12.0,
        ),
        photos: [
            (
                title: "Foto de la sala de lectura",
                url: "https://imgur.com/55555"
            ),
            (
                title: "Foto de la torre",
                url: "https://imgur.com/55555666"
            )
        ],
        urls: {}
    )
]

Lectura y escritura usando Serde

RON está diseñado para ser muy cómodo de usar con Rust, así que la lectura y escritura desde Serde es extremadamente simple y evidente.

Para leer un archivo, debemos declarar las estructuras que lo componen y añadir el trait serde::Deserialize. Podemos usar otras estructuras, Vec, HashMap, ... Finalmente para leer usamos ron::from_str y el tipo al que queremos convertir ese texto:


use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;

use serde::Deserialize;

#[derive(Deserialize)]
struct Place {
    name: String,
    position: Position,
    photos: Vec,
    urls: HashMap
}

#[derive(Deserialize)]
struct Position {
    lat: f64,
    lon: f64,
}

#[derive(Deserialize)]
struct Photo {
    title: String,
    url: String,
}

fn main() {
    let mut file = File::open("places.ron").expect("file doesn't exist");
    let mut contents = String::new();
    file.read_to_string(&mut contents).expect("can't read file");

    let places: Vec = ron::from_str(&contents).expect("malformed file");
    for place in places {
        println!("Place name: {}", place.name);
        println!("Number of photos: {}", place.photos.len());
    }
}

Para la escritura deberemos usar el trait serde::Serialize y la función ron::to_string:


use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct Place {
    name: String,
    position: Position,
    photos: Vec,
    urls: HashMap
}

#[derive(Serialize, Deserialize)]
struct Position {
    lat: f64,
    lon: f64,
}

#[derive(Serialize, Deserialize)]
struct Photo {
    title: String,
    url: String,
}

fn main() {
    let mut file = File::open("places.ron").expect("file doesn't exist");
    let mut contents = String::new();
    file.read_to_string(&mut contents).expect("can't read file");

    let places: Vec = ron::from_str(&contents).expect("malformed file");
    for place in &places {
        println!("Place name: {}", place.name);
        println!("Number of photos: {}", place.photos.len());
    }
    
    let places_str = ron::to_string(&places).expect("can't convert to RON");
    println!("{}", places_str);
}

Hay que tener en cuenta que al pasar leer y guardar perdemos los comentarios por el camino (ya que no se pueden representar dentro de los struct de Rust). Además el formato de salida es mínimo, sin espacios ni nombres de struct.


[(name:"Academia de Caballería",position:(lat:0,lon:0),photos:[],urls:{"facebook":"https://www.facebook.com/Academia"}),(name:"Archivo de Simancas",position:(lat:15,lon:12),photos:[(title:"Foto de la sala de lectura",url:"https://imgur.com/55555"),(title:"Foto de la torre",url:"https://imgur.com/55555666")],urls:{})]

Campos opcionales

Una cosa que no resulta del todo evidente, al menos en la versión actual de RON, es el manejo de campos opcionales dentro de los struct. Actualmente cuando definimos un campo como de tipo Option, el campo tiene que existir en RON, aunque su valor sea None. Este comportamiento puede cambiar con el tiempo pero para la versión de RON 0.6.4 podemos usar la crate serde_with y anotar los atributos que queramos marcar como opcionales así:


#[derive(Serialize, Deserialize)]
struct Photo {
    #[serde(default, skip_serializing_if = "Option::is_none", with = "::serde_with::rust::unwrap_or_skip")]
    title: Option,
    url: String,
}

Ahora os quiero hacer una pregunta, ¿le veis futuro a RON? ¿creéis que podrá ser usado en otros lenguajes en el futuro o no aporta demasiado respecto a otros formatos?

]]>
https://blog.adrianistan.eu/formato-ron-rusty-object-notation Sat, 6 Feb 2021 12:02:14 +0000
¿Qué son los tipos suma? Explicación en TypeScript y Rust https://blog.adrianistan.eu/tipos-suma-typescript-rust https://blog.adrianistan.eu/tipos-suma-typescript-rust El sistema de tipos de un lenguaje de programación tiene que ser algo que nos ayude a nosotros a encontrar fallos antes de tiempo y a hacer el código lo más legible posible. En algunos lenguajes disponemos de tipos suma, los cuáles tienen varias ventajas que podemos aprovechar.

¿Qué son los tipos suma?

Los tipos suma son un subconjunto de los llamados Tipos Algebraicos de Datos, ADT, por sus siglas en inglés. Un tipo suma es un tipo que se compone de varios subtipos, los cuales solo uno de ellos es el que se aplica en un preciso momento. Un OR de tipos si se quiere ver de otra forma.

Por ejemplo, podríamos tener un tipo llamado StringFloat que es la suma de string y float. Así, en StringFloat podemos tener o bien un string o bien un float, pero no a la vez. La principal ventaja es que podemos obtener un resultado parecido a la herencia y al polimorfismo pero más flexible y ligero. Hay que recordar esto no interfiere con la herencia ni dada por el estilo, no estamos con objetos, es solo a nivel de tipos. De hecho, podrían ser tipos totalmente diferentes los componentes de la suma.

De cierto modo en los lenguajes de tipado dinámico ya tenemos sum types, todos lo son ya que no se comprueba nada. Lo interesante es ver como los podemos introducir en el tipado estático. De esta forma podremos hacer uso de esta "dinamicidad" de forma segura. El compilador es capaz de comprobar que usamos los subtipos de la suma siempre que hayamos comprobado que es el subtipo correcto. No todos los lenguajes admiten los tipos suma de la misma forma. TypeScript y Rust sí los admiten de forma poco dolorosa. Veamos ejemplos en ambos lenguajes:


type Position = {
    x: number,
    y: number,
};

type Circle = {
    position: Position,
    radius: number,
}

type Rect = {
    position: Position,
    width: number,
    height: number,
}

type Shape = Circle | Rect;

function main(){
    const shapes: Shape[] = [];
    shapes.push({
        position: {
            x: 50,
            y: 60
        },
        radius: 7
    });
    shapes.push({
        position: {
            x: 50,
            y: 60,
        },
        width: 40,
        height: 30
    });
}

Este código compilaría en TypeScript. Sin embargo si en alguno de los objetos que añadimos al array olvidádemos algún campo fallaría. position siempre es obligatorio pero radius, width y height son "opcionales". Sin embargo, una vez pongamos width se nos obligará a añadir height. El tipo Shape debe ser Circle con todos sus campos o Rect con todos sus campos. ¿Podríamos añadir radius al objeto que ya tiene width y height? Por defecto sí y en ese caso ese objeto sería a la vez Circle y Rect.


struct Position {
    x: f64,
    y: f64,
}

enum Shape {
    Circle {
        position: Position,
        radius: f64,
    },
    Rect {
        position: Position,
        width: f64,
        height: f64,
    }
}

fn main(){
    let mut shapes = Vec::new();
    shapes.push(Shape::Circle {
        position: Position {
            x: 5.0,
            y: 6.0,
        },
        radius: 4.0
    });
    shapes.push(Shape::Rect {
        position: Position {
            x: 5.0,
            y: 6.0,
        },
        width: 70.0,
        height: 80.0
    });
}

En Rust la forma de crear tipos suma es mediante enum. Existen dos formas para hacer los subtipos, con un struct o con una tupla. En el ejemplo uso un struct pero las tuplas son muy usadas en subtipos pequeños. Una gran diferencia que tiene Rust respecto a TypeScript es que no admiten tipos suma anónimos (al menos de momento) por lo que no podemos componer los sum types, es decir, el sum type completo tiene que aparecer en el enum y no podemos juntar varios enum entre sí. Tampoco es posible añadir campos extra, ya que Rust no permite campos extra nunca. Además en Rust es obligatorio el tag, que es el Circle o el Rect que aparece y que tenemos que indicar al crear un dato de ese tipo. En TypeScript el tag es opcional.

Tags

Si queremos distinguir para cierta operación necesitaremos un tag. Ya hemos visto que en Rust es obligatorio, así que no hay nada que contar pero en TypeScript no lo es. Así que si queremos discernir que subtipo dentro de la suma tenemos, habrá que añadir un tag. El tag es un campo del objeto que va a permitir diferenciar ese tipo respecto a los demás. Puede ser por su valor o por su existencia. Normalmente es un campo de tipo type, aunque no es necesario.


// usando como tag una propiedad que solo existe en Circle
function area(a: Shape, b: Shape): number {
    if("radius" in a){
        // aquí el compilador ya sabe que a es de tipo Circle ya que es el único tipo que tienen los Circle que los demás no tienen

        return Math.PI*Math.pow(a.radius, 2);
    } else {
        // aquí el compilador sabe que a es de tipo Rect ya que no es de tipo Circle y no hay más opciones
        return a.height*a.width;
    }
}
// usando como tag una propiedad cuyo valor indica el tipo

type Circle = {
    type: "circle",
    position: Position,
    radius: number,
}

type Rect = {
    type: "rect",
    position: Position,
    width: number,
    height: number,
}

function area(a: Shape, b: Shape): number {
    if(a.type === "circle"){
        return Math.PI*Math.pow(a.radius, 2);
    } else {
        return a.height*a.width;
    }
}

En Rust para discernir el subtipo podemos hacer pattern matching, tanto con if-let, como con match.


// con match
fn area(shape: &Shape) -> f64 {
    match shape {
        Shape::Circle { radius, .. } => std::f64::consts::PI * (radius.powf(2.0)),
        Shape::Rect { width, height, ..} => width * height
    }
}

// con if-let
fn area2(shape: &Shape) -> f64 {
    if let Shape::Circle { radius, .. } = shape {
        return std::f64::consts::PI * (radius.powf(2.0));
    }
    if let Shape::Rect { width, height, ..} = shape {
        return width * height;
    }
    unreachable!();
}

Nótese que la versión con if-let no es exhaustiva y por ello es muy recomendable añadir la macro unreachable al finalizar los ifs. En todo caso, el match es mejor opción.

Modelar NULL

Una de las ventajas más interesantes de los tipos suma, es que si están bien implementados podemos usarlos para distinguir entre un objeto de verdad y un NULL. De este modo, para que nuestro código compile siempre tendremos que comprobar que el objeto existe y no es NULL. En TypeScript lo primero será habilitar la opción strictNullChecks o strict. Con esto activado tendríamos que esto no compila:


    let shape = shapes.find((t: Shape) => t.position.x === 40);
    area(shape, shapes[0]);

Porque shape es ahora de tipo Shape | undefined, y area solo admite Shape. Hay que comprobar que no sea undefined.


    let shape = shapes.find((t: Shape) => t.position.x === 40);
    if(shape){
        area(shape, shapes[0]);
    }

En Rust ya tenemos un tipo suma para esto, Option, que puede ser Some(T) o None.


    let shape = shapes.get(0);
    area(&shape); // ERROR: No compila

Este código no compila porque shape ya no es de tipo Shape sino de tipo Option.


    let shape = shapes.get(0);
    if let Some(shape) = shape {
        area(&shape);
    }

Esto sí compila ya.

Conclusión: tipos restrictivos

Como ya vimos en el artículo sobre Idris, el sistema de tipos tiene que ayudar al programador y para ello tenemos que usarlo creando tipos lo más restrictivos posibles. Todo lo que podamos decir sobre un dato de como tiene que ser deberíamos tiparlo y no usar tipos genéricos que podemos usar en cualquier lado pero requieren comprobaciones que igual no nos acordamos de hacer.

]]>
https://blog.adrianistan.eu/tipos-suma-typescript-rust Sun, 10 Jan 2021 22:26:24 +0000
Easter eggs en Python https://blog.adrianistan.eu/easter-egg-python https://blog.adrianistan.eu/easter-egg-python Python tiene varios easter eggs interesantes. Porque el lenguaje de programación tiene un pequeño hueco para la diversión.

import this

En Python usamos import para importar las funciones, clases y variables de otro fichero. ¿Qué ocurre si importamos this?

¡Se nos muestra el Zen of Python! Se trata de una especie de manifiesto de como debería ser el código elegante (¿y la vida?)

Sin embargo te recomiendo que eches un vistazo al código fuente, ya que también tiene sorpresa: https://github.com/python/cpython/blob/master/Lib/this.py

import antigravity

Si hacemos import antigravity nos llevará a la siguiente tira cómica:

El chiste es una referencia a que Python es sencillo ya que hay librerías para todo, incluso para eliminar la gravedad.

from __future__ import braces

En Python muchas veces se han añadido cambios que podían romper el código antiguo. Para ir adaptando tu código a las versiones futuras se suele usar el módulo __future__ que es especial ya que es capaz de hacer cambios fuertes en el intérprete.

Una de las diferencias que más llaman la atención de Python es que los scopes se realizan mediante indentación de espacios, no con llaves como la familia C (Java, C#, C++, JavaScript, AWK, ...). ¿Y sí en algún momento añaden llaves también en Python?

not a chance

import __hello__

Una forma más cómoda todavía de hacer un Hola Mundo.

hash(float("inf"))

En Python la función hash nos genera un número de un objeto (y en Python todo son objetos, hasta los números). Este número sirve sobre todo en ciertas estructuras de datos, como los diccionarios, para organizar los elementos de forma más eficiente. El caso es que el hash del número infinito es siempre:

¡314159! Es como el inicio del número Pi. El número más famoso. Un número del que destaca su longitud infinita y que nunca se repite...

]]>
https://blog.adrianistan.eu/easter-egg-python Sun, 10 Jan 2021 17:16:31 +0000
Space Pipes https://blog.adrianistan.eu/space-pipes https://blog.adrianistan.eu/space-pipes Acabo de terminar un nuevo minijuego de puzzles llamado Space Pipes. El objetivo es unir todas las piezas del tablero con la casilla central. Para ello podemos rotar las piezas. Existen dos modos de juego: con piezas cuadradas y con piezas hexágonales.

El juego está programado en TypeScript y Prolog (usando React y Tau-Prolog respectivamente). La librería usada para las interfaces futurísticas es Arwes. Al estar realizado con tecnología web podéis jugar desde cualquier navegador. También lo he publicado como app de Android (pero es exactamente igual).

Os animo a que juguéis y lo disfrutéis

]]>
https://blog.adrianistan.eu/space-pipes Sun, 22 Nov 2020 16:25:17 +0000
Tutorial de CHR (Constraint Handling Rules) https://blog.adrianistan.eu/tutorial-chr https://blog.adrianistan.eu/tutorial-chr CHR es un lenguaje de programación lógico basado en reglas, pero a diferencia de Prolog o miniKanren, se aplican "hacia delante". Diseñado en Alemania en 1991 por Thom Frühwirth, se trata de un lenguaje muy pequeño (tiene solo tres casos) y que normalmente se usa a través de otro lenguaje que le aporta expresividad (como Prolog, Haskell, C, Java o JavaScript). En este tutorial aprenderemos a usar CHR y usaremos Prolog como lenguaje base.


El símbolo de CHR es el símbolo chino "chr"

Almacén de hechos

Lo primero que hay que saber, es que a diferencia de otros lenguajes lógicos, CHR mantiene estado de los hechos. Un programa CHR se compone de reglas y hechos. Las reglas manipulan los hechos. Cualquier programa CHR empieza cuando introducimos hechos a ese almacén. Una vez han sido añadidos, se revisa si hay reglas que se cumplen para esos hechos, si es así, se ejecutan, eliminando y/ añadiendo nuevos hechos, volviéndose a ejecutar la búsqueda de reglas a aplicar. Cuando ya no se puedan aplicar reglas, el programa habrá acabado.

He aquí un pseudocódigo:


HECHOS = lista de hechos
REGLAS = lista de reglas

loop:
    for REGLA in REGLAS:
        if REGLA es aplicable con HECHOS:
            ejecutar REGLA y modifica HECHOS
    if no se ha aplicado ninguna REGLA:
        salir

Este sistema de funcionamiento es similar a CLIPS o a Drools y es lo que se llama "forward-chaining" o procesado de reglas hacia delante. Prolog en comparación es "backward-chaining" o hacia atrás.

Las reglas

CHR solo tiene tres tipos de reglas.

  • Simpagation (\ <=>)
  • Simplifcation (<=>)
  • Propagation (==>)

La sintaxis general se puede resumir en esto:


name @ retained \ discarded <=> guard | body.    Simpagation
name @ discarded <=> guard | body.      Simplification
name @ retained ==> guard | body.         Propagation

El campo name es puramente para el programador y es optativo.

A continuación van los hechos que se tienen que cumplir para disparar la regla. Es la parte izquierda de la regla. Dependiendo de la regla que estemos usando, los hechos se eliminan (discarded) o se mantienen (retained) del almacén. La regla Simpagation permite tener hechos que se descartan a la vez que hechos que no, mediante el separador \.

A la derecha de la regla, se ubica el guard, un elemento opcional que sirve para hacer comprobaciones más precisas sobre los hechos. Si se ejecuta satisfactoriamente, la regla continúa, si no, se cancela.

A la derecha del guard se ubica el body, donde podemos definir los nuevos hechos que se van guardar en el almacén.

Un ejemplo tortillero

Veamos un ejemplo en acción. Voy a usar la implementación de CHR de SWI Prolog, por lo que necesitaremos este programa antes.


:- use_module(library(chr)).

:- chr_constraint patata/0, huevo/0, cebolla/0, tortilla/0.

patata, huevo, cebolla <=> tortilla.

Que quiere decir, si tienes patata, huevo y cebolla en el almacén, quítalos y añade tortilla. Para empezar a ejecutarlo, vamos añadiendo hechos al almacén.


➜  chr git:(master) ✗ swipl example1.pl 
Welcome to SWI-Prolog (threaded, 64 bits, version 8.2.2)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit https://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

?- cebolla, patata, huevo.
tortilla.

?- cebolla, patata.
patata,
cebolla.

?- cebolla, patata, huevo, huevo, patata.
patata,
huevo,
tortilla.

?- cebolla, patata, huevo, huevo, patata, cebolla.
tortilla,
tortilla.


Como veis, si existen los tres hechos en almacén, se dispara la regla y ese es el resultado que vemos en la terminal. Si no se puede aplicar la regla, se mantienen los hechos introducidos.

Un ejemplo con Fibonacci

Vamos a ver como se implementaría la típica función de obtener el número correspondiente a la secuencia de Fibonacci. Para ello vamos a usar dos tipos de hechos, fib, que guardan el índice y el valor y upto, que guarda hasta que índice queremos seguir calculando nuevos fib (este programa si no, sería infinito)


:- use_module(library(chr)).

:- chr_constraint fib/2, upto/1.

fib(A, AV), fib(B, BV), upto(N) ==> B is A+1, B < N | X is AV+BV, K is B+1, fib(K, X).

Básicamente lo que hacemos es coger un fib con índice A y valor AV, otro fib con índice B y valor BV y un upto con valor N. No eliminamos ninguno de esos hechos pero si la regla se dispara, ejecutamos primero el guard. El primero comprueba que B es el elemento siguiente de A, y por otro lado, que no nos estamos pasando con el upto. Una vez hecho eso, hacemos las sumas y añadimos otro fib. El uso de is no viene dado por CHR sino por Prolog, que hace de lenguaje host.

Para ejecutarlo, introduciríamos los primeros dos elementos de la secuencia en el almacén y un upto.


➜  chr git:(master) ✗ swipl example2.pl
Welcome to SWI-Prolog (threaded, 64 bits, version 8.2.2)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit https://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

?- fib(1,1), fib(2,1), upto(10).
fib(10, 55),
fib(9, 34),
fib(8, 21),
fib(7, 13),
fib(6, 8),
fib(5, 5),
fib(4, 3),
fib(3, 2),
fib(2, 1),
fib(1, 1),
upto(10).

?- 

Un ejemplo de caminos

Para acabar CHR, vamos a ver un ejemplo más elaborado.

Imaginemos que tenemos un grafo tal que así. Los cables rojos representan conexiones entre los nodos. Cada nodo tiene una posición X e Y. Dado un punto, ¿hasta qué puntos podemos llegar siguiendo las líneas rojas?


:- use_module(library(chr)).

:- chr_constraint connected/2, origin/2, edge/4, node/2, world/0.


world <=> 
    node(1, 1),
    node(1, 2),
    node(2, 1),
    node(2, 2),
    node(3, 1),
    node(3, 2),
    node(4, 1),
    node(4, 2),
    edge(1, 1, 2, 1),
    edge(2, 1, 3, 1),
    edge(3, 1, 4, 1),
    edge(2, 1, 2, 2),
    edge(4, 1, 4, 2).

origin(X, Y) <=> connected(X, Y).
connected(X1, Y1), edge(X1, Y1, X2, Y2) \ node(X2, Y2) <=> connected(X2, Y2). 

Definimos varios tipos de hechos. connected sería un nodo conectado (representado por sus posiciones X e Y), origin el punto de origen, edge es una conexión entre dos puntos, node es un listado de puntos y world lo definimos para simplificar las consultas.

Para iniciar el cómputo, indicamos world (para cargar el resto de hechos del "mundo") y especificamos el punto de origen. Obtendremos bajo los hechos connected aquellos puntos conectados al punto origin.

Hasta aquí el tutorial de CHR, los ejemplos de código están subidos al repositorio de ejemplos de código del blog. ¿Qué te parece este lenguaje? ¿Te parece sencillo? ¿Le ves aplicaciones? ¡Deja tu opinión en los comentarios!

]]>
https://blog.adrianistan.eu/tutorial-chr Wed, 18 Nov 2020 21:38:35 +0000
Teletexto #008 https://blog.adrianistan.eu/teletexto-008 https://blog.adrianistan.eu/teletexto-008 Bienvenidos a una nueva edición del Teletexto, la octava, cargada de enlaces interesantes que he ido recopilando.

  • Vega y Vega Lite son unos lenguajes declarativos para expresar visualizaciones de datos, usando JSON y generando resultados en Canvas o SVG. Vega Lite es la versión de más alto nivel, ideal para empezar. Existen librerías para generar estos JSON compatibles con Vega desde diferentes lenguajes, por ejemplo, Altair con Python.
  • SomosBinarios escribe Elige tecnología clásica y tiene muy buenos puntos. Yo soy el primero que le gusta experimentar con tecnología novedosa pero a la hora de construir un producto de forma rápida y fiable, lo mejor es usar tecnologías ya establecidas.
  • No todos los sistemas operativos son UNIX-like o compatibles POSIX. Hoy en día son minoritarios (salvo Windows) pero tienen peculiaridades muy interesantes y características diferentes. En este artículo se repasan diferentes sistemas de archivos no-POSIX.
  • SQL es el lenguaje más usado para operar con bases de datos relacionales. Lo que quizá no sepas es que antes de convertirse en un estándar (gracia a la presión de IBM y Oracle) existió otro lenguaje, que mucha gente consideraba mejor, QUEL. La base de datos más conocida que usaba QUEL era Ingres, que al sustituir QUEL por SQL pasó a llamarse Postgres. Aquí podrás ver una comparación SQL vs QUEL.
  • ¿Estás empezando con Rust? Con este starter kit sabrás que librerías son las más recomendables para usar en tus proyectos.
  • ¿Cuántos proyectos ha cerrado Mozilla? En Killed by Mozilla encontrarás un interesante listado.
  • ¿Cuántas bases de datos existen? En la base de datos de bases de datos encontrarás respuesta.
  • Falsedades que los programadores creen sobre las coordenadas.
  • ¿Cómo eres de normal? Esta web usa las técnicas más avanzadas de IA para enseñarte cómo las usan las compañías mientras las aplica sobre ti.
  • ¿Quiéres un nombre de dominio libre con un nombre original? Encuentra el tuyo en Domains for the rest of us

La canción para cerrar este teletexto es Rydeen de los japoneses Yellow Magic Orchestra.

]]>
https://blog.adrianistan.eu/teletexto-008 Sat, 7 Nov 2020 16:14:43 +0000
Mapaquiz, ¿y si la respuesta estuviera sobre el mapa? https://blog.adrianistan.eu/mapaquiz-pregunta-sobre-mapa https://blog.adrianistan.eu/mapaquiz-pregunta-sobre-mapa Responder a una pregunta señalando un mapa. Quizá no todas las preguntas del mundo se puedan resolver mirando a un mapa, pero sí las suficientes como para prestarlas atención. Mapaquiz es una aplicación web para jugar y crear mapaquizs. ¿Qué es un mapaquiz? Un mapaquiz es un juego de preguntas y respuestas donde las respuestas están en el mapa. Además, Mapaquiz es el último proyecto que he estado desarrollando en mi tiempo libre, ¿echamos un vistazo?

Aprender jugando

Podemos aprender sobre geografía con los mapaquizs más clásicos. También podemos probar los mapaquizs temáticos sobre Olimpiadas o Famosos del Cine. Jugar es muy simple, simplemente buscamos nuestro mapaquiz, vía página principal o mediante el buscador. Los mapaquizs tiene un idioma, actualmente se soportan dos, inglés y español, aunque en un futuro podría haber más idiomas. Podemos filtrar el idioma en la parte superior.

Una vez hayamos seleccionado nuestro mapaquiz se nos cargará un mapa y una pregunta a la derecha (en pantallas anchas) o abajo (en pantallas estrechas, tipo móvil). Tenemos una pregunta, un tiempo y una puntuación.

A las preguntas que van apareciendo, podemos ir haciendo click en los diferentes territorios del mapa. Si nuestra respuesta es correcta, avanzaremos, si fallamos, se nos restará puntuación y deberemos volverlo a intentar.

Una vez hayamos acabado se nos mostrará nuestra puntuación final y el tiempo que hemos utilizado. Si has iniciado sesión en ese momento, y tu puntuación es alta, esta se quedará gradaba en el marcador para que el resto admire tu rapidez e inteligencia (o facilidad de buscar en Google).

Por último, podemos dejar un comentario sobre el mapaquiz en la parte más inferior.

Creando nuestros propios mapaquizs

Si tenemos una cuenta en Mapaquiz (que no requiere datos personales, solo se pide un correo electrónico, un nombre de usuario y una contraseña), podremos crear nuestros propios mapaquizs.

Para ello iremos a la sección "Mi Perfil", donde veremos los mapaquizs que hemos creado, así como la opción de crear uno nuevo y editar o borrar uno existente.

Al crear un mapaquiz deberemos elegir un mapa base en el idioma en el que vayamos a crear el mapaquiz.

El editor nos permite añadir y eliminar preguntas. En la parte superior tendremos el mapa base. A la derecha introduciremos el texto de la pregunta y sobre el mapa seleccionaremos el territorio que queremos que sea la respuesta. No existe respuesta múltiple de momento. Al hacer click en "Añadir pregunta", añadiremos la pregunta al mapaquiz.

Abajo podremos ver las preguntas ya añadidas así como borrarlas. También está la sección de Ajustes Generales, que debemos rellenar con una descripción del mapaquiz, así como una foto para la portada.

Si todo está bien, veremos un botón de "¡Publicar en Mapaquiz!" y el mapaquiz estará disponible para el resto de usuarios.

Detalles técnicos

Muy bien Adrián todo esto que cuentas, pero ¿cómo funciona?

Realmente lo que hay detrás de Mapaquiz es bastante simple, ya que he intentado no añadir complejidad innecesaria. La base de datos es SQLite y allí se guardan los GeoJSON de los mapas base. El framework principal es Django, con alguna API en Django Rest Framework para el código JavaScript. El código JavaScript en realidad está hecho en TypeScript y usa la librería Leaflet para representar los mapas. Para alojar las imágenes se ha usado un Azure Storage Account. Para las búsquedas se usa la librería Lunr.py. Todo esto es empaquetable en Docker listo para usar con un nginx sobre una Raspberry Pi.

¿Te gusta lo que ves?

Pues comparte con tus amigos, crea mapaquizs, encuentra errores, propón mejoras,...

]]>
https://blog.adrianistan.eu/mapaquiz-pregunta-sobre-mapa Fri, 30 Oct 2020 19:26:21 +0000
¿Qué es Idris y por qué es un lenguaje de programación tan interesante? https://blog.adrianistan.eu/que-es-idris-y-por-que-es-un-lenguaje-de-programacion-tan-interesante https://blog.adrianistan.eu/que-es-idris-y-por-que-es-un-lenguaje-de-programacion-tan-interesante Idris es un lenguaje de programación funcional donde los tipos tenían envidia de las funciones, de primer orden en un lenguaje funcional, y se convirtieron en elementos de primer orden también. Se trata de uno de los primeros lenguajes con soporte a tipos de primer orden. De esta forma podemos implementar dependent types y quantitative types, todo ello con una sintaxis muy limpia inspirada por Haskell. Quizá Idris no sea el próximo lenguaje que debas aprender, solo te lo recomendaría si estás suficientemente interesado, pero las cosas que propone pueden acabar llegando a otros lenguajes de programación en el futuro, y eso es lo verdaderamente interesante.

La versión de Idris al momento de escribir esto es Idris2 0.2.1 y no se recomienda usar en nada serio. Idris está escrito en Idris y genera código nativo a través de Chez Scheme o Racket. También puede compilar a JavaScript. Sin embargo, vamos a ignorar como se instala, ya que al ser experimental, puede cambiar mucho. Si estáis interesados, decídmelo en los comentarios. Aquí nos vamos a fijar simplemente en como funciona.

Hola Mundo

Para no perder las costumbres empezaremos con el típico Hola Mundo en Idris.


main : IO ()
main = putStrLn "¡Hola mundo desde Idris!"

Si conoces Haskell seguro que te resulta familiar el código (con una pequeña variación, : en vez de ::, es exactamente igual). Para los que no conozcan Haskell, la idea es muy simple. En la primera línea definimos una función main cuyo tipo es IO (), es decir, instrucciones para realizar entrada/salida. No toma ningún parámetro de entrada. En la segunda línea implementamos la función, llamamos a putStrLn que es una función compatible con IO () y que nos deja imprimir un texto por pantalla. En la foto se ve como se puede ejecutar el código desde el intérprete y desde el código nativo.

Un ejemplo más avanzado sería el siguiente código, que obtiene datos de teclado:


module Main

main : IO ()
main = do
    putStr "¿Cuál es tu nombre?: "
    x <- getLine
    putStrLn ("¡Hola " ++ x ++ "!")

Al igual que en Haskell, cuando queremos combinar varias funciones que devuelven IO () (mónadas en general), combinadas con otras funciones si queremos, podemos usar la sintaxis do. Básicamente se convierte todo en una especie de función grande que combina en orden todas las llamadas a funciones que pongamos debajo. Usaremos el operador <- para obtener un valor de una función que devuelva una mónada (como IO ()), siempre dentro de la sintaxis do. En funciones que no son mónadas, podemos usar el operador let ... =

Vamos a ver como se definen las funciones sin mónadas. En Idris es obligatorio marcar el tipado de la función, y a continuación la implementación. Los dependent types no se pueden inferir, por tanto deberemos escribirlos siempre. Los tipos de los argumentos se separan mediante -> y el último es el tipo del valor de retorno. Para llamar a una función se escribe su nombre y separado por espacios sus argumentos. Todas las funciones en Idris devuelven algo. Podemos usar if/else, pero a diferencia de otros lenguajes, el else es obligatorio, y ambas sentencias deben devolver un valor.

Veamos un ejemplo con la función longer. Esta función nos devuelve la longitud de la cadena de texto más larga. La longitud de una cadena de texto se calcula mediante la función length.


longer : String -> String -> Nat
longer str1 str2
	= let len1 = length str1
	      len2 = length str2 in
	  if len1 > len2 then len1 else len2

String es el tipo de las cadenas de texto, Nat el de los números naturales.

Tip: podemos usar :t en la consola para obtener el tipo de una función.

Con esto espero que entiendas, de forma básica, la parte no-novedosa de Idris, antes de entrar en la parte innovadora.

Dependent Types

¿Qué es un dependent type? Un dependent type es un tipo que depende del valor concreto que traiga consigo para poder ser completo. El ejemplo más fácil de ver es con listas. En muchos lenguajes tenemos un tipo para listas dinámicas. En Idris también, se llama List. Con este tipo sabemos cuando hacemos una función que vamos a tener una lista de un tipo en concreto, pero podríamos ir más allá y expresar en el tipo la longitud de la lista. Esto no quiere decir que la lista esté fijada a un tamaño, sino que en un determinado momento el tamaño de una lista puede ser n, y al salir de la función debe ser n+1. Veamos un ejemplo


module Main

add : List Int -> Int -> List Int
add list new = new :: list

La función add, toma una lista de enteros, un entero, y devuelve un entero. El código es muy simple, simplemente usa el operador :: para añadir un nuevo elemento enfrente de la lista, y devuelve la lista.

Este código funciona, los tipos cuadran y el compilador no se queja. Sin embargo este código también funciona:


module Main

add : List Int -> Int -> List Int
add list new = list

La filosofía detrás de Idris es que los tipos que usemos deben ser lo más precisos posible para reducir el número de bugs que pasan desapercibidos. Además, normalmente es más sencillo definir una relación de tipos que buscar una implementación que cumpla con ello.

Este ejemplo tonto lo podríamos resolver con dependent types, usando Vect:


module Main

import Data.Vect

addV : Vect n Int -> Int -> Vect (1+n) Int
addV list new = new :: list

El tipo Vect se compone de la longitud de la lista y del tipo de la lista. Fíjate como en la lista de entrada la longitud de la lista es n, y en la lista de salida es 1+n. Realmente podemos introducir cualquier expresión que devuelva un número positivo (Nat) ahí, ya que, los tipos son elementos de primer orden, y como tal estos pueden ejecutar código, llamar a otras funciones, etc con tal de construir el tipo definitivo.

Con este tipado, ya es imposible cometer el fallo de antes:

Fíjate como en el código que he intentado compilar, ambas implementaciones son iguales, pero solo la que usa Vect falla al compilar.

Hemos dicho que los tipos pueden llamar a funciones que definan los tipos, ya que los tipos son elementos de primer orden. Veamos un ejemplo:

Imagina que tenemos código para manipular sobre posiciones GPS. Estas son solamente una tupla de números decimales, la latitud y la longitud.


module Main

getHomePlace : (Double, Double)
getHomePlace = (42.0, -4.0)

Esto sería totalmente válido, pero ¿podríamos hacer una función que nos devolviese (Double, Double) directamente? Por supuesto, ya que las funciones pueden devolver el tipo maestro Type. Estas funciones se suelen empezar por mayúsculas a diferencia de las demás.


module Main

Position : Type
Position = (Double, Double)

getHomePlace : Position
getHomePlace = (42.0, -4.0)

sumPositions : Position -> Position -> Position
sumPositions (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)

Pero esto solo es una parte de las posibilidades que nos abren los tipos dependientes. Otra cosa que podemos hacer es funciones con argumentos ilimitados, ya que el tipo se genera en función de lo que se encuentra el compilador. Sin entrar en muchos detalles, este código genera una función el cual el primer argumento es el número de argumentos que vas a sumar. Luego una primera variable (la cero) y luego tantos argumentos como hayamos indicado. Si algo falla, el código no compilará


module Main

AdderType : Nat -> Type
AdderType Z = Int
AdderType (S k) = Int -> AdderType k

sumar : (numargs: Nat) -> Int -> AdderType numargs
sumar Z acc = acc
sumar (S k) acc = \next => sumar k (next + acc)

Con un poco más de trabajo pueden llegar a implementarse funciones como printf, con argumentos variables dependientes de una cadena de texto, de forma totalmente tipada.

Esto no es todo

Ya hemos visto bastante cosas interesantes. Esto no pretende ser un tutorial sobre Idris, que es un lenguaje grande, sino una introducción a alguna de las cosas novedosas que trae. En Idris 2 se han añadido los quantitative types, que viene a ser como representar mediante tipos las veces que se puede usar un valor. Por ejemplo, si tenemos una función que toma un valor cuyo tipo dice que solo se puede usar una vez, y lo usamos, ya no podremos usarlo más adelante, muy útil para, por ejemplo, gestionar ficheros o sockets.

Si queréis saber más sobre Idris, visitad su página y/o leed el libro Type Driven Development with Idris, que explica de forma muy didáctica el lenguaje.

]]>
https://blog.adrianistan.eu/que-es-idris-y-por-que-es-un-lenguaje-de-programacion-tan-interesante Sun, 27 Sep 2020 22:31:38 +0000
El Algoritmo Simplex https://blog.adrianistan.eu/algoritmo-simplex https://blog.adrianistan.eu/algoritmo-simplex El algoritmo Simplex es el método más conocido para resolver problemas de programación lineal. Diseñado en 1947 por George Dantzig, nos ofrece una forma eficiente y genérica de resolver este tipo de problemas, de gran utilidad en la industria. Se trata de uno de los diez algoritmos más importantes del siglo XX, según IEEE y el American Institute of Physics.

En este post vamos a explicar el algoritmo Simplex. Al final, encontraréis el código de la versión en Rust. Quería comentar el código directamente, pero creo que es un algoritmo demasiado complejo como para entenderlo directamente.

¿Qué es la programación lineal?

La programación lineal es un método para obtener el mejor resultado posible dado un modelo matemático que usa relaciones lineales. Un ejemplo de programación lineal sería, ¿cómo hay que distribuir las antenas de móvil para tener una cobertura completa gastando el menor dinero posible? Como este hay miles de problemas que pueden modelarse con programación lineal. Si quieres saber más sobre esto, hace tiempo hice una entrada explicando qué es la programación lineal más en detalle.

Elaboración de la tabla

El algoritmo Simplex se compone de dos etapas: encontrar una solución factible y después, mejorarla. Con solución factible nos referimos a una solución que cumpliría todas las ecuaciones que vamos a añadir pero que no tiene por qué ser óptima. Una vez tengamos eso, vamos a mejorar la solución hasta que ya no sea posible y siempre transitando entre soluciones factibles, pero mejores.

El algoritmo se basa en la existencia de una tabla, anotada en los extremos. En la primera fila tendremos un 1 seguido del renglón Z y el valor óptimo.

Las siguientes filas representan a las restricciones. Tienen anotada a la izquierda una variable, en el centro las restricciones en sí y en la última columna el valor de esa variable que hace óptimo el resultado.

En esta tabla puedes ver de forma esquemática la tabla:

1 Renglón Z Valor Óptimo
Base Restricciones Valor Variable

El renglón Z se forma con la función objetivo cambiada de signo. El valor óptimo comienza en cero. Las restricciones no tienen modificaciones y el valor de variable inicial es el valor al otro lado de la ecuación de la restricción. Lo más complicado es la elección de variables de la base. Tiene que formar una solución factible desde el principio. Manualmente pueden hacerse muchas cosas, pero de forma automatizada es más complejo.

Una forma fácil de encontrar una solución factible inicial es traducir las inecuaciones mayor o igual que y menor o igual que en igualdades, añadiendo variables de holgura (o slack). Esto en muchos casos podría ser suficiente, pero para asegurarnos, podemos introducir variables artificiales.

El problema de añadir variables artificiales es que los resultados pueden ser falsos, ya que amplían el "número de soluciones". Existen varias formas de añadir variables artificiales sin dar respuestas falsas. Una es el método de la doble fase, que consiste en realizar dos tablas Simplex. Otra solución es el método de la gran M, que consiste en penalizar fuertemente las variables artificiales, para que nunca puedan entrar a la base.

Veamos un ejemplo real:


minimize -3x +  y - 2z
    with  2x - 2y + 3z <= 5
           x +  y -  z <= 3
           x -  y +  z <= 2

La tabla correspondiente sería la siguiente:

z x1 x2 x3 x4 x5 x6 LD
z 1 3 -1 2 0 0 0 0
x4 0 2 -2 3 1 0 0 5
x5 0 1 1 -1 0 1 0 3
x6 0 1 -1 1 0 0 1 2

En este ejemplo, hemos creado 3 variables de slack, para convertir los menor o igual en igual. Estas variables de slack hacen la matriz identidad en su parte de las restricciones, por lo que pueden entrar a la base. Ahora bien, estas variables no aparecen en la función objetivo, así que las marcamos a cero en el renglón Z. Con variables artificiales, se haría todo igual, salvo que en vez de cero, se debe poner un M muy muy grande.

Pivotando

El segundo paso es mejorar la solución hasta llegar al óptimo. ¿Cuándo tenemos un óptimo? Cuando todo el renglón Z sea menor o igual a cero. Si además, todos los elementos de Z restados de su valor en la función objetivo son estrictamente menores que cero, la solución es única.

Nuestro objetivo, por tanto, es conseguir valores negativos en el renglón Z.

Para ello vamos a introducir una variable en la base (que no estuviese ya) y vamos a sacar una. La variable de entrada puede ser cualquiera en cuyo renglón Z sea positiva.

En nuestro caso concreto, vamos a elegir introducir x1, cuyo renglón Z es 3.

La variable de salida es aquella que en la columna de la variable de entrada es positiva y tiene el valor más pequeño en la división de la columna LD por el propio valor. Si no se encuentran valores positivos, el problema no tiene límite y la solución es infinita.

En nuestro ejemplo, tendríamos que calcular para x4=5/2, para x5=3/1 y para x6=2/1. El valor más pequeño el de x6, así que x6 sale de la base.

Ese punto, marcado con negrita, va a ser el pivote.

z x1 x2 x3 x4 x5 x6 LD
z 1 3 -1 2 0 0 0 0
x4 0 2 -2 3 1 0 0 5
x5 0 1 1 -1 0 1 0 3
x6 0 1 -1 1 0 0 1 2

Se pivota de la siguiente forma:

La fila del pivote se tiene que dividir entre el valor del pivote (en este caso hay que dividir entre 1, se queda todo igual). Después se va fila por fila restándolas el valor de la fila del pivote por el elemento de la misma columna de esa fila que el pivote. Es decir, en la primera fila, la del renglón Z, hay que restar la fila de x6 multiplicada por 3. Debería quedarnos al finalizar una columna con todo ceros, salvo el pivote.

El resultado final es el siguiente:

z x1 x2 x3 x4 x5 x6 LD
z 1 0 2 -1 0 0 -3 -6
x4 0 0 0 1 1 0 -2 1
x5 0 0 2 -2 0 1 -1 1
x1 0 1 -1 1 0 0 1 2

Ahora debería entrar la variable x2 y saldría x5.

z x1 x2 x3 x4 x5 x6 LD
z 1 0 0 1 0 -1 2 -7
x4 0 0 0 1 1 0 -2 1
x2 0 0 1 -1 0 0.5 -0.5 0.5
x1 0 1 0 0 0 0.5 0.5 2.5

Por último, debe entrar x3 y salir x4.

z x1 x2 x3 x4 x5 x6 LD
z 1 0 0 0 -1 -1 0 -8
x3 0 0 0 1 1 0 -2 1
x2 0 0 1 0 1 0.5 -2.5 1.5
x1 0 1 0 0 0 0.5 0.5 2.5

En este punto ya hemos alcanzado el óptimo, -8, aunque no es la única solución posible que da este valor. La solución del problema es x1=2.5, x2=1.5 y x3=1.

Resolviendo con la librería simplex de Rust

Estas mismas operaciones (y alguna más) son las que realiza por debajo la librería simplex, disponible para Rust. He intentado que la librería sea lo más sencilla de usar. Con el siguiente código se resuelve el mismo problema:


use simplex::*;

fn main(){
    let program = Simplex::minimize(&vec![-3.0, 1.0, -2.0])
    .with(vec![
        SimplexConstraint::LessThan(vec![2.0, -2.0, 3.0], 5.0),
        SimplexConstraint::LessThan(vec![1.0, 1.0, -1.0], 3.0),
        SimplexConstraint::LessThan(vec![1.0, -1.0, 1.0], 2.0),
    ]);
    let mut simplex = program.unwrap();
    match simplex.solve() {
        SimplexOutput::UniqueOptimum(x) => println!("{}", x),
        SimplexOutput::MultipleOptimum(x) => println!("{}", x),
        _ => panic!("No solution or unbounded"),
    }
    println!("{:?}", simplex.get_var(1));
    println!("{:?}", simplex.get_var(2));
    println!("{:?}", simplex.get_var(3));
}

Existe un algoritmo similar llamado Revised Simplex. Este ocupa menos memoria y las implementaciones más potentes como CPLEX o Xpress se suelen basar en él. Échale un ojo si te interesa.

]]>
https://blog.adrianistan.eu/algoritmo-simplex Sat, 12 Sep 2020 20:10:22 +0000
Supertutorial de Prolog: aprende Prolog en español https://blog.adrianistan.eu/supertutorial-prolog https://blog.adrianistan.eu/supertutorial-prolog Si estás aquí es porque has decidido aprender Prolog o por lo menos, te ha entrado la curiosidad sobre este lenguaje del paradigma lógico. ¡Sabia elección! En este artículo y los siguientes (enlazados al final) podrás aprender todo lo básico de Prolog.

Antes de nada, avisar que ya tenía un tutorial de Prolog aquí, sin embargo, creo que comete varias malas prácticas. En vez de editarlo, prefiero empezar de cero y además explicar más cosas.

Hola Mundo

Antes de entrar en la teoría, abre tu editor y copia/pega lo siguiente a un fichero llamado socrates.pl:


human(socrates).
mortal(X) :- human(X).

La explicación es sencilla. En la primera línea afirmamos que socrates es humano, en la segunda afirmamos que para que X sea mortal, antes tiene que ser humano. Todo esto lo detallaré más adelante.

Ahora necesitarás un intérprete de Prolog. El más popular y el que actualmente recomiendo es SWI-Prolog disponible para Windows, Mac, Linux y más.

Con SWI-Prolog instalado ejecuta desde un terminal:


swipl socrates.pl

Ahora podemos realizar consultas. Prolog es un lenguaje inicialmente diseñado en base a consultas lógicas. Vamos a pedir a Prolog que nos diga si socrates es humano.


?- human(socrates).
true.

A lo que responde true, es decir, es cierto, socrates es humano.

¿socrates es mortal?


?- mortal(socrates).
true.

¿Y adrian?


?- mortal(adrian).
false.

¿Cuántos humanos hay?


?- human(X).
   X = socrates.

Solo socrates. ¿Y cuantos mortales?


?- mortal(X).
   X = socrates.

Y si queremos un hola mundo más clásico, podemos usar write y nl.


?- write('Hola Mundo'), nl.
Hola Mundo
   true.

Historia de Prolog

Prolog (PROgrammation en LOGique) nació en 1972 en la Universidad de Provenza en la ciudad francesa de Marsella (mismo año que C). Diseñado por Alan Colmerauer y Philip Roussel para trabajar en inteligencia artificial, siempre ha estado ligado a este campo. En 1995 fue estandarizado, bajo ISO (como C++, Ada o Fortran). Durante este tiempo se han creado muchas implementaciones. Algunas las puedes ver en Universo Prolog

Sintaxis de Prolog

La sintaxis de Prolog es muy simple. Solo existen tres elementos:

  • Términos
  • Comentarios
  • Quasiquotes

Los dos últimos son elementos excepcionales. Un comentario empieza por el símbolo % seguido del comentario. Los comentarios son textos que se eliminan al procesar la entrada y son útiles para anotar cosas. Los quasiquotes son elementos que permiten introducir sintaxis diferente a la de Prolog en Prolog (HTML, JSON, LISP, JavaScript, ...) pero es algo bastante avanzado.

Los términos, son entonces, el 95% del código Prolog. Estos llevan un punto al final si no forman parte de ningún otro término de forma recursiva. Los términos se dividen a su vez en:

  • Constantes
  • Variables
  • Compuestos

Los constantes son términos que pueden ser átomos, números (y en algunas implementaciones también hay strings). Los átomos son cadenas de texto que empiezan por letra minúscula o llevan comillas simples. Los átomos son elementos inmutables que representan algo. La cualidad del átomo es que solo pueden ser iguales a ellos mismos. Los números son bastante explicativos, cadenas de texto compuestas de dígitos. Prolog no distingue entre ints, floats, etc y muchas implementaciones disponen de precisión arbitraria en los números. Los strings empiezan por comillas dobles y representan cadenas de texto "manipulables". Saber donde se usa string y donde atom en sistemas que usan ambos elementos es algo que se aprende con la práctica.

Las variables son cadenas de texto que empiezan por una letra mayúscula. El funcionamiento de las variables es ligeramente diferente al de los lenguajes de programación tradicionales. Una variable puede entenderse como la solución de un problema (que será un término). Al principio del programa, cuando todavía no se han recorrido condiciones del problema, la variable no estará ligada, eso querrá decir, que puede ser cualquier cosa todavía. Según vaya avanzando el programa, Prolog unificará (más adelante esto) la variable con algún otro término y puede que la variable acabe ligada, es decir, a partir de ese momento, se comporta como si fuese el otro término. Esto sería, en esencia, añadir una restricción sobre la solución.

En el apartado de unificación veremos esto más en detalle.

Los compuestos son términos recursivos, se componen de un átomo, que llamaremos functor, y entre paréntesis y separados por comas, más términos. Esto puede parecer una llamada a una función, pero no. En Prolog no hay funciones. Simplemente es un término compuesto varios términos dentro de él.


% Un comentario
socrates. % Un átomo
15. % Un número
libro(don_quijote, cervantes). % Un compuesto. libro es functor, don_quijote y cervantes son los otros términos.
Variable. % Una variable
serie('Los Simpson', autor(Nombre, Apellido)). % Un compuesto más complejo

Al conjunto de compuestos con el mismo functor se le llama predicado y tiene una aridad, que es el número de términos que tiene entre paréntesis.

Además de esto, los términos pueden llevar asociado un cuerpo. El cuerpo es un conjunto de predicados que tienen que ser verdaderos para poder afirmar que el término es verdadero. A esto se le llama regla. Si el término no lleva cuerpo, se dice que es un hecho y es verdadero siempre.


human(socrates). % Afirmamos que socrates es humano (hecho)
mortal(X) :- human(X). % Afirmamos que para que X sea mortal, antes tiene que ser humano (regla)
% Otra forma de verlo, todos los humanos son mortales

Semántica de Prolog

Al revisar la sintaxis ha surgido un poco algo de la semántica, es decir, ¿qué significa lo que hacemos? Los programas Prolog son una base de conocimiento (como una base de datos pero más potente). Esta base de conocimiento es dinámica y el código fuente es simplemente el estado inicial de la base de "datos".

Inicialmente Prolog se diseña como un lenguaje orientado a consultas, como SQL, donde tenemos un terminal donde ir escribiendo pequeñas sentencias.

En el terminal escribimos predicados para ver si son ciertos o no, dado el conocimiento del programa. Cuando introducimos variables, busca valores para las variables que cumplan el predicado.

Prolog implementa un algoritmo de búsqueda (backtracking) por nosotros.

Backtracking

El algoritmo de backtracking es bastante simple y consiste en lo siguiente:

  1. Busca algún compuesto con el mismo functor, en la base de conocimiento (de arriba a abajo).
  2. Si es un hecho, unifica con él. (explicación más adelante)
  3. Si es una regla, pasa a tratar de demostrar el cuerpo de la regla.
  4. Si llega a un punto en el que no puede continuar, falla. Vuelve hacia atrás, deshace todas las unificaciones realizadas y si hay algún punto de elección, toma otra ruta.

Podemos pensar que el algoritmo va haciendo un árbol intentando encontrar algo en la base de conocimiento que satisfaga el predicado de entrada.

O viendo cada predicado, podemos ver como puede pasar Prolog en un sentido hacia delante o hacia atrás.

Hola mundo, otra vez

Ahora que sabemos todo esto de como funciona Prolog por debajo podemos volver a ejecutar el Hola Mundo del principio y tratar de ver como funciona. Para ello te recomiendo activar el modo trace, que nos deja ver que va haciendo Prolog paso a paso. Escribe trace antes de ejecutar la consulta. Por ejemplo:


?- trace, mortal(X).
   Call: (11) mortal(_6364) ? creep
   Call: (12) human(_6364) ? creep
   Exit: (12) human(socrates) ? creep
   Exit: (11) mortal(socrates) ? creep
X = socrates.

Unificación

… o ¿pueden juntarse dos expresiones en una sin contradecirse?

En Prolog, la operación más básica es la unificación, que podemos realizar mediante el operador = pero que igualmente se realiza cuando busca en la base de conocimiento. La idea es muy simple, ¿podemos asumir que dos términos pueden juntarse en uno solo? Para ello se realiza lo siguiente.

  • Si hay una variable y una constante, la variable queda ligada a la constante.
  • Si hay dos constantes, se comprueba que sean iguales.
  • Si hay dos variables, se recuerda que ambas tienen que valer lo mismo, a partir de ese momento.
  • Y esto se forma recursiva para los compuestos.

Veamos algunos ejemplos:


?- a = b.
   false.
?- a = a.
   true.
?- a = X.
   X = a.
?- X = Y.
   X = Y.
?- 4+5=3+6.
   false.
?- book(Name, Author) = book('Don Quijote', 'Cervantes').
   Name = 'Don Quijote', Author = 'Cervantes'.

Es interesante prestar atención a que que una cosa esté en un lado u otro es indiferente. Además, es interesante como 4+5=3+6 no es true. Esto es debido a que Prolog, con unificación simplemente, compara términos pero no ejecuta operaciones aritméticas. Así pues 4+5 no es el mismo término que 3+6, ya que se escriben de forma diferente.

Operadores

Ya hemos visto como funciona el operador unificación (=). Veamos el resto de los operadores de Prolog:

  • \= es el operador de no unificación. Si tenemos a \= b, será cierto si a y b no unifican.
  • , es el operador lógico AND. Si tenemos a,b , será cierto si tanto a como b lo son.
  • ; es el operador lógico OR. Si tenemos a;b, será cierto si a o b son ciertos. Ambos ciertos también valdría.
  • \+ es el operador lógico NOT. Si tenemos \+a, será cierto si a no lo es.
  • is es el operador = pero además evalúa el lado derecho antes, permitiendo realizar aritmética. X is 2+2. es el equivalente a X = 4.
  • =:= es el operador = pero evalúa ambos lados antes. Aquí 4+5 =:= 3+6 es equivalente a 9 = 9.

Ejemplo: rutas aéreas

Vamos a ver un ejemplo más avanzado de programa Prolog. Se trata de un programa que tiene un listado de vuelos configuradas, con un precio entre dos aeropuertos. Vamos a hacer un programa que nos calcule cuanto nos costaría hacer el viaje de ida y vuelta (si es que es posible).


vuelo(mad,bcn,200).
vuelo(bcn,mad,100).

idavuelta(X, Y, Precio) :-
    vuelo(X, Y, P1),
    vuelo(Y, X, P2),
    Precio is P1+P2.

En el terminal de Prolog:


?- idavuelta(mad,bcn,X).
   X = 300.
?- idavuelta(bcn,mad,X).
   X = 300.
?- idavuelta(Origen,Destino,Precio).
   Origen = mad, Destino = bcn, Precio = 300
;  Origen = bcn, Destino = mad, Precio = 300.
?- idavuelta(mad,nyc,X).
   false.

Vamos a añadir un poco más de complejidad al programa. En la vida real, no existen vuelos directos que conecten todas las ciudades con todas, muchas veces hay que hacer escala. Por ello vamos a añadir que vuelo tenga una regla para vuelos que realizan escalas.


vuelo(mad,bcn,200).
vuelo(bcn,mad,100).
vuelo(bcn,vll,30).
vuelo(vll,bcn,30).
vuelo(mad,vlc,50).
vuelo(vlc,mad,50).

vuelo(X, Y, Precio) :-
    vuelo(X, Z, P1),
    vuelo(Z, Y, P2),
    Precio is P1+P2.

idavuelta(X, Y, Precio) :-
    vuelo(X, Y, P1),
    vuelo(Y, X, P2),
    Precio is P1+P2.

Hemos añadido vuelos de ida y vuelta entre BCN y VLL y entre MAD y VLC. Ahora podemos preguntar por el precio del vuelo entre VLL y VLC, que lógicamente será, VLL->BCN->MAD->VLC (30+100+50). También el precio de ida y vuelta que será 180 más la vuelta, que son 280.


?- vuelo(vll, vlc, X).
   X = 180
?- idavuelta(vll, vlc, X).
   X = 460

Aunque, claro, estas soluciones no son únicas. Podemos dar vueltas entre MAD y BCN el tiempo que queramos. Por eso Prolog, nos ofrece más soluciones, que podemos obtener presionando la letra N


?- vuelo(vll, vlc, X).
   X = 180
;  X = 480
;  X = 780
;  X = 1080
;  X = 1380
;  X = 1680
;  X = 1980
;  X = 2280
;  X = 2580
;  X = 2880
;  X = 3180
;  X = 3480
;  X = 3780
;  X = 4080
;  ...

Listas en Prolog

La lista en Prolog es una estructura de datos básica. Es una lista enlazada, implementada mediante términos compuestos, pero a la que se le aplica azúcar sintáctico para que sea más legible. Las listas se definen con corchetes y se puede usar la barra vertical | para separar el primer elemento del resto. Además existen diversos predicados estándar para trabajar con listas como length/2 (longitud de una lista), member/2 (pertenencia a una lista), append/3 (combinar listas), nth0 (elemento N contando desde cero en una lista), nth1 (elemento N contando desde 1 en una lista).

Como son predicados, pueden trabajar en varios sentidos. Por ejemplo con length/2:

  • length([a,b,c], 3). nos permite comprobar que la lista [a,b,c] tiene 3 elementos.
  • length([a,b,c], N). nos permite obtener la longitud de la lista.
  • length([a,b,c], N), N < 5. nos permite comprobar que la longitud de la lista es menor que 5.
  • length(X, 3). nos permite obtener una lista con tres variables no ligadas en X.

Veamos algunos ejemplos más:


?- length([a,b,c], N).
   N = 3.
?- length(X, 5).
   X = [_A,_B,_C,_D,_E].
?- member(X, [socrates,platon,aristoteles]).
   X = socrates
;  X = platon
;  X = aristoteles
?- member(platon, [socrates, platon, aristoteles]).
   true
?- append([a,b],[c,d], X).
   X = "abcd".
?- append([a,b],X,[a,b,c,d]).
   X = "cd".
?- append(X,Y,[a,b,c,d]).
   X = [], Y = "abcd"
?- append(X,Y,[a,b,c,d]).
   X = [], Y = "abcd"
;  X = "a", Y = "bcd"
;  X = "ab", Y = "cd"
;  X = "abc", Y = "d"
;  X = "abcd", Y = []

Un ejemplo de uso del operador | es el siguiente, para implementa la suma de toda una lista de números.


suma([], 0).
suma([H|T], S) :-
    suma(T, S1),
    S is H+S1.

H es una variable que unifica con el primer elemento de la lista, mientras que T unifica con el resto de la lista. Esto lo podemos usar como:


?- suma([1,2,3,4], X).
X = 10.

Metapredicados

Por último, conviene repasar algunos metapredicados de Prolog. Los metapredicados son predicados que trabajan con otros predicados y son extremadamente útiles.

  • asserta/1 y assertz/1 nos permiten añadir términos a la base de conocimiento, tanto hechos como reglas. Dicho de otra forma, nos permite añadir código en tiempo de ejecución.
  • retractall/1 nos permite eliminar términos de la base de conocimiento que unifiquen con el término que indiquemos.
  • findall/3 encuentra todas las posibles soluciones a un predicado y las almacena en una lista.
  • forall/2 es un predicado que es cierto si para todos las posibles soluciones de un predicado se da una condición que es cierta.
  • maplist/2...8 es un predicado que permite operar con hasta 8 listas en paralelo llamando a un predicado auxiliar.

Hagamos un ejemplo con assertz y retractall:


?- human(X).
false.
?- assertz(human(socrates)).
   true.
?- assertz(human(platon)).
   true.
?- human(X).
   X = socrates
;  X = platon.
?- retractall(human(X)).
?- human(X).
   false.

Ahora veamos un ejemplo del resto de predicados. Teniendo este fichero:


parent(juancarlos, felipe).
parent(juancarlos, cristina).
parent(juancarlos, elena).

married(felipe).
married(cristina).
divorced(elena).

Podemos ejecutar los siguientes comandos:


?- forall(parent(juancarlos, X), married(X)).
   false.
?- forall(parent(juancarlos, X), (married(X);divorced(X))).
   true.
?- findall(X, married(X), Married).
   Married = [felipe,cristina].
?- maplist(write, [felipe, cristina]).
felipecristina   true

Continúa...

Con esto ya tienes suficiente como para realizar programas simples y medianos en Prolog, ya que, en esencia, Prolog es un lenguaje compacto. Sin embargo el lenguaje tiene muchas más cosas interesantes y que merece la pena ver, he aquí algunos enlaces de interés dentro de este blog:

]]>
https://blog.adrianistan.eu/supertutorial-prolog Sat, 29 Aug 2020 09:37:47 +0000
Teletexto #007 https://blog.adrianistan.eu/teletexto-007 https://blog.adrianistan.eu/teletexto-007 No me he olvidado de vosotros. El blog ha estado un poco huérfano debido a mi nuevo trabajo y a tener que haber realizado el trabajo de fin de grado para acabar la carrera. No obstante, esto ya ha pasado y espero ir subiendo el ritmo. Para comenzar, nada mejor que un par de enlaces en la sección de teletexto.

  • ¿Cuáles son las diferencias entre UNIX y MULTICS? Para quién no lo sepa, MULTICS fue el sistema operativo anterior y UNIX fue una versión simplificada para equipos menos potentes. ¿Pero qué cambios realizaron respecto a MULTICS?
  • En una página web actual encontramos HTML, imágenes, muchas veces JavaScript y en casi todas, CSS, un lenguaje para dotar de estilo y maquetación a las webs. Pero CSS no fue el primer lenguaje diseñado para cumplir esta función. Este artículo hace un repaso por aquellos lenguajes se propusieron antes de CSS.
  • DataViz Catalogue es una web que recoge un montón de tipos de visualizaciones de datos que se pueden realizar para mostrar información de forma visual.
  • ¿Qué tal vas de soft skills? Las soft skills son igualmente importante que las hard skills para tu desarrollo profesional y personal. Dentro de este mundillo hay bastantes gurús de dudosa credibilidad, pero esta página llamada SkillsMatch me ha parecido interesante ya que se trata de un proyecto respaldado por la universidad de Estocolmo para tratar de establecer un marco común europeo en materia de soft skills. En la web realizas un cuestionario que irá determinando tus nivel en diferentes habilidades. Además te propone cursos para mejorar esas habilidades.
  • Una de las cosas que estoy haciendo este verano es aprender Idris, concretamente Idris 2, un lenguaje funcional donde los tipos son elementos de primer orden y se pueden construir los llamados dependent types. El lenguaje a primera vista se parece mucho a Haskell, sin embargo, hay 10 cosas que Idris mejora sobre Haskell.
  • Si quieres hacer un juego en Rust, tienes varias librerías: ggez o Amethyst eran las opciones nativas más populares. También tenías Godot-Rust para integrate con Godot y bindings con SDL2 y SFML. Ahora acaba de anunciarse, Bevy, un motor ECS, con muy buena pinta. Tiene ya muchas cosas implementadas ya e intenciones de seguir. Merece la pena darle un vistazo.
  • Ink es una librería que es como React pero para aplicaciones en la línea de comandos. Podrás hacer interfaces de línea de comandos con JSX. No es la librería para crear UIs de terminal más completa ni mucho menos, pero esta es bastante simple si vienes del mundo React.
  • Oxigraph es una base de datos muy nueva, hecha en Rust y basada en tripletas RDF. Trata de implementar el estándar SPARQL al completo, así como diferentes formatos de ingesta RDF (RDF-XML, Turtle, N3, ...)
  • Seguimos con bases de datos. QuestDB es una base de datos para series temporales que también acaba de salir. Implementada en Java, usa el lenguaje SQL más ciertas extensiones para realizar consultas. Sale muy bien parada en ciertos benchmarks por lo que puede ser una alternativa interesante a InfluxDB o a TimescaleDB.
  • Esta palabra no existe. El algoritmo funciona con GPT-2, seguramente con GPT-3, se puedan conseguir resultados más espectaculares todavía.
  • f1zz es una propuesta novedosa de lenguaje basado en lógica difusa. Veremos como evoluciona.

Con estos enlaces me despido y os traigo la canción de este teletexto:

]]>
https://blog.adrianistan.eu/teletexto-007 Sat, 15 Aug 2020 22:36:28 +0000
Regex para torpes https://blog.adrianistan.eu/regex-torpes https://blog.adrianistan.eu/regex-torpes Las expresiones regulares, o de forma abreviada, regex, son pequeños programas que buscan y extraen ciertas cadenas de texto dentro de una cadena mayor. Las expresiones regulares no son Turing-completas, pero son ideales para trabajar con texto. Prácticamente todos los lenguajes de programación incluyen soporte a regex, con diferentes niveles de soporte. Sin embargo, habitualmente mucha gente al ver una expresión regular piensa que son indescifrables e imposibles de entender. Nada más lejos de la realidad. Con este post intento explicar lo más importante de Regex

No importa si no entiendes esto ahora. Cuando acabes de leer el post espero que sí puedas.

Para el código de ejemplo usaré Python, pero todo lo que voy a explicar es compatible con cualquier motor de regex.

Las regex son patrones de texto

Lo primero que hay que tener claro es que las expresiones regulares son patrones. El motor regex va a intentar hacer que el patrón coincida con la entrada en cualquier punto. Los patrones se definen escribiendo los caracteres que queremos que aparezcan.


import re

TEXT = """
Always remember my friend
The world will changes again
And you may have to come back
Through everywhere you have been
When your life was low
You had nowhere to go
People turned there backs on you
And everybody said that you through
I took you in
I made you strong again
Put you back together
Out of all the dreams
You left along the way
You left me shining
Now your doing well
From story's I hear tell
You own the world again
Everyone's your friend
Although I never hear from you
Still its nice to know
You used to love me so
When your life was low
"""

x = re.search(r"friend", TEXT)
if x is not None:
    print("La palabra friend aparece en la letra de la canción")

En este primer ejemplo, usamos caracteres "normales" para crear la expresión regular. El motor regex va a buscar si existe en algún punto de la cadena TEXT la expresión "friend", la cuál es cierta. El procedimiento es el siguiente, el motor va buscando la primera letra, la f en el texto. Si la encuentra, comprueba que el siguiente caracter cumpla también el patrón, es decir, que sea una r. La complejidad, aparente que no real, de las regex, viene cuando introducimos condiciones más avanzadas para estos patrones. Normalmente, además de obtener si se ha podido aplicar el patrón o no, obtendremos el match, que es el texto en concreto que cumple la regla.

Tal y como hemos visto, el procedimiento de búsqueda de patrones es un AND implícito. Si queremos añadir un OR, es decir, varias alternativas, usaremos la barra vertical |.


x = re.search(r"left|right", TEXT)

En este caso, podrá encontrar el texto ya que left existe, aunque right no aparezca.

Grupos de caracteres

Lo anterior nos podría servir para muchos casos de uso, pero muchos serían tremendamente verbosos. Algunos caracteres especiales nos ayudarán.

Los corchetes [ y ] no permiten definir grupos de caracteres. Dentro de estos grupos podemos poner caracteres que podrían ir en esa posición. A la hora de buscar se seleccionará uno.


x = re.search(r"[Tt]he", TEXT)

En el ejemplo, buscará tanto la palabra the como The.

Los grupos de caracteres soportan intervalos, que nos permite definir un rango de caracteres amplio de forma reducida. Los más importantes son 0-9 (dígitos), A-Z (mayúsculas ASCII) y a-z (minúsculas ASCII).


x = re.search(r"[A-Za-z]", TEXT)

El ejemplo anterior sería un patrón para todas las letras ASCII. Hago hincapié en lo de ASCII porque si tuviesemos caracteres con tildes o eñes, no funcionaría, ya que no están dentro del intervalo.

Los grupos de caracteres pueden funcionar de forma inversa, admitiendo cualquier caracter salvo los que están dentro de él. Los grupos de caracteres complementarios empiezan con el caracter ^.


x = re.search(r"[^ ]", TEXT)

En el ejemplo anterior se admite todo menos el espacio.

Los grupos de caracteres también sirven para expresar caracteres que no podemos expresar de otra forma por tener otro significado en regex, como los puntos o los paréntesis.

Multiplicidad

Todo lo que hemos visto está muy bien pero seguimos teniendo patrones que en ocasiones pueden ser muy verbosos. Para ello entran los operadores de multiplicidad, que son cuatro y nos permiten definir las repeticiones admitidas de lo que va inmediatamente antes.

El operador * indica que lo que va antes se puede repetir de 0 a N veces

El operador + indica que lo que va antes se puede repetir de 1 a N veces

El operador ? indica que lo que va antes se puede repetir de 0 a 1 veces

El operador {M} indica que lo que va antes se puede repetir M veces


x = re.search(r"[0-9]*", "hola") # OK
x = re.search(r"[0-9]*", "123") # OK
x = re.search(r"[0-9]+", "hola") # NOPE
x = re.search(r"[0-9]+", "123") # OK
x = re.search(r"[0-9]?", "hola") # NOPE
x = re.search(r"[0-9]?", "123") # OK (pero cada número por separado)

x = re.search(r"[0-9]{4}-[0-9]{2}-[0-9]{2}", "2020-07-26") # OK

Hay que tener en cuenta que el motor regex va a buscar el match más largo que pueda encontrar, por eso usando * o +, buscará siempre el patrón que cumpla la regla que más caracteres lleve. Esto es lógico si tenemos en cuenta que el patrón más largo suele ser el buscado, pero se puede cambiar este comportamiento.

Caracteres especiales

Existen algunos caracteres especiales en regex. El punto . representa cualquier caracter (salvo saltos de línea). Por ejemplo, usando la variable TEXT de arriba:


x = re.search(r".*", TEXT)

Va a ser un patrón dentro del texto y el match va a ser: "Always remember my friend".

Otros caracteres especiales son ^ y $. Representan respectivamente, inicio de cadena y final. Son útiles cuando queremos delimitar el patrón a que necesariamente empiece donde empieza la cadena de texto y cuando queremos que toda la cadena de texto cumpla con el patrón.


x = re.search(r"^(\n|.)*$", TEXT) # OK

x = re.search(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}$", "2020-07-26") # Así evitaríamos que se colase texto antes o después de la fecha.

Extraer texto (capturas)

Una de las mejores características de regex es que podemos extraer contenido dado unos patrones. Para ello, usamos paréntesis ( y ). Los grupos de captura, que es como se llaman, se numeran internamente de izquierda a derecha empezando por el número 1 (número 0 es el match completo).


x = re.search(r"^([0-9]{4})-([0-9]{2})-([0-9]{2})$", "2020-07-26")

print(x.group(0)) # 2020-07-26
print(x.group(1)) # 2020
print(x.group(2)) # 07
print(x.group(3)) # 26

Con esto ya sabemos lo básico de expresiones regulares. Existen más caracteres especiales y cosas más avanzadas como los lookaheads, pero en la gran mayoría de casos no necesitarás más. Ahora intenta leer el gráfico del comienzo del post.

Por último, una pregunta para los comentarios, ¿utilizas regex normalmente o intentas evitarlo?

]]>
https://blog.adrianistan.eu/regex-torpes Sun, 26 Jul 2020 18:51:55 +0000
Agregador de enlaces en la nube con AWS Lambda y Terraform https://blog.adrianistan.eu/agregador-enlaces-aws-lambda-terraform https://blog.adrianistan.eu/agregador-enlaces-aws-lambda-terraform Siempre había querido hacer una especie de planeta o agregador de enlaces ya que personalmente, son sitios que visito mucho. En este post veremos como hacer un agregador de enlaces en la nube (concretamente Amazon Web Services) de forma gratuita usando los servicios Lambda, CloudWatch y S3. Todo lo juntaremos usando Terraform

El agregador de enlaces aka la lambda

El agregador de enlaces será un código que visitará diferentes fuentes, definidas en el formato estándar OPML, filtrará las noticias que son de los últimos 30 días y las combinará con una plantilla para sacar un HTML con los títulos y enlaces a las páginas originales.

AWS Lambda nos permite que este código se ejecute como máximo durante 15 minutos y nos deja varios lenguajes: Ruby, Node.js, Java, Go, C#, PowerShell y por supuesto, Python, que es el que vamos a usar. En AWS Lambda tenemos disponible Python 3.8, que será la versión que usaremos.

Para descargar los feeds usaremos aiohttp, que permite hacer requests de forma asíncrona. feedparser se encargará de leer los feeds. Las plantillas las renderizaremos con Jinja. Finalmente, subiremos el resultado de la web a S3, otro servicio de AWS.

Vamos a ir viendo el código. Primero importamos aquello que vamos a usar y ajustamos el logger a que saque eventos hasta de nivel INFO. Estos logs los podremos ver en CloudWatch más adelante.


import aiohttp
import feedparser
import boto3
from boto3.s3.transfer import S3Transfer
from jinja2 import Environment, FileSystemLoader, select_autoescape

import asyncio
import itertools
import xml.etree.ElementTree as ET
import logging
import mimetypes
import shutil
import urllib
import tempfile
from datetime import datetime
from dataclasses import dataclass
from pathlib import Path

logging.getLogger().setLevel(logging.INFO)

A continuación creamos la clase Item, que contiene cada noticia de un feed en nuestro formato. Tendrá un orden definido por la fecha, así como un constructor desde elementos item de feedparser (podría entenderse como un conversor de items de feedparser a items propios).


@dataclass
class Item:
    title: str
    url: str
    date: datetime
    source: str


    def __lt__(self, other):
        return self.date < other.date

    @property
    def readable_date(self):
        return self.date.strftime("%d/%m/%Y")

    @staticmethod
    def from_feeditem(item, source):
        try:
            pdate = item["published_parsed"]
            published_date = datetime(year=pdate.tm_year, month=pdate.tm_mon, day=pdate.tm_mday)
            return Item(item["title"], item["link"], published_date, source)
        except:
            return None

    def is_in_last_month(self):
        return (datetime.today() - self.date).days < 30

A continuación definimos la función fetch_items, que realiza la conversión desde feed de feedparser hasta un listado de items propios. Está diseñada alrededor de map/filter.


def fetch_items(feed, source):
    all_items = map(lambda x: Item.from_feeditem(x, source), feed["items"])
    all_items = filter(lambda x: x is not None, all_items)
    return filter(lambda x: x.is_in_last_month(), all_items)

Ahora definimos parse_source, que dado el texto de un feed, llama a feedparser y obtiene los elementos correspondientes.


def parse_source(feed_text, source):
    feed = feedparser.parse(feed_text)
    return fetch_items(feed, source)

Llega el turno de la función main, que será asíncrona para poder usar aiohttp. En la primera parte leemos el fichero OPML, obtenemos las URLs y vamos almacenando los items que deberán aparacer en la página web en una lista.


async def main():
    tree = ET.parse("sources.opml")
    body = tree.find("body")

    sources = [ outline.get("title") for outline in body.iter("outline")]
    feeds_url = [ outline.get("xmlUrl") for outline in body.iter("outline")]

    items = list()

    async with aiohttp.ClientSession() as session:
        feeds = map(session.get, feeds_url)
        for feed_task in asyncio.as_completed(feeds):
            try:
                feed = await feed_task
                url = str(feed.url)
                logging.info(f"Downloaded {url} with success")
                feed_text = await feed.text()
                i = feeds_url.index(url)
                source = sources[i]
                items.extend(parse_source(feed_text, source))
            except:
                pass

Allí en el except se podría hacer un control de errores más fino para detectar cuando una URL está caída e informar. El bucle for está gobernado por una función llamada asyncio.as_completed. Esta función pertenece al módulo estándar asyncio y permite tener un bucle que va sacando elementos asíncronos de la lista inicial según se van completando.

El siguiente paso es construir el código HTML con Jinja. No tiene mucho misterio salvo que es importante usar directorios temporales, ya que no podemos escribir en otras carpetas que no sean esas en AWS Lambda.


env = Environment(
            loader=FileSystemLoader("templates"),
            autoescape=select_autoescape(["html"])
    )

    items.sort(reverse=True)

    temp_dir = tempfile.mktemp()
    output_folder = Path(temp_dir) / "_site"
    if not output_folder.exists():
        output_folder.mkdir(parents=True)

    output_file = output_folder / "index.html"

    static_folder = Path.cwd() / "static"
    shutil.copytree(static_folder, output_folder, dirs_exist_ok=True)

    with open(output_file, "w") as f:
        f.write(env.get_template("index.html").render(items=items))

    print(f"OK: {output_folder}")

Finalmente subimos el resultado a S3. No hay que preocuparse por credenciales, ya que la Lambda tendrá credenciales automáticas en su entorno de ejecución. Si quieres hacer pruebas deberás exportar unas variables de entorno eso sí. Importante: marcamos la ACL como public-read para que se pueda leer desde fuera y asignamos mime types a mano, ya que de otro modo, S3 tendrá problemas en servir el HTML. Esto será lo último del main.


    s3 = boto3.client("s3")
    transfer = S3Transfer(s3)
    for path in output_folder.iterdir():
        transfer.upload_file(str(path), "lector.adrianistan.eu", path.name, extra_args={
            "ACL": "public-read",
            "ContentType": mimetypes.guess_type(path.name)[0]
        })

Finalmente, definimos una función que será el punto de entrada de la Lambda, en este caso llamará a main a través de asyncio, para que la asincronía funcione.


def handler(event, context):
    asyncio.run(main())

Con esto ya tendríamos la lambda, pero falta la configuración de AWS

Infraestructura en Terraform

Vamos a crear un fichero amazon.tf en la misma carpeta. Contendrá la infraestructura en AWS como código. Primero definimos que vamos a usar AWS y seleccionamos una región (yo uso eu-west-3, París). También podemos crear un resource group. No es más que una forma de agrupar recursos que contengan el mismo tag, no tiene ningún coste. Vamos a poner que sean recursos del grupo todos los recursos de AWS que tengan el tag app con valor "lector".


provider "aws" {
  region  = "eu-west-3"
  version = "2.60.0"
}

resource "aws_resourcegroups_group" "lector" {
  name = "lector"
  resource_query {
    query = <<JSON
{
  "ResourceTypeFilters": [
    "AWS::AllSupported"
  ],
  "TagFilters": [
    {
      "Key": "app",
      "Values": ["lector"]
    }
  ]
}
JSON
  }
}

Amazon S3

S3 es el servicio de almacenamiento de "objetos" de AWS. Se diferencia de otros almacenamientos en que principalmente está diseñado para acceder mediante una API en contraste con otros almacenamientos de "bloques" que básicamente son discos duros y hay que formatear. Las ventajas de este modelo es que es muy sencillo de mantener. S3 dispone de un modo website, que permite mostrar HTML directamente y también de redirecciones CNAME, de modo que responde a peticiones DNS de dominios que se llamen igual que el bucket.

El bucket por tanto se llamará lector.adrianistan.eu, que es el dominio que voy a apuntar hacia allí. Tiene una ACL de lectura pública y el punto de entrada es "index.html". Además defino un output, para que me muestre por consola la URL del bucket. Esto hay que ponerlo en el registro CNAME del subdominio en el proveedor del dominio que tengamos. Amazon tiene uno llamado Route53, pero para este proyecto no lo he usado.


resource "aws_s3_bucket" "lector-www" {
    bucket = "lector.adrianistan.eu"
    acl = "public-read"

    website {
        index_document = "index.html"
    }

    tags = {
      app = "lector"
    }
}

output "website_cname" {
    value = aws_s3_bucket.lector-www.website_endpoint
}

IAM

AWS está altamente protegido y salvo los usuarios administradores, los demás tienen que pedir permisos de forma explícita. Nuestra lambda necesita permisos, para ser Lambda, para usar S3, y para usar CloudWatch. Estos credenciales son los que usará además nuestra lambda desde el código Python.


resource "aws_iam_role" "lector" {
  name = "lector"

  tags = {
    app = "lector"
  }

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

resource "aws_iam_policy" "lector" {
  name = "lector"
  policy = file("policy.json")
}

resource "aws_iam_role_policy_attachment" "lector" {
  role       = aws_iam_role.lector.name
  policy_arn = aws_iam_policy.lector.arn
}

El fichero policy.json contiene los permisos de la lambda.


{
  "Version": "2012-10-17",
  "Statement": [
      {
        "Effect": "Allow",
        "Action": "s3:*",
        "Resource": "*"
      },
      {
        "Action": [
          "logs:*"
        ],
        "Effect": "Allow",
        "Resource": "*"
      }
  ]
}

En este caso son permisos bastante abiertos, ya que dentro de S3 y CloudWatch Logs pueden hacer lo que quieran sobre cualquier recurso.

AWS Lambda

Las lambdas son programas de código pequeño sin estado que se ejecutan sin necesidad de configurar servidores por cortos periodos de tiempo (máximo 15 minutos). Para configurarlas solo tenemos que elegir permisos, subir el código (en un fichero Zip), la función de entrada, memoria RAM que podrá usar (por defecto 128 MB), timeout (por defecto son 3 segundos solo) y entorno de ejecución.


resource "aws_lambda_function" "lector" {
    filename = "function.zip"
    function_name = "lector"
    role = aws_iam_role.lector.arn
    handler = "main.handler"

    source_code_hash = filebase64sha256("function.zip")
    runtime = "python3.8"

    memory_size = 256
    timeout = 900

    tags = {
      app = "lector"
    }
}

El código lo tenemos en el fichero function.zip. Este fichero se elabora de forma diferente según el lenguaje de programación que usemos. En el caso de Python, si queremos incluir la dependencias deberemos hacer un zip así:


pip install --target ./package -r requirements.txt
cd package
zip -r9 ../function.zip .
cd ..
zip -g function.zip static/*
zip -g function.zip templates/*
zip -g function.zip sources.opml
zip -g function.zip main.py

Básicamente instalamos los paquetes de Pip al mismo nivel que el resto de ficheros. AWS Lambda se ejecuta sobre Amazon Linux, por lo que no hay problema en usar el código nativo que se genera en las instalaciones de Pip desde Ubuntu o Debian.

AWS CloudWatch

CloudWatch es un servicio de AWS con varias finalidades. Por un lado es una plataforma de recolección de logs y métricas. Por otro lado, también permite disparar eventos, por ejemplo, de tipo cron. En Terraform crearemos una regla cron, la asociaremos con la Lambda (¡hay que asignar permisos también!) y por otro lado crearemos una carpeta para los logs, con una retención de 7 días únicamente.


resource "aws_cloudwatch_event_rule" "lector-daily" {
    name = "lector-daily"
    description = "Run Lector Lambda every day"

    schedule_expression = "cron(45 8 * * ? *)"

    tags = {
      app = "lector"
    }
}

resource "aws_cloudwatch_event_target" "lector-daily" {
    rule = aws_cloudwatch_event_rule.lector-daily.name
    target_id = "lector-daily"
    arn = aws_lambda_function.lector.arn
}

resource "aws_cloudwatch_log_group" "lector" {
  name              = "/aws/lambda/${aws_lambda_function.lector.function_name}"
  retention_in_days = 7

  tags = {
    app = "lector"
  }
}

resource "aws_lambda_permission" "cloudwatch-call-lector" {
    statement_id = "AllowExecutionFromCloudWatch"
    action = "lambda:InvokeFunction"
    function_name = aws_lambda_function.lector.function_name
    principal = "events.amazonaws.com"
    source_arn = aws_cloudwatch_event_rule.lector-daily.arn
}

Conclusión

Con todo esto ya tenemos ya la aplicación acabada, un "terraform apply", nos permitirá lanzar la aplicación a la red. He de decir que a priori parece todo muy complejo. La razón es principalmente el tema de permisos de AWS, que es muy granular y restrictivo por defecto. También tenemos que considerar que una vez hecho esto, el mantenimiento de la aplicación es nulo. Esto que he hecho también se puede hacer en Microsoft Azure y en Google Cloud con Azure Functions y Google Cloud Functions respectivamente. Sin embargo, en Azure están limitadas a 10 minutos y creo que este programa puede llegar a tardar alguna vez más de 10 minutos. Finalmente el coste de todo esto. Esto que hemos hecho cuesta 0€. La capa gratuita de ejecución de Lambdas es muy elevada y ni aunque gastásemos los 15 minutos con 3GB de RAM todos los días llegaríamos a gastarlo. Lo mismo para los eventos. Los logs tienen un límite gratuito de 5GB. S3 es el único servicio que tendremos que pagar, pero si solo tenemos estos datos seguramente el redondeo sea 0€.

Puedes visitar la web final en http://lector.adrianistan.eu

]]>
https://blog.adrianistan.eu/agregador-enlaces-aws-lambda-terraform Tue, 26 May 2020 15:45:20 +0000
Teletexto #006 https://blog.adrianistan.eu/teletexto-006 https://blog.adrianistan.eu/teletexto-006 Bienvenidos otra vez a Teletexto, esa sección donde os pongo enlaces interesantes y que no tiene calendario de publicación (sale cuando hay enlaces suficientes y no hay más temas de los que pueda escribir)

  • COBOL es uno de los lenguajes más veteranos de la informática. Era por tanto cuestión de tiempo de que COBOL se adaptase al mundo serverless. Gracias a los CloudFlare Workers, ya puedes usar COBOL en la nube (¡usando entre medias WebAssembly!).
  • PostgreSQL es la mejor base de datos del mundo
  • Ya hemos hablado de las virtudes de PostgreSQL, pero también tiene partes malas. En este post vemos 10 cosas malas de PostgreSQL. Spoiler: no te emociones, seguramente PostgreSQL siga siendo tu mejor opción dentro de las bases de datos.
  • JavaScript ha ido evolucionando con los años de ser un lenguaje raro diseñado en poco tiempo a ser uno de los lenguajes más expresivos e innovadores. Una novedad reciente que no conocía son los proxies.
  • No uso clases para programar (en JavaScript). Interesante lectura. La verdad es que antes la gente se quejaba mucho de que JavaScript no tuviese clases (si tenía objetos, pero su OOP era de prototipos no de clases) y luego al final tampoco se ven tanto como en otros lenguajes.
  • ¿Cómo funciona la red en Kubernetes? Un excelente artículo
  • ¿Quieres administrar tus filtros de Gmail como código? Este programa parecido a Terraform pero adaptado a Gmail es gmailctl
  • ¿Estás programando orientado a producción? Una serie de pautas sobre como debemos gestionar los proyectos para que haya la menor fricción posible entre desarrollo y producción.
  • Los problemas de colas están por todas partes en nuestra sociedad y un buen análisis permite ahorrar mucho tiempo y dinero. Existen muchas formas de modelarlos y resolverlos, y me gustaría hacer algún post explicando esto más en detalle. Como adelanto, una forma de hacerlo es usar SimPy, una librería perfecta para realizar simulaciones de este tipo.
  • Si buscas un motor de búsqueda sobre texto, ElasticSearch es quizá tu mejor opción, pero es un software complejo y consume gran cantidad de recursos. Una solución muy interesante es usar los módulos FTS3, FTS4 y FTS5 de SQLite. Estos potentes módulos nos permiten realizar búsquedas de texto complejas.
  • ¿Necesitas un editor de texto enriquecido para tu web? Una opción es Quill.js. Personalmente antes usaba TinyMCE en este blog y acabé quitándolo porque a veces hacía cosas raras. Actualmente no tengo nada y escribo HTML directamente (con ayudas), pero Quill parece una buena opción a tener en cuenta.
  • Usar máquinas de estado es una opción muy buena para resolver ciertos problemas. En este post explica una forma de implementar máquinas de estados en Rust.

Por último, la canción de hoy pertenece a una de las dictaduras más peculiares del mundo: Turkmenistán.

]]>
https://blog.adrianistan.eu/teletexto-006 Wed, 13 May 2020 18:52:54 +0000
El Universo Prolog https://blog.adrianistan.eu/universo-prolog https://blog.adrianistan.eu/universo-prolog Prolog es un lenguaje de programación dentro del paradigma lógico. Desde su invención en 1972 en Marsella, Francia, Prolog ha sido un lenguaje base para otros lenguajes ya que Prolog prácticamente inventó el paradigma. Las ventajas originales de Prolog son su sintaxis reducida y tersa, altamente modificable ya que es un lenguaje homoicónico donde el código y los datos son lo mismo. De esta forma se permite expresar el conocimiento fácilmente. En el lenguaje solo existen comentarios, términos y variables. El lenguaje funciona mediante conceptos como la unificación SLD y la búsqueda. Pero como todo lenguaje, la gente no se quedó con Prolog a secas, sino que ha habido modificaciones respecto a la versión de 1972. Adentrémonos en el universo de lenguajes inspirados y versiones de Prolog.

Primero exploraremos los que se podrían considerar descendientes más directos y con el mismo espíritu que Prolog original, después veremos cambios más grandes.

ISO Prolog

En 1997, Prolog pasó a ser un lenguaje estandarizado por ISO bajo el nombre de ISO/IEC 13211 y se unió a la lista de lenguajes ya estandarizados por ISO tales como C, SQL, Ada, C++, Fortran, PL/I, ALGOL, COBOL y BASIC. Hoy en día la estandarización de un lenguaje es algo menos importante ya que suele haber implementaciones de software libre, pero antiguamente era muy importante. ISO Prolog define lo que hasta entonces era la evolución más común del lenguaje Prolog original. ISO Prolog se implementa en muchos sitios, veamos los más relevantes.

SICstus

SICstus es una implementación de ISO Prolog de manos de RISE, unos laboratorios de investigación del gobierno de Suecia. Tiene fama de ser una de las implementaciones más fieles al estándar y una de las más rápidas. Pero este intérprete es de pago por lo que tiene menos usuarios de los que debería tener por su calidad técnica

GNU Prolog

GNU Prolog es la versión del proyecto GNU de Prolog. Se trata de otra implementación fiel de ISO Prolog y rápida, ya que es capaz de generar ejecutables nativos. Esto último no es nada habitual en los sistemas Prolog. Sin embargo, GNU Prolog no es de los entornos más amigables ni cuenta con muchas más librerías además de las definidas por ISO Prolog, por lo que en la práctica tampoco es tan usado.

Scryer Prolog

Scryer Prolog es una implementación, todavía en desarrollo, de un entorno fiel a ISO Prolog. Está implementado en Rust y pretende ser un entorno completo con gran cantidad de librerías. Al ser un desarrollo desde cero, pretende alcanzar gran rendimiento usando técnicas modernas.

XSB Prolog

XSB Prolog es una variante de Prolog comunitaria especialmente enfocada en Tabled Logic Programming. Si quieres explorar esta parte de la programación lógica XSB es el mejor sistema, aunque otros sistemas también soportan tabling. El tabling es especialmente interesante en problemas de programación dinámica.

YAP

YAP significa Yet Another Prolog, y sí, es otro Prolog más. Bastante ligero, no termina de implementar ISO Prolog pero no introduce incompatibilidades para que en un futuro pueda serlo. Es mantenido por la Universidad de Porto en Portugal.

B-Prolog

B-Prolog es otra implementación estándar de Prolog. La razón de ser de B-Prolog es la incorporación de bucles foreach, soporte para tabling, list comprehensions, matching clauses y action rules que permiten realizar una programación donde ciertas condiciones se retrasan, dando lugar a una especie de programación orientada a eventos.

Jekejeke

Jekejeke es una implementación de Prolog 100% escrita en Java. Como muchos otros, intenta seguir ISO Prolog.

ECLiPSe

ECLiPSe es un Prolog especializado en CHR, o programación por restricciones. La mayoría de Prolog incluyen soporte a CHR así que no lo he mencionado, pero este está especializado en este tipo concreto de programación.

Tau Prolog

Tau Prolog es quizá una de las implementaciones de Prolog más usadas en el mundo, aunque mucha gente no lo sepa. Se trata de una implementación de ISO Prolog escrita en JavaScript, por lo que puede ejecutarse sin problema en los navegadores y en Node.js. Precisamente ahí, dentro de Yarn, ha encontrado un hueco y es como dependencia de este gestor de paquetes, de donde vienen la mayoría de sus descargas. Se trata de un proyecto del español José Antonio Riaza Valverde.

Todavía más

Otras implementaciones, menos usadas e interesantes son hProlog, Quintus Prolog, CSProlog o Aquarius Prolog, especialmente eficiente en arquitecturas MIPS y SPARC.

SWI Prolog

SWI Prolog es una implementación comunitaria de Prolog, aunque su principal desarrollador, Jan Wielemaker, está respaldado por la Vrije Universiteit Amsterdam. Inicialmente era una implementación más de ISO Prolog, pero a partir de la versión 7 rompe la compatibilidad con el estándar, así pues hoy día no es una implementación del estándar, si bien cumple con gran parte de este.

SWI Prolog se centra en ser práctico y usable como un lenguaje de programación más, altamente productivo. Prueba de ello lo tenemos en las inmensa cantidad de librerías, herramientas y documentación que tiene el proyecto. Librerías de APIs web, GUIs multiplataforma, regex, acceso a bases de datos JDBC, JSON, XML, RDF, PlDoc, PlUnit, debug gráfico, packs, diccionarios, etc. No es la implementación más rápida, aunque está relativamente optimizada. Se autodefine como el Prolog para los grandes proyectos. Actualmente, es mi implementación Prolog predilecta.

Ciao

Ciao es un lenguaje derivado de Prolog. Es posible usarlo como un ISO Prolog más, pero añade bastantes cosas extra y de hecho esta compatibilidad con ISO es simplemente un modo más. La primera diferencia es su propia definición. Ciao no es un lenguaje lógico sino multiparadigma. Introduce varias características de otros lenguajes tales como: funciones, mutabilidad, diccionarios, grafos, concurrencia, paralelismo y ejecución distribuida. También cuenta con librerías para desarrollo web. Además dispone de numerosas herramientas que lo convierten en un entorno productivo de programación. El proyecto Ciao está coordinado por la Universidad Politécnica de Madrid y el centro de investigación español IMDEA y es bastante menos popular que SWI Prolog.

Visual Prolog

Visual Prolog es el heredero de Turbo Prolog de Borland. Este dialecto de Prolog nunca fue cercano al estándar, ya que realizó modificaciones fuertes para que fuese más práctico para desarrollar interfaces de usuario para Windows. Incorpora orientación a objetos, tipado fuerte, llamadas directas a la API Win32, APIs de base de datos y APIs para programación web. Genera ejecutables nativos aunque de forma exclusiva para Windows. Es un producto de pago, aunque cuenta con una edición gratuita limitada.

Logtalk

Logtalk es el lenguaje que surgiría si Smalltalk y Prolog tuvieran un hijo. Se trata de un lenguaje cuya principal característica es añadir programación orientada a objetos a Prolog. Pero no la típica implementación popularizada por C++ y Java, sino una más cercana a Smalltalk. Diseñado por Paulo Moura, uno de los artífices de ISO Prolog, el lenguaje incluye módulos, prototipos, clases, protocolos (interfaces), categorías, programación orientada a eventos y programación concurrente.

Quizá la característica más chocante de Logtalk es que no es un entorno de ejecución, es simplemente una capa intermedia. El código Logtalk tiene que ser ejecutado en algún intérprete como SWI, SICstus, XSB o YAP.

Mercury

Mercury es el lenguaje que surgiría si Haskell y Prolog tuvieran un hijo. Se trata de un lenguaje que añade un tipado estático, fuerte y muy potente. Añade además otras características de lenguajes funcionales. Se trata de un lenguaje de muy buen rendimiento que compila a código nativo. Es usado por el producto comercial PrinceXML de la empresa australiana YesLogic.

Erlang

Erlang es quizá el lenguaje más conocido de esta lista y no es un lenguaje de programación lógica. ¿Entonces por qué está aquí? Porque la tan odiada por algunos sintaxis de Erlang es prácticamente la misma de Prolog. Los diseñadores de Erlang que trabajaban en Ericsson eran grandes fans de Prolog y de hecho los primeros compiladores de Erlang estaban hechos en Prolog. Es por ello que se puede considerar parte del universo Prolog.

Picat

Picat es un lenguaje, en esencia muy similar a Prolog, pero que incorpora una sintaxis incompatible. Picat incorpora estructuras de datos adicionales, funciones, notación de funciones OOP, list comprehensions, asignaciones, bucles y tabling.

Datalog

Datalog es un subconjunto de Prolog. Se le eliminan características para hacerlo puramente declarativo y de esta forma se consigue que acabe siempre en tiempo finito (por tanto no es Turing completo). Existen muchas implementaciones de este lenguaje más sencillo que Prolog pero surgido posteriormente.

Teyjus

Teyjus es una implementación de un lenguaje llamado LambdaProlog. LambdaProlog es un lenguaje que incorpora las ideas de Church de la Teoría Simple de Tipos. Este lenguaje añade tipos de datos abstractos y la sintaxis lambda-árbol para representar variables. Es bastante experimental todavía.

LPS

LPS es un interesante lenguaje que parte de Prolog y añade funcionalidad imperativa para que sea más fácil de utilizar. Tiene especial aplicación en el mundo financiero. Es un proyecto mantenido por el Imperial College de Londres.

Bousi~Prolog

Bousi~Prolog es un Prolog adaptado para lógica difusa. Bastante experimental.

Tamgu

Tamgu es un lenguaje de programación creado por Naver, multiparadigma. Soporta los paradigmas lógico, imperativo y funcional. La parte lógica es extremadamente similar a Prolog, aunque no compatible. No existen átomos, todo funciona vía strings y las variables lógicas deben usar '?' como prefijo.

Otros proyectos interesantes

Otros proyectos relacionados son Flora-2, PARLOG, KL1, DR-PROLOG, P#, N-PROLOG, FASILL, Yield Prolog,... Muchos de estos proyectos son/fueron experimentales y muchos ya se han abandonado o tienen un uso muy minoritario. También existen lenguajes como miniKanren, Curry o Twelf, los cuales no derivan de Prolog, pero sí son parte del paradigma lógico.

]]>
https://blog.adrianistan.eu/universo-prolog Thu, 30 Apr 2020 21:02:11 +0000
Construye un Renault 4 https://blog.adrianistan.eu/construye-renault-4 https://blog.adrianistan.eu/construye-renault-4 El Renault 4 fue un coche tremendamente popular y rentable para la empresa francesa Renault. A día de hoy ya no se fabrica, pero muchas lecciones sobre su diseño se pueden tener en cuenta y ayudarnos a construir nuestro Renault 4 personal, pero en software.

Historia del Renault 4

El Renault 4 se presentó en 1961 en el salón de Frankfurt con la intención de ser el coche más práctico jamás diseñado. Toma muchas ideas del Citröen 2CV, que habiendo sido diseñado antes de la Segunda Guerra Mundial, seguía siendo un coche extremadamente popular. Fue un coche innovador a la vez práctico y económico, gracias a un diseño industrial muy bien logrado. Se vendió en más de 100 países y se mantuvo su producción hasta 1994 en Marruecos y Eslovenia, por lo que estuvo en el mercado unos 33 años (algo impensable hoy en día). Fue tan popular que en España se le llamó cariñosamente cuatro latas y en Argentina, la renoleta. Mucha gente lo ha comparado con los pantalones vaqueros por su flexibilidad.

Fue el primer coche de Renault en tener el motor en la parte delantera y el primero con tracción delantera. Históricamente los coches en ese momento llevaban el motor atrás, aunque Mini había demostrado que se podía perfectamente poner el motor delante sin problema. Fue tal el éxito que hoy en día la mayoría de coches son así y en el caso concreto de Renault, nunca han vuelto a hacer coches que no cumpliesen estos dos puntos.

También fue el primer coche de la historia en tener cinco puertas, es decir, el maletero trasero compartiendo el mismo compartimento que las personas. Además fue el primer coche que incorporaba la función de que los asientos traseros se pudiesen echar atrás para ampliar el espacio del maletero (ya muy amplio de por sí).

Aunque sin duda, la mayor curiosidad del diseño industrial del Renault 4 se encuentra en el eje trasero, donde la distancia entre ejes es diferente a un lado y a otro del coche, teniendo 5 cm más en el lado derecho. Esto es ya que la suspensión se diseño con barras de torsión que son unas barras en forma de L, pero son demasiado largas y no se pueden poner una enfrente de la otra porque chocan. Así pues, se pone una ligeramente delante de otra para que la suspensión entre. De este modo, se consigue que una buena suspensión sin perder espacio interior en el maletero. Además, todas las piezas de la chapa están atornilladas, por lo que se pueden reemplazar por otras fácilmente.

Además de esto, el Renault 4 tenía unos ángulos de entrada y salida de las ruedas muy buenos y una altura sobre el suelo de 27 cm, lo que efectivamente lo convertía en un coche perfecto para ir por el campo. Estamos ante un coche que es a la vez un compacto, un familiar y un todoterreno (sin tener tracción a las cuatro ruedas eso sí).

Las versiones originales llevaban un motor de entre 23 y 32 CV, este último motor proporcionaba una velocidad bastante elevada para la época, de 115 km/h con un consumo de 6L a los 100. Esto era gracias a su reducido peso, solo 660 kg. Este consumo logró ser muy competitivo durante muchos años con otros vehículos similares. Además es bastante corto y estrecho para los estándares actuales.

Una de las cosas más llamativas del coche es su interior, donde brilla la simplicidad máxima. El interior usa la misma pieza como tirador que para el propio bloqueo de la puerta. No tiene ventanilla vertical, solo una pestaña horizontal. El cuadro de mandos solo indica la velocidad y la cantidad de gasolina que queda. Tampoco existe palanca de cambios, sino una varilla que se conecta directamente a la caja de cambios.

Originalmente el coche permitía arrancar con manivela por si la batería fallaba, aunque este detalle se eliminó en versiones posteriores.

Los prototipos del coche recorrieron gran cantidad de kilómetros por territorios inhóspitos como montañas nevadas o desiertos, sin olvidar la carretera y la ciudad.

Sin embargo, el coche fue bastante criticado. Se decía que no aislaba del sonido, que era feo con la chapa directamente expuesta, que no era estable, etc. El tiempo finalmente le dio la razón

¿Qué podemos aprender en el software?

No hay que tener miedo a utilizar soluciones tecnológicas que son minoritarias en el mercado (por ejemplo el motor delantero). Pero más importante, primero hay que conocer bien estas alternativas y como están funcionando. Por ello es vital conocer y analizar el mercado a fondo, realizar una investigación exhaustiva, incluyendo productos que a priori no son tu competencia (Mini).

La mejor pieza es aquella que ha podido ser eliminada. Esta fue una máxima que impera en todo el coche, para ganar espacio, ahorrar costo y tener mayor fiabilidad. En el software ocurre lo mismo, menos es más. Tenemos que buscar código que sea lo más simple posible, pero sin dejar de cumplir los requisitos.

Tests desde el primer momento. Los Renault 4 recorrieron miles de kilómetros antes de salir al mercado, para validar que las ideas eran correctas desde el principio. Cuando salió, el Renault 4 no era el coche perfecto, pero era suficientemente bueno.

La mejora continua. El Renault 4 tuvo muchas variaciones que fue mejorando poco a poco. Aunque de partida fue buen coche, con los años fue mejorando, aumentando la potencia de los motores

Usa componentes fácilmente reemplazables. El Renault 4 tenía fama de fiable y no solo era por su simple pero efectiva mecánica sino porque muchas piezas eran fácilmente sustituibles. En el software deberíamos buscar justamente eso, código fácilmente actualizable para solventar fallos y que no requieran un gran conocimiento del resto del sistema.

El Renault 4 era un coche flexible, tenía gran cantidad de usos. De hecho, con ligeras modificaciones también sirvió para competiciones de motor, quedando en muy buenos puestos en el rally Dakar. El software

El Renault 4 era un coche económico. A parte de ser barato al comprarlo, tampoco consumía mucha gasolina. En el software deberíamos no solo procurar precios asequibles de compra (si aplica) sino un consumo de recursos bajo, no gastar más memoria o ciclos si no está justificado. También debemos considerar el costo de aprender a usar un programa. Los programas deben ser lo más autodescriptivos posibles y permitir la exploración sin riesgo para aprender a usar más funcionalidades poco a poco.

]]>
https://blog.adrianistan.eu/construye-renault-4 Sat, 25 Apr 2020 20:39:22 +0000
Debug gráfico en Prolog https://blog.adrianistan.eu/debug-grafico-prolog https://blog.adrianistan.eu/debug-grafico-prolog Hace ya un tiempo hice un pequeño tutorial de Prolog. Desde entonces hasta hoy he aprendido mucho y seguramente rehaga el tutorial en algún momento, de forma más práctica. Mientras tanto voy a repasar una sección que tenía el tutorial, el debugging

Prolog dispone de un modo de depuración textual, basado en el término trace/0. Cuando aparece, Prolog entra en modo debug y nos va informando de como se van ejecutando las cosas. Qué unificaciones realiza, cuando falla, etc Pero tiene pocas opciones de control

Sin embargo es extremadamente verboso y difícil de manipular. Es por ello que en bastantes entorno gráficos podemos hacer una llamada a gtrace/0, que abrirá un debugger gráfico.

El debugger gráfico nos permite ver el código en un editor tipo Emacs. Nos marca con un color la regla que ha probado, y si su resultado ha sido satisfactorio (color verde) o no (color rojo). Después una regla que no se puede cumplir, salta hasta el punto anterior de decisión con color amarillo. Todos los puntos de decisión que Prolog detecta se marcan arriba a la derecha, en el Call Stack, como caminos alternativos.


Arriba a la derecha se ve el camino alternativo que ha generado between

Arriba a la izquierda tenemos las variables en ese momento, que podemos inspeccionar con detalle

Por último, el control de ejecución. Podemos avanzar paso a paso sobre nuestro código, paso a paso sobre todo el código (para inspeccionar por dentro de una librería por ejemplo), podemos saltarnos una regla y lo más importante, podemos ir marcha atrás en el código para volverlo a inspeccionar. Esto último es algo que muy pocos entornos de depuración tienen y es algo extremadamente útil por si nos saltamos por error algo o queremos mirar detalladamente algo otra vez sin tener que parar y volver a empezar.

  • La flecha hacia abajo nos permite entrar en el código de las librerías de Prolog
  • La flecha recta hacia la derecha sigue todos los pasos de nuestro código
  • La flecha bombeada hacia la derecha avanza el código sin meterse en ninguna regla que no sea la actual.
  • La bandera deja acabar la regla actual
  • La flecha bombeada hacia la izquierda nos deja volver a empezar la regla actual

La flecha hacia abajo nos permite entrar en el código de las librerías de Prolog

El editor gráfico también nos permite añadir breakpoint o spy. Los spy se pueden añadir previamente con tspy/1 que necesita el nombre de una regla


Usando tspy y el botón del espía para ir hasta el el inicio de la regla con tspy

Code swapping

Prolog además nos permite hacer code swapping, es decir, cambiar el código que se está ejecutando sin tener que volver a iniciar el programa. Tiene algunas limitaciones no obstante. Para ello podemos activar el modo edición dentro del debugger (el lápiz) y empezar a modificar reglas siempre que no sea una que no esté en el call stack (en ese caso Prolog se guarda una copia internamente hasta se salga de ella).

Para cargar el código hay que salir del modo edición, e ir a Compile->Make. En el caso de estar editando la regla que estábamos debugeando, caso muy típico, deberemos volver hacia atrás también (flecha bombeada hacia la izquierda)


El vídeo muestra un ejemplo de code swap. Sin embargo, lo mejor es que lo pruebes por ti mismo

Aprendiendo a usar estas herramientas lograremos una fantástica prolugtividad

]]>
https://blog.adrianistan.eu/debug-grafico-prolog Thu, 16 Apr 2020 21:18:22 +0000
Sudoku en Prolog https://blog.adrianistan.eu/sudoku-prolog https://blog.adrianistan.eu/sudoku-prolog Prolog es el lenguaje más importante dentro del paradigma lógico. Uno de los puntos claves de Prolog es su expresividad para modelar un problema, y de la misma forma que ha sido modelado, resolverlo. Veremos como resolver el clásico Sudoku usando Prolog.

El tablero

Lo primero que tenemos que pensar es que estructura de datos va a tener el sudoku internamente. En el caso del sudoku, en la vida real sabemos que es una especie de matriz. Por simplicidad, vamos a implementarlo como una lista de Prolog. Las listas en Prolog son estructuras de datos plenamente integradas con el lenguaje. Será una lista plana de 81 (9x9) elementos. La lista contiene inicialmente los números iniciales del tablero y x donde no tenemos solución. Los números iniciales no van a cambiar nunca, así que ya sabemos que son parte de la solución definitiva.


        [4,x,x,x,6,x,9,1,x,
         2,x,x,x,x,7,x,5,x,
         x,9,x,8,x,x,x,2,x,
         x,x,1,6,x,9,x,x,2,
         x,8,x,x,x,x,x,6,3,
         x,7,x,x,4,x,x,x,x,
         7,x,3,x,x,8,x,9,x,
         x,x,x,x,3,x,4,x,5,
         x,4,x,9,x,x,6,x,x].

En el sudoku hay tres subdivisiones principales: filas, columnas y cuadrados. Vamos a hacer un código en Prolog para definir cada una de estas subdivisiones. Las filas son fáciles de definir:


row(Sudoku, N, Row) :-
    N0 is N-1,
    N1 is N0*9+1, nth1(N1, Sudoku, X1),
    N2 is N0*9+2, nth1(N2, Sudoku, X2),
    N3 is N0*9+3, nth1(N3, Sudoku, X3),
    N4 is N0*9+4, nth1(N4, Sudoku, X4),
    N5 is N0*9+5, nth1(N5, Sudoku, X5),
    N6 is N0*9+6, nth1(N6, Sudoku, X6),
    N7 is N0*9+7, nth1(N7, Sudoku, X7),
    N8 is N0*9+8, nth1(N8, Sudoku, X8),
    N9 is N0*9+9, nth1(N9, Sudoku, X9),
    Row = [X1, X2, X3, X4, X5, X6, X7, X8, X9].

Las filas se componen de 9 valores, los cuáles accedemos a ellos calculando el índice de la lista y accediendo al valor con nth1. Con nth1, las listas empiezan a contar en 1 (¡sacrilegio! ¡blasfemia!). Si usásemos nth0, las listas empezarían a contar en 0. ¡Prolog lo tiene todo!

Las columnas son iguales pero modificando el cálculo del índice lógicamente


column(Sudoku, N, Column) :-
    N1 is 0*9+N, nth1(N1, Sudoku, X1),
    N2 is 1*9+N, nth1(N2, Sudoku, X2),
    N3 is 2*9+N, nth1(N3, Sudoku, X3),
    N4 is 3*9+N, nth1(N4, Sudoku, X4),
    N5 is 4*9+N, nth1(N5, Sudoku, X5),
    N6 is 5*9+N, nth1(N6, Sudoku, X6),
    N7 is 6*9+N, nth1(N7, Sudoku, X7),
    N8 is 7*9+N, nth1(N8, Sudoku, X8),
    N9 is 8*9+N, nth1(N9, Sudoku, X9),
    Column = [X1, X2, X3, X4, X5, X6, X7, X8, X9].

Por último, los cuadrados tienen un pelín más de complicación de calcular los índices, pero nada que no se pueda resolver con un poco de aritmética extra.


square(Sudoku, N, Square) :-
    O is (N-1) // 3,
    P is (N-1) mod 3,
    N1 is O*27+P*3+1 , nth1(N1, Sudoku, X1),
    N2 is O*27+P*3+2 , nth1(N2, Sudoku, X2),
    N3 is O*27+P*3+3 , nth1(N3, Sudoku, X3),
    N4 is O*27+P*3+10 , nth1(N4, Sudoku, X4),
    N5 is O*27+P*3+11 , nth1(N5, Sudoku, X5),
    N6 is O*27+P*3+12 , nth1(N6, Sudoku, X6),
    N7 is O*27+P*3+19 , nth1(N7, Sudoku, X7),
    N8 is O*27+P*3+20 , nth1(N8, Sudoku, X8),
    N9 is O*27+P*3+21 , nth1(N9, Sudoku, X9),
    Square = [X1, X2, X3, X4, X5, X6, X7, X8, X9].

Condiciones del Sudoku

Una vez hemos definido estos términos, podemos pasar a comprobar como tenemos un sudoku válido. La norma dice que en las casillas de cada subdivisión deben estar todos los números, del 1 al 9, y como tienen un tamaño de 9, eso quiere decir que tampoco se pueden repetir.

Un enfoque simple podría ser comprobar que están todos los números en una subdivisión dada. Algo tal que así:


valid0(R) :-
    proper_length(R, 9),
    member(1, R),
    member(2, R),
    member(3, R),
    member(4, R),
    member(5, R),
    member(6, R),
    member(7, R),
    member(8, R),
    member(9, R).

Esta comprobación es correcta, pero cuando queramos resolver el sudoku va a ser extremadamente lenta. La razón es que cuando un número no existe en la subdivisión, lo añadirá para intentar hacer cumplir la regla. El inconveniente es que lo hace con todos los números que no están asignados a la vez, esto de cara a la búsqueda de la solución es muy lento. Es mucho mejor ir añadiendo un número, comprobar todo, luego volver a añadir otro.

Una validación mucho más simple y rápida es comprobar que si la subdivisión, que es una lista, es además un set, es decir, no hay elementos repetidos. Esto no nos añadirá números innecesariamente (¡de hecho no nos añadirá ninguno, lo tendremos que hacer en otro lado!).


valid(R) :-
    is_set(R).

Resolviendo

Vamos a la parte final, vamos a resolver el sudoku, que gracias a las cualidades de Prolog, también nos dice si el tablero es correcto o no.

Lo primero que vamos a hacer es crear variables nuevas en cada X que encontremos en la entrada original. Para esta tarea he decidido usar DCGs, que ya he explicado anteriormente. Si quieres entender el código deberás leerlo antes, si simplemente te lo crees, aquí está:


program([H|T]) --> digit(H), program(T).
program([]) --> [].
digit(N) --> [N], { number(N) } .
digit(X) --> [x].

El código de sudoku se encarga de realizar la transformación de la lista con la DCG y de ejecutar un maplist sobre el tablero. ¿Qué función realiza maplist? Simplemente itera por todos los elementos de la lista, números y variables, ejecutando para ellos una comprobación. Esta comprobación será la que compruebe que los números están bien situados, y nos genere los números que sustituyan a las variables.


sudoku(Sudoku, SolvedSudoku) :-
    phrase(program(SolvedSudoku), Sudoku),!,
    maplist(check(SolvedSudoku), SolvedSudoku).

El último código que nos falta es el de check


check(SolvedSudoku, N) :-
    between(1,9,N),
    row(SolvedSudoku, 1, R1), valid(R1),
    row(SolvedSudoku, 2, R2), valid(R2),
    row(SolvedSudoku, 3, R3), valid(R3),
    row(SolvedSudoku, 4, R4), valid(R4),
    row(SolvedSudoku, 5, R5), valid(R5),
    row(SolvedSudoku, 6, R6), valid(R6),
    row(SolvedSudoku, 7, R7), valid(R7),
    row(SolvedSudoku, 8, R8), valid(R8),
    row(SolvedSudoku, 9, R9), valid(R9),
    column(SolvedSudoku, 1, C1), valid(C1),
    column(SolvedSudoku, 2, C2), valid(C2),
    column(SolvedSudoku, 3, C3), valid(C3),
    column(SolvedSudoku, 4, C4), valid(C4),
    column(SolvedSudoku, 5, C5), valid(C5),
    column(SolvedSudoku, 6, C6), valid(C6),
    column(SolvedSudoku, 7, C7), valid(C7),
    column(SolvedSudoku, 8, C8), valid(C8),
    column(SolvedSudoku, 9, C9), valid(C9),
    square(SolvedSudoku, 1, S1), valid(S1),
    square(SolvedSudoku, 2, S2), valid(S2),
    square(SolvedSudoku, 3, S3), valid(S3),
    square(SolvedSudoku, 4, S4), valid(S4),
    square(SolvedSudoku, 5, S5), valid(S5),
    square(SolvedSudoku, 6, S6), valid(S6),
    square(SolvedSudoku, 7, S7), valid(S7),
    square(SolvedSudoku, 8, S8), valid(S8),
    square(SolvedSudoku, 9, S9), valid(S9).

Aquí juntamos todo lo que hemos creado antes. N será el valor de la casilla de la lista. Si N es ya un número fijado, simplemente validará todas las condiciones, si es una variable, between elegirá un número entre 1 y 9 para que sea N y comprobará las condiciones. Si falla alguna condición, elegirá otro número entre 1 y 9. Si ningún número entre 1 y 9 puede cumplir las condiciones, Prolog irá marcha atrás en el maplist y le dará otro número al elemento anterior, y si no fuese posible, anterior y al anterior y al anterior, así hasta que pueda volver a avanzar. Esto es claro ejemplo de algoritmo de backtracking, que Prolog implementa en su ejecución. Gracias a esto, hemos podido modelar el sudoku fácilmente.

Conclusión

El código completo es el siguiente:


row(Sudoku, N, Row) :-
    N0 is N-1,
    N1 is N0*9+1, nth1(N1, Sudoku, X1),
    N2 is N0*9+2, nth1(N2, Sudoku, X2),
    N3 is N0*9+3, nth1(N3, Sudoku, X3),
    N4 is N0*9+4, nth1(N4, Sudoku, X4),
    N5 is N0*9+5, nth1(N5, Sudoku, X5),
    N6 is N0*9+6, nth1(N6, Sudoku, X6),
    N7 is N0*9+7, nth1(N7, Sudoku, X7),
    N8 is N0*9+8, nth1(N8, Sudoku, X8),
    N9 is N0*9+9, nth1(N9, Sudoku, X9),
    Row = [X1, X2, X3, X4, X5, X6, X7, X8, X9].

column(Sudoku, N, Column) :-
    N1 is 0*9+N, nth1(N1, Sudoku, X1),
    N2 is 1*9+N, nth1(N2, Sudoku, X2),
    N3 is 2*9+N, nth1(N3, Sudoku, X3),
    N4 is 3*9+N, nth1(N4, Sudoku, X4),
    N5 is 4*9+N, nth1(N5, Sudoku, X5),
    N6 is 5*9+N, nth1(N6, Sudoku, X6),
    N7 is 6*9+N, nth1(N7, Sudoku, X7),
    N8 is 7*9+N, nth1(N8, Sudoku, X8),
    N9 is 8*9+N, nth1(N9, Sudoku, X9),
    Column = [X1, X2, X3, X4, X5, X6, X7, X8, X9].

square(Sudoku, N, Square) :-
    O is (N-1) // 3,
    P is (N-1) mod 3,
    N1 is O*27+P*3+1 , nth1(N1, Sudoku, X1),
    N2 is O*27+P*3+2 , nth1(N2, Sudoku, X2),
    N3 is O*27+P*3+3 , nth1(N3, Sudoku, X3),
    N4 is O*27+P*3+10 , nth1(N4, Sudoku, X4),
    N5 is O*27+P*3+11 , nth1(N5, Sudoku, X5),
    N6 is O*27+P*3+12 , nth1(N6, Sudoku, X6),
    N7 is O*27+P*3+19 , nth1(N7, Sudoku, X7),
    N8 is O*27+P*3+20 , nth1(N8, Sudoku, X8),
    N9 is O*27+P*3+21 , nth1(N9, Sudoku, X9),
    Square = [X1, X2, X3, X4, X5, X6, X7, X8, X9].

valid(R) :-
    is_set(R).

program([H|T]) --> digit(H), program(T).
program([]) --> [].
digit(N) --> [N], { number(N) } .
digit(X) --> [x].

check(SolvedSudoku, N) :-
    between(1,9,N),
    row(SolvedSudoku, 1, R1), valid(R1),
    row(SolvedSudoku, 2, R2), valid(R2),
    row(SolvedSudoku, 3, R3), valid(R3),
    row(SolvedSudoku, 4, R4), valid(R4),
    row(SolvedSudoku, 5, R5), valid(R5),
    row(SolvedSudoku, 6, R6), valid(R6),
    row(SolvedSudoku, 7, R7), valid(R7),
    row(SolvedSudoku, 8, R8), valid(R8),
    row(SolvedSudoku, 9, R9), valid(R9),
    column(SolvedSudoku, 1, C1), valid(C1),
    column(SolvedSudoku, 2, C2), valid(C2),
    column(SolvedSudoku, 3, C3), valid(C3),
    column(SolvedSudoku, 4, C4), valid(C4),
    column(SolvedSudoku, 5, C5), valid(C5),
    column(SolvedSudoku, 6, C6), valid(C6),
    column(SolvedSudoku, 7, C7), valid(C7),
    column(SolvedSudoku, 8, C8), valid(C8),
    column(SolvedSudoku, 9, C9), valid(C9),
    square(SolvedSudoku, 1, S1), valid(S1),
    square(SolvedSudoku, 2, S2), valid(S2),
    square(SolvedSudoku, 3, S3), valid(S3),
    square(SolvedSudoku, 4, S4), valid(S4),
    square(SolvedSudoku, 5, S5), valid(S5),
    square(SolvedSudoku, 6, S6), valid(S6),
    square(SolvedSudoku, 7, S7), valid(S7),
    square(SolvedSudoku, 8, S8), valid(S8),
    square(SolvedSudoku, 9, S9), valid(S9).

sudoku(Sudoku, SolvedSudoku) :-
    phrase(program(SolvedSudoku), Sudoku),!,
    maplist(check(SolvedSudoku), SolvedSudoku).

Para usarlo, lo guardamos en un fichero sudoku.pl y ejecutamos SWI-Prolog (swipl)

En la lista S queda la solución del Sudoku para que la inspeccionemos. Esto es un poco feo, pero con SWI-Prolog podemos hacer una API que a través de JSON pueda recibir y devolver sudokus, que luego podemos usar en otro sitio con una interfaz más trabajada.

]]>
https://blog.adrianistan.eu/sudoku-prolog Sun, 12 Apr 2020 22:19:57 +0000
Teletexto #005 https://blog.adrianistan.eu/teletexto-005 https://blog.adrianistan.eu/teletexto-005 Bienvenidos a una nueva edición de Teletexto. Un repaso por la red de enlaces que pueden ser interesantes.

  • Ogg es un formato contenedor de audio y vídeo, software libre y sin patentes. Promovido por la fundación Xiph, se ha usado extensivamente con los códecs Vorbis, Theora y Opus. Sin embargo, Ogg tiene muchas objeciones técnicas.
  • Cada día que pasa el BigData se asienta más en las empresas y organizaciones. Existen multitud de frameworks destinados a trabajar con ingentes cantidades de datos de forma distribuida, tanto en local como en la nube: Spark, Hadoop, Flink, Dataflow, ... Dominar estos frameworks puede ser complicado, por eso Yelp hizo MrJob, una librería Python con la simplicidad por bandera y que compatible por detrás con estos frameworks.
  • Pijul es un sistema de control de versiones, como Git o Subversion, distribuido y que aplica conceptos de teoría de parches presentes en Darcs. No funciona exactamente igual que Darcs (lo mejora supuestamente) y tiene un mayor rendimiento al estar escrito en Rust. De momento hay una forja abierta, Pijul Nest.
  • ¿Programas JavaScript? ¿Haces tests verdad? Espero que sí. Pero siempre viene bien una pequeña guía sobre testing en JavaScript y las mejores prácticas
  • Si has hecho algún parser, ya sea de un lenguaje de programación o de algún otro lenguaje, te sonará, puede que hasta hayas usado, Lex y Yacc. Son dos herramientas clásicas de Unix como lo puede ser GCC, sin embargo hay gente que opina que ya es hora de dejar de usar Lex y Yacc.
  • Si buscas datasets para realizar Machine Learning seguramente conozcas Kaggle, o el repositorio UCI. Hace relativamente poco descubrí OpenML, una alternativa abierta muy interesante, con gran cantidad de datasets. Además, se integra con ciertas librerías como sklearn, que pueden descargar los datasets automáticamente de allí.
  • "Los astros definen tu futuro". La verdad es que yo no creo en la astrología, pero me hizo gracia esta web llamada Co-Star que te calcula con gran precisión tu carta astral. ¿Cómo lo descubrí? Resulta que esta web funciona en Haskell.
  • ¿Cómo deben ser los blogs de ingeniería de las empresas? Una respuesta interesante, basada en la experiencia en Cloudflare y otras empresas
  • En Python es importante que nuestro código pase por linters que nos avisen de posibles fallos, malas prácticas y de cuando nos desviamos del estilo (usualmente PEP8). Pero no existen herramientas estándar. Esta página recoge un listado de linters y sus ventajas e inconvenientes entre sí.
  • Por último, un vídeo de una charla sobre Prolog en Producción. Muy interesante para todo el mundo, aunque la disfrutarás más si ya tienes alguna noción básica de Prolog. Me ha dejado muchas ideas pendientes de probar.

Por último la canción del Teletexo de hoy es Living in the Plastic Age de The Buggles. La historia musical de este grupo y más en concreto de Trevor Horn es mucho más amplia que Video Killed the Radio Star y os invito a que investiguéis.

]]>
https://blog.adrianistan.eu/teletexto-005 Mon, 30 Mar 2020 21:56:18 +0000
Rustizando tu Linux https://blog.adrianistan.eu/rustizando-linux https://blog.adrianistan.eu/rustizando-linux Bienvenidos al primer artículo de Adrianistán desde la instauración del estado de alarma, desde aquí mando un reconocimiento a todas aquellas personas que trabajan en esta crisis que esperemos acabe pronto.

Hace unos días leí un tuit de Jesús Rubio que básicamente mostraba como había sustituido una cantidad importante de comandos típicos de Unix con variantes escritas en Rust. Rust es un lenguaje capaz de sustituir a C en cualquier sitio, sin penalización de rendimiento y con beneficios extra, aunque a la gente que se sienta cómoda con el funcionamiento de C les costará adaptarse a un lenguaje como Rust. Os recuerdo que tenéis un tutorial de Rust en este blog, útil para iniciarse.

En este post veremos algunas de estas herramientas y como se instalan, además de ver si son sustitutos capaces o no a las implementaciones clásicas.


La foto original de Jesús Rubio

ls -> exa

ls es el famoso comando Unix de listar un directorio. Es el equivalente a dir de MS-DOS. Cualquier persona que haya tocado la línea de comandos alguna vez lo conoce. exa es la versión hecha en Rust. Es compatible con la mayoría de opciones de ls aunque el creador admite que no es un reemplazo directo. La razón es que dice que exa tiene mejores opciones por defecto que ls, aunque eso implicase romper la compatibilidad.

Otras características de exa es que se adapta mejor a pantallas grandes, se integra con Git, puede mostrar los atributos extendidos de los ficheros (no solo los estandar), tiene colorines, puede seguir los enlaces simbólicos y mostrar vistas de árbol. Todo ello sin perder velocidad.

Unas cuantas distros como Fedora, Arch, Debian o Ubuntu ya lo empaquetan en sus últimas versiones, así que usa el gestor de paquetes nativo o Nix si prefieres.

En Debian o Ubuntu es tan simple como:


sudo apt install exa

A priori no parece haber mucha diferencia, aunque por debajo funcionan ya de forma diferente. exa hace más llamadas al sistema que ls, y esto es por diseño. ls en su día se diseñó para ordenadores lentos, exa se diseña para ordenadores modernos y por ello, intenta recabar más información. La diferencia, en los ordenadores actuales, de rendimiento es mínima.

Para consultar todas las opciones, revisar el manual o ejecutar la ayuda con --help

grep -> ripgrep (rg)

ripgrep es el reemplazo moderno a grep, la utilidad para buscar sobre texto. Posiblemente ya lo estés utilizando sin saberlo ya que Visual Studio Code lo utiliza para su función de búsqueda.

ripgrep se define como el programa que combina la usabilidad de The Silver Searcher con el rendimiento de grep. ripgrep es 100% Unicode, y tiene algunas características específicas para buscar código, como ignorar ficheros en .gitignore, filtrar búsquedas a ficheros de cierto lenguaje de programación, soporta las regex más potentes (PCRE2), permite buscar en ficheros comprimidos zip, bzip2, lzma, etc. La mala noticia es que debido a estas mejoras, ripgrep no cumple con el estándar POSIX, aunque la importancia de eso, en mi opinión, es debatible.

ripgrep ya está disponible en muchas distribuciones como Debian o Fedora. Una cosa a tener en cuenta es que aunque el programa se llama ripgrep, por comodidad el comando suele ser rg.


sudo apt install ripgrep

{cat, less} -> batcat

batcat es un clon de cat y otros programas más avanzados para mostrar texto como less o more. Soporta resaltado de sintaxis, integración con Git para mostrar cambios, permite mostrar caracteres no legibles, soporta búsquedas y mucho más.


Observad los símbolos + y ~ en el lateral, es la integración con Git

Está disponible en la mayoría de distribuciones, aunque en algunas como Debian el ejecutable no se llama bat sino batcat


sudo apt install bat

{ps, top} -> procs

procs es el sustituo de ps. Añade alguna información extra como los puertos TCP/UDP abiertos, métricas de entrada/salida, integración con Docker, además de modos de búsqueda mejorados y soporte para funcionar como sustituto de top.

Si ejecutamos procs sin ningún parámetro, entrará en un modo similar a top.

procs no está en ninguna distro pero se puede instalar via Snap o via Nix


sudo snap install procs
nix-env -i procs

nano -> kibi

kibi es un editor de texto para línea de comandos escrito en menos de 1024 líneas de código Rust. Si bien no es tan potente como Vim o Emacs, puede ser un buen sustituto a Nano. Soporta Unicode, búsquedas, resaltado de sintaxis, numeración, ...

Este programa es el más complejo de instalar, necesitarás tener Cargo instalado y ejecutar:


cargo install kibi

Y asegurarte de que el binario generado por Cargo se encuentra en el PATH. Además el resaltado de sintaxis no te funcionará porque no tienes los ficheros de configuración. Para seguir con esto, recomiendo ver el proyecto en GitHub.

{find, parallel} -> fd-find

fd-find es una alternativa moderna a find, la herramienta para buscar archivos y opcionalmente ejecutar comandos al encontrarlos, no compatible al 100% ya que al igual que exa, intenta mejorar las opciones por defecto. He de reconocer que find es uno de los programas que menos uso ya que me custa mucho acordarme de sus parámetros y que muchas veces no son coherentes con otras herramientas Unix.

Para ejecutar comandos en paralelo, por ejemplo, convertir todas las imágenes jpg en png (en paralelo):


fd -e jpg -x convert {} {.}.png

El comando fd está ya cogido en muchas distros, así que en Debian viene como fdfind. Se puede instalar desde apt.


sudo apt install fd-find

sh, bash, zsh, fish -> nu

Nu es una shell escrita en Rust que daría para artículos y artículos enteros. Todavía es algo inestable pero promete mucho. En Nu todos los comandos se comunican mediante tablas y formatos de datos estructurados, en lugar del texto plano tradicional de Unix. En ciertos momentos, parece que estamos ante una base de datos. Esta idea no es nueva, ciertos sistemas operativos minoritarios (IBM i) tienen conceptos similares, pero es bastante innovadora en Linux. La única shell para Linux de características similares es PowerShell de Microsoft.

Por ejemplo, de un simple ls (todavía no hay integración con exa) podemos filtrar con where, obtener solo los primeros campos, etc

Además Nu reconoce de forma nativa formatos como JSON, TOML, XML o YAML y permite recorrer sus campos. Por ejemplo, de un fichero de Rust Cargo.toml podemos acceder a la versión de una dependencia fácilmente con open y get.

Nu es en definitiva una shell muy prometedora y espero que siga mejorando en el futuro, aunque todavía no está lista para trabajar a diario con ella. También el soporte a scripts está un poco verde, pero ya se sabe que serán incompatibles con Bash.

Nu lo podéis instalar desde Nix o desde Cargo:


cargo install nu
nix-env -i nushell

Estas han sido solo algunas de la multitud de herramientas hechas en Rust que clonan y mejoran la funcionalidad de las herramientas clásicas de Unix. ¿Crees que llegarán a sustituir a las herramientas clásicas de Unix?

]]>
https://blog.adrianistan.eu/rustizando-linux Tue, 17 Mar 2020 14:36:18 +0000
Teletexto #004 https://blog.adrianistan.eu/teletexto-004 https://blog.adrianistan.eu/teletexto-004 Bienvenidos a una nueva edición de Teletexto, ya sabéis, esa sección donde recopilo enlaces de interés para vuestro uso y disfrute.

Para acabar una canción, en este caso un clásico de Frank T.

]]>
https://blog.adrianistan.eu/teletexto-004 Tue, 3 Mar 2020 22:33:44 +0000
Testing en lenguaje natural con Gherkin y Behave https://blog.adrianistan.eu/testing-lenguaje-natural-behave-python https://blog.adrianistan.eu/testing-lenguaje-natural-behave-python El testing es fundamental para asegurar que nuestras aplicaciones cumplen con unos requisitos de calidad mínimos. Existen muchos tipos de test y formas de hacer tests. En este artículo vamos a ver el Behavior Driven Development y como lo podemos aplicar en Python con Behave.

Behavior Driven Development

El BDD es una técnica que surge a raíz del TDD (Test Driven Development). La esencia del TDD es que tenemos que escribir los tests antes que el propio código. BDD va un paso más allá, tenemos que hacer tests que sirvan para describir las especificaciones en un lenguaje que pueda editar un no-programador. La idea es que pueda haber alguien de producto, que defina las especificaciones y que a su vez son tests de aceptación.

El lenguaje más usado en BDD es Gherkin, cuya base son las sentencias Given, When y Then. Una de las implementaciones de Gherkin en Python más usadas es Behave. Con Given debemos añadir todos los pasos necesarios para llegar hasta el punto donde queremos realizar el test. En When realizamos un estímulo, lo que se prueba. Idealmente sería una única sentencia. Con Then comprobamos que el resultado del estímulo es el esperado. Veamos un ejemplo. Todos ellos admiten combinaciones mediante And y But


Feature: comments

Scenario: add a comment in blog post
Given a post page is loaded
When I add a comment
Then the page is reloaded
And the page shows a confirmation message
And the comment is registered in the database

Scenario: approve comment
Given the comment is registered in the database
And I can't see the comment in the post page
When I approve the comment
Then I can see the comment in the post page

Cada paquete de Give/When/Then se denomina escenario y una feature o característica se compone de varios escenarios. Las features se guardan en un fichero .feature dentro de la carpeta features. Dentro de la carpeta features, si usamos Behave, tendremos una carpeta llamada steps que tendrá dentro código Python para implementar los tests correctamente. Para ello usamos el decorador step. Las funciones admiten un parámetro siempre, llamado context, que es propio de Behave y que puede usarse para mover estado entre steps.


from behave import *

@step("a post page is loaded")
def post_page_load(context):
    pass

@step("I add a comment")
def add_comment(context):
    pass

@step("the page is reloaded")
def page_reloaded(context):
    assert True is True

@step("the page shows a confirmation message")
def page_shows_confirmation_message(context):
    assert True

@step("the comment is registered in the database")
def comment_registered_in_database(context):
    assert not False

@step("I can't see the comment in the post page")
def cant_see_comment(context):
    assert True

@step("I can see the comment in the post page")
def can_see_comment(context):
    assert True

@step("I approve the comment")
def approve_comment(context):
    assert True

En este caso vamos a dejar los tests vacíos, pero aquí habría que implementar una lógica real de test, por ejemplo, con Selenium para interactuar con la web y comprobar que efectivamente la funcionalidad está implementada y funciona tal cual se ha especificado.

Para ejecutar los tests, escribimos behave

Variables y tablas

Estos steps deben describir en lenguaje natural las precondiciones, la interacción y las postcondiciones pero puede que queramos reutilizar steps o parametrizarlos. Para ello tenemos variables y tablas

Las variables se definen en el step rodeadas de llaves y en la función del step aparecen como una variable más en los argumentos de entrada. Por ejemplo podemos modificar el siguiente escenario para añadir un texto de ejemplo.


Scenario: add a comment in blog post
Given a post page is loaded
When I add a comment with text "your post looks insteresting"
Then the page is reloaded
And the page shows a confirmation message
And the comment is registered in the database with text "your post looks interesing"

Y en los steps:


@step("I add a comment with text \"{text}\"")
def add_comment(context, text):
    pass

@step("the comment is registered in the database with text \"{text}\"")
def comment_registered_in_database(context, text):
    assert not False

Sin embargo si nuestro texto es muy grande podemos añadirlo de forma adjunta y accesible a través de la variable context.text


Scenario: add a comment in blog post
Given a post page is loaded
    """
    Lorem Ipsum
    """
When I add a comment 
Then the page is reloaded
And the page shows a confirmation message
And the comment is registered in the database

@step("a post page is loaded")
def post_page_load(context):
    print(context.text)

También existen tablas, que son accesibles a través de la variable context.table


Scenario: add a comment in blog post
Given a post page is loaded
When I add a comment 
    | text | username |
    | Hey! | aarroyoc |
    | Bye! | marlogui |
Then the page is reloaded
And the page shows a confirmation message
And the comment is registered in the database with text "your post looks interesing"

@step("I add a comment")
def add_comment(context):
    for row in context.table:
        print(row["text"])

Environment, context, tags y hooks

Podemos controlar el entorno a través del fichero environment.py que tiene que estar en la carpeta features. En este fichero podemos definir hooks, que son funciones que se ejecutan antes o después de un determinado paso/escenario/feature. Pueden servirnos para realizar tareas de limpieza o de setup.


class WebClient:
    def __init__(self):
        pass
    def stop(self):
        pass

def before_all(context):
    context.client = WebClient()

def after_all(context):
    context.client.stop()

Los hooks disponibles son before_{all, step, scenario, feature} y after_{all, step, scenario, feature}.

Tanto en los hooks como en los steps vemos la variable context siempre. Se trata de una variable controlada por Behave que puede servirnos para pasar información entre los steps y los hooks. Podemos crear por ejemplo, un cliente web con un hook y luego acceder a él en el step gracias a la variable context. O entre distintos steps, pasar información más precisa que no se define en el fichero con Gherkin. Además, context tiene algunas variables predefinidas, como los tags.

Los tags son anotaciones que se ponen sobre los steps, escenarios o features. Si bien en general son texto libre, algunos tienen efecto sobre Behave


Feature: comments

@data-1
Scenario: add a comment in blog post
Given a post page is loaded
When I add a comment 
Then the page is reloaded
And the page shows a confirmation message
And the comment is registered in the database

@wip
Scenario: approve comment
Given the comment is registered in the database
And I can't see the comment in the post page
When I approve the comment
Then I can see the comment in the post page

En el primer caso de tag (data-1), es algo que me he inventado, en un hook podemos comprobar los tags y actuar en consecuencia. En este caso, este tag quiere decir que para este escenario carguemos en la base de datos el conjunto de prueba 1.


def before_scenario(context, scenario):
    if "data-1" in context.tags:
        # Load Data-1
        pass

En el segundo caso, el tag wip es reconocido por Behave y sirve para marcar tests que estamos probando. Estos los podemos ejecutar por separado indicando -w al llamar a Behave.


behave -w

¡Con esto ya tenemos lo suficiente para ir desarrollando tests al estilo BDD! Por supuesto, existen frameworks similares en otros lenguajes, como Cucumber en Java, pero dependiendo de los test que queramos hacer no será necesario usar el mismo lenguaje que la propia aplicación (podemos usarlo para testear desde fuera, por ejemplo, a través una API REST o desde una web).

]]>
https://blog.adrianistan.eu/testing-lenguaje-natural-behave-python Thu, 20 Feb 2020 17:05:26 +0000
Teletexto #003 https://blog.adrianistan.eu/teletexto-003 https://blog.adrianistan.eu/teletexto-003 Esta semana he estado en el evento WeCode pero aun así no podía olvidarme de vosotros, y he aquí un listado de enlaces interesantes como viene siendo habitual en Teletexto.


Mapa de Valladolid en 1943
  • Discord se mueve de Go a Rust (o mejor dicho, un servicio de los muchos que tienen) y en el artículo explican los motivos.
  • CSS es un lenguaje al que mucha gente le tiene un gran odio, ¿por qué es tan complicado se preguntan? Este artículo Old CSS, New CSS explica con bastantes detalles la historia de CSS y por qué se fueron tomando las decisiones que se tomaron hasta llegar a hoy día, donde CSS es una herramienta muy conveniente para casi todo.
  • Hablando de navegadores web, a partir de ahora, Microsoft Edge usará el mismo motor que Chrome. Eso supone el fin de la era de los motores Trident (y sus spin-off Tasman y EdgeHTML). En Hoy la era Trident acaba hacen un repaso por algunas de las tecnologías más innovadoras que tenía Trident. He de decir que estoy muy sorprendido por la potencia de muchas de sus propuestas, aunque luego no llegasen a ser usadas masivamente.
  • ¿Te aburre tu música habitual? Date una vuelta por Forgotify y escucha las canciones más olvidadas dentro de la inmensidad del catálogo de Spotify.
  • Aquí servidor usa GNOME. Aunque en general me siento cómodo, no podría estar más de acuerdo con las mejoras que propone Jattan
  • ¿Os gusta escribir bien? Para escribir en inglés existe una famosísima extensión llamada Grammarly, pero para una lengua con tantos hablantes como el español no había nada. Hasta ahora, LorcaEditor es contraparte hispanohablante, y aunque es mucho más básico y sencillo, estoy seguro de que mejorará.
  • Ha salido Pandas 1.0, la librería de tratamiento de datos más popular para Python. Aquí el changelog oficial y aquí un artículo explicativo.
  • Durante la Segunda Guerra Mundial, EEUU sospechaba que España podía unirse a la contienda en cualquier momento, por eso realizó mapas de las principales ciudades por si era necesario atacarlas. Estos mapas, realizados en 1943, son una maravilla para explorar las ciudades en aquella época. Aquí el listado de mapas.
  • La teoría de categorías se ha vuelto famosa, y en este artículo de El País, hablan sobre qué es.
  • Haskell es un lenguaje nacido en 1994 (un año antes que Java) pero quiere seguir estando a la vanguardia de lenguajes de investigación y experimentación. En Haskell problems for a new decade hablan justamente de los retos que tiene Haskell para la nueva década.
  • ¿El rectángulo cordobés? El rectángulo cordobés
  • ¿Ruby es lento? Probablemente, pero solo representa el 15% de los costes operaciones de Basecamp. Si quisieras optimizar estos costes, deberías apuntar a otros problemas antes.
  • Schema.org, una de las ontologías más populares ha lanzado la versión 6.0, cambios aquí.
  • ¿Alguna vez has tenido curiosidad por Smalltalk? En este vídeo de 7 minutos, se ve un flujo de trabajo con Smalltalk para no iniciados.
  • Google domina el mercado de las búsquedas, con Bing, Cliqz, Yandex y Baidu como motores minoritarios o regionales. ¿Qué pasa si queremos escapas de esto? Una solución muy interesante es YaCy, un motor de búsqueda descentralizado, que funciona gracias a la capacidad de cómputo combinada de toda la red.
  • Un podcast, en concreto Undefined hablando de testing.

La canción de este Teletexto es instrumental pero un maravillosa

]]>
https://blog.adrianistan.eu/teletexto-003 Sun, 9 Feb 2020 12:16:48 +0000
Desplegar una página estática con GitHub Actions y Netlify https://blog.adrianistan.eu/desplegar-web-estatica-github-netlify https://blog.adrianistan.eu/desplegar-web-estatica-github-netlify Hace unos días desplegué una nueva versión de mi página de inicio: https://adrianistan.eu. Se trata de un sitio estática, hecha con Lektor, alojado en Netlify y que gracias a GitHub Actions se publica una nueva versión cada vez que hay un commit nuevo. En este post explicaré como construí el despliegue continuo para que tú también puedas hacerlo.

Herramientas

El sitio web lo construí con Lektor, un generador de sitios estáticos hecho en Python por el creador de Flask, que cuenta con un panel de administración estilo WordPress para poder editarlo. Es de los mejores generadores estáticos que he probado, ya que personalmente se ajusta a muchas de mis preferencias personales, más que Hugo o Jekyll.

Usaré Netlify para alojar mi paǵina, es un servicio de una calidad excelente, con una capa gratuita muy generosa. Otras alternativas eran Amazon S3, Firebase Hosting, Zeit, GitHub Pages, GitLab, ... pero las descarté porque además ya uso Netlify en otros proyectos.

Para alojar el código usaré GitHub, mi opción por defecto, y usaré el nuevo servicio de GitHub Actions para realizar la compilación del sitio y subirlo a Netlify

Entre medias usaré Docker para poder probar en mi ordenador comportamientos muy similares a los que obtendremos en GitHub Actions.

Netlify

De la página de Netlify necesitamos dos variables: el Site ID y un token para poder desplegar. El token lo podemos generar en los ajustes de usuario, en la sección de Personal Access Tokens.

Este token lo almacenamos en GitHub, yendo a Ajustes y desde allí a la sección de Secretos. Lo llamamos NETLIFY_AUTH_TOKEN.

Para obtener el Site ID, necesitamos crear un sitio en Netlify (sube cualquier cosa para que se cree) y copia el Site ID desde la pantalla de ajustes generales. Estos Site ID son públicos, así que los podemos dejar en el código fuente.

Ya hemos acabado con Netlify. Desde Netlify también podemos configurar las DNS y HTTPS pero eso es algo sencillo, que veréis en los ajustes como hacer.

Docker

Vamos a crear una imagen con el entorno de ejecución que tiene todo lo necesario para construir y publicar la página. Usaremos docker-compose para definir dos comandos: los dos operando sobre la carpeta del proyecto, builder y publisher. El fichero Dockerfile es el siguiente:


FROM debian:buster

RUN apt-get update && apt-get install -y python3.7-dev python3-pip nodejs npm && \
    rm -rf /var/lib/apt/lists/*

COPY requirements.txt /tmp/requirements.txt
RUN pip3 install -r /tmp/requirements.txt
RUN npm install -g netlify-cli@2.30.0
WORKDIR /opt/adrianistan.eu

Partimos de Debian Buster e instalamos Python 3.7 y Node.js (que en Buster es la versión 10, suficiente por ahora). Para instalar los paquetes de Python usamos un fichero requirements.txt usado por Pip


Lektor==3.1.3

Creamos además un fichero docker-compose.yml para guardar los comandos.


version: "3.6"
services:
  builder:
    build: .
    command: lektor build -O out
    volumes:
    - ./:/opt/adrianistan.eu
  publisher:
    build: .
    command: netlify deploy --dir=out --prod
    environment: 
      NETLIFY_AUTH_TOKEN:
      NETLIFY_SITE_ID: 76f0a0da-c560-431e-a5c2-743809bf96e0
    volumes:
    - ./:/opt/adrianistan.eu

Aquí es importante el tema de las variables de entorno. NETLIFY_AUTH_TOKEN hay que dejarlo vacío para que Docker coja la variable del exterior, mientras que NETLIFY_SITE_ID lo podemos poner ya que es un dato público. Los dos comandos operan sobre la misma imagen y sobre los mismos volúmenes, que es la carpeta sobre la que está.

Podemos probar que funciona de forma sencilla:


docker-compose build
docker-compose run builder
NETLIFY_AUTH_TOKEN=XXXXXXX docker-compose run publisher

Y debería desplegar correctamente la página sin pedirnos nada

GitHub Actions

Para crear una acción en GitHub necesitamos crear un archivo en la carpeta .github/workflows, de tipo YAML. Las GitHub Actions definen unos parámetros para saber cuando se inician y uno o más jobs. Cada job puede tener su vez varios steps. Los jobs son paralelos, los steps son secuenciales. Cada job define además una imagen base, nosotros usaremos Ubuntu 18.04, pero nos da un poco igual mientras tenga Docker.


name: Deploy
on: [push]
jobs:
  build:
    runs-on: ubuntu-18.04
    steps:
    - name: Checkout
      uses: actions/checkout@v1
    - name: Build Docker images
      run: docker-compose build
    - name: Build website
      run: docker-compose run builder
    - name: Deploy website to Netlify
      run: docker-compose run publisher
      env:
        NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}

El primer paso es obtener el código, después repetimos los mismos comandos que en local, debidamente separados en steps diferentes. Para definir variables de entorno usamos env y para acceder a los secretos que almacena GitHub accedemos al objeto global secrets.

Y ya estaría. Ahora cada vez que subamos un cambio, GitHub arrancará la acción, que desplegará en Netlify una nueva versión de la página

Lo bueno de este sistema es que es muy personalizable. Si no usas Lektor y usas Hugo, Jekyll o Gatsby, puedes cambiar simplemente la imagen Docker y los comandos en docker-compose y seguirá funcionando. Incluso puedes hacer virguerías mucho mayores. También hay gente que en vez de usar Docker usa Nix con el mismo propósito: tener todo definido en archivos comunes compartidos entre desarrollo y GitHub Actions. Sin embargo, Nix me daba muchos problemas para instalar el paquete npm de Netlify y en general Nix no viene instalado en las imágenes de GitHub Actions, así que sería un paso extra.

Hay gente que dirá que para qué usar Docker si puedes instalarlo con comandos similares directamente en el fichero de workflow de GitHub Actions. Y la diferencia es que tu sistema y GitHub Actions van a tener comportamientos más separados. Puede que nunca falle, pero puede que sí, ya que estás trabajando con dos entornos diferentes y que pueden evolucionar por caminos diferentes. Por eso recomiendo usar Docker o Nix o algo que nos permita tener entornos iguales tanto en el desarrollo como el despliegue.

Un paso extra que podríamos añadir sería subir la imagen de Docker a un registro, y usar esa imagen en nuestro docker-compose.yml, en vez de generarla cada vez. De este modo sí que sí, todas nuestras nuestras builds usasen el mismo exacto entorno, inmutable, y si quisiéramos cambiarlo deberíamos generar una nueva imagen y subirla al registro de nuevo

]]>
https://blog.adrianistan.eu/desplegar-web-estatica-github-netlify Fri, 31 Jan 2020 20:15:40 +0000
Primeros pasos con Nix: un Linux más funcional https://blog.adrianistan.eu/primeros-pasos-nix-linux-funcional https://blog.adrianistan.eu/primeros-pasos-nix-linux-funcional Como ya sabéis, me encanta experimentar cosas alternativas dentro de la informática. Cosas que quizá no sean populares ni vayan a serlo, pero aportan un punto de vista diferente. Es por eso que en este blog se habla de Prolog, hablamos de web semántica, de Rust y se habla de Haiku. Porque para ver el típico framework hecho en Java ya tienes cientos de blogs por ahí. En este blog intento hablar de cosas de las que poca gente habla. Hoy nos toca hablar de Nix, un gestor de paquetes funcional, y base de NixOS, una distribución Linux muy interesante. Sin embargo aquí solo nos centraremos en Nix.

Nix es un gestor de paquetes, al igual que pacman en Arch Linux, apt en Debian o yum en RedHat. Si solo fuese un sistema más, no tendría interés, no solo hay estos tres, hay muchos más. Pero Nix ofrece algo diferente. Nix es funcional. Nix no mantiene un estado global del sistema y nos permite describir entornos de forma declarativa. Nix permite hacer rollbacks instantáneos (incluso varias versiones). Nix es además un lenguaje de programación, similar a Haskell (perezoso, puro, con currificación, pero con tipado dinámico). Nix permite tener instaladas a la vez varias versiones de un mismo paquete, sin provocar conflictos. En la gran mayoría de sistemas de paquetes, el funcionamiento es sencillo: se desempaqueta, reemplazando los ficheros si los hubiese, dentro del sistema de carpetas global. En Nix, se gestiona un PATH de forma dinámica que nos permite acceder a los paquetes residentes en /nix. Pero no nos adelantemos, vayamos poco a poco.

Instalando Nix

Hay dos formas de instalar Nix. Una es instalarse NixOS, la distro diseñada para trabajar con Nix de forma nativa. Pero también podemos instalar Nix en cualquier otra distro Linux y en macOS. Para ello deberemos ejecutar el siguiente comando:


curl https://nixos.org/nix/install | sh

Esto nos instalará Nix para nuestro usuario, aunque necesitará permisos de root. Por defecto crea un perfil. En Nix podemos tener infinitos perfiles. Cada perfil es un entorno, con paquetes diferentes que no interfieren entre sí. Podemos pensar en ello como si fuesen contenedores o máquinas virtuales, pero en realidad son simplemente colecciones de enlaces simbólicos al Nix Store, la carpeta donde se guardan todos los paquetes (por defecto /nix/store). Cada perfil carga ciertos programas en el PATH, así como sus librerías.

Para cargar un perfil y usarlo desde Bash/Zsh tendremos que ejecutar el script nix.sh correspondiente:


source ~/.nix-profile/etc/profile.d/nix.sh

También lo podemos añadir a nuestro fichero .bashrc o equivalente para que no tengamos que hacerlo manualmente

.

Instalar, borrar y actualizar paquetes

El comando principal para manipular los paquetes dentro de los perfiles es nix-env. Para ver los paquetes que tenemos disponibles para instalar ejecutamos:


nix-env -qa

Para acordarse mejor, qa significa "query all". Si queremos ver los paquetes que tenemos instalados en el perfil, podemos ejecutar solamente


nix-env -q

Que es simplemente query. Para filtrar los paquetes podemos usar expresiones regulares estándar. Por ejemplo:


nix-env -qa "firefox.*"

Busca todos los paquetes disponibles que empiezen por firefox. Para instalar paquetes tenemos el flag i, de install. Vamos a instalar cowsay.


nix-env -i cowsay

Al instalar descarga el paquete y sus dependencias si no existiesen ya, las deja en el Nix Store y genera los enlaces simbólicos para que sean accesibles desde el perfil actual. Aquí pueden surgir varias dudas.

¿Si ya existe un paquete con otra versión que ocurre? Nix es quizá el gestor de paquetes más inteligentes al respecto. Si tienes ya descargada la misma versión exacta debido a otro paquete, se reutiliza, aprovechando el espacio. Sin embargo, Nix también quiere garantizar el funcionamiento de los programas y el determinismo de cuando se construyó el paquete, así que cuando las versiones divergen, se instalan ambas en paralelo. ¡Y ambas versiones de la librería funcionan a la vez en el mismo perfil! Esto se consigue gracias a la forma en la que se construyen los paquetes, que veremos más adelante.

¿Qué es el fichero drv? Es el fichero que almacena una derivación. Cuando realizamos modificaciones de los paquetes en Nix, estamos haciendo derivaciones. Esto nos facilita poder revertir los cambios y hace muy difícil que Nix se corrompa.

¿De dónde se descargan los paquetes? Los repositorios en Nix se llaman channels. Por defecto se usa nixpkg-unstable como repositorio, que es de tipo rolling release, disponiendo de los últimos paquetes siempre. Existen otros, no obstante, principalmente usados por NixOS. Los canales son en principio repositorios de código Nix, que dicen como compilar los paquetes. En la mayoría de los paquetes existen ya versiones compiladas, pero si no las hubiera la instalación procedería a compilar de cero el paquete.

Vamos a usar el programa

Ahora vamos a quitar el programa. Podemos hacer dos cosas. Podemos hacer rollback y restaurar la versión anterior del perfil o podemos realizar una operación de borrado (que genera una nueva derivación).

Para borrar, generando una nueva derivación:


nix-env -e cowsay

Para hacer rollback, podemos ejecutar:


nix-env --rollback

Que nos sirve para ir a la versión inmediatamente anterior. También podemos listar las generaciones y saltar a una de ellas


nix-env --list-generations
nix-env -G NUMERO_GENERATION

Cuando borramos o actualizamos un paquete, no se elimina nada, todo se mantiene, de esta forma podemos hacer rollbacks instantáneos. Pero una vez ya estamos seguros de que no necesitamos ese paquete por si acaso, podemos limpiar las generaciones antiguas donde se usaba. Para ello debemos ejecutar periódicamente el siguiente comando:


nix-collect-garbage
nix-collect-garbage -d

La segunda opción es más agresiva y perderemos la habilidad absoluta de hacer rollback, dejándonos solo con la última generación.

Para actualizar los paquetes usamos este comando:


nix-env -u

Gestionando canales

Los repositorios en Nix se llaman canales. Todas las operaciones con respecto a los canales se realizan con el comando nix-channel. Podemos ver los canales que tenemos con:


nix-channel --list

El canal nixpkgs-unstable es el que podemos encontrar en GitHub: https://github.com/NixOS/nixpkgs/.

Podemos actualizar los canales con update:


nix-channel --update

Para añadir un canal usamos add


nix-channel --add https://nixos.org/channels/channel-name nixos 

El lenguaje Nix

Nix es realmente un lenguaje de programación funcional, especialmente diseñado para manejar la paquetería del sistema, pero completo. Es similar a Haskell, aunque sin tipado estático. No voy a entrar en muchos detalles de este lenguaje, pero vamos a ver algunas cosillas que serán útiles para construir paquetes y manejar ficheros .nix

Podemos acceder a un REPL de Nix escribiendo


nix repl

En Nix todo son expresiones, que se evalúan de forma perezosa como en Haskell. La artimética es sencilla, con la única diferencia de que hay que hay que tener cuidado con la división. El símbolo / significa ruta de archivo, no división. Hay que usar la función builtins.div para dividir.

También existen listas, strings y attribute sets, que son como diccionarios. Para asignar variables usamos la sintaxis let/in, que también existe en Haskell. Básicamente, on let definimos las variables y en in la expresión que las usa.

Para acceder a los atributos de los attribute sets podemos usar la sintaxis punto o usar with para no tener que escribir. Así, estas dos líneas son equivalentes.

En Nix también hay sentencias if, pero al ser expresiones, siempre deben retornar un valor, el else es obligatorio siempre

Las funciones en Nix son anónimas, pero podemos almacenarlas en variables. Admiten varios argumentos, aunque si vienes de Haskell, que sepas que Nix también realiza currificación. Las funciones de Nix también admiten parámetros que sean un attribute set, y los puede descomponer directamente. Además esto nos permite definir argumentos opcionales.

Lo último que debemos saber del lenguaje es el uso de import, que sirve para cargar la expresión resultado de un archivo en otro

Creando paquetes

Una vez conocemos el lenguaje Nix, no es evidente como se crean paquetes. Existen dos formas básicas de crear paquetes: in-tree y out-tree. Los paquetes in-tree son los que componen nixpkgs, y básicamente cada paquete se define por una función que es llamada desde un punto de entrada principal, que es el índice de paquetes. Si queremos subir un paquete a nixpkgs deberemos hacerlos así. Sin embargo, si queremos empaquetar algo que no queremos que llegue a nixpkgs debemos hacerlo de forma que sea independiente, aunque usando algunas funciones de nixpkgs.

Lo primero que vamos a hacer es importar nixpkgs, para poder usar sus utilidades. La mayoría de paquetes se beneficiarán de las funciones prediseñadas. Stdenv tiene funcionalidad genérica, pero útil para programas en C/C++. Para otros lenguajes podemos usar este mismo módulo u otros más específicos (de Rust, de Haskell, ...). La expresión es una llamada a mkDerivation, con ciertos datos, entre ellos un builder, que será un script que construirá la aplicación, el origen de los datos (función fetchurl normalmente) y algunas dependencias, con buildInputs. Podemos pasar cualquier variable de Nix a Bash, simplemente añadiendo más variables a mkDerivation.


let 
    nixpkgs = import  {} ;
in
nixpkgs.stdenv.mkDerivation {
    name = "pcc-1.0.0" ;
    builder = ./builder.sh ;
    src = nixpkgs.fetchurl {
        url = https://github.com/aarroyoc/pcc/archive/3f90d424494f4d1971ea34e66883fdee8a587b1f.zip;
        sha256 = "ec80f0c8af5dc9d6f0fbb691a4132ac8d44e42dd05865e23c80c2e0f0219d56f";
    };
    buildInputs = [ nixpkgs.unzip nixpkgs.bison nixpkgs.flex];
}

o si usamos with para simplificar


with import  {};

stdenv.mkDerivation {
  name = "pcc-1.0.0";
  builder = ./builder.sh;
  src = fetchurl {
    url = https://github.com/aarroyoc/pcc/archive/3f90d424494f4d1971ea34e66883fdee8a587b1f.zip;
    sha256 = "ec80f0c8af5dc9d6f0fbb691a4132ac8d44e42dd05865e23c80c2e0f0219d56f";
  };
  buildInputs = [ unzip bison flex ];
}

Y el builder.sh contiene:


source $stdenv/setup

unzip $src
cd pcc-*
make
mkdir -p $out/bin
cp pcc $out/bin/

Dentro del script hay que cumplir varias normas. La primera línea, si usamos stdenv, debe ser llamar al setup. La variable src es la que hemos definido como resultado de fetchurl y la variable out representa la estructura que va a tener el paquete.

Con Nix podemos importar otros paquetes como base y personalizarlos obteniendo así paquetes personalizados de forma sencilla y reproducible.

Podemos compilar el paquete con nix-build


nix-build pcc.nix

E instalarlo


nix-env -f pcc.nix -i pcc

Lo que hacemos con nix-env es indicar que use otro archivo nix en vez de nixpkgs y de él, instale pcc.

Construyendo entornos

Con Nix también podemos construir entornos que tengan ciertos paquetes cargados, ideal para documentar el software exacto necesario para trabajar y desplegar programas. Para ello usaremos mkShell


let
  pkgs = import  {};
in

pkgs.mkShell {
    name = "python-datascience";
    buildInputs = with pkgs; [
        python38
        python38Packages.numpy
        python38Packages.scikitlearn
        python38Packages.scipy
        python38Packages.matplotlib
    ];
}

Y lo cargamos


nix-shell datascience.nix

Y ya tendríamos un entorno para ejecutar nuestros scripts de NumPy, SciPy y Sklearn. Estos shells se construyen como si fuesen paquetes y también se pueden distribuir. Si faltase algún paquete de Python, podríamos añadirlo fácilmente a Nix. Por ejemplo, suponiendo que no tuviésemos paquete para requests, usaríamos las funciones de Nix para Python


let
  pkgs = import  {};
  requests = pkgs.python38.pkgs.buildPythonPackage rec {
      pname = "requests";
      version = "2.22.0";
      src = pkgs.python38.pkgs.fetchPypi {
          inherit pname version;
          sha256 = "1d5ybh11jr5sm7xp6mz8fyc7vrp4syifds91m7sj60xalal0gq0i";
      };
      doCheck = false;
      buildInputs = with pkgs; [
          python38
          python38Packages.chardet
          python38Packages.idna
          python38Packages.urllib3
      ];
  };
in

pkgs.mkShell {
    name = "python-datascience";
    buildInputs = with pkgs; [
        python38
        python38Packages.numpy
        python38Packages.scikitlearn
        python38Packages.scipy
        python38Packages.matplotlib
        requests
    ];
}

Conclusión

Nix es un gestor de paquetes muy potente, ofrece mejoras respecto a apt, yum, dnf, etc y se acerca mucho a Docker y otros sistemas modernos. Es puramente funcional, que se refleja en una curva de aprendizaje inicial más elevada. A priori no parece que ninguna gran distro que quiera pasarse a Nix, pero se puede instalar de forma paralela en tu Linux o Mac. Si quieres ir un paso más allá, NixOS utiliza el lenguaje Nix para todavía más partes de la administración del sistema y existen proyectos como home-manager que te permiten configurar entornos completos en Nix de una forma más extensa que solo decidir los paquetes que va a disponer.

]]>
https://blog.adrianistan.eu/primeros-pasos-nix-linux-funcional Sat, 25 Jan 2020 19:36:28 +0000
Teletexto #002 https://blog.adrianistan.eu/teletexto-002 https://blog.adrianistan.eu/teletexto-002 Bienaventurados todos aquellos lectores del Teletexto. En esta segunda edición voy a seguir repasando algunos proyectos, artículos y noticias que me parecen de interés.

 

  • Esta semana ha sido triste para el mundo Rust. La librería actix-web, una de las más usadas para construir aplicaciones web fue declarada muerta y retirada de forma repentina por su creador, cansado de cientos de críticas hacia su librería que construía en sus ratos libres. Reflexiona sobre ello Steve Klabnik en A sad day for Rust.
  • ¿No sabes que es DevOps? ¿Quieres informarte un poco sobre el tema? En el podcast número 8 de Undefined hablamos de DevOps (sí, yo incluido)
  • Las falacias son las trampas en el mundo de la lógica. Parecen argumentos convincentes pero no lo son. En https://falacias.escepticos.es/ tienen un listado muy completo de estas falacias, explicadas de forma gráfica, con ejemplos y como rebatirlas.

  • ¿Son los SMS de confirmación seguros? Aunque muchas webs (en especial bancos) hayan pasado a usar estos servicios para conseguir un 2FA, hay muchos motivos para pensar que no son seguros. En https://www.issms2fasecure.com/ exponen los resultados de una investigación que confirmó que los SMS apenas suponían un problema para acceder a los sistemas.
  • ¿Os acordáis del Agromapa de Castilla y León? Pues hace ya un tiempo, datos.gob.es, el portal nacional, incluyó esta aplicación en su catálogo: https://datos.gob.es/es/aplicaciones/agromapa-de-castilla-y-leon
  • ¿Deberían llevar los blogs un apartado para comentarios? Aunque parece una característica imprescindible, hay gente que piensa que no, que los blogs son estrictamente personales y si alguien quiere responderte, pueden hacerlo también en su propio blog. Reflexión de Chris Done. Estad tranquilos, aunque me ha hecho meditarlo, en este blog se quedarán los comentarios.
  • Hubo un tiempo en el que el mundo open source no sabía que sistema de control de versiones usar. Llegó a haber tres alternativas muy decentes para intentar sustituir a Subversion: Mercurial, Git y Bazaar. Finalmente Git ha ganado aunque Mercurial sigue vivo en sitios como Facebook o Mozilla. Bazaar fue un proyecto de Canonical, usado principalmente en entornos cercanos a Ubuntu, y desde 2016 no ha tenido ninguna nueva versión. Finalmente ha habido un fork, llamado Breezy, que combina lo mejor de Bazaar con el soporte a Git de forma transparente. Es además la única forma de gestionar repositorios Bazaar con Python 3.
  • ¿Cómo salir de VIM? Solo respuestas incorrectas.
  • ¿Cómo construir buen software? Un artículo muy interesante sobre los errores que llevan al software a fracasar. Adoptar la mentalidad que propone sobre el software puede ser muy útil, que por otro lado, parece de perogrullo.
  • Ryan C Gordon es un desarrollador conocido entre otras cosas, por mantener la librería SDL y haber portado gran cantidad de juegos a Linux. Recibió dinero de Google, pero no quiso aceptarlo ppr la política de esta empresa ante los sindicatos. Decidió repartir su dinero (y el de gente que quisiera colaborar) en ayudar a proyectos pequeños. Lo ha llamado Icculus Microgrant 2019. Hay proyectos muy interesantes, aunque uno era muy conocido para mí, SWI Prolog.
  • Si en el anterior Teletexto presentaba TerminusDB, en este presento Dgraph, una base de datos de grafos (por tanto similar a Neo4J y también a TerminusDB). Usa un lenguaje de consulta similar a GraphQL.
  • Acabamos el Teletexto, con 12 consejos para escribir SQL libre de fallos y con más rendimiento.
  • Finalmente la canción de este Teletexto es Canción Consumo de Luis Eduardo Aute:

 

]]>
https://blog.adrianistan.eu/teletexto-002 Sun, 19 Jan 2020 20:12:38 +0000
Prolog DCG: gramáticas de clausula definida https://blog.adrianistan.eu/prolog-dcg-gramaticas-clausula-definida https://blog.adrianistan.eu/prolog-dcg-gramaticas-clausula-definida

Hoy, 14 de enero, es el WORLD LOGIC DAY. Para celebrarlo, una entrada que tenía ya escrita sobre nuestro lenguaje de programación lógico preferido: Prolog.

Una de las caracterśiticas más potentes de Prolog es su potencia para la manipulación simbólica. Uno de los primeros usos que se le dio fue la definición de gramáticas a través de cláusulas. Estas definiciones constan de una sintaxis especial, aunque no es más que azúcar sintáctico sobre el lenguaje basado en predicados y términos de siempre. Aquí veremos la potencia de estas gramáticas dentro de Prolog y los usos que se les puede llegar a dar, desde simplemente validar "listas" a construir compiladores de lenguajes de programación.

Pero vamos a ir poco a poco desgranando todo esto.

Gramáticas

En esencia un DCG es simple: es un conjunto de reglas que describen un tipo concreto de lista. Estas "reglas" en ciencias de la computación tienen un nombre especial: gramáticas. Las gramáticas nos permiten generar lenguajes, entendiendo lenguajes como cualquier conjunto de símbolos, uno detrás de otro (o sea, el español, Python o una imagen digital). Las gramáticas se dividen en varias categorías, según los lenguajes que son capaces de generar. Esta diferenciación viene de Noam Chomsky, que a parte de filósofo y activista, se le considera el padre de las gramáticas.

Las gramáticas regulares son las que generan lenguajes regulares. Los lenguajes regulares son aquellos que podemos analizar mediante expresiones regulares. Las expresiones regulares o Regex no dejan de ser una manera de definir gramáticas regulares. Los DCG de Prolog nos permiten llegar a las gramáticas dependientes de contexto, el último paso antes de llegar a las recursivamente enumerables que son prácticamente intratables. Sin embargo, para no complicarlo mucho, vamos a hacer los ejemplos de este post con gramáticas independientes de contexto. Otros programas capaces de llegar a este punto son el famoso programa de Unix Yacc (en GNU es Bison) y el famoso ANTLR. Además, prácticamente todos los lenguajes de programación están definidos por gramáticas independientes de contexto.

Una gramática muy simple sería por ejemplo, podría definir un lenguaje que es una cadena de x. Mientras el "código" sea una sucesión de x, es parte del lenguaje, pero cualquier otra cosa es un error. Conceptualmente lo podríamos definir así, siendo S una variable y x un término:


S -> .
S -> xS

Donde . significa "nada" o "cadena vacía". La gramática se define de forma recursiva. Dado una cadena de entrada S, se puede descomponer según la primera regla en cadena vacía o en un caracter x más S otra vez. Pongamos un ejemplo:

xx forma parte del lenguaje porque S = xx, podemos descomponerlo según la segunda regla en x(S=x). Hemos aceptado de este modo el primer caracter y nos sigue quedando una parte por analizar. Como ha aparecido una S nueva, volvemos a aplicar la segunda regla y obtenemos xx(S=). No hemos acabado, nos sigue quedando una S, pero al aplicar la primera regla ya nos quedamos sin variables y la gramática ha aceptado la cadena. Por tanto xx forma parte del lenguaje.

Esto se puede complicar mucho, he aquí una gramática algo más compleja:


Frase -> SujetoVerbo
Sujeto -> DeterminanteNombre
Determinante -> el
Nombre -> gato
Nombre -> perro
Verbo -> ladra
Verbo -> maulla

No es difícil comprobar que frases como "elgatoladra" o "elperromaulla" son válidas, pero "lavacaladra" no.

Vuelta a Prolog

Volvamos a Prolog con nuestro lenguaje X. La gramática la podemos expresar con las DCG de esta forma:


program --> [].
program --> [x], program.

Podemos usar el predicado phrase para comprobar si una cadena pertenece al lenguaje X:

Así xxxx forma parte del lenguaje y xxyx no forma parte. Prolog tiene la característica, de que si sobre algo no puede esclarecer si es cierto o falso, buscará la forma de satisfacer las condiciones. Podemos usar esto para ir generando todos los lenguajes posibles.

Parseando programas

Podemos usar las capacidades de Prolog para añadir variables que nos permitan realizar un parseo y obtener así una estructura de árbol del documento.

Para este ejemplo, primero vamos a definir un lenguaje de calculadora, que permita de momento solamente sumar.


program --> expr, oper, expr.
expr --> ['('], expr, oper, expr, [')'].
expr --> [N], {number(N)}.
oper --> [+].

La sintaxis con llaves nos permite llamar a predicados auxiliares para construir una variable. Esta gramática admite expresiones de este tipo: 45 + ((70+45)+56).

Ahora para parsear, añadimos variables a las DCG para ir guardando un árbol AST.


program(node(O, E1, E2)) --> expr(E1), oper(O), expr(E2).
expr(node(O, E1, E2)) --> ['('], expr(E1), oper(O), expr(E2), [')'].
expr(node(number, N)) --> [N], {number(N)}.
oper(+) --> [+].

Y vemos el resultado de parsear diferentes expresiones

Y ahora realizar un compilador/intérprete para este lenguaje es trivial.


program(node(O, E1, E2)) --> expr(E1), oper(O), expr(E2).
expr(node(O, E1, E2)) --> ['('], expr(E1), oper(O), expr(E2), [')'].
expr(node(number, N)) --> [N], {number(N)}.
oper(+) --> [+].

execute(node(number, Out), Out).
execute(node(+, E1, E2), Out) :-
    execute(E1, OutE1),
    execute(E2, OutE2),
    Out is OutE1 + OutE2.

execute(Program) :-
    phrase(program(Tree), Program),
    execute(Tree, Out),
    format(Out).

¡Y funciona correctamente!

Añadir el resto de operaciones (resta, multiplicación, división) queda en manos del lector. Por supuesto, para ejecutar los programas todavía tenemos que pasar una cadena de elementos, que en terminología de lenguajes se llaman tokens, pero eso se podría hacer con otra gramática DCG o de otra forma dependiendo del lenguaje (en el mundo Unix clásico se suele hacer con Lex, cuya versión en Linux suele ser Flex).

Como conclusión, hemos visto que la manipulación de símbolos de Prolog es potente y nos permite describir de forma corta y elegante, así como realizar un parseado de forma sencilla también.

]]>
https://blog.adrianistan.eu/prolog-dcg-gramaticas-clausula-definida Tue, 14 Jan 2020 13:02:47 +0000
Teletexto #001 https://blog.adrianistan.eu/teletexto-001 https://blog.adrianistan.eu/teletexto-001 Bienvenidos a una nueva sección del año 2020. Se trata de Teletexto, una sección donde recopilaré enlaces que considero interesantes junto con un pequeño texto. La idea de esta sección viene de que tengo un Trello con ideas gigante. Muchas de ellas no las voy a tocar, pero me gustaría mencionarlas en el blog, ya que me parecen cosas muy interesantes. Así pues, el contenido de esta sección será principalmente técnico, pero guiado por mi curiosidad y mi interés en las cosas que vaya viendo. Esta sección además me servirá para poder publicar un poco más rápido. Finalmente, se llama Teletexto, precisamente por ser una muy buena metáfora de lo que es: resúmenes cortos de información y tecnología algo ya viejuna, pero sumamente interesante. Y sin nada más que decir, empezamos la primera edición.

  • Impure Pics, esta curiosa página es un conjunto de memes e imágenes explicativas sobre el mundo de la programación funcional, Haskell, Scala, PureScript,... ¡Incluso tenemos un quiz para saber que tipo de typeclass somos! Muy recomendable su visita aunque no sepas de programación funcional.

  • ¿Alguna vez has pensado cuántas cosas pueden ir dentro de la etiqueta HEAD en HTML? https://htmlhead.dev/ tiene un listado muy completo de qué puede ir. Por supuesto, eso no quiere decir que tu página deba llevar todas ellas, pero siempre está bien darle un repaso.
  • Hace poco descubrí que existe un deporte llamado Combate Medieval. Al parecer, trata de recrear de forma lúdica y competitiva, el combate de la edad Media (pero sin morir claro). Hay incluso una liga nacional. Para más información, la web recoge las normas y principales clubs de España.
  • scikit-learn, sklearn para los amigos, es una de las mejores librerías de Machine Learning que existen en la actualidad, sobre todo si quieres probar cosas diferentes a redes neuronales (que también tiene, pero ahí no es tan buena) o quieres realizar operaciones de limpieza de datos, test, etc. Recientemente, han sacado la versión 0.22 que incluye, entre otras cosas:
    • Una nueva API de plotteado
    • Los algoritmos de ensemble: StackingClassifier y StackingRegressor
    • Permutation Importance
    • Soporte para valores desconocidos en el Gradient Boosting
    • Poda de árboles más completa
    • Mejoras en el módulo de OpenML
    • Y muchas más cosas
    • Lista de cambios de sklearn 0.22
  • Siguiendo con actualizaciones, Alpine Linux, una de las distros más importantes en el mundo cloud, ha lanzado la versión 3.11.0, abriendo la rama 3.11. Al poco publicaron la 3.11.2 por bugs iniciales, pero los cambios importantes son:
    • Linux 5.4
    • Soporte a Raspberry Pi 4
    • Soporte inicial a GNOME y KDE
    • Soporte a Vulkan
    • MinGW-w64 y Rust disponibles
    • Actualización genérica de paquetes
    • Python 2 está obsoleto. Se han borrado la mayoría de paquetes relativos a Python 2 aunque algunos todavía quedan en esta versión.
    • Lista de cambios de Alpine Linux 3.11
  • Otra actualización más, Django 3.0, ha salido con la principal novedad el soporte a ASGI, que es el equivalente a WSGI pero en entornos asíncronos. Esto es un cambio interno principalmente.
  • UNIX empieza a contar su tiempo interno desde el 1 de enero de 1970, una convención llamada UNIX Epoch, y que se usa en bastantes sitios. Pero en el sistema operativo OpenVMS no, se empieza a contar desde el 17 de noviembre de 1858. Los motivos son bastante interesantes y se pueden leer aquí.
  • Bases de datos hay muchas, y de tipos diferentes (no solo de tablas relacionales va a vivir el mundo). En este sector hubo algo de movimiento con la web semántica, y se diseñaron bases de datos especiales para esto. Sin embargo, no acabaron de despegar y su uso es muy minoritario. TerminusDB es una base de datos nueva, hecha en Prolog y que intenta dar una vuelta a esto volviendo a abrazar RDF y todo lo que conlleva. Es uno de mis descubrimientos personales del año pasado y les deseo lo mejor. En mi opinión creo que el enfoque que adopta no es el mejor, aunque es práctico. Por un lado los documentos (siempre en JSON-LD) se gestionan con una sencilla API REST y por otro lado las consultas se realizan con WOQL en vez de SPARQL.
  • Por último, acabamos con un enlace de sumo interés para aquellos que usen o vayan a usar PostgreSQL, y también recomendable a cualquier persona que toque SQL: Postgres Bits. Se trata de las diapositivas de una conferencia de Heroku sobre Postgres y nos cuenta 12 cosas que tiene Postgres que quizá no conozcamos. Empezamos por los WITH y los ARRAYs y acabando con UUIDs y HSTORE (el almacenamiento clave-valor de Postgres).

Para despedirnos del primer Teletexto vamos a poner una canción y también os pido que si habéis encontrado algo interesante, lo pongáis en los comentarios, y así el Teletexto será más interesante para todos.

]]>
https://blog.adrianistan.eu/teletexto-001 Wed, 8 Jan 2020 14:08:41 +0000
Diesel, un ORM para Rust https://blog.adrianistan.eu/diesel-orm-rust https://blog.adrianistan.eu/diesel-orm-rust Un tipo de librería muy popular en lenguajes dinámicos son los ORM. Los ORM son librerías que hacen de intermediario entre la base de datos y nuestra aplicación, permitiéndonos expresar en el lenguaje de programación deseado las estructuras y datos y procedimientos. Rust, a pesar de ser un lenguaje de programación estático, cuenta con un potente ORM gracias al sistema de macros. Se llama Diesel  y es compatible con PostgreSQL, SQLite y MySQL. Un ejemplo uso de Diesel es este mismo blog, que lo usa para almacenar posts y comentarios en una base de datos PostgreSQL.

Enfoque

Dentro de los ORM existen varios enfoques, por un lado están aquellos que lo único que hacen es hacer accesibles las estructuras de datos desde el lenguaje, realizando las conversiones de tipos. Estos ORM más básicos se suelen llamar data mappers. Diesel soporta esto, pero además soporta un enfoque donde las operaciones también se realizan en Rust y también podemos usar migraciones (a través de una CLI).

Personalmente no uso la CLI, pero para proyectos nuevos puede ser más que interesante. En este tutorial, intentaremos ver todas las formas de usar Diesel.

Creando el proyecto con migraciones

Lo primero será crear un proyecto Rust, con cargo new y añadir Diesel como dependencia en el fichero Cargo.toml. Todo el código lo tenéis aquí por si en algún momento necesitáis referencia. Será el momento de instalar diesel_cli y crear la base de datos.


cargo new diesel-sample
cargo install diesel_cli --no-default-features --features sqlite
diesel setup --database-url db.sqlite3

Se habrá generado un fichero diese.toml y una carpeta migrations vacía. Vamos a crear una migración nueva (init) en SQL.


diesel migration generate init

Habrá generado una carpeta dentro de migrations con dos ficheros up y down. Up sirve para hacer la migración, down para revertirla. El contenido de up.sql será el siguiente:


CREATE TABLE post (
	id INTEGER,
	title TEXT NOT NULL,
	content TEXT NOT NULL,
	PRIMARY KEY(id));

Y el de down.sql


DROP TABLE post;

Podemos ejecutar la migración con:


diesel migration run

Y deshacerla con


diesel migration redo

Además de eso se nos habrá generado un fichero llamado schema.rs, en el lugar especificado por el fichero diesel.toml (normalmente será en src). Este fichero sigue una estructura muy interesante y es el verdadero pegamento entre nuestra aplicación y la base de datos. Las migraciones lo único que hacen es ejecutar el SQL y tomar nota del resultado final para generar el fichero schema.rs, que contiene una macro.

Es decir, ahora podemos seguir, vayas a usar migraciones o no.

Schema.rs

Si usas migraciones no deberías editar este archivo a mano pero si no vas a usar migraciones (sistemas heredados o simplemente tienes otra forma de manejar el SQL) puedes crearlo y editarlo a mano. La sintaxis es sencilla:


table! {
    post (id) {
        id -> Integer,
        title -> Text,
        content -> Text,
    }
}

Por un lado hay un table! por cada tabla en la base de datos. Esta lleva el nombre de la tabla (Post) y su clave primaria (id), a continuación se define un listado de campos y su tipo. Estos tipos no son ni de Rust ni SQL, son tipos que define Diesel.

Otras macros que pueden aparecer en el fichero schema.rs son joinable y allow_tables_to_appear_in_same_query.

Joinable indica que las tablas están relacionadas entre sí y se puede hacer un JOIN. Así pues, en el caso de que hubiese comentarios en la base de datos, y estos tuvieran un campo post_id, lo podríamos relacionar así:


joinable!(comment -> post (post_id));

Normalmente siempre se usa juno con allow_tables_to_appear_in_same_query, que permite que en una misma query se pueda acceder a dos tablas:


allow_tables_to_appear_in_same_query!(post, comment);

Queryable

Ahora podemos definir estructuras en Rust que representen el resultado de una consulta a la base de datos, esto lo podemos hacer con Queryable. La estructura no tiene por qué ser idéntica a la tabla, incluso puede tener menos campos o más resultado de un JOIN. Pero es muy importante que el orden de los campos estructura. Si queremos despreocuparnos podemos usar QueryableByName, pero tendremos que indicar la tabla. Pongámoslo todo junto. Además, primero vamos a meter algunos datos en la base de datos:


sqlite3 db.sqlite3
> INSERT INTO post VALUES (1, "El Guardián entre el Centeno", "El libro de los psicópatas");
> INSERT INTO post VALUES (2, "On the road", "Esencia de la generación beat");

El código es el siguiente:


#[macro_use]
extern crate diesel;

use diesel::prelude::*;
use diesel::sqlite::SqliteConnection;
use self::schema::*;

mod schema;

#[derive(Queryable)]
struct Post{
    id: i32,
    title: String,
    content: String,
}

fn main() {
    let conn = SqliteConnection::establish("db.sqlite3").unwrap();

    let posts = post::table.load::<Post>(&conn).unwrap();
    for post in posts {
        println!("Title: {}\tContent: {}", post.title, post.content);
    }
}

Lo primero que hay que hacer es importar diesel con macros. Luego cargamos el preludio de Diesel, la conexión con Sqlite y nuestro esquema. Hay unos helpers disponibles bajo el submódulo dsl. Sin embargo, en mi experiencia, la colisión de nombres se vuelve un problema real y es mejor ser más verboso.

Esta selección es muy sencilla, veamos otra que tire de WHERE.

Para añadir condiciones usamos filter. Las condiciones tienen que escribirse con una API fluent, no se pueden usar condiciones igual que en Rust de momento. Por ejemplo, para pedir los posts con ID > 1:


    let posts = post::table
        .filter(post::id.gt(1))
        .load::<Post>(&conn)
        .unwrap();

Donde gt significa "greater than" y es el equivalente a >.

SELECT y JOIN

La API de Diesel admite más opciones por supuesto. Podemos elegir los campos que queremos que se carguen de la base de datos. Evidentemente, la estructura a la que carguemos los datos deberá soportarlo. De hecho, si solo seleccionamos una columna podremos ahorrarnos este paso e ir directamente con tipos primitivos.


    let posts = post::table
        .select(post::title)
        .load::<String>(&conn)
        .unwrap();
    for post_title in posts {
        println!("Title: {}", post_title);
    }

Para realizar JOIN, es necesario que las tablas estén relacionadas por joinable y una vez hecho eso, podemos hacer el join directamente, sin especificar IDs ni claves foráneas, simplemente agregando inner_join a la petición.

Insertable

¿Y si queremos insertar algo en la base de datos? Diesel nos ofrece Insertable, para poder volcar estructuras en las base de datos. Su uso es muy similar a Queryable pero añadiendo el nombre de la tabla. De hecho, en el ejemplo que voy a poner, PostInsert y Post podrían ser la misma estructura pero lo voy a separar. Al igual que en SQL, no tenemos que proveer todos los campos si el esquema lo permite. Añadimos los datos con insert_into.


#[derive(Insertable)]
#[table_name="post"]
struct PostInsert{
    id: i32,
    title: String,
    content: String,
}

...

let new_post = PostInsert {
        id: 5,
        title: "Test".to_string(),
        content: "Lorem Ipsum".to_string(),
    };

    diesel::insert_into(post::table)
        .values(&new_post)
        .execute(&conn);

SQL

Diesel nos permite escribir queries en SQL directamente también. Para ello tenemos sql_query. Las estructuras donde obtendremos la información deben tener QueryableByName, esto es debido a que Queryable hace la traducción simplemente por el orden de los elementos, QueryableByName la hace teniendo en cuenta el nombre de las columnas.


#[derive(QueryableByName)]
#[table_name="post"]
struct PostRaw {
    id: i32,
    title: String,
}
...
    let posts = diesel::sql_query("SELECT id,title FROM post WHERE id > 0").load::<Post>(&conn).unwrap();
    for post in posts {
        println!("Title: {}\tID: {}", post.title, post.id);
    }

De este modo, Diesel solo realiza las funciones de data mapper y podemos reusar nuestro código SQL o si preferimos SQL, podemos usarlo.

UPDATE y DELETE

Para realizar UPDATE o DELETE, tenemos que seleccionar primero las filas (con find o filter) y después ejecutar.

En el caso de UPDATE, usaremos set para ir seteando los nuevos valores. Para introducir el valor usamos eq (igual):


diesel::update(post::table.find(1))
        .set(post::title.eq("Título actualizado".to_string()))
        .execute(&conn);

Y el DELETE sería similar.


    diesel::delete(post::table.find(1))
        .execute(&conn);

Y con esto ya tendríamos las operaciones básicas de CRUD y alguna más implementadas con Diesel.

Con el paso del tiempo, Diesel ha resultado ser una de las librerías más interesantes del mundo Rust, una librería estable y que nos ahorra mucho trabajo y nos ayuda a encontrar errores. Todo el código del artículo está en GitHub.

]]>
https://blog.adrianistan.eu/diesel-orm-rust Fri, 3 Jan 2020 15:43:12 +0000
Yield, generadores y corrutinas en Python https://blog.adrianistan.eu/yield-generadores-python https://blog.adrianistan.eu/yield-generadores-python Estaba realizando uno de los problemas del Advent of Code 2019, cuando tuve la oportunidad de usar generadores y yield en Python, y para mi sorpresa, mucha gente los desconocía. Os pongo en situación. Si recordáis, el año pasado intenté publicar mis soluciones al Advent of Code comentadas aquí, aunque no logré acabar y sigo teniendo pendientes de hacer los últimos días de 2018. Este año lo he vuelto a intentar pero no he publicado nada por aquí, lo que considero que ha sido una buena decisión, ya que gastaba mucho tiempo y eran posts bastante densos de leer con poca utilidad. Sin embargo, he seguido comentando con compañeros, a través de Telegram sobre todo, diferentes soluciones. Ese día había que ejecutar 4 procesos de forma circular, uno detrás de otro pero manteniendo el estado en cada uno de ellos. Aunque existen otras formas válidas de resolverlo, considero que merece la pena echar un vistazo a los generadores y corrutinas de Python.

Empecemos por lo más sencillo de definir, un generador.

¿Qué es un generador?

Un generador es una función que permite ser pausada, devolviendo información, para posteriormente ser restaurada. En las paradas en el intercambio de datos solo se produce desde la corrutina, hacia la función que la llamó. De ahí viene su nombre: funciones "generadoras" porque van generan información.

Visto así puede incluso parecer parecido a la programación con hilos pero no os engañéis, todo esto sucede en un único hilo. Cuando se llega al punto de parada en el generador, definido dentro de esta, se guarda el estado de la función, variables locales dentro de ella y se devuelve algún dato a la función original. y se vuelve a la función original. Cuando se vuelve a restaurar la corrutina, se carga en memoria el estado de la función y se continúa hasta la siguiente parada.

Esto cambia un poco nuestra mentalidad, ya que normalmente se enseña que las funciones se llaman, con unos datos de entrada, y cuando acaban devuelven un valor.

 

Sintaxis de los generadores en Python

La palabra clave para implementar corrutinas es yield. yield hace una parada de la corrutina y devuelve un valor. Podemos pensar en yield como un return, pero sobre el que después sigue la función. Para controlar la corrutina disponemos de varios métodos. El primero, es usar for para ir llamando constántemente al generador.


def generator():
    yield "Hola"
    yield "Mundo"

def main():
    f = generator()
    for x in f:
        print(x)

main()

Obteniendo como resultado "Hola Mundo". Usar for es la mejor manera de controlar un generador cuando tenemos solamente uno. En general, los generadores también funcionarán en cualquier función que trabaje con iteradores, como itertools.takewhile.


from itertools import takewhile

def fib():
    a = 0
    b = 1
    while True:
        c = a+b
        a = b
        b = c
        yield c

def main():
    f = fib()
    a = takewhile(lambda x: x<100, f)
    print(list(a))

main()

Este ejemplo, almacena en una lista todos los números de la secuencia de Fibonacci menores que 100.

Otra forma de controlar los generadores es a través de la función next. Una llamada a next ejecuta el generador hasta la siguiente parada y devuelve el contenido de yield.


def gen():
    yield "Hola"
    yield "Mundo"

def main():
    f = gen()
    print(next(f))
    print(next(f))

main()

Es equivalente al Hola Mundo anterior hecho con for. Usar next es mucho más flexible pero es preferible usar for si podemos.

¿Qué es una corrutina?

Una corrutina es una función que se puede suspender su ejecución y posteriormente restaurarla. En estas paradas puede haber un intercambio de datos en ambos sentidos.

A diferencia de los generadores, aquí hay transmisión de información también de la función base a la corrutina. Esto se hace a través de la función send y el hecho de que yield devuelve valores.

Veamos este sencillo ejemplo.


def gen():
    x = yield "Hola"
    yield x

def main():
    f = gen()
    print(next(f))
    y = f.send("Mundo")
    print(y)

main()

Aquí podemos ver como el primer yield recibe un dato, que se envía a través de send. send es a su vez un next, así que el valor de vuelta de send es el de la siguiente parada.

En Python no llegaron las corrutinas puras hasta Python 3.3, sin embargo, eran casos muy excepcionales los que necesitaban la funcionalidad extra, el yield from. El yield from es una sintaxis que permite que la corrutina llame a otraa funciones y sean estas funciones las que se encarguen de hacer el yield. Si no lo indicásemos, las nuevas funciones serían simplemente corrutinas de las corrutinas, con yield from logramos eliminar eso.


def gen():
    yield from sub()

def sub():
    yield "Hola Mundo"

def main():
    f = gen()
    print(next(f))

main()

Los casos de uso de las corrutinas se solapan mucho con los de los objetos en el mundo OOP, ya que ambos mecanismos nos permiten mantener estado entre dos contextos. En el caso de los objetos, las llamadas a métodos permiten "restaurar" la ejecución, mientras que en las corrutinas, una llamada a next o send.

Los generadores y corrutinas existen en muchos lenguajes de corte imperativo como Python, Go o Lua y en otros de corte funcional como Scheme y Haskell.

¿Conocías los generadores y las corrutinas? ¿Los has usado alguna vez? Cuenta tu experiencia en los comentarios

 

]]>
https://blog.adrianistan.eu/yield-generadores-python Fri, 27 Dec 2019 16:19:59 +0000
Historia de la privacidad https://blog.adrianistan.eu/historia-privacidad https://blog.adrianistan.eu/historia-privacidad Últimamente se habla mucho de la privacidad. Si bien antes era algo anecdótico, tras la llegada de las redes sociales y los dispositivos móviles, la privacidad se ha convertido en un tema mucho más común. Más aún con los escándalos como el de Cambridge Analytica que obligaron a Mark Zuckerberg a declarar. Pero entonces me surgió una curiosidad. ¿De dónde sale la privacidad? ¿Los romanos tenían privacidad? ¿Por qué en la historia de la humanidad es un concepto que parece inexistente?

Lo dejé pasar, pero entonces, viendo un episodio de House (sí, ya sé, la serie acabó hace mucho, qué se le va a hacer), Taub dice en un momento: la privacidad es un invento moderno, las tribus antiguas no la tenían, ya que iba en contra de la propia supervivencia como grupo ocultarse información (o algo así). No sabía si esa afirmación era cierta y me puse a investigar.

Orígenes

Es difícil saber si en las primeras tribus de Homo Sapiens existían conceptos tales como privacidad o la intimidad. Es posible que en las tribus primigenias, no hubiese separación público-privada, ya que todo el grupo podría tratarse como una gran familia, donde todos los miembros se tratasen entre sí como iguales miembros de una tribu. Es plausible, pero no hay demasiadas evidencias, salvo el hecho de que posiblemenre viviesen en cuevas todos juntos.

Las primeras referencias a la privacidad llegan de manos de Aristóteles, filósofo griego, quien hablaba de esferas de la vida. La primera hacía referencia al ámbito público, la polis y se relacionaba con la vida política. La segunda hacía referencia al ámbito privado, la oikos y se relacionaba con la vida domeśtica. Estas son las primeras referencias claras que tenemos a una idea de privacidad.

Sin embargo, la idea de privacidad en el imaginario colectivo es difusa y muchas veces hace referencia a conceptos similares pero diferentes dependiendo del autor. John Stuart Mill hablaba en 1859 en su ensayo On Liberty de lo público/privado en relación al control gubernamental y a la auto regulación. Según él, es necesaria la privacidad para poder tener libertad y no ser títeres de quien gobernase.

En el plano antropológico, Margaret Mead descubrió que la privacidad, aunque expresada de formas diferentes, era un rasgo común en multitud de culturas. Alan Westin analizó comportamientos animales descubriendo que el deseo de privacidad no era único al Homo Sapiens, aunque también, se expresaba de formas diferentes.

Hasta este punto el concepto de privacidad puede referirse a varias ideas: la separación entre gobierno/individuo, conocimiento prohibido, confidencialidad o el mero hecho de la soledad.

Concepto moderno

Quizá el primer estudio sistemático sobre la privacidad sea el ensayo de Samuel Warren y Louis Brandeis de 1890, The Right to Privacy. Este ensayo es muy interesante, ya que parte de un contexto muy similar al actual y es que "debido a los cambios tecnológicos como la fotografía o los periódicos, es necesario defender este derecho implícito en nuestro modo de vida". Se propone por primera vez que haya regulaciones respecto a la privacidad, que en definitiva, siguen los principios asumidos pero no escritos, de gran parte de la población anglosajona en ese momento. La privacidad es invadida cuando se hace pública información relativa a la vida privada de una persona, por parte de un tercero. Se defiende también "el derecho a la soledad". La privacidad se justifica como un derivado del derecho a la personalidad, y que esta personalidad no pueda ser violada por otros individuos. Sin privacidad, argumentan, la personalidad no se puede mantener y esta es un elemento vital de cualquier ser humano.

Una vez sentada la base de la privacidad con Warren y Brandeis, poco a poco se irá incorporando a códigos y legislaciones, como un derecho más.

En 1948, aparecerá en la Declaración Universal de Derechos Humanos un artículo referente a la protección de la vida privada (artículo 12):

Nadie será objeto de injerencias arbitrarias en su vida privada, su familia, su domicilio o su correspondencia, ni de ataques a su honra o a su reputación. Toda persona tiene derecho a la protección de la ley contra tales injerencias o ataques.

En 1978, la Constitución Española se convertirá en una de las primeras constituciones en recoger este derecho, a través de su artículo 18:

1. Se garantiza el derecho al honor, a la intimidad personal y familiar y a la propia imagen.

2. El domicilio es inviolable. Ninguna entrada o registro podrá hacerse en el sin consentimiento del titular o resolución judicial, salvo en caso de flagrante delito.

3. Se garantiza el secreto de las comunicaciones y, en especial, de las postales, telegráficas y telefónicas, salvo resolución judicial.

4. La Ley limitará el uso de la informática para garantizar el honor y la intimidad personal y familiar de los ciudadanos y el pleno ejercicio de sus derechos.

Sin embargo, estos reconocimientos no han estado exentos de crítica desde el punto de vista filosófico.

Críticas a la privacidad

Existen cuatro vertientes críticas de la privacidad:

Reduccionismo de Thomson: La privacidad no es un derecho porque en realidad, todas las "invasiones" de la privacidad no son más que violaciones de otros derechos ya reconocidos. Por tanto, la privacidad no es un derecho en sí, sino una mezcolanza de situaciones en las que aplican otros derechos diferentes. El concepto de privacidad es por tanto redundante.

Crítica económica de Posner: La información protegida bajo la privacidad no es diferente a cualquier otro tipo de información. Además, al mantenerla privada, estamos en la mayoría de casos cometiendo ineficiencias en el sistema económico. Posner argumenta que el único sentido que tiene una información de ser privada es cuando, su valor económico es mayor al mantenerse en secreto.

Visión de Bork: para Bork la privacidad fue un derecho inventado, sin justificación aparente, ya que no se deriva de ninguna ley natural o de un derecho anterior preexistente. Es por ello que Bork argumenta que la privacidad como derecho, se basa en la tradición de ciertas jurisdicciones que han podido otorgarle el papel de pseudoderecho, pero realmente no lo es.

Crítica feminista: si bien existen muchos puntos de vista dentro de la filosofía feminista, una posición es bastante crítica. Esta argumenta que la privacidad no es más que un mecanismo que permite ocultar la represión y el abuso que sufren las mujeres en el entorno familiar. En un mundo sin privacidad, este tipo de actos no quedarían en el entorno meramente doméstico, que dejaría de existir, sino que serían públicos y serían más inusuales y más efectivamente penados cuando sucediesen. Otro enfoque apunta a que si bien esto es algo que sucede actualmente, un mundo sin privacidad completa también podría ser igual de perjudicial para las mujeres, aunque de otra forma, decidiendo por ejemplo en sus preferencias reproductivas.

Conclusiones

Después de analizar todo esto, lo primero que se nos puede venir a la cabeza es que la privacidad es un concepto poco definido, y a la vez, asumido como algo "evidente" en muchas culturas (en particular, la occidental que será la más próxima a los lectores de este blog). Es muy curiosa la historia que ha habido hasta llegar aquí y será interesante ver dentro de 50 años cómo evoluciona.

]]>
https://blog.adrianistan.eu/historia-privacidad Sun, 24 Nov 2019 23:28:42 +0000
Ponencia Linux y Tapas 2019: "Web 3 0: redes descentralizadas" https://blog.adrianistan.eu/linux-tapas-web-30-redes-descentralizadas https://blog.adrianistan.eu/linux-tapas-web-30-redes-descentralizadas El pasado 19 de octubre, tuvo lugar en la ciudad de León el evento Linux y Tapas 2019. Era mi primera vez en este evento y encima, además decidí dar una ponencia. El evento en sí me gustó mucho. Primero se queda en la catedral de León y luego nos dirigimos todos juntos al barrio húmedo a comer a base de tapas. Nunca había estado en el barrio húmedo y me sorprendió muy gratamente. Durante este tiempo puedes hablar con el resto de asistentes. Conocí a mucha gente interesante y pudimos hablar de gran cantidad de temas diferentes, eso sí, en su mayoría temas que no podrías sacar en conversaciones con tus amigos no técnicos.

Posteriormente fueron las charlas en la fundación Sierra Pambley, un sitio muy bien situado. Allí tuvimos charlas muy interesantes sobre la educación (hubo dos, aunque totalmente diferentes), sobre ciberseguridad y una sobre Raspberry Pi a cargo de El Atareao, a quién se le premió por su acitividad en las redes y en el software libre durante este tiempo. También estuvo mi charla, la cuál fue grabada y os dejo aquí para que la disfrutéis. Podéis descargar el fichero ODP en mi repositorio de presentaciones.

Una vez más, muchas gracias a todos los participantes y organizadores del evento. ¡Espero que nos veamos el año que viene!

Ver presentación en mi repositorio

]]>
https://blog.adrianistan.eu/linux-tapas-web-30-redes-descentralizadas Fri, 1 Nov 2019 16:54:42 +0000
Crónica Neuronal: matrices de expresión genética para leucemia https://blog.adrianistan.eu/cronica-neuornal-matrices-expresion-genetica-leucemia https://blog.adrianistan.eu/cronica-neuornal-matrices-expresion-genetica-leucemia Bienvenidos a un nuevo episodio de la serie Crónica Neuronal. Hoy vamos a tocar un problema del campo de la bioinformática. En concreto, vamos a usar matrices de expresión genética para identificar si un paciente de leucemia la tiene de tipo ALL o de tipo AML. Ambas leucemias tienen síntomas muy parecidos y es interesante poder encontrar un modelo de aprendizaje automático que pueda distinguirlas.

¿Qué es una matriz de expresión genética?

La expresión genética es el grado en que un gen se manifiesta en la formación de una proteína, que luego tiene efectos en el organismo. Este grado se mide a través de la presencia de mRNA, aunque no es exactamente proporcional, suele ser adecuado en muchas ocasiones.  

Una matriz de expresión genética no es más que un conjunto de muestras (samples), donde se analiza la expresión genética de muchos genes. Cada casilla representa la expresión genética de un gen en concreto en un individuo en particular. La idea es, con esta información, obtener ciertos patrones de genes que nos ayuden y nos den pistas en la investigación de enfermedades. También nos puede servir para diagnosticar a individuos nuevos enfermedades, así como su estado y poder así obtener mejores tratamientos.

En general, este tipo de datos son complejos de analizar por aprendizaje automático, ya que por lo general existen muy pocos individuos y muchos genes a tener en cuenta. Hay una historia, no demostrada todavía, relacionada con las farmacéuticas que son quienes generan estos ficheros, dice que estas no dan todos los datos que tienen para esforzar a los investigadores a trabajar y luego cuando publican resultados, estas farmacéuticas los pueden comprobar con mucha más facilidad. Un lugar de donde se pueden conseguir matrices de expresión genético es el archivo ArrayExpress mantenido por el European Bioinformatics Institute.

Vistazo a los datos

Para esta crónica neuronal, voy a usar Weka, el software de aprendizaje automático de la Universidad de Waikato (Nueva Zelanda). Es un programa hecho en Java que funciona en Windows, MacOS y Linux.

En este caso, el fichero de datos del que parto ya está en formato ARFF (el nativo de Weka, es similar a CSV pero hipervitaminado) y normalizados (escalado). Una inspección rápida nos hace ver que hay 7129 atributos diferentes, sin nombre, solo están numerados (son los genes). Hay una clase, de tipo binario: ALL y AML. Es decir, para cada paciente de leucemia se nos da su expresión genética de 7129 genes y se nos da su tipo de leucemia real. Solo tenemos 72 pacientes para entrenar.

Vamos a usar validación cruzada con 10 pliegues para el test de los modelos. Vamos a probar los siguientes algoritmos: J48, NaiveBayes, IBK1, Regresión Logística, Perceptrón Multicapa con una capa oculta de 10 neuronas y SVM con kernel lineal.

Si no has usado Weka antes, los pasos para realizar esto son realmente simples. Dirígete a la pestaña "Classify". Una vez allí clica sobre "Choose" y elige el algoritmo de entre las diferentes carpetas.

Si el algoritmo tuviese parámetros de ajuste, se haría clic sobre el nombre del algoritmo en negrita una vez seleccionado.

Pero en la gran mayoría de los algoritmos usaremos los ajustes por defecto. Los resultados son los siguientes:

Algoritmo Tasa de Acierto
J48 0.7916
NaiveBayes 0.9861
IBK1 0.8472
Regresión Logística 0.9027
MLP (H10) 0.9722
SVM (lineal) 0.9861

 

Los mejores resultados los obtienen SVM y NaiveBayes. Esto último nos puede decir que no hay muchos genes correlacionados, sino que son independientes entre sí. Regresión Logística y MLP han tardado considerablemente más que el resto.

Ahora vamos a ver qué atributos son más importantes. Es lógico pensar que muchos de estos atributos (genes) son superfluos, y no afectan en el diagnóstico del tipo de leucemia. 

A continuación vamos a analizar qué genes son más relevantes para la tarea de construir un clasificador entre los tipos de leucemia.

Para ello primero usaremos métodos de filtrado.

Filtrado

Los métodos de filtrado se basan en diferentes heurísticas que nos permiten seleccionar un conjunto de atributos relevantes. La mayoría de estas heurísticas elaboran un ránking, dentro de las cuáles podemos elegir el número de atributos que queramos. Los algoritmos para el filtrado que vamos a probar son: Incertidumbre simétrica, ReliefF, SVM recursivo y CfsSubset (este último no genera ranking).

Algoritmo

Atributos (4)

Incertidumbre simétrica

0.74 1834 attribute1834

0.74 4847 attribute4847

0.737 1882 attribute1882

0.734 3252 attribute3252

ReliefF

0.26503581944 3252 attribute3252

0.25909453333 4196 attribute4196

0.21482608611 1779 attribute1779

0.19528160278 4847 attribute4847

SVM

7129 1882 attribute1882

7128 1834 attribute1834

7127 1779 attribute1779

7126 1796 attribute1796

CfsSubset

No se pudo calcular

Si nos quedamos solo con cuatro atributos, los resultados son los siguientes. Estos resultados se obtienen en Weka en la pestaña de Selección de Atributos. Eligiendo el algoritmo y un método de búsqueda (Ranker, o en su defecto, GreedyStepwise).

Podemos repetir el procedimiento con 8, 16 y 32 atributos. No os voy a poner los atributos, pero sí vamos a ver como se comportan.

Vemos que en general hay genes que se repiten entre métodos, como el 1779 o el 4847. Estos genes pueden ser determinantes para diagnosticar los diferentes tipos de leucemia.

 

J48

NaiveBayes

IBK1

Reg. Log

MLP (H10)

SVM (lineal)

Incertidumbre simétrica (4)

0.9027

0.9444

0.9166

0.9444

0.9305

0.9305

Incertidumbre simétrica (8)

0.8472

0.9444

0.9305

0.9444

0.9583

0.9305

Incertidumbre simétrica (16)

0.8472

0.9583

0.9583

0.9583

0.9861

0.9444

Incertidumbre simétrica (32)

0.8611

0.9583

0.9583

0.9583

0.9722

0.9722



Con solo 4 atributos seleccionados por incertidumbre simétrica, obtenemos muy buenos resultados con algunos métodos: NaiveBayes y Regresión Logística.

 

J48

NaiveBayes

IBK1

Reg. Log

MLP (H10)

SVM (lineal)

ReliefF (4)

0.9166

0.9166

0.8888

0.9444

0.9444

0.9444

ReliefF (8)

0.8611

0.9722

0.9444

0.9027

0.9305

0.9444

ReliefF (16)

0.8472

0.9444

0.9305

0.9305

0.9305

0.9722

ReliefF (32)

0.8333

0.9583

0.9305

0.9583

0.9722

0.9722



En este caso con 4 y 32 atributos se obtienen resultados similares al método anterior. No obstante, los algoritmos con buen desempeño son diferentes.

 

J48

NaiveBayes

IBK1

Reg. Log.

MLP (H10)

SVM (lineal)

SVM (4)

0.9166

0.9722

1

1

1

1

SVM (8)

0.9166

0.9722

0.9861

0.9861

1

1

SVM (16)

0.875

1

1

1

1

1

SVM (32)

0.8472

0.9861

1

1

1

1



La selección por SVM es excelente, ya que con solo 4 genes, logra tasas de acierto del 100% en 4 métodos diferentes. Seguramente estos 4 genes tengan la relación más directa con la enfermedad.

Envoltorio

Otro método de selección de atributos es el método del envoltorio. Aquí se elige un algoritmo de clasificación y según su desempeño se van descartando atributos irrelevantes. Idealmente, el mismo algoritmo envuelto es el que luego se usa en clasificación, pero aquí probaremos todos con todos.

Algoritmo

Atributos

J48

Selected attributes: 4847 : 1

attribute4847

NaiveBayes

Selected attributes: 6,461,760,6615 : 4

attribute6

attribute461

attribute760

attribute6615

IBK1

Selected attributes: 28,1834,3258,3549 : 4

attribute28

attribute1834

attribute3258

attribute3549

Reg. Log.

Selected attributes: 202,1882,6049 : 3

attribute202

attribute1882

attribute6049

MLP (H10)

Selected attributes: 1796,1834,2288 : 3

attribute1796

attribute1834

attribute2288

SVM (lineal)

Selected attributes: 162,1796,2111,3252 : 4

attribute162

attribute1796

attribute2111

attribute3252

En general la mayoría de envoltorios se decantan por 3 o 4 atributos, pero muchas veces diferentes. J48 se decanta por solo un único atributo.

 

J48

NaiveBayes

IBK1

Reg. Log.

MLP (H10)

SVM (lineal)

WrapJ48

0.9444

0.9305

0.9166

0.9305

0.9444

0.8611

WrapNB

0.9166

0.9861

0.8333

0.9583

0.9583

0.8194

WrapIBK1

0.8888

0.9861

1

0.9444

0.9444

0.8888

WrapRegLog

0.9583

0.9861

0.9444

1

0.9722

0.8333

WrapSVM

0.8888

0.9305

0.9166

0.9444

0.9861

0.9722

En general el comportamiento es bueno con el método que hizo de envoltorio, y peor en el resto. Sorprende MLP (H10) que logra resultados muy decentes con todas las selecciones y SVM, que da resultados muy mediocres salvo con su envoltorio.

Si comparamos métodos de filtro con métodos de envoltorio, nos encontramos con que hemos visto métodos de filtro muy superiores como SVM, si bien el método de envoltorio con el mismo algoritmo en ambas etapas es también una opción muy interesante.

Por norma general, los métodos de envoltorio son mejores pero también mucho más costosos de ejecutar. Es por ello, que ante las selecciones de filtrado obtenidas antes, normalmente nos quedaríamos con esos atributos. En este caso, y por mera curiosidad, hemos continuado con los métodos de envoltorio, mucho más lentos.

Si tuviésemos que construir un sistema hoy para detectar entre los tipos de leucemia, la opción sería eliminar atributos mediante SVM recursivo y construir el clasificador bien con SVM o con otro de los algoritmos que aciertan siempre, ya que no tenemos otros criterios todavía para discernir.

Espero que os haya gustado este episodio de Crónica Neuronal. En este caso, no he usado Python sino que he usado Weka, pero espero que aporte perspectiva y ayude a la gente a conocer por un lado los arrays de expresión genética, y por otro, los algoritmos de selección de atributos relevantes, sin entrar en el detalle de como funcionan.

 

]]>
https://blog.adrianistan.eu/cronica-neuornal-matrices-expresion-genetica-leucemia Mon, 21 Oct 2019 22:33:58 +0000
Tu código va a morir https://blog.adrianistan.eu/tu-codigo-va-a-morir https://blog.adrianistan.eu/tu-codigo-va-a-morir Memento mori. En la Antigua Roma, cuando un general venía victorioso de una campaña, se le organizaba un desfile por las calles de Roma. Lanzaban vítores y proclamas en su honor. No obstante, al lado de él había un siervo, que le iba repitiendo constantemente las limitaciones de la naturaleza humana, con el fin de impedir que incurriese en la soberbia y pretendiese, a la manera de un dios omnipotente, usar su poder ignorando las limitaciones impuestas por la ley y la costumbre. (Wikipedia). Esto se resume en un aforismo, Memento Mori, "Recuerda que vas a morir". Este chorro de realidad nos devuelve a la humildad, y a la vez, si uno lo piensa, nos deja entrever que los problemas de ahora no son tan importantes como muchas veces creemos que son.

Fotograma de la película Quo Vadis, el esclavo le repite al general "Memento Mori". Personalmente, fue al ver esta película cuando supe de la existencia de esta tradición.

Hace unas semanas estuve en la Tech Party de Madrid. Allí escuché algo que ha retumbado en mi mente, por ser fácil de entender y a la vez, por las consecuencias que conlleva. Tu código va a morir.

Es cierto, por muy bueno que sea tu código, algún día morirá. Por muy malo que sea, también morirá (aunque quizá no lo suficientemente rápido dirán algunos). No te creas que por haber escrito el mejor código de la historia, mereces un reconocimiento especial. No creas que por usar ese framework de moda con ese nuevo lenguaje, vas automáticamente a ser mejor, alguien digno de aplauso y recuerdo colectivo. Al final el código morirá y algo más moderno lo reemplazará igualmente, quizá incluso ya no exista la razón de ser de ese software.

Tampoco pienses que el código debe ser perfecto, busca la calidad, evidentemente, pero aléjate del perfeccionismo. En ocasiones ciertos bugs es mejor que esten presentes de forma crónica en un software, como si de una enfermedad, en ocasiones molesta pero leve, se tratase. Tu código debe madurar a buen ritmo, como un bebé, que poco a poco asume más responsabilidades y tareas, sin que ello le ocasione un desequilibrio interno (algo típico de familias desestructuradas). "Buen ritmo", eso quiere decir que no podrás trabajar en ello indefinidamente, si quieres que sea útil, algún día deberás poder decir que está listo. Y si no, piensa que tú también morirás y no podrás acabarlo. 

En ocasiones, la gente dice: "yo moriré, pero es software libre, alguien lo tomará", "la empresa lo seguirá usando". Y aunque supongo que podría darse el caso, la gran mayoría de los sistemas, de las comunidades, acaban siendo reemplazadas por otras. Es el ciclo vital. Recuerda que no eres excepcional, lo más probable es que lo que hagas no tenga un gran impacto. Será olvidado. No le des más importancia al código de la que tiene.

Y esto no lo digo para desanimarte o para hacerte creer que lo que haces no vale nada. Al contrario, te lo digo para que te liberes. Para romper las cadenas que te ata, muchas veces de forma subconsciente, ese deseo de perdurar en la eternidad. Ese deseo de destacar, de producir el código perfecto. Pero recuerda, al final, somos más bien como esas lágrimas, que se perderán en la lluvia. Y no habrá que atormentarse por ello.

]]>
https://blog.adrianistan.eu/tu-codigo-va-a-morir Wed, 25 Sep 2019 15:41:29 +0000
Terraform, infraestructura como código declarativo https://blog.adrianistan.eu/terraform-cloud-declarativa https://blog.adrianistan.eu/terraform-cloud-declarativa Como alguno de los lectores ya sabrá, he empezado a trabajar este verano en Telefónica como becario. El proyecto donde estoy es 100% cloud y para ello usamos muchas herramientas. Hoy os vengo a hablar de Terraform, una herramienta que nos permite declarar la infraestructura como código y de forma declarativa.

¿Por qué?

Antiguamente, cuando se desplegaban servicios, o una de dos: o se usaban servidores físicos, que se tenían que comprar, instalar, mantener, ... o se usaba un hosting compartido, el cuál para ciertas cosas puede estar bien, pero tiene muchas limitaciones. Hoy en día, gracias a los proveedores cloud, podemos alquilar infraestructura bajo demanda. Terraform es una herramienta para dejar por escrito toda la infraestructura en la nube que necesita nuestro servicio y crearla/modificarla según modifiquemos los archivos. Además es declarativo, lo que quiere decir que tenemos que expresarnos en código según lo que queremos obtener, no cómo, por tanto también cumple el papel de documentación. Piensa en Terraform como en los planos de un edificio. Nosotros definimos las vigas, paredes, tuberías sobre el papel y los obreros se encargan de construirlo. Terraform es eso, nosotros definimos máquinas, bases de datos y el programa se encarga de construir la infraestructura en la nube.

Funcionamiento interno

Terraform tiene varios componentes que vamos a definir primero:

  • recurso: cualquier cosa que exista en la nube (o fuera) sobre la que Terraform tenga control de creación, modificación y destrucción
  • datos: puntos donde podemos obtener información para nuestro código.
  • salidas: valores generados durante la ejecución del plan y que pueden ser de interés (direcciones IP, contraseñas, ...)
  • código: aquí es donde vamos a escribir la infraestructura que necesitamos (máquinas virtuales, clústeres de Kubernetes, balanceadores de carga, discos, ...). Representa el estado óptimo del sistema.
  • estado: aquí Terraform almacena la infraestructura real, con mucha más información que la que existe en el código. Además le sirve a Terraform para acordarse entre ejecuciones de la infraestructura que controla
  • proveedores: los recursos y los datos necesitan de un plugin que conecte la nube con Terraform. Terraform contiene multitud de proveedores: Azure, AWS, Google Cloud, Netlify, OpenStack, Kubernetes, Let's Encrypt, Helm, Digital Ocean, OVH, Alibabba Cloud, Oracle Cloud, PostgreSQL, Triton, VMware vSphere, Heroku, Linode, Packet, 1&1 y muchos más. Adicionalmente, existen proveedores creados por terceros.
  • plan: se trata del paso en cual Terraform compara el estado con el código y encuentra diferencias entre el estado real y el óptimo. Opcionalmente el estado se puede actualizar antes (lo hace por defecto) para tener un estado lo más real posible. Cuando tengamos un plan lo podemos aplicar y entonces Terraform modificará la infraestructura.

Una máquina virtual sencilla

Para este ejemplo voy a usar Microsoft Azure, pero se pueden usar otros proveedores similares haciendo los cambios adecuados. Cualquier estudiante puede pedir el GitHub Education Pack que regala 100$ para gastar en Azure.


provider "azurerm" {
  version = "~> 1.28.0"
}

provider "random" {}

resource "random_string" "username" {
  length = 12
  special = false
}

resource "random_password" "password" {
  length = 12
  special = true
}

resource "azurerm_resource_group" "main" {
  name     = "blog"
  location = "France Central"
}

resource "azurerm_public_ip" "main" {
  name                = "blog-ip"
  location            = "${azurerm_resource_group.main.location}"
  resource_group_name = "${azurerm_resource_group.main.name}"
  allocation_method   = "Static"
}

resource "azurerm_virtual_network" "main" {
  name                = "blog-network"
  address_space       = ["10.0.0.0/16"]
  location            = "${azurerm_resource_group.main.location}"
  resource_group_name = "${azurerm_resource_group.main.name}"
}

resource "azurerm_subnet" "internal" {
  name                 = "blog-internal"
  resource_group_name  = "${azurerm_resource_group.main.name}"
  virtual_network_name = "${azurerm_virtual_network.main.name}"
  address_prefix       = "10.0.2.0/24"
}

resource "azurerm_network_interface" "main" {
  name                = "blog-nic"
  location            = "${azurerm_resource_group.main.location}"
  resource_group_name = "${azurerm_resource_group.main.name}"

  ip_configuration {
    name                          = "conf1"
    subnet_id                     = "${azurerm_subnet.internal.id}"
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id = "${azurerm_public_ip.main.id}"
  }
}

resource "azurerm_virtual_machine" "main" {
  name                  = "blog-vm"
  location              = "${azurerm_resource_group.main.location}"
  resource_group_name   = "${azurerm_resource_group.main.name}"
  network_interface_ids = ["${azurerm_network_interface.main.id}"]
  vm_size               = "Standard_DS1_v2"

  storage_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "18.04-LTS"
    version   = "latest"
  }
  storage_os_disk {
    name              = "disk-os-1"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "Standard_LRS"
  }
  os_profile {
    computer_name  = "blog"
    admin_username = "${random_string.username.result}"
    admin_password = "${random_password.password.result}"
  }
  os_profile_linux_config {
    disable_password_authentication = false
  }
}

output "login_information" {
  value = "${random_string.username.result}:${random_password.password.result}:${azurerm_public_ip.main.ip_address}"
}

 

El código es sencillo y declarativo. Definimos dos proveedores (azurerm y random) para los recursos que vamos a definir. Los recursos se identifican por el tipo de recurso y por un identificador único. Luego definimos usuario y contraseña aleatorios para la máquina, un grupo de recursos de Azure, una dirección IP pública, la infraestructura de red y finalmente una máquina virtual con Ubuntu. En output mostramos la IP pública de la máquina, su usuario y contraseña, para poder conectarnos. Aquí podríamos pasar a usar otras herramientas como Ansible para instalar todo lo necesario en la máquina virtual.

Aquí hemos visto el uso de referencias, con el símbolo del dólar. Estas referencias le permiten a Terraform construir/destruir la infraestructura en el orden correcto. Por defecto Terraform intenta realizar todas las operaciones en paralelo, salvo que necesite una información que venga de otro recurso. En ese caso, tiene que ser creado con éxito para poder proceder a la creación del siguiente. Si no podemos usar referencias, podemos usar depends_on.

Hagamos un terraform apply para realizar un plan y aplicarlo. Además veremos las salidas definidas.

Y vemos como ha sido creado en Azure con éxito.

Ahora si modificamos algo desde la web y volvemos a ejecutar terraform apply, se detectará que el estado real es diferente al óptimo y tratará de revertir el cambio. También si modificamos los ficheros se intentará modificar el entorno real. 

Variables y bucles

Terraform admite datos externos y bucles. Vamos a verlo. Con variable podemos introducir datos desde variables de entorno, la CLI o un fichero .tfvars. Estas variables pueden tener un valor por defecto. Las variables se definen con -var="var_name=var_value", con las variables de entorno TF_VAR_var_name=var_value y el fichero terraform.tfvars.

Los bucles se pueden realizar con count, si cada elemento no tiene identidad (por ejemplo, el número de réplicas de una VM igual) o con for_each si cada elemento debe tener identidad (por ejemplo, una VM para cada país).


provider "azurerm" {
  version = "~> 1.28.0"
}

variable "name" {
  default = "adrianistan"
}

variable "country" {
  default = [
    "es",
    "ar"
  ]
}

resource "azurerm_resource_group" "main" {
  name     = "blog"
  location = "France Central"
}

resource "azurerm_app_service_plan" "main" {
  name                = "main-appserviceplan"
  location            = "${azurerm_resource_group.main.location}"
  resource_group_name = "${azurerm_resource_group.main.name}"
  kind                = "Linux"
  reserved            = true

  sku {
    tier = "Basic"
    size = "B1"
  }
}

resource "azurerm_app_service" "main" {
  for_each            = toset(var.country)
  name                = "${var.name}-${each.value}"
  location            = "${azurerm_resource_group.main.location}"
  resource_group_name = "${azurerm_resource_group.main.name}"
  app_service_plan_id = "${azurerm_app_service_plan.main.id}"

  site_config {
    linux_fx_version = "DOCKER|nginx:1.17.3"
  }
}

output "web" {
value = "${formatlist("%s", [for o in azurerm_app_service.main : o.default_site_hostname])}"
}

En este ejemplo usamos un bucle for_each para generar dos Azure App Services (cargados con nginx), uno para España y otro para Argentina.

Comprobamos como funciona:

Espero que esta breve introducción a Terraform os haya resultado interesante. Se trata de un lenguaje sencillo, declarativo y donde la mayor parte de nuestros problemas vendrán de conocer la documentación de cada proveedor al dedillo.

 

]]>
https://blog.adrianistan.eu/terraform-cloud-declarativa Tue, 17 Sep 2019 20:38:10 +0000
Podcast sobre Docker https://blog.adrianistan.eu/podcast-docker https://blog.adrianistan.eu/podcast-docker Acaba de salir el segundo episodio del podcast Undefined. Se trata de un podcast donde algunos conocidos hablamos de temas de informática. En este episodio, dedicado a Docker, soy uno de los colaboradores así que: ¡a escucharlo todos!

Todavía estamos empezando este proyecto así que puede haber ciertas cosas que no se sientan tan profesionales como otros podcasts, iremos mejorando, pero vuestros comentarios son importantes. ¡A disfrutar!

]]>
https://blog.adrianistan.eu/podcast-docker Mon, 19 Aug 2019 17:23:28 +0000
Re: Rust no es un buen reemplazo de C https://blog.adrianistan.eu/rust-no-es-buen-reemplazo-c https://blog.adrianistan.eu/rust-no-es-buen-reemplazo-c Como muchos ya sabéis, podéis contactar conmigo de muchas formas. De entre ellas, Lector anónimo eligió el correo para enviarme esta pregunta:

Hola.

Sigo tu blog desde hace algún tiempo y tienes un contenido interesante.
Soy aficionado a la programación y sé algo de Python. Estaba pensando en
aprender Rust. El caso es que leí un artículo y me gustaría saber tu
opinión, o sugerencia para que escribas algo en tu blog.

https://drewdevault.com/2019/03/25/Rust-is-not-a-good-C-replacement.html

Saludos.Lector anónimo.

Le respondí por correo pero os copio mi mensaje por si a alguno más le interesa:

Hola Lector anónimo,
 
Muchas gracias por seguir el blog, aunque no lo creas, me anima mucho conocer gente nueva que lo haga.
 
Respecto al artículo, me sonaba de haberlo leído antes (es de marzo así que es posible). No estoy de acuerdo en la mayoría de puntos aunque quizá el que más me moleste es el que no hay una especificación porque es una verdad a medias: no existe un estándar externo tipo ISO, ANSI o ECMA pero todos los cambios se someten a un proceso de RFC (https://github.com/rust-lang/rfcs/tree/master/text) que es el mismo que se usa en Internet y muy similar a las PEP de Python. Igualmente con lo de Cargo, por una vez que alguien hace algo decente en este campo con el tema de dependencias (que en C es un caos y realmente no se debería aspirar a eso).
 
Por otro lado, el tono general del artículo es que Rust es muy complejo y que C es más simple. Pero la cuestión de fondo, y que justifica en mi opinión la complejidad de Rust (la cuál no voy a negar) es que el problema que quiere resolver ES complejo y simplificarlo solo te puede llevar a dos opciones: no servir en todos los casos de uso o trasladar la complejidad a otro campo, que en el mundo de C, es la mente del programador que debe ser consciente de lo que hace.
 
Lo mismo con el tema de la concurrencia, si es complejo de hacer en C como el admite es porque es difícil y porque cuando se diseñó el lenguaje no era algo que se tuviese en cuenta (¿cuántos multicores había en los 70?), pero a diferencia de lo que él dice, sí merece la pena muchas veces usar diferentes hilos, sobre todo ahora en que los procesadores no aumentan su velocidad (incluso disminuye, por eficiencia energética) pero sí su número de cores.
 
Y luego finaliza con el tema de la seguridad que para él no es importante, pero ES importante, sobre todo ahora que el software puede provocar:
  • pérdidas de vidas humanas (piensa en los aviones, los coches autónomos o las herramientas médicas avanzadas)
  • hackeos, robos de datos y otro tipo de ataques en pleno auge de los ciberataques
En fin, yo he programado mucho en C y realmente me parece un buen lenguaje para su época, lo respeto mucho, pero creo que a día de hoy hemos avanzado algo y hemos encontrado cosas, que sí, pueden ser más complejas, pero resuelven de forma más correcta los problemas. Por eso si bien no soy partidario de iniciar proyectos nuevos en C, tampoco soy de esos que va pregonando que todo se reescriba en Rust jajaja.
 
Respecto a si debes aprenderlo... Bueno, eso es algo personal, yo lo probaría un poco y vería que tal. Laboralmente todavía no tiene mucho tirón (en mi trabajo por ejemplo no lo uso, aunque cada vez somos más los de la secta jejejeje) pero es muy interesante. Quizá todavía le falta algo de madurez pero con el tiempo llegará (así a lo tonto Python ya tiene 30 años por ejemplo y Rust no llega a 10 todavía).
 
Si tienes alguna duda más, no te cortes y pregúntame, veré que puedo hacer jajaja
 
Un saludo, Adrián
]]>
https://blog.adrianistan.eu/rust-no-es-buen-reemplazo-c Sat, 17 Aug 2019 12:11:13 +0000
jq, el sed del siglo XXI https://blog.adrianistan.eu/jq-sed-siglo-xxi https://blog.adrianistan.eu/jq-sed-siglo-xxi Que levante la mano quién no ha oído hablar de sed. Una herramienta presente en cualquier sistema UNIX, se trata de un editor de texto en modo streaming. Aunque sed es una herramienta muy potente, con un lenguaje de programación propio que es Turing completo, la mayor cantidad de usos son sustituir o extraer datos usando expresiones regulares y los comandos sp de sed. jq es sed para el siglo XXI porque trabaja de forma nativa con JSON, es decir, trabaja con objetos, no con texto plano.

El caso del texto plano

Tradicionalmente en UNIX se han tomado varias decisiones de diseño, que a falta de otros sistemas más populares, se han tomado como acertadas. Una de ellas es el concepto de texto plano. En UNIX el formato por defecto de cualquier cosa ha sido texto plano. Texto plano entendido como que no se tiene en cuenta el formato o la estructura del fichero. Las herramientas del sistema han sido diseñadas para trabajar con él (grep, sed, cut, ...). Esto hace muy difícil acceder a partes concretas de un archivo. Ciertas herramientas como AWK soportan formatos estructurados como CSV, de forma bastante cómoda, pero CSV sigue siendo un formato con limitaciones.

Existen sistemas que han tomado esto de otra forma, por ejemplo PowerShell. La shell de Microsoft usa objetos como elemento básico de comunicación, soportando atributos y arrays y desterrando las operaciones de texto plano a un segundo plano (ba dum tss).

El texto plano no es malo, y muchas veces es lo mejor. Consume pocos recursos, es simple de entender, pero muchas veces necesitamos una estructura por detrás que nos ayude a manipular los datos. En mi opinión debería haber más documentos estructurados (que luego pueden contener atributos con text plano claro) por defecto.

jq, la esencia de sed para JSON

Si hablamos de formatos para documentos, quizá el más popular sea JSON. JSON es simple y efectivo, poco verboso, soporta booleanos, números, cadenas de texto plano, objetos y arrays. JSON casualmente usa ficheros de texto plano para guardarse, pero es un error usar grep, sed y AWK para buscar en estos ficheros. Al hacerlo estamos ignorando por completo la estructura del documento y esto puede hacer todo más complejo, dar resultados erróneos, etc

jq, aplica las ideas de estas herramientas clásicas de UNIX al sistema de documentos de JSON. Lo puedes instalar en Debian/Ubuntu fácilmente:


sudo apt install jq

El hola mundo de jq sería aplicar el selector ., para seleccionar todo el documento JSON.

Para todos los ejemplos voy a usar el siguiente fichero JSON, totalmente inventado:


{
    "cars" : [
        {
            "name": "Renault 21",
            "company": "Renault",
            "year" : 1980
        },
        {
            "name": "Citröen C-Zero",
            "company": "Citröen",
            "year": 2009
        },
        {
            "name": "Opel Monza",
            "company": "Opel",
            "year": 1978
        },
        {
            "name": "Pegaso Z-102",
            "company": "Pegaso",
            "year": 1951
        }
    ],
    "drivers" : [
        {
            "name" : "James Hunt",
            "birthplace" : "United Kingdom"
        },
        {
            "name" : "Niki Lauda",
            "birthplace" : "Austria"
        },
        {
            "name" : "Jim Clark",
            "birthplace": "United Kingdom"
        },
        {
            "name" : "Juan Manuel Fangio",
            "birthplace": "Argentina"
        },
        {
            "name" : "Fernando Alonso",
            "birthplace": "Spain"
        }
    ]
}

Acceso a datos

Para acceder a los datos usamos una sintaxis similar a la de JavaScript. Accedemos mediante punto a los atributos y con corchetes a los arrays. Si queremos indicar que un campo es opcional, usamos ?. Por ejemplo, si queremos acceder solamente a los coches:

Un concepto básico de jq son los pipes, de forma similar a Bash, podemos pasar la salida de un comando a otro. Sin embargo, los pipes de jq no transmiten texto plano, sino documentos JSON. Veamos un ejemplo, para acceder al campo year podemos realizarlo de forma compuesta usando pipes. En primer lugar seleccionaríamos el subdocumento del coche y en el siguiente paso accederíamos al elemento year del subdocumento.

Otro concepto muy potente de jq es el poder realizar map sobre cada elemento del array. De esta forma, todo lo que indiquemos a continuación se realizará para todos los documentos del array. Estos arrays también soportan slicing, por lo que podemos decir que el map se realice solo desde el elemento X al elemento Y de la forma [X:Y].

Si además queremos que la salida sea un array válido de JSON, encerramos la expresión entre corchetes.

Búsquedas con regex

Ya hemos visto como acceder a los elementos. Vamos ahora a ver como realizar búsquedas por regex. El comando test nos permite ejecutar una comprobación dada una expresión regular y devuelve true o false.

Sin embargo, esto no es muy útil, ya que la mayoría de las veces no vamos a querer un listado de true y falses. select es un comando que filtra los subdocumentos que recibe dependiendo de su expresión en el interior es true o false. Combinándolos podemos filtrar la salida según la búsqueda.

Aplicando las reglas de los pipes podemos realizar la búsqueda solo en un campo pero obtener el subdocumento completo. Por ejemplo, aquí obtenemos un listado de pilotos que han nacido en Reino Unido.

Generando documentos

Hasta ahora hemos visto como acceder, buscar y filtrar información del documento. Pero jq permite también generar documentos JSON nuevos de salida. Para ello, hay que usar las llaves y escribir la estructura de nuestro documento.

Aquí en este ejemplo, hemos decidido quedarnos con los coches y eliminar el campo de company del JSON. Usamos map, pipes y todo entre corchetes, para obtener un array de salida. Evidentemente, como los campos de entrada y salida se llaman igual, esto se puede simplificar.

A jq también le podemos pasar variables externas desde fuera con el argumento --arg. Si usamos el argumento -n podemos generar documentos JSON desde cero.

Las variables se referencian por su nombre siempre precedidas del símbolo del dólar (como en Bash). Se pueden crear variables usando as $variable.

Funciones y operadores aritméticos

jq contiene un buen puñado de funciones y operadores aritméticos. Por ejemplo, podemos usar la resta para calcular en vez del año de fabricación del coche, su edad actual (suponiendo que vivimos en 2019).

Existen funciones muy interesantes, algunas de ellas son length (longitud de una cadena de texto o de un array), has (comprobar si existe una propiedad), in (la función inversa de has), map (aplicar una función a todos los elementos y devolver el nuevo array), del (elimina un subdocumento), add (añade todos los elementos entre sí), anyall (comprueban si una condición se cumple en algún elemento o en todos), flatten (simplifica los arrays, aplanándolos), sortsort_by (para ordenar), group_by (para agrupar en base a un campo), unique (elimina los elementos duplicados), while (aplica una operación hasta que se deje de cumplir la condición), join (al estilo SQL) y muchos más. 

En jq además existen operadores condicionales (if-then-else, and, ornot) y try-catch para detectar errores. No obstante, en la mayoría de las ocasiones no los vas a usar y es mucho más legible usar los elementos presentados anteriormente.

En general, si tienes un buen dominio de la programación funcional, jq te parecerá bastante intuitivo, ya que las similaridades son evidentes.

Asignaciones

Una cosa muy interesante que tiene jq es poder editar los archivos directamente, sin tener que generar uno nuevo, a través de las asignaciones. La asignación básica es |= que permite modificar un documento con una versión nueva. Por ejemplo, si queremos editar la compañía del coche, únicamente cuando es Opel, podemos recurrir a una combinación de if-then-else con |=.

Como vemos, el fichero de salida es idéntico al original pero modificando Opel por Vauxhall. 

Conclusiones

jq es una herramienta muy potente, pensada para trabajar con documentos JSON en un flujo de trabajo similar al de Bash y Sed pero con nociones de la estructura del archivo. jq se puede usar en cualquier sistema prácticamente y es muy potente, como podéis haber visto. Espero que os haya picado el gusanillo y a partir de ahora lo empecéis a utilizar. Muy interesante es su combinación con curl para poder trabajar con APIs web desde la terminal.

]]>
https://blog.adrianistan.eu/jq-sed-siglo-xxi Mon, 12 Aug 2019 17:38:16 +0000
Crónica Neuronal: Indian Liver Patients Record https://blog.adrianistan.eu/cronica-neuronal-indian-liver-patients-record https://blog.adrianistan.eu/cronica-neuronal-indian-liver-patients-record Otro día de verano y otro día de Crónica Neuronal. Hoy he elegido un dataset médico, se trata de Indian Liver Patients Record, o lo que es lo mismo, Registro de pacientes de hígado en la India. Las enfermedades relacionadas con el hígado han ido en aumento en los últimos años: el alcohol, la polución, la comida en mal estado, las drogas y los pepinillos son algunas de las causas de este aumento. En el dataset, originalmente de UCI y bajo licencia Creative Commons Zero, tenemos datos médicos de varias personas y si deben recibir tratamiento para el hígado o no. El objetivo es identificar, dado un paciente nuevo, si debería iniciar un tratamiento del hígado o si por el contrario, está sano.

Análisis de datos

Las variables o features del dataset son las siguientes:

  • Age: edad de la persona
  • Gender: sexo de la persona
  • Total Bilirubin: bilirrubina total en el hígado en mg/dL. Es la suma de la bilirrubina directa y la indirecta
  • Direct Bilirubin: bilirrubina directa en mg/dL.
  • Alkaline Phosphotas: fosfatasa alcalina en IU/L
  • Alanina aminotransferasa: enzima conocida también como transaminasa glutámico pirúvica, en IU/L
  • Aspartate Aminotransferas: enzima conocida también como aspartato transaminasa, en IU/L
  • Total Protiens: proteínas totales, en g/dL
  • Albumin: albúmina, una proteína que se genera en la sangre, en g/dL
  • Albumin and Globulin Ratio: ratio de albúmina por glóbulos en sangre
  • Dataset: si necesitan tratamiento o no

Vamos a entrar más en detalle de las variables usando Seaborn.


import pandas as pd
import numpy as np
import seaborn as sns

data = pd.read_csv("indian_liver_patient.csv")

data.describe()

sns.countplot(data["Gender"])

sns.countplot(data["Dataset"])

sns.distplot(data["Total_Bilirubin"])

Hay solo dos variables categórica en este dataset: la clase y Gender. La clase no hace falta transformarla, ya que ya son dos números. Vamos a analizar Gender, con countplot, que tendremos que transformar con OneHotEncoder:

Tenemos muchísimos más datos de hombres que de mujeres. En una prediccón real haría falta tener más datos de mujeres. Puede ser que esto no influya o puede que sí (como en muchas enfermedades, un caso muy evidente, el cáncer de mama) o incluso puede que se deba factores culturales. Pero de cara a un diagnóstico deberíamos poder tener datos equilibrados. 

En el caso de la clase, también vemos que está desequilibrada, con muchos más datos de gente que debería recibir tratamiento (1) de los que no (2). A la hora de realizar el test del algoritmo deberemos estratificar, para asegurarnos que se incluye esta misma proporción en los conjuntos de entrenamiento reducidos.

Podemos analizar la distribución de alguna variable continua, con distplot, pero no he encontrado nada relevante aquí (parece obvio que hay un único valor mayoritario).

Usando jointplot en Seaborn podemos ver correlaciones variable a variable. Usando pairplot, podemos ver todas de golpe.

Así a priori no observo valores demasiado extraños, así que de momento, mantendremos todos los valores.

Analizando los datos compruebo que existen 4 valores no existentes en la columna Albumin_and_Globulin_Ratio. Procedo a rellenarlos con una media de valores. Hacemos el OneHotEncoding:


data.isna().sum()

data["Albumin_and_Globulin_Ratio"] = data["Albumin_and_Globulin_Ratio"].fillna(data["Albumin_and_Globulin_Ratio"].mean())

data = pd.get_dummies(data)

X = data.drop(columns=["Dataset"])
Y = data["Dataset"]

A continuación, probamos diferentes algoritmos de la familia de árboles: DecisionTree y RandomForest. Probé el criterio de entropía (ID3, C4.5 y similares) y el de Gini. Y Gini era más estable, así que es mi elección.


from sklearn.model_selection import train_test_split


train_x, test_x, train_y, test_y = train_test_split(X,Y,test_size=1/3,stratify=Y,random_state=0)

from sklearn.tree import DecisionTreeClassifier

tree = DecisionTreeClassifier(criterion="gini", max_depth=3, min_samples_split=4, max_features=4)
tree.fit(train_x, train_y)
predict_y = tree.predict(test_x)

np.sum(predict_y == test_y)/test_y.shape[0]

from sklearn.ensemble import RandomForestClassifier

tree = RandomForestClassifier(criterion="gini", max_depth=None, min_samples_split=4, max_features=4, n_estimators=100)
tree.fit(train_x, train_y)
predict_y = tree.predict(test_x)

np.sum(predict_y == test_y)/test_y.shape[0]

Con DecisionTreeClassifier y el criterio de Gini, lo tuneé con max_depth=3, min_samples_split=4 y max_features=4. Todo ello para que no haga sobreajuste, típico en árboles. Este max_features es interesante. Podríamos haberlo hecho antes, pero con los árboles da igual. Básicamente si dos variables están muy correlacionadas, aportan prácticamente la misma información, serán ignoradas en la construcción del modelo, reduciendo el sobreajuste. El resultado es un 70% de acierto.

Con RandomForest he conservado los ajustes de max_features y de min_samples_split pero he desactivado la profundidad máxima. Consistentemente se obtiene un 73-74% de acierto.

Por último, para XGBoost, que era la primera vez que lo usaba, le metí en un bucle que va modificando valores, probando muchas configuraciones posibles. Esto llevó un rato, pero consiguió una configuració


import xgboost as xgb

res = dict()

for d in range(1,10):
    for l in range(1,9):
        for m in range(1,5):
            for g in range(0,10):
                tree = xgb.XGBClassifier(max_depth=d, learning_rate=l/100, n_estimators=100, objective="binary:logistic", booster="gbtree", n_jobs=4, min_child_weight=m, gamma=g/10)
                tree.fit(train_x, train_y)
                predict_y = tree.predict(test_x)

                res[(d,l,m,g)] = np.sum(predict_y == test_y)/test_y.shape[0]
                print(res[(d,l,m,g)])
max(res, key=res.get)

El valor óptimo fue profundidad máxima de 7, learning_rate de 0.08, min_child_weight de 3 y gamma de 0.3. Estos valores fueron lo mejor que pude encontrar para este holdout específico. Pero al cambiar el random_state del Holdout, estos aciertos se desmoronaron hasta llegar al 64%. Los parámetros no son buenos, simplemente había coincidido que iban muy bien. Con esta tragedia, es un buen momento para recordar uno de los métodos alternativos a Holdout: validación cruzada, que sin duda usaré en otro dataset de otra Crónica Neuronal.

Conclusión

Hemos obtenido fácilmente modelos con más del 70% de acierto de forma consistente, con RandomForest y xgboost. Probé también K-Vecinos, con resultados parecidos pero más lento y el perceptrón multicapa, sin poder igualar resultados. Espero que este dataset os haya gustado, espero vuestros aportes en los comentarios.

 

]]>
https://blog.adrianistan.eu/cronica-neuronal-indian-liver-patients-record Tue, 6 Aug 2019 21:34:07 +0000
Crónica Neuronal: House Prices https://blog.adrianistan.eu/cronica-neuronal-house-prices https://blog.adrianistan.eu/cronica-neuronal-house-prices Bienvenidos a una sección del blog titulada Crónica Neuronal. En esta sección resolveremos problemas reales de Inteligencia Artificial de forma práctica. La estructura es la siguiente. Yo presento un problema, muchas veces sacado de Kaggle, SpainML u otro sitio y voy contando como lo voy resolviendo, escribiendo paso a paso mis pensamientos en cada momento. De hecho, yo no he resuelto estos problemas, sino que los voy resolviendo sobre la marcha mientras escribo la crónica. A los problemas les dedico un tiempo limitado y puede ser posible (como en este caso) que no llegue a resultados satisfactorios.

House Prices

Este primer problema ha sido creado por Kaggle, forma parte de la categoría de problemas para principiantes. El problema consiste en predecir el valor de venta real (es decir, por el que se ejecuta una compra-venta) de casas de Ames, Iowa. Para ello disponemos de un historial de compra-ventas en la zona de Ames, con gran cantidad de detalles:

  • MSSubClass: Tipo de edificio
  • MSZoning: Clasificación de la zona
  • LotFrontage: Linear feet of street connected to property
  • LotArea: Lot size in square feet
  • Street: Tipo de acceso por carretera
  • Alley: Tipo de acceso por callejón
  • LotShape: Forma de la propiedad
  • LandContour: Rugosidad de la propiedad
  • Utilities: Tipo de utilidades disponibles
  • LotConfig: Configuración de la parcela
  • LandSlope: Inclinación de la parcela
  • Neighborhood: Barroi
  • Condition1: Proximidad a carretera principal o ferrocarril
  • Condition2: Proximidad a carretera principal o ferrocarril (si hubiese un segundo)
  • BldgType: Tipo de vivienda
  • HouseStyle: Estilo de la vivienda
  • OverallQual: Calidad de materiales y construcción
  • OverallCond: Calidad de la casa en general
  • YearBuilt: Fecha original de construcción
  • YearRemodAdd: Fecha de remodelación
  • RoofStyle: Tipo de techo
  • RoofMatl: Material del techo
  • Exterior1st: Material del exterior de la casa
  • Exterior2nd: Material del exterior de la casa
  • MasVnrType: Recubrimiento exterior decorativo
  • MasVnrArea: Área del recubrimiento exterior decorativo
  • ExterQual: Calidad del material exterior
  • ExterCond: Condiciones actuales del material exterior
  • Foundation: Tipo de pilares
  • BsmtQual: Altura del sótano
  • BsmtCond: Condiciones del sótano
  • BsmtExposure: Exposición del sótano al exterior
  • BsmtFinType1: Calidad del primer área acabada del sótano
  • BsmtFinSF1: Tipo del primer área acabada
  • BsmtFinType2: Calidad del segundo área acabada del sótano
  • BsmtFinSF2: Tipo del segundo área acabada
  • BsmtUnfSF: Área sin acabar del sótano
  • TotalBsmtSF: Área del sótano
  • Heating: Tipo de calefacción
  • HeatingQC: Calidad de la calefacción
  • CentralAir: Sistema de aire acondicionado centralizado
  • Electrical: Sistema eléctrico
  • 1stFlrSF: Área primera planta
  • 2ndFlrSF: Área segunda planta
  • LowQualFinSF: Área finalizada de baja calidad
  • GrLivArea: Área habitable sobre el suelo
  • BsmtFullBath: Sótano con baño completo
  • BsmtHalfBath: Sótano con baño incompleto
  • FullBath: Baños completos sobre el suelo
  • HalfBath: Baños incompletos sobre el suelo
  • Bedroom: Dormitorios sobre el suelo
  • Kitchen: Cocinas
  • KitchenQual: Calidad cocinas
  • TotRmsAbvGrd: Habitaciones sobre el suelo (sin baños)
  • Functional: Rating de funcionalidad
  • Fireplaces: Número de chimeneas
  • FireplaceQu: Calidad chimeneas
  • GarageType: Tipo garaje
  • GarageYrBlt: Año del garaje
  • GarageFinish: Calidad interior del garaje
  • GarageCars: Número de coches en el garaje
  • GarageArea: Área del garaje
  • GarageQual: Calidad del garaje
  • GarageCond: Condiciones actuales del garaje
  • PavedDrive: Entrada al garaje asfaltada
  • WoodDeckSF: Área exterior recubierta de madera
  • OpenPorchSF: Área de porche abierto
  • EnclosedPorch: Área de porche cerrado
  • 3SsnPorch: Área de porche tres-estaciones
  • ScreenPorch: Área de porche pantalla
  • PoolArea: Área de la piscina
  • PoolQC: Calidad de la piscina
  • Fence: Calidad de la valla
  • MiscFeature: Miscelánea
  • MiscVal: Valor de la miscelánea
  • MoSold: Mes de venta
  • YrSold: Año de venta
  • SaleType: Tipo de venta
  • SaleCondition: Condición de la venta

Como vemos, el número de variables es enorme. Hay que tener en cuenta que muchas propiedades pueden no influir o influir de forma poco significativa. En caso de detectarlo puede ser convenientes eliminarlas, ya que pueden añadir ruido innecesario.

Se trata de un problema de regresión, ya que se nos pide predecir un valor numérico dentro de un continuo (los precios, a efectos prácticos son continuos) y un infinito. Esto se opone a los problemas de clasificación, donde intentamos predecir algo dentro de una lista de posibles valores.

Cargando datos y Holdout

Vamos a empezar abriendo Jupyter y cargando los datos en formato CSV con Pandas. Si leemos el CSV vemos que tiene una columna extra llamada, Id. Vamos a eliminarla, ya que esta columna única para cada instancia nos provocaría sobreajuste.

Además, vamos a preparar un sistema de prueba. Me decanto por el método Holdout con 2/3-1/3. Esto es que nuestros datos de entrenamiento y de test son 2/3 y 1/3 del total respectivamente. Cuando tengamos un buen algoritmo, podremos usar todos los datos para la clasificación en Kaggle. De momento vamos a eliminar además las variables para las que existan valores desconocidos con dropna.


import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split

data_csv = pd.read_csv("train.csv")

data = data_csv.drop(columns=["Id"])

data.dropna(axis="columns", inplace=True)
data = pd.get_dummies(data)

x = data.drop(columns=["SalePrice"])
y = data["SalePrice"]

train_x, test_x, train_y, test_y = train_test_split(x, y, test_size=1/3)

Árboles de decisión

Siempre es recomendable probar los conjuntos de datos con algoritmos sencillos de aprendizaje automático. Los árboles de decisión son rápidos y pueden ofrecernos información sobre el conjunto de datos muy interesante. En mi caso voy a usar DecisionTreeRegressor perteneciente a sklearn. No obstante, este módulo no admite variables categóricas. Para ello podemos usar LabelEncoder OneHotEncoder también de sklearn y poder realizar una transformación. ¿Cuál usar? La teoría dicta que LabelEncoder cuando las categorías tienen un orden y OneHotEncoder cuando no tiene sentido. Como en muchas propiedades no sabemos si es intrínsecamente mejor Ladrillo o Piedra, usaremos OneHotEncoder por defecto. En este caso no voy a usar sklearn, sino el método de Pandas get_dummies. Además creo que no va a hacer falta normalizar los datos, así que nos saltamos este paso.


from sklearn.tree import DecisionTreeRegressor

tree = DecisionTreeRegressor(criterion="mse", splitter="best", max_depth=None, min_samples_split=10, min_samples_leaf=5)
tree.fit(train_x, train_y)
predict_y = tree.predict(test_x)

1 - np.sum(abs(predict_y - test_y) < 0.1*test_y)/test_y.shape[0]

Por último, para evaluar los datos elegimos que una diferencia del 10% entre el valor real y el valor predicho nos vale. Este árbol de decisión, tiene un criterio MSE, no tiene altura máxima pero a cambio le he puesto que el tamaño de las hojas sea mínimo de 5 (es decir, una ramificación final solo lo puede ser si al menos 5 instancias de entrenamiento caen en ella) y un número de split de 10 (para crear una rama hace falta por lo menos 10 ejemplos). Los resultados son bastante mediocres: una tasa de error del  50%. Pero está suficientemente bien, como para hacer una entrega de prueba en Kaggle.

Realizando las predicciones

Aquí me encontré con un problema. Los datos de test tenían iferentes datos que los de entrenamiento y el OneHotEncoding con get_dummies fallaba. La solución que utilicé es cargar los datos de entrenamiento y test en un mismo DataFrame de Pandas, hacer el get_dummies y después volverlos a separar. Después de realizar esto tenemos que volver a entrenar el modelo con todos los datos posibles y realizar la predicción y guardarla en el CSV. El código completo es el siguiente:


import pandas as pd
import numpy as np

data_csv = pd.read_csv("train.csv")
test_csv = pd.read_csv("test.csv")
size = test_csv.shape
all_data = pd.concat((test_csv,data_csv),sort=False)

all_data.dropna(axis="columns",inplace=True)
all_data = pd.get_dummies(all_data,drop_first=True)

test = all_data[0:size[0]]
data = all_data[size[0]:]

x = data
y = data_csv["SalePrice"]


from sklearn.model_selection import train_test_split


train_x, test_x, train_y, test_y = train_test_split(x,y,test_size=1/3)


# ARBOLES DE DECISION

from sklearn.tree import DecisionTreeRegressor

tree = DecisionTreeRegressor(criterion="mse",splitter="best",max_depth=None,min_samples_split=10,min_samples_leaf=5)
tree.fit(train_x,train_y)
predict_y = tree.predict(test_x)


1 - np.sum(abs(predict_y - test_y) < 0.1*test_y)/test_y.shape[0]


# PREDECIR

tree.fit(x,y)

test_out = tree.predict(test)
out = pd.DataFrame({"Id" : test_id, "SalePrice" : test_out})

out.to_csv("out.csv",index=False)

El fichero out.csv lo podemos subir a Kaggle.

El resultado es mejor de lo esperado, quizá Kaggle usa un interavalo de admisión más alto que el mío. Igualmente, lo voy a dejar en el 10%

K-Vecinos

Voy a probar el algoritmo de K-Vecinos. No tengo muchas esperanzas en él, pero la interfaz de programación en sklearn es muy parecida a la los árboles de decisión.


from sklearn.neighbors import KNeighborsRegressor

knr = KNeighborsRegressor(n_neighbors=5,p=2,metric="minkowski")
knr.fit(train_x,train_y)
predict_y = knr.predict(test_x)

1 - np.sum(abs(predict_y - test_y) < 0.1*test_y)/test_y.shape[0]

Y efectivamente, el resultado es horrible. K-Vecinos tiene una tasa de error del 60%. No creo que merezca la pena insistir mucho en este algoritmo. Lo he configurado con distancia de Minkowski y P=2 (lo que equivale a la distancia euclídea).

Regresión Lineal

La regresión lineal en cambio sí que creo que puede ser interesante probarla. 


from sklearn.linear_model import LinearRegression

reg = LinearRegression()
reg.fit(train_x,train_y)
predict_y = reg.predict(test_x)

1 - np.sum(abs(predict_y - test_y) < 0.1*test_y)/test_y.shape[0]

Los resultados son los mejores hasta ahora: 43%. Pero podría ser mejor. Decido probar con máquinas de vector soporte.

SVM

Si la regresión lineal ha obtenido los mejores resultados, con una SVM seríamos capaces de llegar a ese mismo valor y posiblemente mejorarlo. Sin embargo, con un SVR lineal no pude llegar a replicarlo


from sklearn.svm import SVR

svr = SVR(kernel="linear", max_iter=-1)
svr.fit(train_x,train_y)
predict_y = svr.predict(test_x)

1 - np.sum(abs(predict_y - test_y) < 0.1*test_y)/test_y.shape[0]

Y conseguí un mísero 51% de error. Y este era el kernel que mejor funcionaba, el lineal. Probé poly con diferentes grados, rbf y sigmoid y todos ellos daban un resultado peor. Es hora de sacar la maquinaria pesada y entrar a tope con redes neuronales.

Perceptrón multicapa

He decidido saltarme directamente el Adaline y otros modelos más simples ya que no creo que mejoren la regresión lineal. Al perceptrón multicapa al principio le quise dar un gran número de neuronas (150 al principio) en una capa oculta, con una función de activación RELU y un solver de tipo ADAM. Como el resultado era muy parecido al de la SVM. Empecé a hacer cambios. LBFGS es bueno en datasets pequeños y efectivamente, los resultados mejoraron. Modifiqué a geometría de la red. Con 3 capas de 100 y RELU empecé a obtener resultados mucho mejores que con cualquier método, pero muy sensibles a variaciones aleatorias. 

Aquí apliqué normalización con StandardScaler, pero no cambió demasiado el resultado.


# Perceptron Multicapa
from sklearn.neural_network import MLPRegressor
from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
ss.fit(train_x)
train_x_ss = ss.transform(train_x)
test_x_ss = ss.transform(test_x)

mlp = MLPRegressor(hidden_layer_sizes=(100,100,100),activation="relu",solver="lbfgs",max_iter=50000)
mlp.fit(train_x_ss, train_y)
predict_y = mlp.predict(test_x_ss)

1 - np.sum(abs(predict_y - test_y) < 0.1*test_y)/test_y.shape[0]

Segundo test

Vuelvo a subir a Kaggle. Esta vez obtengo una tasa de error de 0.17975. Ha mejorado, pero bastante poco.

Random Forest

Los árboles parecieron ir bien, pruebo los Random Forest, con buenos resultados. Tasa de error en local del 39% y 0.17223 en Kaggle. No obstante, en Kaggle parezco haberme quedado estancado.


from sklearn.ensemble import RandomForestRegressor

rand = RandomForestRegressor()
rand.fit(train_x,train_y)
predict_y = rand.predict(test_x)

1 - np.sum(abs(predict_y - test_y) < 0.1*test_y)/test_y.shape[0]

Limpiando datos

Aquí empecé a buscar ayuda en Internet. Algo que no había realizado y que es muy interesante, es limpiar los datos. Eliminar algunas columnas específicamente y rellenar otras con valores faltantes con otros valores. Volví a ejecutar el Random Forest obteniendo un 38% en local pero un 0.14754 en Kaggle.

Conclusión

No he conseguido mi objetivo de llegar al top 25% de Kaggle. Todavía me quedan muchas cosas por aprender. Para próximos problemas debo realizar un análisis exploratorio de los datos más avanzado (aquí casi no lo he realizado) y debo entender otras técnicas de regresión (he de decir que prefiero problemas de clasificación a día de hoy). ¿Vosotros tenéis alguna idea de como mejorar los resultados, os escucho en los comentarios? Además, os dejo el código final.


import pandas as pd
import numpy as np

data_csv = pd.read_csv("train.csv")
test_csv = pd.read_csv("test.csv")
test_id = test_csv["Id"]
size = test_csv.shape
all_data = pd.concat((test_csv,data_csv),sort=False)

all_data.drop(['SalePrice','Utilities', 'RoofMatl', 'MasVnrArea', 'BsmtFinSF1', 'BsmtFinSF2', 'BsmtUnfSF', 'Heating', 'LowQualFinSF',
               'BsmtFullBath', 'BsmtHalfBath', 'Functional', 'GarageYrBlt', 'GarageArea', 'GarageCond', 'WoodDeckSF',
               'OpenPorchSF', 'EnclosedPorch', '3SsnPorch', 'ScreenPorch', 'PoolArea', 'PoolQC', 'Fence', 'MiscFeature', 'MiscVal'],
              axis=1, inplace=True)

all_data['MSSubClass'] = all_data['MSSubClass'].astype(str)


all_data['MSZoning'] = all_data['MSZoning'].fillna(all_data['MSZoning'].mode()[0])


all_data['LotFrontage'] = all_data['LotFrontage'].fillna(all_data['LotFrontage'].mean())

all_data['Alley'] = all_data['Alley'].fillna('NOACCESS')

all_data.OverallCond = all_data.OverallCond.astype(str)


all_data['MasVnrType'] = all_data['MasVnrType'].fillna(all_data['MasVnrType'].mode()[0])

for col in ('BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2'):
    all_data[col] = all_data[col].fillna('NoBSMT')

all_data['TotalBsmtSF'] = all_data['TotalBsmtSF'].fillna(0)


all_data['Electrical'] = all_data['Electrical'].fillna(all_data['Electrical'].mode()[0])


all_data['KitchenAbvGr'] = all_data['KitchenAbvGr'].astype(str)


all_data['KitchenQual'] = all_data['KitchenQual'].fillna(all_data['KitchenQual'].mode()[0])


all_data['FireplaceQu'] = all_data['FireplaceQu'].fillna('NoFP')


for col in ('GarageType', 'GarageFinish', 'GarageQual'):
    all_data[col] = all_data[col].fillna('NoGRG')

means 0
all_data['GarageCars'] = all_data['GarageCars'].fillna(0.0)

popular values
all_data['SaleType'] = all_data['SaleType'].fillna(all_data['SaleType'].mode()[0])

all_data['YrSold'] = all_data['YrSold'].astype(str)
all_data['MoSold'] = all_data['MoSold'].astype(str)

all_data
all_data['TotalSF'] = all_data['TotalBsmtSF'] + all_data['1stFlrSF'] + all_data['2ndFlrSF']
all_data.drop(['TotalBsmtSF', '1stFlrSF', '2ndFlrSF'], axis=1, inplace=True)

all_data = pd.get_dummies(all_data,drop_first=True)

test = all_data[0:size[0]]
data = all_data[size[0]:]

x = data
y = data_csv["SalePrice"]


from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder, OneHotEncoder, LabelBinarizer


train_x, test_x, train_y, test_y = train_test_split(x,y,test_size=1/3)


# ARBOLES DE DECISION

from sklearn.tree import DecisionTreeRegressor

tree = DecisionTreeRegressor(criterion="mse",splitter="best",max_depth=None,min_samples_split=10,min_samples_leaf=5)
tree.fit(train_x,train_y)
predict_y = tree.predict(test_x)


1-np.sum(abs(predict_y - test_y) < 0.1*test_y)/test_y.shape[0]


# K-MEDIAS

from sklearn.neighbors import KNeighborsRegressor

knr = KNeighborsRegressor(n_neighbors=7,p=1,metric="minkowski")
knr.fit(train_x,train_y)
predict_y = knr.predict(test_x)

1- np.sum(abs(predict_y - test_y) < 0.1*test_y)/test_y.shape[0]


# Regresion Lineal

from sklearn.linear_model import LinearRegression

reg = LinearRegression()
reg.fit(train_x,train_y)
predict_y = reg.predict(test_x)

1 - np.sum(abs(predict_y - test_y) < 0.1*test_y)/test_y.shape[0]


# SVM
from sklearn.svm import SVR
from sklearn.preprocessing import MinMaxScaler

svr = SVR(kernel="linear", degree=3, gamma="scale", max_iter=-1)
svr.fit(train_x,train_y)
predict_y = svr.predict(test_x)

1 - np.sum(abs(predict_y - test_y) < 0.1*test_y)/test_y.shape[0]



# Perceptron Multicapa
from sklearn.neural_network import MLPRegressor
from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
ss.fit(train_x)
train_x_ss = ss.transform(train_x)
test_x_ss = ss.transform(test_x)

mlp = MLPRegressor(hidden_layer_sizes=(100,100,100),activation="relu",solver="lbfgs",max_iter=50000)
mlp.fit(train_x_ss, train_y)
predict_y = mlp.predict(test_x_ss)

1 - np.sum(abs(predict_y - test_y) < 0.1*test_y)/test_y.shape[0]



# Random Forest
from sklearn.ensemble import RandomForestRegressor

rand = RandomForestRegressor(n_estimators=100)
rand.fit(train_x,train_y)
predict_y = rand.predict(test_x)

1 - np.sum(abs(predict_y - test_y) < 0.1*test_y)/test_y.shape[0]


# PREDECIR

rand.fit(x,y)

test_out = rand.predict(test)
out = pd.DataFrame({"Id" : test_id, "SalePrice" : test_out})
out.to_csv("out.csv",index=False)
]]>
https://blog.adrianistan.eu/cronica-neuronal-house-prices Sun, 4 Aug 2019 16:10:41 +0000
Web Semántica desde cero: Linked Data y SOLID https://blog.adrianistan.eu/web-semantica-linked-data-solid https://blog.adrianistan.eu/web-semantica-linked-data-solid Ya hemos visto tres componentes de la Web Semántica: RDF, RDF Schema y SPARQL. Sin embargo, todavía no hemos sido capaces de hiperconectar la información presente en diferentes servidores. La especificación Linked Data, nos da unas pautas a seguir sobre como usar HTTP para conseguir que todo esté hiperconectado. Además veremos el concepto de SOLID, que propuso Tim Berners-Lee para que la ciudadanía recuperase el poder sobre sus datos en la web.

Linked Data

Lo primero que hay que tener en cuenta es que Linked Data nos define ciertas restricciones , entre otras, las IRI deberán ser Web IRI, eso quiere decir que tienen que usar HTTP o HTTPS obligatoriamente como protocolo. Esto es así, porque un concepto clave es la dereferenciación. Es decir, dado un identificador, poder acceder al recurso en bruto. Esto es sencillo en recursos digitales, simplemente usa la dirección web donde está el recurso, pero en recursos físicos o conceptos abstractos es complicado. Por eso se distingue entre la dirección web del recurso y las de la información. Pongamos un ejemplo sencillo, la receta de la tarta de Santiago.

Podemos definir una IRI para el recurso tal que así: https://adrianistan.eu/recipe/tarta-santiago. En las tripletas que usen la tarta de Santiago usaremos ese identificador de recurso. ¿Pero qué debe de hacer el servidor cuando accedamos a ese recurso con un GET de HTTP? En primer lugar, nuestra petición vendrá acompañada de un campo Accept que deberá indicar el formato que queremos leer. Si es una máquina podemos pedir XML, JSON-LD o Turtle. Si es un humano podemos indicar HTML. Entonces, el servidor manda una respuesta HTTP 303 See other, indicando la URL a la que tenemos que acceder ahora, en el formato correcto. Así, desde una aplicación que pieda JSON-LD tendríamos una redirección:

http://adrianistan.eu/recipe/tarta-santiago -> http://adrianistan.eu/recipe/tarta-santiago.json

Y ya podríamos acceder a la información. Si en cambio, queremos leer la receta en formato humano la redirección sería así:

http://adrianistan.eu/recipe/tarta-santiago -> http://adrianistan.eu/recipe/tarta-santiago.html

Según los formatos que queramos leer tendremos una URL diferente, pero el IRI será el mismo siempre.

Una plataforma LinkedData completa debe ofrecer una serie de servicios para todos los verbos HTTP: GET, PUT, PATCH, HEAD, OPTIONS, DELETE, POST. De este modo se pueden diseñar APIs basadas en LinkedData. Sin embargo, este sistema no ha tenido mucho éxito y no voy a explicarlo en detalle.

SOLID

En 2018, Tim Berners-Lee, padre de la web, propuso SOLID, un proyecto para restablecer el poder de los usuarios en la web. SOLID significia Social Linked Data y es una aplicación de las técnologías de Linked Data para tratar de promover un sistema descentralizado. Podemos pensar en Solid como una especie de pincho USB. Esto son los pod. La gente tiene sus propios pods, ya sea autogestionados, o en sitios como los  de correo electrónico. Un pod no es más que un espacio de almacenamiento, pero fundamentado sobre las ideas de Linked Data. Así, nuestro pod contiene todos nuestros datos (a través de RDF) de cada aplicación y datos personales. Cuando una aplicación quiere acceder a nuestros datos, le podemos dar permiso desde nuestro pod para que acceda la información que consideremos oportuna. Al usar Linked Data y RDF, diversas aplicaciones son capaces de usar los mismos datos sin cambiar de formato.

Le deseo mucha suerte al proyecto SOLID, aunque no ha tenido mucho apoyo por parte de los desarrolladores. En parte el proyecto es bastante utópico, no es tan fácil que todas las empresas dejen sus datos y hagan las transición a SOLID tan fácilmente, solo ofrece ventajas para los usuarios, no para las empresas. Por otro lado, Linked Data nunca ha sido popular fuera de ambientes académicos o científicos (al parecer estas tecnologías se usan bastante en biomedicina), la mayoría de desarrolladores no conoce bien la tecnología y apenas existen librerías. Proyectos de software libre populares como Mastodon o PeerTube ni siquieran soportan SOLID.

Por otro lado, la transición a PODs por parte de los usuarios no es trivial y requiere de enseñar a los usuarios y a que sean conscientes de administrar sus propios pods.

He estado pensado sobre esto últimamente y creo que aunque la idea es buena tiene problemas prácticos. Pero, ¿y si combinamos los pods de SOLID con IPFS? ¿Y si SOLID se integrase en los navegadores? Yo mientras tanto voy a ir experimentando con esta tecnología en futuros proyectos.

]]>
https://blog.adrianistan.eu/web-semantica-linked-data-solid Mon, 22 Jul 2019 16:24:47 +0000
Web Semántica desde cero: SPARQL https://blog.adrianistan.eu/web-semantica-sparql https://blog.adrianistan.eu/web-semantica-sparql Hasta ahora hemos visto de forma bastante teórica el funcionamiento de RDF y de RDF Schema. Hemos dicho que RDF es un modelo de datos, interoperable y semántico. No obstante, hasta ahora no hemos accedido a la información allí expuesta. Por eso en este artículo veremos SPARQL, el lenguaje de consultas de RDF; del mismo modo que el modelo relacional tiene SQL o los documentos XML tienen XQuery.

SPARQL es un lenguaje de consultas frente a una base de datos RDF. Esta base de datos puede ser local o ser un servidor. Los servidores más conocidos son Virtuoso, Apache Jena, MarkLogic y Amazon Neptune. La sintaxis de SPARQL está inspirada en Turtle y en SQL. En un origen, SPARQL solo era un lenguaje de consulta, es decir, no podía añadir, editar o borrar información. Existe una extensión, relativamente aceptado llamado SPARQL Update que añade capacidades de edición a las consultas.

Los apellidos más comunes en la Wikipedia, un ejemplo de consulta SPARQL

SELECT

La sentencia más importante es SELECT. La salida de SPARQL siempre es en formato tabla, pudiendo dar para una misma consulta varias filas de resultados. Cada columna es una propiedad de las que pedimos en SELECT. Para describir qué valores de salida deben aparecer usamos variables. Las variables en SPARQL empiezan por el símbolo de interrogación. Estas variables las usaremos más adelante para filtrar.

FROM

La sentencia FROM en SPARQL es opcional. Indica el o los grafos sobre los que se debe buscar. Por defecto existe un grafo activo, si no lo indicamos, se usará este.

WHERE

La sentencia que nos permite filtrar las tripletas RDF. Su sintaxis es muy simple, simplemente escribimos tripletas con variables entre llaves. El motor SPARQL realizará unificación buscando las variables para las que todas las tripletas existen en la base de datos. Cada tripleta se separa por un punto. Podemos usar namespaces al igual que en otras partes de la web semántica

Con esto podemos escribir ya una consulta sencilla.


PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?city ?city_name
WHERE {
  ?city wdt:P31 wd:Q515 .
  ?city wdt:P17 wd:Q29 .
  ?city rdfs:label ?city_name .
}

Esta consulta, que podéis ejecutar en Wikidata vosotros mismos, nos devuelve una tabla de recursos y nombres de ciudades de España. Las tripletas, un pelín crípticas debido a la nomenclatura de Wikidata, son las siguientes:

  1. (CITY,esInstanciaDe,wikidata:Ciudad)
  2. (CITY,pais,wikidata:España)
  3. (CITY,rdfs:label,CITY_NAME)

Al ejecutar esta sentencia, el motor SPARQL busca valores para CITY y CITY_NAME para los que las tres tripletas estén definidas.

Aquí vemos el resultado de la ejecución. Como vemos, todas las salidad de la foto corresponden a Madrid, el mismo recurso pero en diferentes idiomas. Eso es porque rdfs:label soporta diferentes idiomas para el valor. Afortunadamente podemos filtrar por lenguaje gracias a una carecterística de RDF que permite especificar el lenguaje de las tripletas (xml:lang).

FILTER

La sentencia que debemos usar es FILTER, para filtrar los resultados en base a otros:


PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?city ?city_name
WHERE {
  ?city wdt:P31 wd:Q515 .
  ?city wdt:P17 wd:Q29 .
  ?city rdfs:label ?city_name .
  FILTER (lang(?city_name) = 'es')
}

Los resultados ahora son únicos ya que nos muestra solo los nombres en Español. FILTER es muy potente, también admite expresiones booleanas (como ?edad > 30) y regex para realizar una comprobación con expresiones regulares de la cadena de texto.

DISTINCT

Sin embargo, puede haber valores repetidos. En ocasiones simplemente queremos los elementos diferentes, no nos importa si se repiten o no. Podemos usar DISTINCT.

COUNT, SUM, MAX, MIN, ...

Al igual que en SQL, en SPARQL podemos usar agregadores en las consultas. Esto requiere SPARQL 1.1:


PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT (COUNT(?city_name) AS ?n_city)
WHERE {
  ?city wdt:P31 wd:Q515 .
  ?city wdt:P17 wd:Q29 .
  ?city rdfs:label ?city_name .
  FILTER (lang(?city_name) = 'es')
}

 

OPTIONAL

En SPARQL podemos definir tripletas en un WHERE que queremos que se cumplan, pero que no queremos que rechace los datos si no lo hace. Esto es normalmente usado para obtener datos que pueden existir o no. Se usa un bloque OPTIONAL dentro del WHERE.


PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?city_name ?brother_name
WHERE {
  ?city wdt:P31 wd:Q515 .
  ?city wdt:P17 wd:Q29 .
  ?city rdfs:label ?city_name .
  OPTIONAL {
    ?city wdt:P190 ?brother .
    ?brother rdfs:label ?brother_name .
    FILTER ( lang(?brother_name) = 'es') 
  }
  FILTER (lang(?city_name) = 'es')
}

En este caso obtenemos también las ciudades hermanas de una ciudad. Sin embargo, es algo que no todas las ciudades tienen por qué tener.

UNION

Al igual que en SQL, podemos disponer de varias condiciones diferentes, las cuales ambas son válidas. En SPARQL se expresa con la palabra UNION y dos bloques de llaves. Cada bloque es independiente del otro. Podemos decir que se ejecutan dos consultas y se juntan los resultados. Esto puede ser muy útil si tenemos datos con ontologías parecidas pero no iguales y queremos obtener datos de ambas de ellas a la vez.

Property Paths

Se trata de una característica que nos ahorra trabajo. Permiten describir caminos sobre el grafo, de longitud variable, de forma mucho más cómoda y breve. Sin entrar mucho en detalle, podemos usar la barra (/) para concatenar propiedades. Si tenemos una estructura tal que X p1 Y y Y p2 Z y solo nos interesan X y Z, podemos escribir X p1/p2 Z.


PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?city_name ?brother_name
WHERE {
  ?city wdt:P31 wd:Q515 .
  ?city wdt:P17 wd:Q29 .
  ?city rdfs:label ?city_name .
  OPTIONAL {
    ?city wdt:P190/rdfs:label ?brother_name .
    FILTER (lang(?brother_name) = 'es') .
  }
  FILTER (lang(?city_name) = 'es')
}

ORDER BY

Al igual que en SQL, se puede ordenar la tabla saliente por un campo de los presentes en SELECT.

LIMIT

Podemos limitar el número de resultados con LIMIT al final de la consulta.

CONSTRUCT

Si con SELECT generamos tablas, con CONSTRUCT podemos construir documentos RDF como salida de la consulta. Para ello usamos el lenguaje de tripletas que usamos en la cláusula WHERE, salvo que aquí las variables serán los valores de salida.


PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

CONSTRUCT {
  ?city rdfs:label ?city_name .
}
WHERE {
  ?city wdt:P31 wd:Q515 .
  ?city wdt:P17 wd:Q29 .
  ?city rdfs:label ?city_name .
  OPTIONAL {
    ?city wdt:P190/rdfs:label ?brother_name .
    FILTER (lang(?brother_name) = 'es') .
  }
  FILTER (lang(?city_name) = 'es')
}

ASK

Otra opción además de SELECTCONSTRUCT es ASK. Con ASK preguntamos si un conjunto de tripletas en cuestión existen en la base de datos. Esto nos devuelve true o false.


PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

ASK {
  ?city rdfs:label "Valladolid"@es .
  ?city wdt:P31 wd:Q515 .
  ?city wdt:P17 wd:Q29 .
}

ASK no admite WHERE, porque en realidad ya es un WHERE. Aquí hay un detalle sobre el soporte a idiomas de RDF. En la consulta la string es "Valladolid"@es y no "Valladolid". Esto es así para indicar que la string es Valladolid y está en español. No es lo mismo "Valladolid"@fr que "Valladolid"@es, para RDF son dos valores distintos.

INSERT / DELETE

Originalmente, SPARQL no permitía modificar los datos. Sin embargo, HP y otras empresas desarrollaron una extensión llamada SPARQL Update para poder hacerlo. Finalmente, fue incorporado a SPARQL 1.1. La sintaxis es Turtle. La primera orden es INSERT DATA.


PREFIX dc: <http://purl.org/dc/elements/1.1/>

INSERT DATA{
    <http://libros.com/2001> dc:title "2001: una odisea en el espacio" ;
                                           dc:creator "Arthur C. Clarke" .
}

DELETE DATA es exactamente igual. Si queremos usar variables, tenemos con una sintaxis ligeramente diferente para INSERT DELETE.


# DATOS ANTES
@prefix foaf: <http://xmlns.com/foaf/0.1/> . 
<http://example/president25> foaf:givenName "Bill" . 
<http://example/president25> foaf:familyName "McKinley" . 
<http://example/president27> foaf:givenName "Bill" . 
<http://example/president27> foaf:familyName "Taft" . 
<http://example/president42> foaf:givenName "Bill" . 
<http://example/president42> foaf:familyName "Clinton" .

# SPARQL
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
DELETE { ?person foaf:givenName 'Bill' } 
INSERT{ ?person foaf:givenName 'William' }
WHERE { ?person foaf:givenName 'Bill' }

# DATOS DESPUÉS
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
<http://example/president25> foaf:givenName "William" .
<http://example/president25> foaf:familyName "McKinley" .
<http://example/president27> foaf:givenName "William" .
<http://example/president27> foaf:familyName "Taft" .
<http://example/president42> foaf:givenName "William" .
<http://example/president42> foaf:familyName "Clinton" .

Conclusión

Con esto queda explicado el 90% del uso de SPARQL, el lenguaje de consulta propuesto por W3C para manipular grafos RDF. Es un lenguaje potente y con bastante soporte. Idealmente, los clientes podrían hacer las consultas directamente al servidor, supliendo en bastantes casos de uso a otras tecnologías como REST o GraphQL.

]]>
https://blog.adrianistan.eu/web-semantica-sparql Fri, 19 Jul 2019 21:06:08 +0000
Web Semántica desde cero: RDF Schema https://blog.adrianistan.eu/web-semantica-rdf-schema https://blog.adrianistan.eu/web-semantica-rdf-schema En el artículo anterior comentamos la piedra angular de la web semántica, el modelo de datos RDF. RDF es muy flexible y potente, pero ¿cómo conseguimos dar significado a las relaciones? Al fin y al cabo, las tripletas no dejan de ser tuplas de strings de cara al ordenador. Tiene que haber un componente, que permita hacer de intermediario entre el significado humano y la máquina. Esto son las ontologías. Las ontologías definen el sentido semántico de los recursos de RDF. RDF Schema nos permite diseñar ontologías de forma sencilla. Como veremos, añade operadores que podemos usar en RDF para definir las ontologías. Por tanto, RDF Schema no es comparable a XML Schema o JSON Schema, que sirven para validar que un documento sigue una estructura, sino que sirve para dotar de significado a la información.

Clases

El primer concepto clave de RDF Schema son las clases. Los recursos pueden pertenecer a una clase. La clase de un recurso se especifica con la propiedad ya vista anteriormente de rdf:type. Las clases se relacionan entre sí mediante propiedades. Aquí se pueden definir dos conceptos: rangodominio. Su significado es similar al de las funciones matemáticas. El rango representa los conjuntos de valores que puede tomar un atributo. Y el dominio, los recursos sobre los que tiene sentido aplicar ese atributo. Por tanto en una tripleta que use una propiedad, el recurso deberá estar dentro del dominio y el valor dentro del rango. Mencionar que las propiedades son también recursos, en esencia.

Además se definen relaciones de herencia entre clases, de tal modo que existen subclases y superclases, como en programación orientada a objetos. Las subclases heredan los atributos de su superclase y además son también superclase. La herencia que soporta RDF Schema es herencia múltiple, donde una clase puede tener varias superclases. También existe herencia de atributos, habiendo subatributos y superatributos. Si tenemos un atributo A, y un subatributo B se entiende que usar el atributo B implica el atributo A para el mismo objeto y valor.

Sin embargo, a diferencia de la POO, aquí las clases son conceptos abiertos y podemos integrar nuevas propiedades sobre la marcha, modificando clases ya existentes. Esta flexibilidad es necesaria, para que funcione en un mundo tan heterogéneo como es la web.

Clases predefinidas por RDF Schema

  • rdfs:Class: La superclase absoluta. Toda clase es hija de esta
  • rdfs:Resource: La clase que de la que heredan todos los recursos (las propiedades también son recursos)
  • rdfs:Literal: La clase de la que heredan todos los valores atómicos o literales
  • rdf:Property: La clase de la que heredan todas las propiedades

Propiedades predefindas por RDF Schema

  • rdfs:range: Para indicar el rango de una propiedad
  • rdfs:domain: Para indicar el dominio de una propiedad
  • rdfs:subPropertyOf: Para indicar que una propiedad hereda de otra
  • rdfs:subClassOf: Para indicar que una clase hereda de otra
  • rdfs:comment: Descripción en formato humano de un recurso
  • rdfs:label: El nombre en formato humano de un recurso
  • rdfs:isDefinedBy: Para indicar que recurso define a otro
  • rdfs:seeAlso: Información adicional sobre el recurso
  • rdfs:member: Miembro del recurso
  • rdf:type: La clase de un recurso

Ahora veamos un ejemplo de ontología en formato XML.

Aquí se definen dos clases: animal y horse. Y horse se define como subclase de animal. Por tanto un recurso de la clase horse es a la vez de la clase animal.

Esta ontología es más completa, porque define propiedades también

Inferencia

Una característica de las ontologías es que podemos realizar inferencias a través de ellas. Inferir algo es generar información nueva aplicando reglas lógicas. Veamos un ejemplo. Basándonos en la ontología de la última foto vamos a tener dos tripletas:

  1. ("Matemática Discreta","isTaughtBy","Grigoris Antoniu")
  2. ("isTaughtBy","rdfs:range","Miembro Académico")

Entonces podemos deducir que:

  • ("Grigoris Antoniu","rdf:type","Miembro Académico")

Ya que Grigoris está admitido en el rango de isTaughtBy y el rango de isTaughtBy solo admite "Miembro Académico".

Con esto ya tenemos lo básico para diseñar ontologías. Actualmente existen muchas ontologías: Schema.org (colaboración entre Google, Bing, Yandex, recursos web principalmente), Dublin Core (recursos digitales), DOAP (describir proyectos de software), FOAF (personas y actividades), SIOC (comunidades online), SKOS (para describir diccionarios, tesauros, conceptos).

RDF Schema es simple y fácil de entender, pero poco potente. Para solventar algunos de los problemas de RDF Schema se diseñó OWL. En el siguiente artículo veremos algo ya mucho más práctico, realizar consultas en SPARQL sobre recursos RDF.

]]>
https://blog.adrianistan.eu/web-semantica-rdf-schema Thu, 18 Jul 2019 12:43:45 +0000
Web Semántica desde cero: RDF https://blog.adrianistan.eu/web-semantica-rdf https://blog.adrianistan.eu/web-semantica-rdf Este artículo es el primero de una serie de tutoriales que van a explorar la web semántica a día de hoy, fuera de todo hype inicial y todo el universo de los datos enlazados. Lo primero será entender el concepto fundamental y lo segundo, RDF, piedra angular de la web semántica tal y como la describe W3C.

La idea fundamental de la web semántica, es tener una web con significado, interpretable tanto por humanos como por máquinas. En la web tradicional, las webs no tienen significado, para un ordenador no deja de ser texto que no es capaz de interpretar con facilidad. La web semántica es una red, interconectada de datos con valor semántico. El valor semántico se expresa a través de ontologías. RDF es el modelo de datos propuesto por W3C para modelar esta red. No es la única propuesta, también existen microformats, microdata y otros formatos. Sin embargo, RDF es lo suficientemente maduro como para basarnos en él directamente, además de ser muy flexible.

En esta imagen, cortesía de W3C, podemos ver el concepto de datos enlazados, en un grafo, donde la información se relaciona a través de propiedades comunes que todos entienden, pero la información reside en servidores diferentes. Así, aunque el perfil de usuario de Bob exista en http://example.org, sabemos que le interesa la Mona Lisa y que su autor fue Leonardo da Vinci. Simplemente siguiendo el grafo, podemos conocer esa información, aunque Bob no especificase ni guardase en su servidor en ningún momento que la Mona Lisa fue obra de Leonardo da Vinci.

¿Qué es RDF?

RDF son las iniciales de Resource Description Framework y es un modelo de datos. Define una forma en la que se representa toda la información. RDF se basa en el concepto, ya usado con anterioridad, de Objeto-Atributo-Valor (también llamado sujeto-predicado-objeto). Esto es muy sencillo de entender y a la vez, muy flexible. Básicamente, toda la información se almacena en tripletas. Una tripleta son tres valores: recurso, propiedad y valor. El recurso es el individuo sobre el que decimos algo, la propiedad es la característica sobre la que decimos algo y el valor es el contenido. Por ejemplo, podemos modelar una persona fácilmente usando tripletas.


("Alonzo Church","fecha-nacimiento","1903-06-14")
("Alonzo Church","lugar-nacimiento","Washington D.C.")
("Alonzo Church","profesion","matematico")

Los valores pueden ser a su vez recursos o pueden ser valores atómicos. De este modo se logra un grafo de relaciones. Las tripletas RDF se pueden almacenar en muchos formatos diferentes. Al principio, se hizo mucho énfasis en la sintaxis XML. Hoy día también son muy comunes las sintaxis Turtle, N3 o JSON-LD. RDF presenta un modelo de datos semitipado. Sobre esto hablaremos más adelante. En esto que hemos visto surge un problema, ¿cómo identificamos cada objeto dentro de un sistema interoperable? Si estuviésemos en Microsoft alguien habría dicho GUID, pero en la W3C la respuesta fue IRI.

¿Qué es IRI?

IRI son las iniciales de Internationalized Resource Identifier, se trata de la versión más extensa de las que las URL son un caso particular. Los IRI se definen como los identificadores en toda la web, según W3C. Una IRI puede ser http://www.google.com/ o tel:+34000111222. Ambas cadenas permiten identificar un recurso. Ese es el concepto de IRI. Si el identificador es a la vez un "puntero" ya que nos permite a su vez localizar la información, hablamos de IRL. En RDF tenemos que usar IRI, es decir, no tienen por qué permitir la localización de un recurso, pero sí lo deben identificar de forma inequícova y exclusiva.

Namespaces

En RDF hemos dicho que necesitamos usar IRI en las tripletas. Los IRI pueden ser muy largos, y con muchas partes repetidas. Para ello, se usan los espacios de nombres, que al igual que en XML, nos permiten acortar lo que escribimos. Un beneficio secundario es que podemos usar espacios de nombres ya definidos y tener así la interoperabilidad que es la base de la web semántica. Esto son las denominadas ontologías. Algunas de las más conocidas son Schema.org, Dublin Core y FOAF.

Sintaxis de RDF

Vamos a crear un ejemplo real de uso de RDF y comprobaremos las diferencias entre las distintas sintaxis. Para ello, vamos a elegir un conjunto de canciones.

Tripletas Objeto-Atributo-Valor


("https://blog.adrianistan.eu/song/Vagabundear","https://blog.adrianistan.eu/web-semantica-rdf/autor","https://blog.adrianistan.eu/group/NuestroPequeñoMundo")
("https://blog.adrianistan.eu/song/Vagabundear","https://blog.adrianistan.eu/web-semantica-rdf/album","https://blog.adrianistan.eu/web-semantica-rdf/CantarTierraMia")
("https://blog.adrianistan.eu/song/CantigaApocrifa","https://blog.adrianistan.eu/web-semantica-rdf/autor","https://blog.adrianistan.eu/group/NuestroPequeñoMundo")
("https://blog.adrianistan.eu/song/CantigaApocrifa","https://blog.adrianistan.eu/web-semantica-rdf/album","https://blog.adrianistan.eu/web-semantica-rdf/CantarTierraMia")
("https://blog.adrianistan.eu/group/NuestroPequeñoMundo","https://blog.adrianistan.eu/web-semantica-rdf/pais","https://blog.adrianistan.eu/country/España")
("https://blog.adrianistan.eu/group/NuestroPequeñoMundo","https://blog.adrianistan.eu/web-semantica-rdf/genero","https://blog.adrianistan.eu/gender/Folk")
("https://blog.adrianistan.eu/web-semantica-rdf/CantarTierraMia","https://blog.adrianistan.eu/web-semantica-rdf/fecha","1975")

Ahora veamos como estas tripletas, que ya usan IRI, se pasan a las diferentes sintaxis.

Grafo

La representación de grafo es útil para ver las interrelaciones, aunque rápidamente se puede volver bastante difícil de leer. En cualquier caso, siempre tenemos que tener en mente que el modelo RDF genera un grafo.

XML

La sintaxis XML es verbosa, pero admite muchas opciones. En primer lugar definimos una etiqueta global RDF, que en este caso está dentro del namespace rdf, que usamos para referirnos al estándar RDF de 1999 (la última versión es de 2014, pero en este ejemplo no hay cambios). Cada recurso tiene un rdf:Description, pero si queremos que este objeto tenga un tipo o clase (que se indica con rdf:type) podemos simplemente usar ese nombre como etiqueta (en el ejemplo lo hacemos con ex:cancion). Esto forma parte de RDF Schema y lo veremos en el siguiente artículo. Cada objeto tiene un IRI, expresado con el atributo rdf:about. rdf:about admite IRIs absolutas y relativas. También existe, rdf:ID, que solo admite rutas relativas, pero algo mejor. Cuando el valor de una tripleta es atómico, lo podemos incluir directamente, si es otro recurso, usamos rdf:resource

Los ficheros XML se pueden validar en el servicio de W3C.

JSON-LD


{
  "@context": {
    "ex": "https://blog.adrianistan.eu/web-semantica-rdf/",
    "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
    "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
    "xsd": "http://www.w3.org/2001/XMLSchema#"
  },
  "@graph": [
    {
      "@id": "https://blog.adrianistan.eu/group/NuestroPequeñoMundo",
      "ex:genero": {
        "@id": "https://blog.adrianistan.eu/gender/Folk"
      },
      "ex:pais": {
        "@id": "https://blog.adrianistan.eu/country/España"
      }
    },
    {
      "@id": "https://blog.adrianistan.eu/song/Vagabundear",
      "@type": "ex:cancion",
      "ex:album": {
        "@id": "#CantarTierraMia"
      },
      "ex:autor": {
        "@id": "https://blog.adrianistan.eu/group/NuestroPequeñoMundo"
      }
    },
    {
      "@id": "#CantarTierraMia",
      "ex:fecha": "1975"
    },
    {
      "@id": "https://blog.adrianistan.eu/song/CantigaApocrifa",
      "@type": "ex:cancion",
      "ex:album": {
        "@id": "#CantarTierraMia"
      },
      "ex:autor": {
        "@id": "https://blog.adrianistan.eu/group/NuestroPequeñoMundo"
      }
    }
  ]
}

JSON-LD fue creado mucho más recientemente, debido a la pérdida de popularidad de XML. Una parte de la comunidad quería seguir usando RDF con JSON, así surgió JSON-LD (JSON for Linking Data).

Como vemos, es menos verboso. Se distinguen dos partes fundamentales: @context y @graph. En @context van los namespaces que vamos a usar. En @graph tenemos un array con los recursos. Cada recurso tiene una propiedad @id, su IRI (tanto relativos como absolutos) y sus propiedades. Las propiedades pueden ser valores directamente u objetos con propiedad @id, que hacen referencia a otro objeto. JSON-LD es muy sencillo de usar, pero tiene poco soporte.

N3


@prefix ex: <https://blog.adrianistan.eu/web-semantica-rdf/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xml: <http://www.w3.org/XML/1998/namespace> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<https://blog.adrianistan.eu/song/CantigaApocrifa> a ex:cancion ;
    ex:album <#CantarTierraMia> ;
    ex:autor <https://blog.adrianistan.eu/group/NuestroPequeñoMundo> .

<https://blog.adrianistan.eu/song/Vagabundear> a ex:cancion ;
    ex:album <#CantarTierraMia> ;
    ex:autor <https://blog.adrianistan.eu/group/NuestroPequeñoMundo> .

<#CantarTierraMia> ex:fecha "1975" .

<https://blog.adrianistan.eu/group/NuestroPequeñoMundo> ex:genero <https://blog.adrianistan.eu/gender/Folk> ;
    ex:pais <https://blog.adrianistan.eu/country/España> .

N3 fue un formato diseñado específicamente para RDF por Tim Berners-Lee y W3C. Es posiblemente el lenguaje menos verboso. Al principio se definen los namespaces. A continuación se definen los recursos. El IRI del recurso y a continuación separado por punto y coma, propiedad y valor, finalizando con un punto. Siempre que queramos usar un IRI usamos comillas latinas. Los valores atómicos entre comillas inglesas. N3 dispone de muchas cosas, como soporte para reglas, un subconjunto con todo lo necesario para RDF es Turtle. Este código por ejemplo, también es válido en Turtle.

N-Triples


<https://blog.adrianistan.eu/song/Vagabundear> <https://blog.adrianistan.eu/web-semantica-rdf/album> <#CantarTierraMia> .
<https://blog.adrianistan.eu/group/NuestroPequeñoMundo> <https://blog.adrianistan.eu/web-semantica-rdf/genero> <https://blog.adrianistan.eu/gender/Folk> .
<https://blog.adrianistan.eu/song/CantigaApocrifa> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://blog.adrianistan.eu/web-semantica-rdf/cancion> .
<https://blog.adrianistan.eu/song/CantigaApocrifa> <https://blog.adrianistan.eu/web-semantica-rdf/autor> <https://blog.adrianistan.eu/group/NuestroPeque\u00F1oMundo> .
<https://blog.adrianistan.eu/group/NuestroPequeñoMundo> <https://blog.adrianistan.eu/web-semantica-rdf/pais> <https://blog.adrianistan.eu/country/Espa\u00F1a> .
<https://blog.adrianistan.eu/song/CantigaApocrifa> <https://blog.adrianistan.eu/web-semantica-rdf/album> <#CantarTierraMia> .
<#CantarTierraMia> <https://blog.adrianistan.eu/web-semantica-rdf/fecha> "1975" .
<https://blog.adrianistan.eu/song/Vagabundear> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://blog.adrianistan.eu/web-semantica-rdf/cancion> .
<https://blog.adrianistan.eu/song/Vagabundear> <https://blog.adrianistan.eu/web-semantica-rdf/autor> <https://blog.adrianistan.eu/group/NuestroPeque\u00F1oMundo> .

El último formato es básicamente lo mismo que he puesto al principio, pero de forma estandarizada. N-Triples es muy simple e incluso menos verboso que XML en ocasiones, pero hay que repetir los IRI de objeto cada vez. Tampoco soporta namespaces.

RDFa, microdata, ...

Existen otros formatos para representar RDF, quizá el más popular de los que faltan sea RDFa. Este formato se acopla a HTML y permite introducir RDF en HTML. Este formato es muy usado en SEO, aunque Google también reconoce JSON-LD.

Un paso más

A continuación lo más interesante sería definir ontologías o schemas, para informar de un determinado valor semántico. Gracias a RDF Schema podremos definir relaciones de significado básicas.

Con esto acabamos el primer apartado dedicado a la web semántica. Este artículo ha sido el maś teórico pero es clave para comprender lo que haremos más adelante

 

]]>
https://blog.adrianistan.eu/web-semantica-rdf Tue, 16 Jul 2019 20:11:15 +0000
miniKanren, programación lógica diminuta https://blog.adrianistan.eu/minikanren-programacion-logica https://blog.adrianistan.eu/minikanren-programacion-logica Cuando hablamos de programación lógica, lo primero que se nos viene a la mente es Prolog, un lenguaje del que ya he hablado varias veces, y que fue uno de los primeros lenguajes lógicos así como uno de los más populares. Hoy vengo a hablaros de una familia de lenguajes, llamada miniKanren, que demuestra que cualquier lenguaje de programación puede adoptar el paradigma lógico con solo 3 instrucciones. miniKanren está disponible como DSL en muchos lenguajes: Scheme, Haskell, Clojure (llamado core.logic), Python, C#, Elixir, Go, JavaScript, Rust, ... y prácticamente cualquier lenguaje.

Historia

miniKanren surgió originalmente en el libro The Reasoned Schemer, escrito por Daniel P. Friedman, William E. Byrd, Oleg Kiselyov, and Jason Hemann y publicado por MIT Press siendo la última edición la de 2018. miniKanren se implementa originalmente en Scheme (algo bastante habitual para el MIT) pero sus principios básicos se pueden implementar en cualquier lenguaje de programación de tipo general.

Así, es posible añadir el paradigma lógico a cualquier lenguaje añadiendo únicamente 3 funciones. Este conjunto de 3 funciones se denomina microKanren y normalmente las implementaciones de miniKanren implementan microKanren en el lenguaje de destino y el resto de miniKanren se implementa usando las funciones definidas en microKanren. Así miniKanren es una familia de lenguajes que no comparten sintaxis (la sintaxis depende del lenguaje de destino) aunque generalmente es muy parecida. Yo usaré miniKanren para Python, ya que es una implementación completa en un lenguaje bastante popular.

Las funciones de microKanren son las siguientes:

  • ==
  • fresh
    • Introduce un nuevo scope, definiendo variables lógicas. A su vez, realiza la conjunción de las relaciones que se definen.
  • conde
    • Introduce la posibilidad de múltiples respuestas, definiendo varios conjuntos independientes donde todas los predicados tienen que ser ciertos a la vez. Esto es una mezcla de AND y OR a la vez. Veremos un ejemplo más abajo.

Tutorial básico de miniKanren

Para el pequeño tutorial voy a usar LogPy, un dialecto de miniKanren para Python. Se instala fácilmente a través de PyPI.

Una vez lo tengamos instalado, vamos a ejecutar nuestro primer programa en miniKanren. 


from kanren import run, eq, var

x = var()
sol = run(1,x,eq(x,42))
print(sol[0])

En la primera línea importamos las funciones que vamos a usar, run, eq y var. var nos permite crear variables lógicas, que podemos usar en nuestros programas miniKanren. Cumple la misma función que fresh. run ejecuta el programa miniKanren. Tiene varios argumentos. El primero es el número de soluciones a buscar, el segundo es sobre qué variables vamos a buscar soluciones. El resto de argumentos, son predicados del programa miniKanren. En este caso usamos eq que es == pero como Python no admite la sintaxis, se modifica el nombre. Básicamente estamos pidiendo que X unifique con 42. La solución, que es un array de soluciones, nos indica claramente que para que X unifique con 42, X tiene que ser 42.

Si te acuerdas de Prolog, probablemente te estés preguntando como hacer relaciones, por supuesto miniKanren las soporta:


from kanren import run, var, Relation, facts

x = var()
parent = Relation()
facts(parent,("Homer","Bart"),
        ("Homer","Lisa"),
        ("Abe","Homer"))

sol = run(2,x,parent("Homer",x))
print(sol[0])
print(sol[1])

Para definir relaciones, primero creamos una relación. Después insertamos hechos correspondientes a esa relación. Esa relación la podemos usar en el código miniKanren de forma muy similar a Prolog. En este caso pedimos dos soluciones, ya que sabemos que existen dos posibles valores de X: Bart y Lisa.

Como hemos dicho, miniKanren no es un lenguaje independiente, es más bien un lenguaje ad-hoc al lenguaje que estamos usando. Por eso podemos combinar funciones de Python con miniKanren.


from kanren import run, eq, conde, var, Relation, facts

x = var()

parent = Relation()
facts(parent,("Homer","Bart"),
        ("Homer","Lisa"),
        ("Abe","Homer"))

def grandparent(x,z):
    y = var()
    return conde((parent(x,y), parent(y,z)))

sol = run(1,x,grandparent(x,"Bart"))
print(sol[0])

Obteniendo como respuesta "Abe". Aquí usamos conde. Conde hemos dicho antes que es un OR y AND a la vez. En un primer nivel es un OR, es decir, conde(A,B) es verdadero si A o B son verdaderos. Sin embargo si A y B son predicados compuestos, entre ellos se aplica la relación AND. Por ejemplo, conde((A,B,C),(D,E)) significa que o bien A, B y C son ciertos o D y E son ciertos para que el predicado sea cierto.

Con esto ya estamos listos para recrear el Hola Mundo del paradigma lógico, la mortalidad de Sócrates.


from kanren import run, var, fact, Relation

x = var() 
human = Relation()

fact(human,"Socrates")

def mortal(x):
    return human(x)

sol = run(1,x,mortal(x))
print(sol)

Así de simple y sencillo y sin tener que cambiar de lenguaje de programación.

Un predicado que suelen llevar las implementaciones de miniKanren que no es estrictamente necesario es membero que es cierto cuando X pertenece a la lista.


from kanren import run, membero, var

x = var()
sol = run(1,x,
    membero(x,[1,2,3]),
    membero(x,[2,3,4])
    )
print(sol)

En este caso habría dos posibles respuestas (2 o 3) ya que ambos números cumplen la condición de estar en ambas listas a la vez. La respuesta final vendrá dada por la implementación de miniKanren (en LogPy da 2).

 

Diferencias con Prolog

Una vez visto miniKanren, quizá haga falta saber las diferencias que tiene respecto a Prolog. En primer lugar, Prolog es un lenguaje independiente, con el que podemos diseñar aplicaciones desde cero. Esto sin embargo suele ser un problema, ya que aunque para ciertas tareas la programación lógica es muy buena, para otras es un verdadero infierno. Además, esto ha hecho que el número de librerías que existen para Prolog sean muy limitadas. Con miniKanren no tenemos ese problema. Básicamente estamos usando el lenguaje base y cuando queramos realizar programación lógica usamos las funciones de miniKanren.

Prolog también es un lenguaje que se deriva del sistema lógico en varios puntos, como los cortes, o la base de datos modificable de hechos (assertretract). Esto otorga flexibilidad, pero pierde en limpieza. Además, Prolog no realiza el chequeo de ocurrencias en la unificación, paso matemáticamente necesario pero computacionalmente costoso y que no suele suponer grandes diferencias de resultados en la práctica. Además Prolog soporta operaciones aritméticas. Esto no significa que en miniKanren no se puedan usar, pero dependen del lenguaje base, son por tanto indiferentes de cara a este DSL. Por otro lado en miniKanren no existe mutabilidad y todo código escrito es thread-safe, aunque no existen muchas implementaciones que aprovechen el paralelismo. En las búsquedas, Prolog suele usar el método primero en profundidad mientras que miniKanren suele ser primero en anchura.

Conclusión

Si bien Prolog es un lenguaje muy interesante, el hecho de ser algo separado lo ha hecho perder mucho interés durante los años y aunque se puede empotrar, esto no es fácil. miniKanren es un lenguaje lógico puro, que si bien es algo más limitado, la experiencia de uso es mejor. Quizá la mejor opción a día de hoy para realizar programación lógica en sistemas en producción. No obstante a día de hoy sigue faltando mucha documentación sobre miniKanren, aunque si tienes experiencia con Prolog, lo entenderás rápidamente.

]]>
https://blog.adrianistan.eu/minikanren-programacion-logica Fri, 14 Jun 2019 16:58:44 +0000
Cosas que (probablemente) no sabías de Python https://blog.adrianistan.eu/cosas-no-sabias-python https://blog.adrianistan.eu/cosas-no-sabias-python Python es un lenguaje muy popular hoy en día. Aunque pueda no ser el mejor, su desempeño es bueno, con mucha documentación, librerías, es cómodo y fácil de aprender. Python además sigue la filosofía de baterías incluidas, es decir, de intentar llevar de serie casi todo lo que vayas a poder necesitar. En este artículo vamos a ver algunas partes de Python no tan conocidas pero igualmente interesantes y útiles.

Frozensets y sets

Los sets y frozenset son estructuras de datos que replican el funcionamiento de los conjuntos de matemáticas. Esto quiere decir que es un contenedor de elementos, donde cada elemento solo puede aparecer una vez y no hay orden establecido entre los elementos. Los sets/frozensets tienen varias operaciones: unión, intersección, diferencia, diferencia simétrica y se puede comprobar si un conjunto es disjunto, subconjunto o superconjunto respecto a otro. La diferencia fundamental entre entre set y frozenset es que frozenset es inmutable, lo cuál puede ser mejor según nuestro problema (mejor estilo de programación y más rendimiento) pero es que además es hasheable, lo que significa que podemos usarlo en sitios donde se requiera que exista un hash, como por ejemplo, las claves de un diccionario.


a = set([27,53])
b = frozenset([27,42])

c = a | b # Unión
d = a & b # Intersección
e = a ^ b # Diferencia simétrica
f = a - b # Diferencia

print(c)
print(d)
print(e)
print(f)

if 42 in b:
    print("42 dentro de B")

if a >= b:
    print("A es superconjunto y B es subconjunto")

x = dict()
x[b] = 43
# x[a] = 43 daría error

Los sets además soportan set comprehensions:


a = { x for x in range(1,10)}

 

Statistics

Desde Python 3.4 existe un módulo llamado statistics que nos trae operaciones básicas de estadística ya implementadas. Evidentemente si vamos a hacer un uso intensivo, es mejor recurrir a las funciones de NumPy/SciPy pero en muchas ocasiones no necesitamos tanta potencia y este módulo nos viene de perlas. El módulo implementa funciones de media aritmética, media armónica, mediana, moda, varianza poblacional y varianza muestral (con sus respectivas desviaciones). Todas las funciones admiten los tipos int, float, Decimal y Fraction.


import statistics

data = [1,2,2,2,3,3,4,5,8]

a = statistics.mean(data)
b = statistics.median(data)
c = statistics.mode(data)
d = statistics.pvariance(data)
e = statistics.variance(data)

print(a)
print(b)
print(c)
print(d)
print(e)

Decimal y Fraction

Estos dos tipos sirven para representar números, pero son diferentes entre sí y también a int y float. En primer lugar veamos por qué son necesarios. En Python existe int para números enteros y float para números con parte decimal. Sin embargo, los float no son precisos. Los floats en Python son similares a los de C y usan el estándar IEEE 754. Esto está bien porque el hardware funciona así y es rápido, pero es imposible hacer cuentas de forma precisa e incluso representar algunos números es imposible. Para ello existen Decimal y Fraction que son dos maneras diferentes de conseguir precisión.

Decimal usa una precisión arbitraria para representar el número decimal como un número entero (por defecto con 28 posiciones). Decimal está especialmente recomendado para operaciones financieras, donde la precisión se conoce a priori. Fraction por otra parte usa el principio de que cualquier número racional se puede representar con una división de números enteros. Por tanto, almacena dos números, un numerador y un denominador, y los mantiene separados. Al hacer operaciones, como en cualquier otra fracción, se operan los números por separado, siempre manteniendo que tanto numerador como denominador sean enteros. De esta forma, y teniendo en cuenta que int tiene precisión arbitraria en Python, podemos representar con gran precisión todos los números racionales. Ambos métodos tienen el inconveniente de ser más lentos que float y gastar más memoria, pero son más precisos.

En este ejemplo que pongo abajo, se hace tres veces la misma operación: 0.1+0.2 que tiene que dar 0.3. Como verás si ejecutas el código, solo las versiones hechas con Decimal y Fraction hacen la operación bien, mientras que float falla.


a = 0.1
b = 0.2
c = 0.3

if a+b == c:
    print("Float: operación correcta")

from decimal import Decimal

pi = Decimal('3.14159')
a = Decimal('0.1')
b = Decimal('0.2')
c = Decimal('0.3')

if a+b == c:
    print("Decimal: operación correcta")

from fractions import Fraction
a = Fraction(1,10)
b = Fraction(2,10)
c = Fraction(3,10)

if a+b == c:
    print("Fracion: operación correcta")

F-Strings

Las f-strings fueron añadidas en Python 3.6 y son cadenas de texto que admiten formato de forma muy sencilla y flexible. Es la opción recomendada, sustituyendo al resto de otras formas de hacerlo (aunque siguen funcionando).


usuario = "Godofredo"

mensaje = f"El usuario {usuario} ha entrado al sistema"
print(mensaje)

Pathlib

En nuestro día a día es frecuenta trabajar con archivos en diferentes directorios. Pathlib (disponible desde Python 3.4) nos ayuda a manejar de forma sencilla rutas de forma multiplataforma. Pero aunque no necesitemos multiplataforma, las abtracciones de Pathlib son muy interesantes.


from pathlib import Path

root = Path('dev')
print(root)
# dev

path = root / 'pcc'

print(path.resolve())
# /home/aarroyoc/dev/pcc

Functools

El módulo functools es de los más interesantes de Python, sobre todo si vienes de la programación funcional, ya que nos permite manipular funciones. Quiero destacar tres funciones de este módulo: partial, lru_cache y reduce.

reduce nos permite reducir un iterable a un valor usando una función. Es una operación muy común en programación funcional y a partir de Python 3 hay que usarla a través de este modulo. Por ejemplo, podemos programar una función factorial de la siguiente forma:


from functools import reduce

def factorial(n):
    def multiply(a,b):
        return a*b

    return reduce(multiply,range(1,n+1))

Otra función interesante es lru_cache, disponible a partir de Python 3.2, la cuál es una cache LRU (Last Recently Used) ya construida para nuestro uso y disfrute. Si no lo sabéis, las cachés LRU son un tipo de caché con un tamaño fijo donde el elemento que se elimina cuando falta espacio es el último en ser usado (es decir, leído o añadido a la caché). Las cachés LRU se usan en muchos sitios (como en las propias CPUs. Además, esta versión de Python dispone de tamaño infinito si se lo configuramos, lo cuál es muy interesante. La función se usa como decorador y compara los argumentos de llamada para ver si la función ya fue llamada con esos argumentos, y en ese caso devolver el valor calcualdo con anterioridad. Si no existe, lo calcula y lo almacena. Esto es lo que se llama memoización, una técnica muy usada en programación dinámica.


@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

Usar lru_cache en funciones recursiva aumentará el consumo de memoria, pero puede mejorar la velocidad significativamente si se llama muchas veces a una función con los mismos argumentos.

Por último, partial nos permite definir funciones parciales. ¿Asombroso eh? Si no has tocado programación funcional seguramente te parezca que no tiene sentido. Para que nos entendamos, una función parcial es una función que ya tiene algunos argumentos rellenados, pero otros no. No estamos hablando de argumentos por defecto, porque eso se define sobre la propia función, sino de una función que es una especialización de otra más genérica. Veamos un ejemplo, vamos a definir un nuevo print que no añada un salto de línea al final:


from functools import partial

printx = partial(print,end='')

printx("Hola amigos")
print(" de Adrianistán")

De este modo, printx se define como la función parcial de print con el argumento end ya configurado a caracter vacío en vez de '\n' que es que usa print por defecto.

Depuración

Si quieres depurar código Python, lo normal es usar pdb, el módulo con funciones de depuración. Sin embargo, su uso se hacía un poco farragoso, así que desde Python 3.7 se puede usar breakpoint. Simplemente llama a breakpoint (no hay que importar nada) y el programa entrará en modo depuración.


import statistics

data = [1,2,2,2,3,3,4,5,8]

a = statistics.mean(data)
b = statistics.median(data)
c = statistics.mode(data)
breakpoint()
d = statistics.pvariance(data)
e = statistics.variance(data)

print(a)
print(b)
print(c)
print(d)
print(e)

Otras funciones útiles de depuración son dir, vars, type y help. Dir muestra los atributos de un objeto o las variables locales, vars es similar pero más completa y difícil de leer. type devuelve el tipo de una variable y help muestra la documentación asociada a una clase o función.

Enums

En algunos lenguajes de programación existe un tipo llamado enum o enumeration, que suele ser un tipo que admite únicamente un conjunto finito de valores (normalmente pequeño y mapeadas a un número entero, pero con la seguridad del tipado extra). Python no disponía de esta funcionalidad (como muchos lenguajes de script) hasta Python 3.4, donde podemos crear clases que sean enumeraciones.


from enum import Enum, auto

class OperatingSystem(Enum):
    LINUX = auto()
    MACOSX = auto()
    WINDOWSNT = auto()
    HAIKU = auto()
    SOLARIS = auto()

print(OperatingSystem.HAIKU)

Data classes

En la programación orientada a objetos es habitual a veces encontrarnos con clases donde la mayor parte de las líneas las perdemos en definir el constructor con datos, getters y setters. Las data classes, disponibles a partir de Python 3.7 nos permiten ahorrarnos todo este trabajo.


from dataclasses import dataclass

@dataclass
class PC:
    cpu: str
    freq: int
    price: int = 1000

    def discount(self):
        return self.price*0.8

pc = PC("Intel Core i7",4000,2000)
print(pc.discount())
print(pc)

Tipado

Al usar data classes te habrás dado cuenta que hay que poner tipos. Esto se llama type hints y no son tipos como en otros lenguajes. Me explico. En Python existen tipos, pero no existe forma de forzar a que un argumento de una función sea de un tipo o de otro. Los type hints no son más que indicaciones que Python sabe como ignorar, el intérprete de Python no les hace caso. Son los IDEs, los linters como mypy y algunas librerías como pydantic los que en todo caso realizan la verificación de tipos. Los tipados tal y como se conocen ahora se añadieron en Python 3.5 y en el módulo typing hay muchos tipos avanzados.


from typing import Dict

Options = Dict[str,str]

def add_option(options: Options, key: str, content: str) -> None:
    options[key] = content

a = dict()
add_option(a,"b","c")

Collections

El módulo collections tiene numerosas estructuras de datos más avanzadas para mayor comodidad o mejor rendimiento. Voy a comentar tres: defaultdict, Counter y deque.

defaultdict es una estructura de datos que particularmente encuentro muy interesante. Se trata de diccionarios con valor por defecto. Muy simple de entender y más útil de lo que parece. Por ejemplo, en una cuadrícula bidimensional de todo ceros salvo unos pocos elementos que queremos que sean uno. Luego al analizar si se nos pregunta por una coordenada siempre podremos devolver el valor del diccionario (sin realizar ninguna comprobación).


from collections import defaultdict

mapa = defaultdict(lambda: 0)
mapa[(0,0)] += 1
mapa[(15,67)] += 1

Counter es un contador, simple y llanamente, pero extremadamente cómodo y sorprendentemente útil. Existen dos formas básicas de crear un contador, una es pasando un iterable y Counter se encargará de ir contando todos los elementos y otra es pasar ya un diccionario de las cuentas hechas. Los contadores tienen operaciones especiales como la suma, la resta, la unión y la intersección de contadores.


from collections import Counter

c = Counter("Rodrigo Diaz de Vivar")
print(c)
d = Counter({"d": 6, "i": 8})

e = c + d

print(e)

Por último, deque es una lista doblemente enlazada. Esto puede ser mucho más interesante que list para ciertas operaciones. List es un simple array, con acceso aleatorio muy rápido, pero las modificaciones pueden ser lentas. En deque las modificaciones son rápidas pero el acceso aleatorio es muy lento. Las operaciones son prácticamente las mismas que en list, así que no voy a poner código.

Itertools

El módulo itertools de Python es otro gran módulo lleno de funcionalidad interesante, en este caso para trabajar con iteradores, basado en los lenguajes APL, Haskell y SML.

Lo primero a mencionar son los iteradores infinitos: countcyclerepeat. Por ejemplo, cycle es un iterador que toma un iterador y cuando este se acaba lo vuelve a repetir, así hasta el infinito.

Luego tenemos otras funciones como takewhile (genera un iterador a partir de otro iterador mientras se cumpla una condición), chain (que une iteradores), groupby (genera subiteradores según un atributo de agrupamiento) y tee (que genera N subiteradores).


from itertools import *

a = range(1,100)
b = takewhile(lambda x: x<50,a)
c = dropwhile(lambda x: x<50,a)
d = chain(b,c)
e = sorted(d,key=lambda x: x%2 == 0)
for k, g in groupby(e,key=lambda x: x%2==0):
    print(k)
    for x in g:
        print(x,end=' ')
    print("")

Por último, el módulo tiene funciones muy útiles de combinatoria como el producto cartesiano, las permutaciones, las combinaciones (con y sin repetición).

Zip, reversed y enumerate

La función zip es un builtin, es decir, no hay que importar nada. La función zip junta dos iteradores en uno y un iterador de tuplas. Zip tiene la longitud del iterador más corto, si necesitas que tome la longitud del más largo, la función zip_longest de itertools hace justamente eso.

La función reversed recorre el iterador en orden inverso y enumerate proporciona una tupla con el elemento y su posición en el iterador.

Any y all

Any y all son otros dos builtins de Python. Devuelven true cuando algún elemento del iterador cumple la condición (any) o todos la cumplen (all). Usando estas expresiones podemos comprobar que una palabra es palindrómico de la siguiente forma:


def palindromic(sequence):
    return all(
        n == m
        for n, m in zip(sequence, reversed(sequence))
    )
print(palindromic("abba"))

property

Cuando trabajamos con código orientado a objeto en Python, es recomendable usar propiedades, en vez de funciones manuales de getters y setters.


class P:

    def __init__(self,x):
        self.x = x

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, x):
        if x < 0:
            self.__x = 0
        elif x > 1000:
            self.__x = 1000
        else:
            self.__x = x

staticmethod y classmethod

Estas funciones también son decoradores de funciones dentro de una clase. Su uso es similar es parecido. classmethod es para métodos que pueden llamarse tanto de forma estática como con un objeto instanciado, un ejemplo típico son los métodos factoría. staticmethod sin embargo se refiere a métodos estáticos dentro de una clase, aunque en Python tampoco se usan demasiado.

isinstance e issubclass

Para acabar con las funciones propias de la orientación a objetos, estas dos funciones nos permiten comprobar si un objeto es instancia de una clase y si una clase es subclase de otra.

Conclusiones

Como veis, Python tiene muchas cosas interesantes. Espero que en esta lista haya al menos alguna cosa que no conociéseis. Si además conoces alguna otra cosa no tan conocida pero que consideras útil dentro de Python, puedes ponerla en los comentarios.

]]>
https://blog.adrianistan.eu/cosas-no-sabias-python Sun, 9 Jun 2019 15:13:38 +0000
Programación web en Prolog https://blog.adrianistan.eu/programacion-web-prolog https://blog.adrianistan.eu/programacion-web-prolog Como ya hemos visto con anterioridad, Prolog es un muy buen lenguaje para ciertos problemas. Hoy en día, el lenguaje que se creía que sería el futuro está prácticamente olvidado. No obstante, existen algunas personas que se han esforzado para que Prolog siga siendo un lenguaje útil. ¿Y qué requisito es básico para un lenguaje útil? ¡Que pueda usarse para hacer un servidor web claro! Es por eso que decidí emprender una aventura quijotesca y ver hasta donde era capaz de llegar Prolog en el desarrollo web. Para ello voy a usar SWI Prolog.¿Suena alocado? Pues ya lo estás usando, el servidor de gestión de imágenes de Adrianistán (https://files.adrianistan.eu) está hecho en Prolog.

Handlers y configuración

Como todo buen web framework, en este debe poder configurarse unas rutas (handlers) y algunos parámetros tales como el puerto, número de hilos (Prolog por defecto usará varios hilos para manejar las peticiones web en paralelo) y más cosas. Este código es el más simple que nos permite acceder a una página y mostrar un hola mundo.


:- use_module(library(http/thread_httpd)).
:- use_module(library(http/http_dispatch)).
:- use_module(library(http/html_write)).
:- use_module(library(http/http_unix_daemon)).


:- http_handler(/,index,[]).

index(_Request) :-
    format('Content-Type: text/plain~n~n'),
    format('Hola Mundo').

:- http_daemon([port(4777),fork(false)]).

Si ejecutamos con SWI Prolog, veremos que el servidor arranca:

Como vemos, el código es super simple. Centrémonos más en la parte de los handlers. Es tan simple que parece que faltan cosas, ¿cómo definir rutas con parámetros? ¿y la distinción entre GET y POST? Todo es muy sencillo como veremos.

El predicado http_handler tiene aridad 3. En primer lugar la ruta, en segundo lugar el predicado que responderá a la petición y en tercer lugar una lista con las opciones.

En primer lugar las rutas se definen con algo denominado FileSpec, que es una construcción de Prolog que permite expresar rutas relativas a partir de un alias absoluto. No solo se usa aquí, sino en todas partes. Fíjate en como usamos use_module. Tenemos library y dentro una ruta. library representa un path absoluto con ese alias y dentro se representa el resto de la ruta. Otro alias predefinido y que podemos usar en el contexto de una aplicación web es root. root(.) es equivalente a /. Si queremos soportar paths variables, ¡usamos variables de Prolog! Luego nos aseguramos que que esas variables también esten presentes en el predicado de la respuesta.

Aquí alguien se puede extrañar ya que este predicado no tiene la misma aridad arriba que abajo, aparentemente. En realidad como http_handler siempre añade la variable Request, Prolog no tiene ningún problema.


:- use_module(library(http/thread_httpd)).
:- use_module(library(http/http_dispatch)).
:- use_module(library(http/html_write)).
:- use_module(library(http/http_unix_daemon)).


:- http_handler(/,index,[]).
:- http_handler(root(user/User),user(User),[method(get)]).

index(_Request) :-
    format('Content-Type: text/plain~n~n'),
    format('Hola Mundo').

user(User,_Request) :-
    format('Content-Type: text/plain~n~n'),
    format('Usuario: '),
    format(User).

:- http_daemon([port(4777),fork(false)]).

Por otro lado en las opciones podemos definir gran variedad de ajustes, entre ellos el método de la petición. Como bien es sabido en Prolog, Prolog unifica, ¡incluso en una aplicación web! Así que la clave es cuanto más explícitos seamos, en menos ocasiones unificará el handler.

Ficheros, HTML y JSON

Ya hemos visto como funcionan los handlers pero hemos estado generando las respuestas con unas llamadas a format un poco cutres. Por fortuna, Prolog nos hace la vida más fácil si vamos a devolver un fichero, un HTML dinámico o un archivo JSON.

Para ficheros, es trivial, simplemente respondemos con el predicado http_reply_file.

Para HTML hay dos maneras de hacerlo. Una es usando el DSL, otra es usando plantillas. Aquí vamos a ver el DSL ya que es más estilo Prolog. Para responder usando este método usamos el predicado reply_html_page. Este toma un head y un body. Este DSL es muy intuitivo y permite escribir estructuras anidadas así como atributos. Este DSL además permite ser usado con DCG, así que se puede escribir en un predicado aparte y se pueden hacer includes de forma cómoda. Además se pueden pasar variables, aunque en este ejemplo no lo hago. 

Por último, para responder un JSON usamos el predicado reply_json. SWI Prolog admite varias sintaxis para representar JSON. Podemos usar dentro un término json la sintaxis Clave-Valor, Clave=Valor o Clave(Valor). Esta última es la que he usado. Además a partir de SWI Prolog 7 se pueden usar los diccionarios de Prolog también.

Finalmente aquí un ejemplo con los tres predicados en uso:


:- use_module(library(http/thread_httpd)).
:- use_module(library(http/http_dispatch)).
:- use_module(library(http/html_write)).
:- use_module(library(http/http_json)).
:- use_module(library(http/http_unix_daemon)).


:- http_handler(/,index,[]).
:- http_handler(root(user/User),user(User),[method(get)]).
:- http_handler('/foto.jpg',static,[]).
:- http_handler('/api',api,[]).

index(_Request) :-
    reply_html_page(
        title('Prolog WebApp'),
        [\index_body]).

index_body -->
    html([
        h1('Prolog WebApp'),
        p([style('color: red'),id('parrafo')],'Hola Mundo'),
        ul([
            li('Alonso Quijana'),
            li('Sancho Panza')   
        ])
    ]).

api(_Request) :-
    reply_json(json([
       name('Alonso Quijana'),
       email('quijana@mail.xp') 
    ])).

user(User,_Request) :-
    format('Content-Type: text/plain~n~n'),
    format('Usuario: '),
    format(User).

static(Request) :-
    http_reply_file('burgos.jpeg',[],Request).

:- http_daemon([port(4777),fork(false)]).

Peticiones POST

Vamos a ver como leer esos formularios desde Prolog. En primer lugar para obtener un listado de todas las variables POST usamos el predicado http_read_data. Después simplemente buscamos en la lista con el archiconocido predicado member, unificando con una variable libre para obtener ya el dato directamente.


form(Request) :-
    http_read_data(Request,Data,[]),
    member(email=Email,Data),
    format('Content-Type: text/plain~n~n'),
    format('El correo indicado es: '),
    format(Email).

Autenticación Basic

Un ejemplo más elaborado es la autenticación HTTP. Aquí defino un predicado auth que va a contener la validación.


auth(Request) :-
    Realm = 'Prolog WebApp',
    (
        string_codes("123456789",Password),
        member(authorization(Header),Request),http_authorization_data(Header,basic(admin,Password)) -> true
        ;
        throw(http_reply(authorise(basic, Realm)))
    ).

Para usarla, simplemente hay que poner el predicado auth en la primera línea de cualquier predicado de handler.

Conclusiones

El código final está en el repositorio de ejemplos del blog. Respecto a la base de datos. No lo he comentado porque sería otro asunto, además Prolog ya es una base de datos gracias a assert y retract. SWI Prolog incluye además una muy buena implementación de RDF. Espero que con este post os haya abierto la mente a un tipo de programación diferente, la lógica, que no solo sirve para resolver problemas "raros" sino para aplicaciones web de forma extremadamente sencilla (¡una vez comprendamos Prolog claro!). Se trata de un enfoque muy interesante y con el que se puede llegar a ser muy productivo. Yo me esperaba algo mucho peor, pero la gente detrás de SWI Prolog se ha esforzado para que todo sea simple y claro.

 

 

]]>
https://blog.adrianistan.eu/programacion-web-prolog Thu, 25 Apr 2019 17:22:18 +0000
Bosque, el nuevo lenguaje de programación de Microsoft https://blog.adrianistan.eu/bosque-lenguaje-programacion-microsoft https://blog.adrianistan.eu/bosque-lenguaje-programacion-microsoft Hace unos pocos días Microsoft Research, la parte de investigación de Microsoft, publicó Bosque, un nuevo lenguaje de programación. El lenguaje sigue en fase experimental y el compilador es un poco patatero (está hecho en TypeScript y necesita Node.js para funcionar). Pero eso es lo de menos, porque son detalles. Lo importante son las ideas que aporta.

La filosofía de Bosque

Como muchos otros lenguajes, Bosque nace de unas ideas que pretende seguir. La filosofía detrás de Bosque es la siguiente:

El lenguaje de programación Bosque ha sido diseñado para escribir código que es simple, obvio y fácil de razonar con él tanto para humanos como para máquinas. Las características claves del diseño proporcionan formas de evitar la complejidad accidental en el proceso de desarrollo. El objetivo es mejorar la productividad de los desarrolladores, mejorar la calidad del software y habilitar una nueva clase de compiladores y herramientas de desarrollo.

En su descripción además nos informa que toma conceptos de ML (el padre de Haskell) y JavaScript, además el autor, Mark Marron, también ha admitido que hay algunas cosas de P que le gustan.

Hola Mundo en Bosque

Actualmente el compilador está en fase experimental, si quieres probarlo estas son los comandos necesarios para hacerlo funcionar en Linux (Node.js tiene que estar instalado)


git clone https://github.com/Microsoft/BosqueLanguage
cd BosqueLanguage/ref_impl
npm install
npm run-script build
npm test
node bin/test/app_runner.js FICHERO_BOSQUE

Y un hola mundo tiene la siguiente pinta.


namespace NSMain;

entrypoint function main(): String {
	return "Hola Mundo";
}

Y el resultado es el siguiente:

A primera vista ya parece un poco raro ya que hemos devuelto directamente un string en la función main. En Bosque no hay efectos colaterales, lo que también implica que no puede haber (de momento) entrada y salida más allá de las propias funciones de punto de entrada. En Bosque el punto de entrada es arbitrario, pero de momento la mayoría de ejemplos usan la función main dentro de NSMain.

Variables inmutables

En Bosque todas las variables son inmutables. Eso quiere decir que no es posible modificar su contenido una vez han sido asignadas:


namespace NSMain;

function add(x: Int, y: Int): Int {
	return x+y;
}

entrypoint function main(): Int {
	var result = add(7,12);
	return result;
}

No obstante, es posible usar azúcar sintáctico y tener variables "mutables" con var!, la gracia es que debido a otros diseños de Bosque que veremos más adelante, no altera el resultado global.

Parámetros por referencia

También es posible modificar ciertos parámetros por referencia directamente de forma segura, aseguranda la ausencia de efectos colaterales. Esto permite una programación más cómoda.

Tipado de Strings

Los diseñadores de Bosque saben que el tipo String es muy versátil y se usa para muchas cosas, pero para el compilador sigue siendo String. ¿Por qué no tipar estos strings? En un ejemplo sacado de la documentación, se usa Zipcode como un tipo de String. Los zipcodes solo pueden pasar a string y un string no puede pasar directamente a Zipcode si no es verificado antes. Así evitamos mezclar Zipcodes y Emails (por ejemplo), que en otros lenguajes serían ambos Strings.


function foo(zip: String[Zipcode], name: String) {...}

var zc: String[Zipcode] = ...;
var user: String = ...;

foo(user, zc) //Type error String not convertible to String[Zipcode]
foo(zc, user) //ok

Invocaciones flexibles

Aquí de forma parecida a Python, Bosque permite gran flexibilidad a la hora de pasar argumentos.


function nsum(d: Int, ...args: List[Int]): Int {
    return args.sum(default=d);
}

function np(p1: Int, p2: Int): {x: Int, y: Int} {
    return @{x=p1, y=p2};
}

//calls with explicit arguments
var x = nsum(0, 1, 2, 3); //returns 6

var a = np(1, 2);         //returns @{x=1, y=2}
var b = np(p2=2, 1);      //also returns @{x=1, y=2}

//calls with spread arguments
var t = @[1, 2, 3];
var p = nsum(0, ...t);    //returns 6 -- same as explicit call

var r = @{p1=1, p2=2};
var q = np(...r);         //returns @{x=1, y=2} -- same as explicit call

Operaciones de golpe

Bosque permite hacer modificaciones sobre una estructura o tupla de golpe, en una sola línea, indicando todos los campos que se modifican y su operación. Esto puede ser muy interesante si permite generar instrucciones SIMD automáticamente y aprovechar de una vez toda esa potencia subyacente que tienen los procesadores actuales.

Manejo de nones

En Bosque existe el valor especial none similar al NULL o undefined de otros lenguajes. Bosque tiene un tratamiento especial para este tipo de valores que permite que sea más cómodo trabajar con ellos. La verdad es que esto me ha sorprendido ya que la tendencia actual es a reducir los NULL. Aunque claro, si se permiten solamente en situaciones controladas por el compilador podría ser muy interesante, ya que sería más simple y sencillo que las alternativas que existen en Haskell y Rust.

Procesamiento iterativo

Bosque no tiene bucles. No al menos nada comparable al WHILE y al FOR. Otro trabajo de investigación de Microsoft (Mining Semantic Loop Idioms) afirmó que en la gran mayoría de casos todos los bucles pertenecen a unos casos concretos de bucle. Es por ello, y con el objetivo de hacer la programación más predecible, que no existen los bucles como tal. Otra vez, un ejemplo desde la documentación


var v: List[Int?] = List@{1, 2, none, 4};

//Chained - List@{1, 4, 16}
v->filter(fn(x) => x != none)->map[Int](fn(x) => x*x)

//Piped none filter - List@{1, 4, 16}
v |> filter(fn(x) => x != none) |> map[Int](fn(x) => x*x)

//Piped with noneable filter - List@{1, 4, 16}
v |??> map[Int](fn(x) => x*x)

//Piped with none to result - List@{1, 4, none, 16}
v |?> map[Int](fn(x) => x*x)

Recursión

En la mayoría de lenguajes funcionales se suelen suplir algunas de las carencias habituales (bucles, variables inmutables, etc) con complejas jerarquías de recursión. Estos sistemas funcionan pero no son muy claros. Bosque quiere romper con este esquema permitiendo solo llamadas recursivas a funciones que explícitamente lo marquen.

Igualdad

Las comprobaciones de igualdad siempre están definidas por el tipo. Nunca se comprueba la referencia, evitando así uno de los tipos de fallos más comunes en lenguajes OOP.

Y muchas otras cosas. El objetivo final es que cada programa Bosque tenga un único y bien definido resultado, y cada función lo mismo. De este modo será más fácil encontrar y razonar sobre los fallos, obteniendo software de mejor calidad en menos tiempo.

Habrá que ver como evoluciona Bosque, que ahora mismo está muy verde y sujeto a muchos cambios. Puede ser una interesante alternativa en el futuro o un simple experimento de Microsoft Research que no vaya a más (o cuyos resultados se apliquen a C# por ejemplo). Vosotros, ¿qué opináis?

 

]]>
https://blog.adrianistan.eu/bosque-lenguaje-programacion-microsoft Wed, 24 Apr 2019 16:12:49 +0000
Miri: una máquina virtual para Rust https://blog.adrianistan.eu/miri-maquina-virtual-rust https://blog.adrianistan.eu/miri-maquina-virtual-rust Hace poco salía la noticia de que Miri ya estaba disponible para instalar en las versiones nightly de Rust. Pero, ¿qué es Miri? y ¿por qué es importante una máquina virtual para Rust?

Rust es un lenguaje compilado a código máquina. No necesita intérpretes ni máquinas virtuales para funcionar, sin embargo, hay situaciones donde Miri es útil. Hay dos casos en particular donde es interesante y se habilita la posibilidad de un tercero:

  • Evaluar las expresiones const de forma más eficiente
  • Detectar fallos que solo pueden ser detectados en runtime
  • Permitirá tener un REPL de Rust en el futuro

¿Cómo? Las expresiones const son código Rust que se ejecuta en tiempo de compilación, para obtener una constante que finalmente será la que vaya en el ejecutable. Cuando rustc se encuentra con una de ellas, compila el código necesario para obtener el valor de vuelta de esa función. Este proceso es largo, sobre todo teniendo en cuenta que ese código solo va a ser ejecutado durante la compilación del programa entero, por lo que no es necesaria una compilación completa a código máquina. De este modo se mejora la velocidad en la mayoría de expresiones const.

Por otro lado, Rust no es capaz de prevenir todos los fallos posibles dada su naturaleza y en código unsafe todavía menos. Miri comprueba los siguientes fallos:

  • Accesos fuera de memoria y use-after-free
  • Uso incorrecto de variables no inicializadas
  • Violación de precondiciones intrínsecas (llegar a unreachable_unchecked , llamar copy_nonoverlapping con rangos que se superponen, ...)
  • Accesos a memoria no lo suficientemente bien alineados
  • Violaciones de invariantes de tipos básicos (por ejemplo un bool que no sea ni 0 ni 1 internamente o un valor de enum fuera de rango)

En este sentido Miri aporta algo similar a Valgrind de C pero mejorado.

Actualmente Miri tiene algunas limitaciones, por ejemplo, no soporta concurrencia, acceso al sistema de archivos o acceso a la red.

¿Cómo usar Miri?

Miri está disponible solo en algunas versiones nightly. Al tiempo de escribir este post, los siguientes comandos permiten obtener la copia más reciente de Miri:


rustup toolchain install nightly-2019-04-17
rustup component add miri --toolchain=nightly-2019-04-17
cargo +nightly-2019-04-17 miri test
cargo +nightly-2019-04-17 miri run

El uso es muy sencillo, test ejecuta los test y run ejecuta la aplicación si es un binario.

Pongamos un ejemplo de un test que funciona normalmente pero falla en Miri.


#[test]
fn does_not_work_on_miri() {
    let x = 0u8;
    assert!(&x as *const _ as usize % 4 < 4);
}

Ejecutando cargo test todo funciona bien pero con miri test falla.

Y con esto ya tenemos otra herramienta más para nuestra programación en Rust

]]>
https://blog.adrianistan.eu/miri-maquina-virtual-rust Thu, 18 Apr 2019 12:15:18 +0000
Programación lineal: alcanzando la perfección https://blog.adrianistan.eu/programacion-lineal-alcanzar-perfeccion https://blog.adrianistan.eu/programacion-lineal-alcanzar-perfeccion La programación lineal es una disciplina dentro de las matemáticas, más concretamente, del campo de la investigación operativa muy interesante. La problemática que trata de resolver es la de asignar recursos limitados entre actividades competitivas de la mejor manera posible, es decir, optimizar este reparto. Programación significa en este contexto planificación.

La historia de la programación lineal está muy ligada a la investigación operativa. Esta disciplina tuvo su auge durante la Segunda Guerra Mundial, cuando los ejércitos financiaron a los matemáticos para encontrar formas óptimas de asignar los recursos limitados de los que se disponía durante la guerra (comida, medicamentos, soldados, aviones, ...). Durante estos años se sentaron las bases pero no fue hasta el año 1947 cuando George Dantzig desarrolló el algoritmo símplex. Este algoritmo, ha sido calificado como uno de los 10 algoritmos más importantes del siglo XX, y no es para menos, ya que abre la puerta a resolver un montón de problemas en un tiempo relativamente eficiente.

El algoritmo símplex tiene una complejidad de [latex]O(2^{n})[/latex], muy costoso computacionalmente, pero afortunadamente la mayoría de problemas se resolverán en tiempo polinómico.

En primer lugar vamos a describir las características de la programación lineal, ya que existen otros modelos, programación entera, programación binaria, ...

  • Modelo lineal: es decir, todo lo que aparecen son funciones lineales. Esto significa que vamos a trabajar SIEMPRE con números reales (esta es una de las grandes limitaciones)
  • Variables de decisión: variables del modelo que deben determinarse, la solución suele formar parte de ellas
  • Función objetivo: Una función donde intervienen una o más variables de decisión y que tenemos que optimizar: ya sea maximizar o minimizar.
  • Restricciones: desigualdades e igualdades que tienen que cumplir las variables de decisión en una solución válida, también llamada, factible

Imagina que tenemos 30 piezas de hierro. Nuestro objetivo es tener el mayor número de piezas de hierro al cabo de 100 segundos. Para ello podemos comprar un extractor básico o un extractor eléctrico. El extractor básico tiene un coste de 10 piezas de hierro y el eléctrico 23 piezas. La velocidad de minado del primero es 0.25 piezas por segundo y el segundo es 0.5 piezas por segundo. ¿Cuál sería la forma óptima de hacerlo?

Bien, el modelo de programación lineal es muy simple, hay dos variables de decisión (cantidad de extractores básico y eléctricos que compramos), una función objetivo a maximizar que representa la cantidad final de hierro que vamos a tener. Esto se calcula teniendo en cuenta el hierro que va a producir en 100 segundos y el coste que tiene comprar uno. Y una restricción que sirve para indicar que tenemos una cantidad de hierro limitado y cada extractor tiene un precio en hierro diferente. Esto se expresa de la siguiente forma:

 

$$X_1: \text{cantidad de extractores básicos a fabricar} \\ X_2: \text{cantidad de extractores eléctricos a fabricar} \\ \text{maximizar} z= (100*0.25-10)X_1 + (100*0.5-23)X_2 \\ 10X_1 + 23X_2 \le 30 \\ X_i \ge 0, i=1,2$$

Y ya está. Ahora, si usamos un programa que soporte el algoritmo símplex obtendremos la solución. Existen muchos programas, y por lo general, bastante caros: IBM CPLEX, Xpress de FICO, Gurobi e incluso el GNU GLPK. No obstante, estos programas son bastante avanzados y admiten muchas opciones. Para resolverlo voy a usar SWI Prolog, pero no nos centremos mucho en los detalles:


:- use_module(library(simplex)).

extractor(S) :-
    gen_state(S0),
    extractor_constraints(S0,S1),
    maximize([15*x1,27*x2],S1,S).

extractor_constraints -->
    constraint([10*x1,27*x2] =< 30).

El resultado es que X1=3 y X2=0, lo que quiere decir, que lo óptimo es gastarse el hierro en comprar 3 extractores básicos y ninguno avanzado. Una cosa que podemos plantearnos es si modificando el coste de compra del extractor avanzado, la solución pasa a ser otra. Simplemente modificamos la restricción y la función objetivo:

$$X_1: \text{cantidad de extractores básicos a fabricar} \\ X_2: \text{cantidad de extractores eléctricos a fabricar} \\ \text{maximizar} z= (100*0.25-10)X_1 + (100*0.5-20)X_2 \\ 10X_1 + 20X_2 \le 30 \\ X_i \ge 0, i=1,2$$

Y en este caso la solución sí es diferente, pero es una fracción, lo cuál no tiene mucho sentido. Este es uno de los inconvenientes principales de la PL, que las variables son infinitamente divisibles.

Mezclas

Vamos a poner un ejemplo un poco más complejo, en este caso con mezclas.

Una refinería mezcla 4 tipos de crudos para producir 3 tipos de gasolina los datosde interés son los siguientes:

Crudos Octanaje Barriles diarios €/barril
1 68 4000 31
2 86 5050 33
3 91 7100 36
4 99 4300 39

 

Gasolina Octanaje mínimo PVP Demanda diaria
1 95 45 Como máximo 10000 barriles
2 90 43 Cualquier cantidad
3 85 41 Al menos 15000 barriles

La compañía vende el crudo que no utiliza en la producción de gasolinas a 39€/barril si su índice de octanaje está por encima de 90 y a 37€/barril si está pordebajo de 90.

¿Cuál es el plan de producción óptimo?

$$X_{ij}: \text{barriles de crudo i para producir gasolina de tipo j} \\ V_i: \text{barriles de crudo tipo i que se venden} \\ C_i: \text{barriles de crudo de tipo i que se compran} \\ G_J: \text{barriles de gasolina de tipo j que se producen y se venden} \\ Max z = 45G_1+43G_2+41G_3+37V_1+37V_2+39V_3+39V_4-31C_1-33C_2-36C_3-39C_4 \\ C_1 \le 4000 \\ C_2 \le 5050 \\ C_3 \le 7100 \\ C_4 \le 4300 \\ G_1 \le 10000 \\ G_3 \ge 15000 \\ 68X_{11} + 86X_{21} + 91X_{31} + 99X_{41} \ge 95G_1 \\ 68X_{12} + 86X_{22} + 91X_{32} + 99X_{42} \ge 90G_2 \\ 68X_{13} + 86X_{23} + 91X_{33} + 99X_{43} \ge 85G_3 \\ X_{11} + X_{21} + X_{31} + X_{41} = G_1 \\ X_{12} + X_{22} + X_{32} + X_{42} = G_2 \\ X_{13} + X_{23} + X_{33} + X_{43} = G_3 \\ X_{11} + X_{12} + X_{13} + V_1 = C_1 \\ X_{21} + X_{22} + X_{23} + V_2 = C_2 \\ X_{31} + X_{32} + X_{33} + V_3 = C_3 \\ X_{41} + X_{42} + X_{43} + V_4 = C_4 $$

Cuya solución es la siguiente: X13=3457.41, X21=1509.97, X23=3540.03, X33=7100, X41=3397.44, X43=902.564, G1=4907.41, G3=15000, V1=542.593, C1=4000, C2=5050, C3=7100 y C4=4300.

Transporte

Un tipo especial de PL es el problema del transporte. Tenemos N orígenes y M destinos, cada ruta entre origen y destino tiene un coste. ¿Cómo transportamos de forma óptima?

Por ejemplo, tenemos dos fábricas de chips verdes, A y B, y queremos transportarlo a 3 sitios, X, Y y Z. A-X cuesta 50, A-Y cuesta 100, A-Z cuesta 70. B-X cuesta 60, B-Y cuesta 30 y B-Z cuesta 200. En A hay 200 chips, en B hay 300, y queremos en 100 chips en X, 200 en Y y 200 en Z.

$$X_{ij}: \text{chips que se mueven de i a j} \\ Min z = 50X_{AX} + 100X_{AY} + 70X_{AZ} + 60X_{BX} + 30X_{BY} + 200X_{BZ} \\ X_{AX}+X_{AY}+X_{AZ} = 200 \\ X_{BX}+X_{BY}+X_{BZ}=300 \\ X_{AX}+X_{BX}=100 \\ X_{AY} + X_{BY} = 200 \\ X_{AZ} + X_{BZ} = 200 \\ X_i \ge 0, i=A,B j=X,Y,Z$$

transporte(S) :-
    gen_state(S0),
    transporte_constraints(S0,S1),
    minimize([50*ax,100*ay,70*az,60*bx,30*by,200*bz],S1,S).

transporte_constraints -->
    constraint([ax,ay,az] = 200),
    constraint([bx,by,bz] = 300),
    constraint([ax,bx] = 100),
    constraint([ay,by] = 200),
    constraint([az,bz] = 200).

La solución es Xaz = 200 (todo lo que se produce en A va a Z) y Xbx = 100 y Xby = 200 (desde B se mandan 100 a X y 200 a Y).

El problema del transporte es aplicable no solo a transporte propiamente dicho, sino que se puede aplicar a muchos otros problemas.

Espero que esta corta introducción a la programación lineal os haya picado la curiosidad por si no lo conocíais. Esto es una disciplina muy amplia y se podría hacer un blog solo de esto.

]]>
https://blog.adrianistan.eu/programacion-lineal-alcanzar-perfeccion Sun, 14 Apr 2019 23:29:06 +0000
Agromapa de Castilla y León https://blog.adrianistan.eu/agromapa-castilla-leon https://blog.adrianistan.eu/agromapa-castilla-leon Hace unos meses salió la convocatoria del Concurso de Datos Abiertos de Castilla y León 2018. Esta era la tercera vez que se organizaba y la primera en la que me sentía suficientemente preparado como para presentar algo.

El objetivo del concurso es muy simple, hay que elaborar un proyecto usando, entre otras cosas, los datos abiertos que ofrece la Junta de Castilla y León. El resultado fue Agromapa, una web interactiva donde podemos ver los cultivos más populares de los municipios de forma muy vistosa, así como saber en qué zonas se cultivan unos y otros. Una forma didáctica de saber más sobre la comunidad. Para el proyecto usé la librería D3, usando un fichero GeoJSON que obtuve de OSM Boundaries (los datos son de OpenStreetMap, pero desde OpenStreeMap es difícil sacar cierta información). Además he usado datos de Wikidata, como las fotos de las especies naturales y de los municipios. Todo ello usando además los CSV de la propia Junta de Castilla y León, los cuáles tenían algunos problemas (por ejemplo había palabras mal escritas en los datos de columna, un "bug" que me llevó un buen rato encontrar).

Además usé Python con Pandas para generar el GeoJSON con todos los datos que se muestran en pantalla. Este fichero ocupa la friolera de 17MB, y aunque soy consciente que podía haber sido más pequeño, no tuve tiempo para optimizar mucho el fichero (implicaba entre otras cosas rehacer algunas cosas)

Aquí tenéis una imagen y un enlace al proyecto.

http://adrianistan.eu/agromapa

Además, todo el código lo tenéis disponible en GitHub, para que podáis ver como se ha hecho cada cosa.

https://github.com/aarroyoc/agromapa

El caso es que presente el proyecto y no salió ganador, pero salió con una mención especial. Esto significa que no hay dotación económica pero si reconocimiento por su parte de ser un buen proyecto. Hoy era el día de la entrega de premios, a la que había sido invitado. Fue en el Monasterio de Nuestra Señora del Prado, actual sede de la Consejería de Educación y acudieron el Vicepresidente de la Junta y la Consejera de Economía y Hacienda.

En general me ha gustado mucho la experiencia, he mejorado mis capacidades con D3, SVG y SPARQL y el resultado ha gustado a mucha gente, entre familiares, amigos y miembros del jurado. ¡Os invito a que le echéis un vistazo!

]]>
https://blog.adrianistan.eu/agromapa-castilla-leon Mon, 8 Apr 2019 16:43:13 +0000
¿Qué pasó con la Web Semántica? https://blog.adrianistan.eu/que-paso-con-web-semantica https://blog.adrianistan.eu/que-paso-con-web-semantica La web semántica ha sido un concepto estudiado y desarrollado por W3C prácticamente desde el comienzo de la web misma por el propio Tim Berners-Lee, aunque su desarrollo se aceleró con el artículo de 2001 en Scientific American, The Semantic Web. La idea es muy sencilla, si la web ha servido para enlazar documentos, de distintos orígenes, la web semántica es lo mismo, pero reduciéndonos a lo más fundamental, los datos en sí y además, de forma comprensible para ser procesado por una máquina.

El concepto es muy prometedor, pero con el paso de los años cada vez se oye menos sobre el tema. En este artículo analizaremos las tecnologías que componen la web semántica y los problemas que tiene en el mundo actual.


Demasiados estándares

La web semántica dispone de varios estándares con la misma finalidad, o con objetivos superpuestos. Esto es una redundancia que impide a los desarrolladores saber exactamente a qué adherirse. Por ejemplo, es habitual pensar que RDF es un tipo de XML. Pero esto es incorrecto. RDF es un estándar abstracto que no especifica como se debe almacenar su información. Aparte de XML existen muchas otras implementaciones, válidas, para representar datos RDF: Turtle, N3, N-Quads, RDFa, JSON-LD,...

No solo RDF sufre este problema. En los lenguajes de representación de reglas de inferencia también ocurre. Existe de hecho un estándar diseñado para la interoperabilidad entre estos lenguajes, RIF: Although originally envisioned by many as a "rules layer" for the semantic web, in reality the design of RIF is based on the observation that there are many "rules languages" in existence, and what is needed is to exchange rules between them.. Mientras tanto W3C mantiene otro estándar para las reglas, SWRL (Semantic Web Rule Language). Existen además otros estándares inpendientes: R2ML y RuleML.

Estos estándares disponen muchas veces de varios perfiles o dialectos, diseñados para cargas de trabajo diferentes. RIF por ejemplo, tiene varios dialectos, algunos estandarizados (DTB, Core, FLD, BLD y PRD) y otros sin estandarizar (CASPD, URD y SILK principalmente).

 



Esta fragmentación también se ha visto en el propio formato RDF. Al ser demasiado abstracto, algunos programadores diseñaron de forma más pragmática los formatos microformats y microdata, que aunque pueden ser sustituidos por RDFa, estos primeros son más sencillos de comprender. Estos estándares tienen unas ontologías fijas, útiles, adaptadas y sin posibilidad de ser extendidas o reemplazadas (aunque microformats2 si es más potente es este aspecto). La idea es evolucionar la web en vez
de revolucionar la web, proponiendo cambios que causen poca fricción en el desarrollo web actual. Esto forma parte del movimiento de la web semántica en letras minúsculas.


Demasiada abstracción y falta de formación

Muchos estándares de la W3C se definen de forma muy abstracta, priorizando la corrección de los estándares desde un punto de vista teórico. Los desarrolladores no están acostumbrados a tales niveles de abstracción y esperan tecnología mucho más concreta.

Si a esto se le suman pocos ejemplos de uso práctico, poca documentación de entrada y una falta de marketing respecto a otras tecnologías, muy poca gente va a poder entender el modelo mental de RDF y sacar toda su potencia.


Burocracia

Algunas partes de la web semántica sufren de un problema burocrático. No solo los estándares llegan tarde y sin implementación de referencia, sino que muchas veces el proceso de realizar modificaciones o añadir un elemento es lento y laborioso.

El proceso de comité de W3C ha demostrado ser lento e ineficaz en otras áreas. Por ejemplo, los navegadores han dejado de seguir el estándar HTML de W3C para centrarse en el estándar HTML de WHATWG, una organización creada por ellos mismos, mucho más rápida en definir nuevas características. Gracias a WHATWG se pudo lograr HTML5, que luego fue estandarizado por W3C cuando todos los navegadores ya habían implementado las características del estándar. Otro caso similar existe con SVG, el estándar de gráficos vectoriales, que ha sido criticado por nunca lanzar la revisión SVG 2.0, debido a problemas internos.

Otros componentes de la web semántica como las URI ni siquiera son asignadas por la W3C sino que la asignación de esquemas depende de IANA.


Protección de datos

A nivel legal, muchos empresas y organizaciones prefieren mantener los datos de carácter personal de forma privada. No existe un sistema integrado que permita acceder solamente a ciertos agentes acceder a determinada información de una base de datos. La web semántica parece preparada únicamente para datos de carácter totalmente abierto, no parcialmente abiertos.

Valor de los datos

Según diversos analistas los datos se han convertido en el nuevo petróleo. Una empresa vale más por los datos que tiene que por los servicios que presta. Si asumimos esto, ¿por qué regalar los datos? Sería tan tonto como regalar dinero. Las empresas han aprendido a mantener los datos de forma cerrada, para consumo propio o previo pago de grandes cantidades de dinero.

Existen empresas, no obstante, que facilitan APIs para acceder y manipular la información. La gracia de estos sistemas es que están limitados en cuanto a peticiones y a la información a la que podemos acceder. Estas APIs, aunque suelen seguir un modelo REST común, no son interoperables entre sí. De este modo se intenta mantener la filosofía del jardín cerrado. Que los desarrolladores puedan hacer software que complemente a mi plataforma y no al revés.

A grandes empresas como Google o Facebook no les interesa que toda su información sea accesible por otros desarrolladores, ya que les restaría valor a su empresa. Es evidente que estas empresas no van a invertir en estas tecnologías, al menos para su desarrollo público. En el caso de Google está actitud es muy evidente. En 2010 compró Freebase, una base de datos semántica abierta bastante importante, para después cerrarla e integrar su tecnología en Google Knowledge Graph.

Sólamente organizaciones como Wikipedia, MusicBrainz, DBpedia y los distintos organismos públicos mantienen bases de datos accesibles de forma totalmente abierta y transparente de los datos. Muchas de estas acciones englobadas dentro del movimiento Open Data pero la adopción por parte de las empresas ha ido decreciendo.


Escalabilidad

Se ha criticado que los modelos basados en RDF no son escalables. En efecto la mayoría de implementaciones no han sido tan trabajadas como lo han podido ser bases de datos relacionales, o software de procesamiento Big Data. Es cierto que servidores como Virtuoso son capaces de manejar miles de millones de tripletas, pero sigue habiendo problemas subyacentes relacionado con la dificultad del indexado de RDF.


Laboriosidad

Formalizar el conocimiento lleva un esfuerzo considerable. Lleva esfuerzo tanto en la parte de creación de los esquemas como en la parte de generación de los datos. Con la aparición de sistemas Big Data, la tendencia es a almacenar los datos tal y como es generada, para luego ser analizada. Aunque se podrían usar estos sistemas, para intentar formalizar los datos, no parece ser una práctica común. Existen pocas herramientas enfocadas a hacer menos laborioso el trabajo, con la única excepción de Protégé para la elaboración de ontologías.

Han surgido ontologías más simples, con enfoque práctico en organizaciones como Schema.org, donde los buscadores (Google, Bing, Yandex, ...) definen ontologías válidas para RDFa, con el fin de dotar a sus buscadores de mayor poder semántico. Otros ejemplos son la iniciativa Dublin Core.


Falta de implementaciones

La web semántica dispone de muchas programas, de tipo académico, muchos de ellos abandonados al poco tiempo. No obstante no hay apenas sistemas listos para producción. Muchos sistemas de gestión de bases de datos con soporte a tecnologías de la web semántica no lo hacen de forma exclusiva. Existen varias bases de datos con soporte a SPARQL, Virtuoso, MarkLogic, Apache Jena, Amazon Neptune, Neo4j, aunque no en todos estos sistemas SPARQL es la opción recomendada por sus autores. Algunos estándares no están implementados (OWL2 solo es implementado por MarkLogic de forma completa).

Fuera de las bases de datos existen todavía menos librerías que manejen RDF de forma sencilla para el programador, siendo el único ejemplo destacable la librería RDFLib para Python, que incluye un intérprete de SPARQL para consultas locales, así como un adaptador para usar PostgreSQL como almacén de tuplas RDF.

W3C no se ha encargado de mantener implementaciones de referencia de sus estándares.


Conclusiones

Creo firmemente que estamos ante una situación de worse is better. Concepto ingenieril donde los haya que viene a decir se
resume en estas máximas:

  • El diseño debe ser simple, tanto en implementación como en interfaz. La simplicidad es el elemento más importante del diseño
  • El diseño no debe ser inconsistente. La consistencia puede ser sacrificada por la simplicidad, o pueden eliminarse del diseño las partes menos comunes o que introducen complejidad innecesaria
  • El diseño cubrir todas las situaciones cuál probables sea que ocurran. Todos los casos razonablemente probables deben ser cubiertos. En todo caso, la completitud es la última propiedad a tener en cuenta.

Este estilo, denominado escuela de Nueva Jersey tiene su máximo exponente en UNIX y C, piezas tecnológicas de gran éxito cuya fama se basa en el uso de estas reglas. Un enfoque ingenieril basado en compromisos. Todo en contraposición a la escuela de MIT, de origen académico, identificado popularmente con Lisp. El enfoque académico no admite incorreciones, inconsistencias y completitud. La simplicidad es deseable, pero sin sacrificar en ningún momento las características anteriores.

Las tecnologías de la web semántica parecen cumplir el enfoque de la escuela del MIT. Un producto académico, que engloba a todo, a costa de reducir simplicidad.

Afortunadamente muchos de los problemas mencionados aquí están siendo tratados actualmente en una lluvia de ideas, denominada EasierRDF y W3C tiene intención de ser más dinámica, sobre todo después de haber perdido el control estandarizador de HTML y otras tecnologías clave, en favor de organizaciones más dinámicas.

Además, se están desarrollando nuevos estándares de gran interés, como IPLD (InterPlanetaryLinkedData) que permite extender la web semántica a IPFS, Git y plataformas basadas en blockchain como Ethereum y Bitcoin.

]]>
https://blog.adrianistan.eu/que-paso-con-web-semantica Sun, 10 Mar 2019 10:56:10 +0000
API de Adrianistán https://blog.adrianistan.eu/api-adrianistan https://blog.adrianistan.eu/api-adrianistan Con unos días de diferencia respecto a la publicación del nuevo blog, ha llegado una característica muy interesante, la API del blog.

Se trata de una API semántica, usando el lenguaje de consulta SPARQL, el cual es una recomendación de W3C. La API solo permite hacer consultas. Aunque teóricamente en SPARQL también se pueden modificar datos, no está activado por motivos evidentes. La API es de acceso público y cualquiera puede usarla, además al ser SPARQL podéis combinar las consultas con bases de datos de otros servidores. La API expone una ontología basada en Schema.org. En esa página encontraréis los nombres de los campos, su formato, etc

En este corto post voy a demostrar como hacer uso de esta funcionalidad. En este artículo no pretendo explicar SPARQL, aunque si veo interés puede ser un futuro tutorial.

Una petición simple

Para hacer una petición, podemos usar cURL o cualquier programa similar para lanzar una petición a https://blog.adrianistan.eu/api. Hay tres requisitos:

  • la petición debe ser POST
  • la petición debe declarar su Content-Type como application/sparql-query (tal y como dicta el estándar W3C)
  • en el cuerpo debe estar la consulta SPARQL en texto plano

Veamos un ejemplo sencillo usando cURL


curl -X POST https://blog.adrianistan.eu/api -d "SELECT ?id WHERE { ?id ?b ?c.} LIMIT 5" --header "Content-Type: application/sparql-query"

El resultado es el siguiente:

El resultado está en formato JSON. Actualmente no es configurable el formato de salida.

Una petición más compleja

Esta primera petición no aportaba gran cosa, simplemente devolvía resultados. Ahora vamos a hacer una query seria.

La query devolverá las URL de la gente que comenta en el blog:


PREFIX schema: <http://schema.org/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

SELECT DISTINCT ?url 
WHERE {
    ?comment rdf:type schema:Comment.
    ?comment schema:author ?author.
    ?author schema:url ?url.
    FILTER(!regex(?url,"adrianistan.eu","i")).
}

Y si enviamos la query a la API, nos responde con las URL de vuestros comentarios:

Como véis, la API es bastante potente y aunque su uso vaya a ser anecdótico, quería tener una API de consulta en condiciones.

 

]]>
https://blog.adrianistan.eu/api-adrianistan Wed, 13 Feb 2019 19:06:42 +0000
Año nuevo, blog nuevo https://blog.adrianistan.eu/ano-nuevo-blog-nuevo https://blog.adrianistan.eu/ano-nuevo-blog-nuevo Llevaba un mes entero sin escribir, ¿creíais que estaba descansando en una playa paradisíaca?

Evidentemente no, primero porque las playas no son mis lugares favoritos, y segundo porque realmente estaba haciendo algo gordo. En realidad eran varias cosas, pero la primera que puede ver la luz ahora mismo es esta. Este propio blog. ¿Lo véis? Es nuevo. Está hecho a mano en Rust. Y aunque todavía le faltan algunas cosas, ya es lo suficientemente capaz como para sustituir a WordPress.

WordPress me gusta, funciona bien, es un producto intuitivo, con buen marketing y con una factura técnica aceptable. Pero había varios "problemas":

  • WordPress usa MariaDB (o MySQL), ahora mismo quiero aprovechar la potencia de SQL estándar y en eso solo PostgreSQL me ofrece lo que busco. No es que en este blog lo vaya a usar, pero así evito tener que mantener dos bases de datos paralelamente en la misma Raspberry
  • soy un programador que no ha programado su blog (y aunque sea una tontería, admiro mucho a la gente que se programa sus propios blogs)
  • quería probar Rust en producción
  • quería probar tecnología relacionada con la web semántica

Como veis, ningún motivo real, podríamos decir que lo he hecho por que sí.

Tecnologías usadas

Este sistema de blog, que he llamado Diario y que es software libre, aunque hay que modificar unas cuantas cosas, es un mejunje de varias tecnologías. Por un lado quería usar Rust en producción y por otro quería probar tecnología relacionada con la web semántica, así que hay varios lenguajes de programación involucrados.

  • Rust es el lenguaje de programación principal
    • Rocket es el servidor web, esto me obliga a usar nightly. Sin embargo Rocket me parece un framework web muy bien diseñado y funciona muy bien
    • Diesel como ORM. Pero no estoy usando migraciones, así que al principio me costó pillar la documentación. Es un ORM muy bien diseñado pero cuando hay un error de compilación es bastante críptico
    • Tera como motor de plantillas
    • y alguna librería más como Chrono, Regex, Ring o Serde
  • PostgreSQL como base de datos
  • Python para la funcionalidad del formulario de contacto. La razón es que no conseguí hacer funcionar lettre en un tiempo razonable para poder enviar emails. Así que uso el módulo smtp de Python.
  • XSLT, para generar los feeds RSS, el sitemap y para alguna cosa más
    • XSLT no lo había usado nunca, pero no es complicado, eso sí, no hay librerías en Rust para usarlo, así que he usado xsltproc de línea de comandos

Quizá todo te parezca normal, hasta lo de XSLT. ¿Por qué he usado XSLT cuando ya tengo un sistema completo en Rust, que solo es cuestión de programar los feeds RSS y ya? Pues porque quiero que este blog tenga una API, una API de la web semántica.

Para ello, tiene que aceptar peticiones SPARQL. Todavía no lo he conseguido, pero viendo la falta de herramientas y librerías para trabajar con SPARQL, mi idea es generar un fichero con información RDF donde se almacena TODA la parte pública del blog. Allí se ejecutarán las consultas y de paso, me sirve para generar ficheros RSS y similares en XML de forma fácil y sencilla, transformando el fichero RDF original.

Código fuente

El código fuente del sistema, por si queréis echarle un ojo, está aquí: https://github.com/aarroyoc/diario

Feedback

Todavía quedan muchas cosas, como por ejemplo la API SPARQL (aunque eso casi está). He intentado además que todas las URLs antiguas sean compatibles con el nuevo sistema, aunque puede haber fallos, lo mismo con el formato de algunas páginas. Me gustaría que comentáseis que cosas échais en falta, hay que añadir, qué tal el diseño del blog y ese tipo de cosas. Al final sois vosotros los que vais a leerlo más veces que yo.

]]>
https://blog.adrianistan.eu/ano-nuevo-blog-nuevo Mon, 11 Feb 2019 21:44:53 +0000
Algoritmos genéticos: un caso práctico https://blog.adrianistan.eu/algoritmos-geneticos-un-caso-practico https://blog.adrianistan.eu/algoritmos-geneticos-un-caso-practico Existen muchos tipos de algoritmos. Hoy vamos a hablar de un tipo de algoritmo no demasiado conocido, los algoritmos genéticos, que son de la familia de los algoritmos evolutivos. Además veremos su aplicación práctica en el vectorizado de imágenes con un programa que he realizado en Rust.

¿Qué es un algoritmo genético?

Un algoritmo genético es un algoritmo inspirado en el mecanismo de la naturaleza de la evolución biológica, descrita por Darwin allá por 1859 en el libro El Origen de las Especies. La idea es tener un conjunto de individuos (posibles soluciones). Estos individuos son evaluados para ver qué tan buenos son. Quedarnos con los mejores y proceder a la creación de nuevos individuos como resultados de la recombinación genética de dos individuos (como en la reproducción sexual). Posteriormente añadir alguna mutación genética para explorar nuevas posibilidades ligeramente diferentes. Proceder de nuevo a la selección natural hasta que tengamos individuos lo suficientemente buenos para nosotros.

Normalmente no son los algoritmos más eficientes ni tienen por qué dar un resultado óptimo, pero pueden servir para dar aproximaciones bastante buenas al resultado óptimo. Existen estrategias para mejorar los algoritmos genéticos como la gestión de la antigüedad de los individuos, para evitar máximos locales; la conservación de individuos con mal desempeño, para conservar mayor variedad genética; ...

Como veremos más adelante, uno de los elementos más importante de estos algoritmos es la función de evaluación, que muchas veces es más complicada de programar de lo que parece.

Un caso práctico: vectorizado de imágenes

Para ver como estos conceptos teóricos funcionan en la práctica, vamos a hacer un programa que vectorice imágenes. ¿Esto qué es? Digamos que hay dos tipos de imágenes en informática. Por un lado tenemos las imágenes que almacenan los colores en píxeles (rasterizadas) y por otro lado aquellas que almacenan la imagen como fórmulas matemáticas, que se aplican cuando se quiere ver la imagen (vectoriales). Los formatos más comunes de imágenes rasterizadas son JPEG, PNG, GIF y WEBP. Los formatos más comunes de imágenes vectoriales son SVG y AI.

A las imágenes rasterizadas no se les puede hacer zoom hasta el infinito, se ven borrosas
A las imágenes vectoriales se les puede hacer zoom infinito, no pierden calidad

Pasar de una imagen vectorial a una rasterizada es trivial, pero el proceso inverso no lo es, y es justo donde vamos a aplicar el algoritmo genético.

En nuestro caso, vamos a tomar siluetas, negras sobre fondo blanco y tratar de vectorizarlas con curvas de Bézier.

 

Ejemplo de ejecución en la versión final de Mendel Vectorizer. La curva azul es la imagen vectorial generada.

Curvas de Bézier

En los años 60, Pierre Bézier, que trabajaba para Renault, diseñó un tipo de curva para el diseño asistido por ordenador (CAD). Estas son las conocidas como curvas de Bézier. Nuestro algoritmo tratará de encontrar la curva de Bézier más similar a la curva de la imagen rasterizada. Para ello necesitamos un punto de inicio, un punto final y dos puntos de control.

Curva cúbica de Bézier

En nuestro algoritmo, las curvas serán los individuos, y las coordenadas de los puntos de control serán los genes (habrá 4 genes por tanto).

Este es el código que he usado para definir las curvas de Bézier en Rust.

#[derive(Copy, Clone)]
pub struct Point {
    pub x: f64,
    pub y: f64,
}
impl Point {
    pub fn distance(&self, other: &Point) -> f64 {
        ((self.x - other.x).powf(2.0) + (self.y - other.y).powf(2.0)).sqrt()
    }
    pub fn middle(&self, other: &Point) -> Point {
        Point {
            x: (self.x + other.x) / 2.0,
            y: (self.y + other.y) / 2.0,
        }
    }
}
#[derive(Clone)]
pub struct Bezier {
    pub start: Point,
    pub control1: Point,
    pub control2: Point,
    pub end: Point,
}
impl<'a> Bezier {
    pub fn iter(&self) -> BezierIter {
        BezierIter {
            bezier: self,
            position: 0.0,
        }
    }
}
pub struct BezierIter<'a> {
    bezier: &'a Bezier,
    position: f64,
}
impl<'a> Iterator for BezierIter<'a> {
    type Item = Point;
    fn next(&mut self) -> Option {
        if self.position > 1.0 {
            return None;
        }
        let x = self.bezier.start.x * (1.0 - self.position).powf(3.0)
            + 3.0 * self.bezier.control1.x * self.position * (1.0 - self.position).powf(2.0)
            + 3.0 * self.bezier.control2.x * self.position.powf(2.0) * (1.0 - self.position)
            + self.bezier.end.x * self.position.powf(3.0);
        let y = self.bezier.start.y * (1.0 - self.position).powf(3.0)
            + 3.0 * self.bezier.control1.y * self.position * (1.0 - self.position).powf(2.0)
            + 3.0 * self.bezier.control2.y * self.position.powf(2.0) * (1.0 - self.position)
            + self.bezier.end.y * self.position.powf(3.0);
        self.position += 0.01;
        Some(Point { x, y })
    }}

Encontrar puntos iniciales

El primer paso de nuestro algoritmo es buscar los puntos iniciales (y finales) de las curvas. En definitiva esto es un problema de búsqueda de esquinas.

Ejemplo de aplicación de FAST9 a una imagen

Existen varios algoritmos de búsqueda de esquinas: Harris, FAST9, FAST12, ... No obstante, no tuve muy buenos resultados en las imágenes con las que trabajaba. Así que esta parte del algoritmo se la dejo al humano. El humano se encargará de poner los puntos, en orden, que tiene que tratar de unir el algoritmo con curvas de Bézier.

Función de evaluación

Muchas veces la función de evaluación es el punto más delicado de estos algoritmos. En este caso la idea fundamental es identificar si la curva de Bézier está encima de la curva original. Para ello tomamos 100 puntos equidistantes de la curva de Bézier (con la ecuación paramétrica de la curva es muy sencillo).

[latex]\mathbf{B}(t)=\mathbf{P}_0(1-t)^3+3\mathbf{P}_1t(1-t)^2+3\mathbf{P}_2t^2(1-t)+\mathbf{P}_3t^3 \mbox{ , } t \in [0,1].[/latex]

Estos puntos se comparan con la imagen real, si ahí en la imagen original había una línea no pasa nada, si no, se resta 100. De este modo se penaliza gravemente salirse de la curva. Esto se hace así ya que la otra opción evidente (bonificar el estar sobre en la línea) podría dar lugar a resultados inesperados.

Ejemplificación de la función de evaluación. Los puntos amarillos están dentro de la línea. Los puntos verdes están fuera de la línea, penalizando a la curva en su "adaptación al medio".

Pongamos como ejemplo una función de evaluación que bonifique por estar sobre la línea y no penalice por salirse de esta. Una línea bien adaptada a estas condiciones podría recorrerse toda la imagen, cruzando todas las líneas posibles, generando un garabato totalmente inútil pero muy bueno según esta función. Por ello, nuestra función de evaluación se basa en penalizar las salidas de la línea.

La función de evaluación presentada no es perfecta, ya que puede pasar de largo el punto final y dar la vuelta. Esta curva es más larga que la óptima, pero al estar completamente dentro de la línea negra original, la función de evaluación no la puede clasificar como peor que otras alternativas. Para solventar este problema una idea es que la longitud de la curva tenga una implicación en la función. No obstante, el cálculo de la longitud de una curva de Bezier es demasiado complejo y no lo he codificado. También podría aproximarse a través de segmentos rectos entre los 100 puntos calculados anteriormente.

Ejemplo de curva con puntuación máxima pero no óptima desde el punto de vista humano
pub fn evaluate(image: &GrayImage, line: &Bezier) -> f64 {
    let mut eval = 0.0;
    for point in line.iter() {
        let x = point.x as u32;
        let y = point.y as u32;
        if image.in_bounds(x, y) {
            let pixel = image.get_pixel(x, y);
            if pixel.data[0] < 200 {
                eval += 1.0;
            } else {
                eval -= 100.0;
            }
        } else {
            eval -= 100.0;
        }
    }
    eval
}

Selección natural

La función de selección natural deja únicamente las 500 mejores curvas, de acuerdo a la función de evaluación, es decir, las mejor adaptadas de momento. Para la ordenación, Rust usa un algoritmo denominado Timsort, con coste O(nlogn). Sin embargo, en todo el algoritmo trabajamos con poblciones finitas, por lo que puede asimilarse a una constante, con coste O(1). 

pub fn natural_selection(image: &GrayImage, mut population: Vec) -> Vec {
    population.sort_by(|a, b| {
        let a = evaluate(&image, &a);
        let b = evaluate(&image, &b);
        b.partial_cmp(&a).unwrap()
    });

    population.into_iter().take(GOOD_ONES).collect()
}

Población inicial

La población inicial se forma con 1000 curvas generadas con parámetros totalmente aleatorios. Los valores de cada coordenada, eso sí, están comprendidos entre -d y d siendo d la distancia en línea recta entre los puntos de inicio y final.

let mut population = Vec::new();
        let mut rng = thread_rng();
        let distancia = start.distance(&end);
        for _ in 0..1000 {
            let xrand: f64 = rng.gen_range(-distancia, distancia);
            let yrand: f64 = rng.gen_range(-distancia, distancia);
            let mut control1 = start.middle(&end);
            control1.x += xrand;
            control1.y += yrand;
            let mut control2 = start.middle(&end);
            control2.x += xrand;
            control2.y += yrand;
            population.push(Bezier {
                start,
                end,
                control1,
                control2,
            });
        }

Recombinado

El proceso de recombinación permite mezclar los mejores especímenes tratando de conseguir uno mejor. Este algoritmo genético es de tipo RCGA (Real Coded Genetic Algorithm) ya que los genes son números reales, en contraposición a los típicos genes binarios.Para estos algoritmos existen distintas variantes, aquí se usa el sistema de blend. El sistema de blend implica que de entre los dos padres se toman los valores mínimos y máximos para cada coordenada. Posteriormente se elige un nuevo valor de forma aleatoria con la condición de que esté dentro del rango de mínimo y máximo definido anteriormente.

            // CROSSOVER
            // Blend o Linear (Blend) https://engineering.purdue.edu/~sudhoff/ee630/Lecture04.pdf
            let mut i: usize = 0;
            let mut babies = Vec::new();
            while i < GOOD_ONES {
                let line1 = &population[i];
                let line2 = &population[i + 1];

                let min_x = line1.control1.x.min(line2.control1.x);
                let max_x = line1.control1.x.max(line2.control1.x);
                let min_y = line1.control1.y.min(line2.control1.y);
                let max_y = line1.control1.y.max(line2.control1.y);
                let control1 = Point {
                    x: rng.gen_range(min_x, max_x),
                    y: rng.gen_range(min_y, max_y),
                };

                let min_x = line1.control2.x.min(line2.control2.x);
                let max_x = line1.control2.x.max(line2.control2.x);
                let min_y = line1.control2.y.min(line2.control2.y);
                let max_y = line1.control2.y.max(line2.control2.y);
                let control2 = Point {
                    x: rng.gen_range(min_x, max_x),
                    y: rng.gen_range(min_y, max_y),
                };

                babies.push(Bezier {
                    start,
                    end,
                    control1,
                    control2,
                });

                i += 2;
            }
            population.append(&mut babies);

Mutación

La fase de mutación permite generar pequeñas variaciones aleatorias respecto a la población. Afecta al 10% de la población aunque solo afecta a una coordenada a la vez.

Al ser un algoritmo RCGA, no vale simplemente con cambiar el valor de un bit. El enfoque utilizado en este algoritmo es el de una distribución normal de cambios de media 0. La distribución tiene la forma N(0,d/2). Esto implica que en la mayoría de las ocasiones la variación (tanto positiva como negativa) en la coordenada será bastante pequeña.

Distribución normal, aunque esta no es de media 0
 population = population
                .into_iter()
                .map(|mut line| {
                    if rng.gen::() < 0.10 {
                        let normal = Normal::new(0.0, distancia / 2.0);
                        let mutation_where: u32 = rng.gen_range(1, 5);
                        // Solo muta un gen, respecto a una Normal
                        match mutation_where {
                            1 => line.control1.x += rng.sample(normal),
                            2 => line.control1.y += rng.sample(normal),
                            3 => line.control2.x += rng.sample(normal),
                            4 => line.control2.y += rng.sample(normal),
                            _ => (),
                        }
                    }
                    line
                })
                .collect();

El programa: Mendel Vectorizer

El programa definitivo, Mendel Vectorizer, disponible en GitHub, tiene más detalles. La interfaz gráfica está hecha usando GTK, la interfaz de línea de comandos usa Clap y el algoritmo se ejecuta en paralelo usando paso de mensajes entre los hilos. El programa genera un fichero SVG resultado que puede ser abierto en programas como Inkscape o Adobe Illustrator.

El fichero generado por Mendel Vectorizer en Inkscape

Para usar Mendel Vectorizer en tu ordenador, sigue los siguientes pasos.

Descárgate una copia del código con Git. Compila con Rust (una edición que soporte la edición 2018, mínimo la 1.31) y ejecútalo.

git clone https://github.com/aarroyoc/mendel-vectorizercd mendel-vectorizercargo buildcargo run

También podéis descargaros la memoria en PDF del programa.

]]>
https://blog.adrianistan.eu/algoritmos-geneticos-un-caso-practico Tue, 18 Dec 2018 00:00:00 +0000
Advent of Code 2018: segunda semana https://blog.adrianistan.eu/advent-of-code-2018-segunda-semana https://blog.adrianistan.eu/advent-of-code-2018-segunda-semana Segunda semana del Advent of Code. En esta semana ya hemos tenido algunos problemas muy interesantes. Intentaré comentarlos de la mejor manera posible. Todo el código está aquí y en este otro post comenté mis soluciones de la primera semana.

Día 8

El día 8 se nos propone un problema de grafos también. Básicamente se nos define un árbol, donde cada nodo puede tener hijos y metadatos. En la primera parte nos piden sumar todos los metadatos.

Aquí al contrario que en el día 7, no voy a usar networkx. Era más difícil adaptar networkx al problema que hacer el árbol a mano. Uno de los puntos complicados de este problema es el parseo de la entrada, que hice de forma recursiva. Cada nodo es un diccionario con las propiedades tamaño, una lista de metadatos y una lista de nodos hijo.

En la segunda parte se define el concepto de valor de nodo y como calcularlo. También es bastante sencillo de implementar. Finalmente hacemos un recorrido por el árbol de tipo primero en anchura (BFS) con una deque de Python.

from collections import deque
def read_node(start,numbers):
    length = 2
    child_nodes = numbers[start]
    metadata_entries = numbers[start+1]
    children = list()
    while child_nodes > 0:
        child_node = read_node(start+length,numbers)
        children.append(child_node)
        length += child_node["length"]
        child_nodes -= 1
    metadata = list()
    while metadata_entries > 0:
        metadata.append(numbers[start+length])
        length += 1
        metadata_entries -= 1
    node = dict([("length",length),("metadata",metadata),("children",children)])
    return node

def read_file(file):
    with open(file) as f:
        line = f.readline()
    numbers = [int(x) for x in line.split()]
    G = read_node(0,numbers)
    return G
def node_value(N):
    if len(N["children"]) == 0:
        return sum(N["metadata"])
    else:
        s = 0
        for i in N["metadata"]:
            if i-1 < len(N["children"]):
                s += node_value(N["children"][i-1])
        return s
        
def day8(file):
    G = read_file(file)
    to_visit = deque()
    to_visit.append(G)
    metadata_sum = 0
    while len(to_visit) > 0:
        N = to_visit.popleft()
        metadata_sum += sum(N["metadata"])
        to_visit.extend(N["children"])
    print("METADATA SUM: %d" % metadata_sum)
    print("NODE VALUE: %d" % node_value(G))

Día 9

Este día fue muy interesante. Se nos explica un juego, que consiste en ir añadiendo canicas en una circunferencia y cuando el número de canica que añadimos es múltiplo de 23, obtenemos puntos y quitamos una canica 7 puestos por detrás.

Aquí tuve una mala decisión de diseño ya que al principio quise hacer esto con una lista de Python (el equivalente a vector en otros lenguajes de programación). La idea era sencilla y funcionaba hasta que llegó la parte 2. La parte 2 te pedría calcular los puntos teniendo en cuenta 100 veces más canicas. Esto fue un grave problema para mi código. Calculo que tardaría 6 horas en calcularlo, pero antes optimicé. La optimización consistía en usar una lista circular doblemente enlazada. ¿Esto qué es? Se trata de una lista enlazada, doblemente, porque cada nodo tiene referencia al elemento siguiente y al anterior. Y es circular porque ambos extremos están unidos. Esto permite las inserciones y borrados en O(1). Además los movimientos relativos (en este problema todos son así) son extremadamente sencillos. La implementación de esta estructura de datos en Python es muy sencilla (en otros lenguajes es más complicado). No me molesté en hacer funciones que me hiciesen sencilla la vida y las conexiones/desconexiones las hago a mano directamente en el código del problema.

from collections import defaultdict
class Marble:
    def __init__(self,value,left=None,right=None):
        self.value = value
        self.left = left
        self.right = right
        if self.left == None:
            self.left = self
        if self.right == None:
            self.right = self

def day9(PLAYERS,LAST_MARBLE):
    SCORE = defaultdict(lambda: 0)
    player = 0
    marble = 0
    current_marble_pos = 0
    current_marble = None
    while marble <= LAST_MARBLE:
        if marble > 0 and marble % 23 == 0:
            SCORE[player] += marble
            pivote = current_marble.left.left.left.left.left.left.left
            SCORE[player] += pivote.value
            pivote.left.right = pivote.right
            pivote.right.left = pivote.left
            current_marble = pivote.right
        else:
            if current_marble == None:
                current_marble = Marble(marble)
            else:
                current_marble = Marble(marble,current_marble.right,current_marble.right.right)
                current_marble.left.right = current_marble
                current_marble.right.left = current_marble
        player += 1
        player = player % PLAYERS
        marble += 1
    return max(SCORE.values())

Curiosamente, en la propia librería de Python deque tiene una operación llamada rotate que permite hacer este problema en poquísimas líneas y de forma muy eficiente. Pero desconocía la existencia de esa función (que lo que hace es mover la "cabeza" de la lista enlazada que es deque).

Día 10

Este problema es muy interesante. Se nos da una serie de puntos que van moviéndose por la pantalla. En un determinado momento estos puntos se juntan y forman un mensaje en pantalla.

Aquí lo interesante no es mover los puntos, eso es trivial, simplemente es sumar la velocidad cada vez las coordenadas. Lo interesante es saber cuando parar. Existen varias ideas:

  • Revisión humana de cada iteración
  • Comprobar que no haya puntos separados del resto (con grafos)
  • Comprobar que el área de concentración de puntos es mínima

Y alguna más. Para el ejemplo la primera idea servía. Pero en la prueba real, era más complicado. A mí se me ocurrió la tercera opción, la cuál es bastante eficiente. En cada iteración calculamos el área que contiene a todos los puntos, cuando ese área ya no se reduce más, hemos llegado al mensaje.

import re
def read_file(file):
    stars = list()
    p = re.compile("position=<([ -][0-9]+), ([ -][0-9]+)> velocity=<([ -][0-9]+), ([ -][0-9]+)>")
    with open(file) as f:
        lines = f.readlines()
    for line in lines:
        m = p.match(line.strip())
        try:
            pos_x = int(m.group(1))
        except:
            print(line)
        pos_y = int(m.group(2))
        vel_x = int(m.group(3))
        vel_y = int(m.group(4))
        stars.append([pos_x,pos_y,vel_x,vel_y])
    return stars
def print_stars(stars):
    stars = sorted(stars,key=lambda x: x[0],reverse=True)
    min_width = stars[-1][0]
    max_width = stars[0][0]
    min_height = min(stars,key=lambda x: x[1])[1]
    max_height = max(stars,key=lambda x: x[1])[1]
    s = str()
    for j in range(min_height,max_height+1):
        p = [star for star in stars if star[1] == j]
        for i in range(min_width,max_width+1):
            if len(p) == 0:
                s += "."
            else:
                if any(map(lambda star: star[0] == i and star[1] == j,p)):
                    s += "#"
                else:
                    s += "." 
       s += "\n"
    return s
def step(stars):
    a = map(lambda x: [x[0]+x[2],x[1]+x[3],x[2],x[3]],stars)
    return list(a)
# LA RESPUESTA CORRECTA TIENE AREA MINIMA

def area(stars):
    stars = sorted(stars,key=lambda x: x[0], reverse=True)
    min_width = stars[-1][0]
    max_width = stars[0][0]
    min_height = min(stars,key=lambda x: x[1])[1]
    max_height = max(stars,key=lambda x: x[1])[1]
    area = (max_width-min_width)*(max_height-min_height)
    return area
def day10(file):
    stars = read_file(file)
    a = area(stars)
    steps = 0
    while area(step(stars)) < a:
        stars = step(stars)
        steps += 1
        a = area(stars)
    print_stars(stars)
    print(steps)

La parte de dibujado me costó y ahí tuve un fallo que me costó media hora aproximadamente en resolver. Una mejor opción, pero que no se me ocurrió, hubiese sido usar Pillow y crear una imagen. Es mucho más fácil que dibujar sobre una terminal (y posiblemente más rápido).

Día 11

Para este problema hay 3 posibles algoritmos. En la primera parte nos piden que de una matriz extraigamos el cuadrado de 3x3 con mayor valor. La matriz hay que construirla pero es trivial. Yo decido usar un diccionario, con clave la tupla de coordenadas. Vamos recorriendo todas las posiciones y calculamos el valor. Ahora para buscar el cuadrado, simplemente vamos probando todos los posibles cuadrados.

En la segunda parte nos dicen que bsuquemos el cuadrado máximo pero el tamaño puede ser cualquiera. Aquí con la fuerza bruta ya tarda demasiado. Mi solución fue usar programación dinámica, para ello la clave pasa a tener un valor más, el tamaño del cuadrado. Cuando creamos la tabla estamos asignando valor al cuadrado 1x1 de posición X,Y. Representado es la tupla (x,y,1). Según vamos avanzando hasta 300x300 vamos guardando los resultados intermedios, de modo que podamos reutilizarlos. Por ejemplo, el valor de (x,y,4) solamente es la suma de (x,y,2), (x+2,y,2), (x,y+2,2) y (x+2,y+2,2). Evidentemente esto solo funciona en los tamaños pares. En los tamaños impares decidí coger el cuadrado de dimensión inmediatamente menor y calcular los laterales con los cuadrados de tamaño 1. Este sistema funciona mucho mejor que la fuerza bruta pero es lento. Los profesionales usaron el algoritmo Summed Area Table (del que desconocía su existencia). Este algoritmo es el óptimo para este problema.

 

def generate_fuel(x,y,idg):
    fuel = (((x+10)*y)+idg)*(x+10)
    fuel %= 1000 
    fuel = (fuel // 100) - 5
    return fuel
def generate_table(idg):
    fuel = {(x,y,size):0 for x in range(1,301) for y in range(1,301) for size in range(1,301)}
    for x in range(1,301):
        for y in range(1,301):
            fuel[(x,y,1)] = generate_fuel(x,y,idg)
    return fuel
def find_best(fuel):
    max_point = [-1,-1]
    max_score = -1
    for x in range(1,301):
        for y in range(1,301):
            if x+3 > 301 or y+3 > 301:
                continue
            score = fuel[(x,y,1)]+fuel[(x+1,y,1)]+fuel[(x+2,y,1)]+fuel[(x,y+1,1)]+fuel[(x+1,y+1,1)]+fuel[(x+2,y+1,1)]+fuel[(x,y+2,1)]+fuel[(x+1,y+2,1)]+fuel[(x+2,y+2,1)]
            if score > max_score:
                max_score = score
                max_point = [x,y]
    return max_point[0],max_point[1]
def find_best_any_size(fuel):
    max_score = -1
    max_point = [-1,-1,-1]
    for size in range(2,300+1):
        for x in range(1,301):
            for y in range(1,301):
                if x+size > 301 or y+size > 301:
                    continue
                if size % 2 == 0:
                    mid = size // 2
                    fuel[(x,y,size)] = fuel[(x+mid,y,mid)]+fuel[(x,y+mid,mid)]+fuel[(x+mid,y+mid,mid)]+fuel[(x,y,mid)]
                else:
                    fuel[(x,y,size)] = fuel[(x,y,size-1)]
                    for i in range(x,x+size-1):
                        fuel[(x,y,size)] += fuel[(i,y+size-1,1)]
                    for j in range(y,y+size-1):
                        fuel[(x,y,size)] += fuel[(x+size-1,j,1)]
                    fuel[(x,y,size)] += fuel[(x+size-1,y+size-1,1)]
                score = fuel[(x,y,size)]
                if score > max_score:
                    max_score = score
                    max_point = [x,y,size]
    return max_point[0],max_point[1],max_point[2]
def day11():
    fuel = generate_table(1133)
    x,y = find_best(fuel)
    print("BEST POINT: %d,%d" % (x,y))
    x,y,size = find_best_any_size(fuel)
    print("BEST POINT ANY SIZE: %d,%d,%d" % (x,y,size))
if __name__ == "__main__":
    day11()

Día 12

El día 12 me trajo recuerdos de un algoritmo con el que me peleé mucho, el denominado HashLife. El problema es un autómata celular unidimensional. Las reglas vienen dadas como patrones. La única diferencia es que hay que guardar su posición para luego calcular un número. La primera parte es bastante sencilla.

import re
from collections import defaultdict

def read_file(file):
    rules = defaultdict(lambda: ".")
    rule_prog = re.compile("([.#]+) => ([.#])")
    with open(file) as f:
        lines = f.readlines()
    state = lines[0].split(": ")[1].strip()
    for line in lines[2:]:
        m = rule_prog.match(line.strip())
        rules[m.group(1)] = m.group(2)
    return state,rules

def parse_state(pots):
    state = dict()
    for i,p in enumerate(pots):
        state[i] = p
    return state

def find(rules,current):
    if current in rules:
        return rules[current]
    else:
        size = len(current)
        mid = size // 2
        left = find(rules,current[0:mid])
        right = find(rules,current[mid:])
        rules[current] = left + right
        return rules[current]


def iter(state,rules):
    new_state = dict()
    xmin = min(state.keys())
    xmax = max(state.keys())
    for x in range(xmin-2,xmax+3):
        current = ("%c%c%c%c%c" % (
                    state.get(x-2,"."),
                    state.get(x-1,"."),
                    state.get(x,"."),
                    state.get(x+1,"."),
                    state.get(x+2,".")
                    ))
        new = rules[current]
        if new == "#" or xmin <= x <= xmax:
            new_state[x] = new
    return new_state

def sum_pots(state):
    n = 0
    for pot in state:
        if state[pot] == "#":
            n += pot
    return n

def print_state(state):
    xmin = min(state.keys())
    xmax = max(state.keys())
    s = str("XMIN %d : " % xmin)
    for x in range(xmin-2,xmax+3):
        s += state.get(x,".")
    print(s)


def day12(file):
    state,rules = read_file(file)
    state = parse_state(state)
    for i in range(20):
        #print_state(state)
        state = iter(state,rules)
    #print_state(state)
    n = sum_pots(state)
    return n

if __name__ == "__main__":
    day12("input.txt")

La segunda parte nos pedía lo mismo pero para el número ¡50000000000! Inmediatamente pensé en optimizarlo de forma similar a HashLife. La idea consiste en almacenar patrones mayores a los de las reglas (que son todos de tamaño 5), para poder evitar cálculos innecesarios.Además añadí un recolector de basura para ir eliminando por la izquierda las celdas inútiles.

No obstante, y aunque es muchísimo más eficiente, sigue sin ser capaz de procesar tal bestialidad de número en un tiempo razonable.

Y he aquí lo que me ha cabreado, porque no he podido sacarlo. A partir de cierto momento, el dibujo siempre es el mismo pero desplazándose a la derecha. De modo que el valor del siguiente paso siempre es la suma de una constante. Finalmente modifiqué el código para que buscase una situación en la que el número fuese resultado de una suma de una constante. Una vez hecho eso, calcula con una multiplicación lo que valdría cuando llegase a 50000000000.

import re
from collections import defaultdict

XMIN = -2

def find(rules,current):
    if len(current) < 5:
        return ""
    if current in rules:
        return rules[current]
    elif len(current) == 5:
        return "."
    else:
        size = len(current)
        left=find(rules,current[0:size-1])
        right=find(rules,current[size-5:])
        rules[current] = left+right
        return rules[current]

def read_file(file):
    rules = defaultdict(lambda: ".")
    rule_prog = re.compile("([.#]+) => ([.#])")
    with open(file) as f:
        lines = f.readlines()
    state = lines[0].split(": ")[1].strip()
    for line in lines[2:]:
        m = rule_prog.match(line.strip())
        rules[m.group(1)] = m.group(2)
    return state,rules


def print_state(state):
    print(state)

def sum_pots(state):
    n = 0
    for i,c in enumerate(state):
        if c == "#":
            n += i + XMIN
    return n

def day12(file):
    global XMIN
    state,rules = read_file(file)
    XMAX = len(state)+1
    state = "..%s.." % state
    sums = list()
    i = 0
    while len(sums) < 3 or sums[-1]-sums[-2] != sums[-2]-sums[-3]:
        state = find(rules,"..%s.." % state)
        if state[0] == "." and state[1] == "." and state[2] == "." and state[3] == ".":
            state = state[2:]
            XMIN += 2
        if state[0] == "#" or state[1] == "#":
            state = "..%s" % state
            XMIN -= 2
        if state[-1] == "#" or state[-2] == "#":
            state = "%s.." % state
        sums.append(sum_pots(state))
        i += 1
    diff = sums[-1]-sums[-2]
    missing = 50000000000 - i
    n = missing*diff + sums[-1]

    return n

Y con esto pude finalmente calcular el resultado.

Día 13

El día 13 teníamos unas vías de tren. En estas vías había unos trenecitos que se desplazaban siguiendo unas normas. El objetivo en la primera parte era conocer el donde se producía el primer choque.

from PIL import Image

XMAX = 0
YMAX = 0
STEP = 0

nextDirection = dict()
nextDirection["start"] = "left"
nextDirection["left"] = "center"
nextDirection["center"] = "right"
nextDirection["right"] = "left"

def relative_direction(original,movement):
    print("CROSS")
    if movement == "center":
        return original
    if original == "v":
        if movement == "left":
            return ">"
        else:
            return "<"
    elif original == ">":
        if movement == "left":
            return "^"
        else:
            return "v"
    elif original == "^":
        if movement == "left":
            return "<"
        else:
            return ">"
    else:
        if movement == "left":
            return "v"
        else:
            return "^"

def day13(file):
    global XMAX
    global YMAX
    global STEP
    plano = dict()
    carts = list()
    with open(file) as f:
        lines = f.readlines()
    YMAX = len(lines)
    XMAX = len(lines[0])
    for y,line in enumerate(lines):
        for x,char in enumerate(line):
            # SI HAY UN CARRITO, DEDUCIR TIPO DE VIA
            if char == "^" or char == "v" or char == "<" or char == ">":
                if (x,y-1) in plano:
                    plano[(x,y)] = "|"
                else:
                    plano[(x,y)] = "-"
                carts.append([x,y,char,"left"])
            else:
                plano[(x,y)] = char
    
    end = False
    while not end:
        carts.sort(key=lambda x: x[1])
        carts.sort(key=lambda x: x[0])

        for cart in carts:
            # CHECK CRASH
            for crt in carts:
                if cart[0] == crt[0] and cart[1] == crt[1] and id(cart) != id(crt):
                    print("CRASH AT %d-%d" % (cart[0],cart[1]))
                    end = True
            try:
                x = cart[0]
                y = cart[1]
                print(cart)
                if cart[2] == ">":
                    if plano[(x+1,y)] == "/":
                        cart[2] = "^"
                    elif plano[(x+1,y)] == "\\":
                        cart[2] = "v"
                    elif plano[(x+1,y)] == "+":
                        cart[2] = relative_direction(cart[2],cart[3])
                        cart[3] = nextDirection[cart[3]]
                    cart[0] += 1
                elif cart[2] == "<":
                    if plano[(x-1,y)] == "/":
                        cart[2] = "v"
                    elif plano[(x-1,y)] == "\\":
                        cart[2] = "^"
                    elif plano[(x-1,y)] == "+":
                        cart[2] = relative_direction(cart[2],cart[3])
                        cart[3] = nextDirection[cart[3]]
                    cart[0] -= 1
                elif cart[2] == "^":
                    if plano[(x,y-1)] == "/":
                        cart[2] = ">"
                    elif plano[(x,y-1)] == "\\":
                        cart[2] = "<"
                    elif plano[(x,y-1)] == "+":
                        cart[2] = relative_direction(cart[2],cart[3])
                        cart[3] = nextDirection[cart[3]]
                    cart[1] -= 1
                elif cart[2] == "v":
                    print()
                    if plano[(x,y+1)] == "/":
                        cart[2] = "<"
                    elif plano[(x,y+1)] == "\\":
                        cart[2] = ">"
                    elif plano[(x,y+1)] == "+":
                        cart[2] = relative_direction(cart[2],cart[3])
                        cart[3] = nextDirection[cart[3]]
                    cart[1] += 1
            except:
                breakpoint()
        STEP += 1
        print_train(plano,carts)

def print_train(plano,carts):
    im = Image.new("RGB",(XMAX,YMAX))
    for x,y in plano:
        if plano[(x,y)] != " ":
            im.putpixel((x,y),(255,255,255))
        if plano[(x,y)] == "+":
            im.putpixel((x,y),(120,120,120))
    for cart in carts:
        if cart[2] == ">":
            im.putpixel((cart[0],cart[1]),(255,0,0))
        elif cart[2] == "<":
            im.putpixel((cart[0],cart[1]),(0,255,0))
        elif cart[2] == "^":
            im.putpixel((cart[0],cart[1]),(0,0,255))
        else:
            im.putpixel((cart[0],cart[1]),(0,120,120))

    im.save("train-%d.png" % STEP,"PNG")

if __name__ == "__main__":
]]>
                https://blog.adrianistan.eu/advent-of-code-2018-segunda-semana
                Sun, 16 Dec 2018 00:00:00 +0000
            
        
            
                Las mejores librerías gratuitas para gráficas en PHP
                https://blog.adrianistan.eu/las-mejores-librerias-gratuitas-para-graficas-en-php
                https://blog.adrianistan.eu/las-mejores-librerias-gratuitas-para-graficas-en-php
                

"Los datos son el nuevo petróleo" es algo que dicen muchas personas y que parece confirmarse si se leen noticias tecnológicas. Y en efecto, actualmente podemos generar una cantidad de datos inmensa de los que podemos extraer una información muy valiosa. Y ahí esta la clave, en "extraer". Los datos como tal no sirven para nada. Al igual que el petróleo, no lo queremos en bruto, sino refinado, tratado. Con los datos pasa lo mismo. Para representar esta información desde hace muchos años estadística ha ido diseñando métodos para representar datos y extraer información de ellos.

Antiguamente estas gráficas se realizaban a mano y su creación era muy costosa. Hoy en día, como seguro que sabrás si sigues las noticias tecnológicas actuales, podemos usar herramientas para generar estas gráficas de forma sencilla, directamente en un servidor. En este artículo vamos a ver las mejores librerías para generar gráficas en PHP.

phpChart

La primera librería de la lista es phpChart. Se trata de una librería de pago, pero con muchas opciones para las gráficas. Desafortunadamente, solo las versiones más caras soportan gráficos que no sean de barras, de líneas y de sectores. La librería funciona generando código JavaScript para el cliente que le permite dibujar la gráfica usando Canvas. Esta gráfica es interactiva, pudiendo el usuario hacer zoom, cambiar de modo de gráfico y permitiendonos hacer animaciones.

pChart

Otra opción es generar imágenes para las gráficas en el servidor. Esto es precisamente lo que hace pChart. Originalmente era un mero wrapper sobre la librería estándar de gráficos de PHP (GD) para añadir anti-alising, pero con el tiempo se enfocó a las gráficas estadísticas. Sus principales cualidades son una gran calidad en las visualizaciones y un rendimiento óptimo. pChart es gratuita si nuestra aplicación es software libre y de pago si no lo es. Requiere las extensiones GD y FreeType para funcionar correctamente. pChart soporta gran cantidad de gráficas y es usado por empresas como Intel o Airbus e instituciones como la NASA y el CERN. La librería tiene capacidades interactivas, aunque algo más reducidas que otras soluciones.

JpGraph

Otra librería que permite renderizar en el servidor es JpGraph. Esta librería, gratuita para usos no-comerciales, está diseñada con orientación a objetos. Tiene una gran variedad de gráficas disponibles, de buena calidad y como ellos promocionan: ligeros. Existe una versión PRO con todavía más tipos de gráficos, aunque son bastante específicos y normalmente no harán falta. La librería necesita que GD esté instalado y es compatible con PHP7.

D3.js

Para acabar, una de mis personalmente favoritas, D3.js. D3 no es una librería para generar gráficas y tampoco es PHP. Se trata de una librería JavaScript con utilidades para los documentos basados en datos, la idea es incluir esta librería y usarla donde quieras mostrar las gráficas. La ventaja de D3 es que te permite hacer lo que quieras y tiene una gran comunidad. Es open-source, y aunque es algo más difícil de usar, genera unos resultados excelentes, usando SVG de por medio. De hecho, D3 no generará código visual, sino que nos ayudará a hacerlo nosotros mismos. He ahí la dificultad y a su vez, su gran flexibilidad.

]]>
https://blog.adrianistan.eu/las-mejores-librerias-gratuitas-para-graficas-en-php Sat, 15 Dec 2018 00:00:00 +0000
Advent of Code 2018: primera semana https://blog.adrianistan.eu/advent-of-code-2018-primera-semana https://blog.adrianistan.eu/advent-of-code-2018-primera-semana Advent of Code. El objetivo es resolver un problema de programación al día, hasta el día de navidad, a modo de un particular calendario de adviento. Este año, como ya no viene siendo tan habitual, me he propuesto hacerlos todos y explicar mis soluciones. Esta semana todos han sido resueltos en Python.



Los programas que he usado, así como los enunciados y los datos de entrada que me han tocado (a cada uno le tocan datos diferentes) están en este repositorio de GitHub.

Día 1


El primer reto que nos proponen es muy sencillo. Se nos pasa un archivo con sumas y restas y tenemos que calcular el resultado final. Pongamos un ejemplo trivial:

+1, +1, -2 = 0

El procesdimiento es sencillo, leer cada operación, extraer el signo, convertir a número y aplicar la operación detectada por el signo.
def apply_freq(freq):
with open("input.txt") as f:
lines = f.readlines()
for line in lines:
signo = line[0:1]
numero = int(line[1:])
if signo == "+":
freq += numero
elif signo == "-":
freq -= numero
return freq

if __name__ == "__main__":
freq = 0
freq = apply_freq(freq)
print("FREQ FINAL: %d" % freq)

La segunda parte es más interesante, ya que nos dice que tenemos que aplicar la misma cadena de operaciones de forma indefinida hasta encontrar una resultado repetido.

Aquí la clave es conocer el funcionamiento de un Set o conjunto. Se trata de una estructura de datos que no permite elementos repetidos. La idea está en ir almacenando los resultados que van saliendo hasta encontrar uno que ya esté dentro. Al encontrarlo salir e indicarlo.
FREQS = set()

def apply_freq(freq):
with open("input.txt") as f:
lines = f.readlines()
for line in lines:
signo = line[0:1]
numero = int(line[1:])
if signo == "+":
freq += numero
elif signo == "-":
freq -= numero
if freq in FREQS:
return (True,freq)
else:
FREQS.add(freq)
return (False,freq)

if __name__ == "__main__":
freq = 0
while True:
end,freq = apply_freq(freq)
if end:
break
print("FREQ FINAL: %d" % freq)

Aquí viene una anécdota interesante. Estoy haciendo estos retos con unos amigos en un grupo de Telegram y al poco de yo haberlo terminado empezaron a preguntar cuánto tardaba en ejecutarse la segunda parte. Al parecer les iba muy lento, yo sorprendido les dije que había sido instantáneo y que estaba usando Python. Ellos se preguntaban como podía ser, ya que lo habían hecho en C y les estaba tardando varios minutos.

Una representación de un set o conjunto. No puede haber elementos repetidos. No existe orden definido

La respuesta tiene que ver con el set. Yo sinceramente fui a él directamente pero otros compañeros no, y usaron listas y arrays. La búsqueda que realizaban para saber si el elemento estaba dentro era lineal. Comparada con la búsqueda en un set implementado con tabla hash que es constante, el rendimiento es muy inferior. He aquí un ejemplo de como lo más importante de cara a la eficiencia es el algoritmo y las estructuras de datos que usamos para resolver un problema.

Día 2


El segundo día se nos propone otro problema de dificultad similar. Sobre una lista de palabras tenemos que contar cuantas palabras tienen 2 letras repetidas y 3 letras repetidas, para finalmente multiplicar ambos números.

En este caso fui bastante pragmático y opté por usar la clase Counter de Python. Counter cuenta cuantas veces aparece una letra y lo deja en un diccionario. Podemos ignorar las claves, ya que lo único que tenemos que hacer es buscar en los valores si está el 3 y el 2, y si es así, sumar uno a nuestro contador.
from collections import Counter

twice = 0
triple = 0

with open("input.txt") as f:
lines = f.readlines()
for line in lines:
c = Counter(line)
if 3 in c.values():
triple += 1
if 2 in c.values():
twice += 1
total = twice*triple
print("CHECKSUM: %d" % total)

La segunda parte ya es más interesante. Se nos pide que la misma lista de palabras, encontremos las dos palabras que solo se diferencian en una letra (se tiene en cuenta el orden). Aquí el algoritmo es un poco más lento ya que para cada palabra la comparamos con el resto de palabras, hasta encontrar aquella que efectivamente tiene un cambio de únicamente una palabra.

Para ello hace falta una función que nos diga cuantas palabras hay de diferencia entre dos palabras, que es bastante sencillita.
def changes(base,word):
changes = 0
for i,letter in enumerate(base.strip()):
if not letter == word[i]:
changes += 1
return changes

def find_similar(lines):
for base in lines:
for word in lines:
if changes(base,word) == 1:
return (base,word)

with open("input.txt") as f:
lines = f.readlines()
base,word = find_similar(lines)
final = str()
for i,letter in enumerate(base.strip()):
if letter == word[i]:
final += letter
print("FINAL %s"%final)

Una vez tengamos las dos palabras que solo se diferencian en una letra, hacemos un bucle similar al usado para encontrarlas, solo que esta vez para formar la palabra con todas las letras que tienen en común, ignorando la diferente.

Día 3


En el día 3 nos proponen un problema muy interesante. Tenemos una lista de parcelas rectangulares, definidas por la posición de su esquina superior-izquierda y su ancho y alto, todo en metros. Para la primera parte tenemos que encontrar cuantos metros cuadrados del universo ficticio están en dos o más parcelas a la vez.

Lo primero que hay que hacer es leer la entrada, que esta vez ya no es tan trivial. Un simple RegEx nos permite obtener toda la información de forma sencilla.
from dataclasses import dataclass
import re

@dataclass
class Claim():
id: int
x: int
y: int
width: int
height: int

def read_file():
claims = []
with open("input.txt") as f:
lines = f.readlines()
prog = re.compile(r"#([0-9]+) @ ([0-9]+),([0-9]+): ([0-9]+)x([0-9]+)");
for line in lines:
result = prog.match(line.strip())
claim = Claim(
id=int(result.group(1)),
x=int(result.group(2)),
y=int(result.group(3)),
width=int(result.group(4)),
height=int(result.group(5))
)
claims.append(claim)
return claims

Para el algoritmo en sí, vamos a usar defaultdict. La idea es tener una tabla hash, cuya clave sean las coordenadas del mundo y su valor, el número de parcelas que estan sobre ella. Usamos defaultdict para que por defecto cualquier coordenada que no existiese con anterioridad tenga valor 0.

Así pues vamos a recorrer todas las parcelas y para cada parcela vamos a visitar todos los metros cuadrados que contiene en la tabla hash, sumando 1 para indicar que ese metro cuadrado pertenece a una parcela (más).
if __name__ == "__main__":
claims = read_file()
area = defaultdict(lambda: 0)
for claim in claims:
for i in range(claim.x,claim.x+claim.width):
for j in range(claim.y,claim.y+claim.height):
area[(i,j)] += 1
overlaps = count_overlaps(area)
print("Overlaps: %d" % overlaps)

La segunda parte nos indica que solo existe una parcela que no esté invadida por otra parcela. Hay que buscar cuál es e informar del ID que tiene. Para esto he simplemente vuelto a recorrer cada parcela comprobando si tienen algún metro cuadrado con uso por más de 1 parcela. Si para una parcela no se encuentran metros compartidos, automáticamente se devuelve su ID (ya que solo hay una parcela de estas características).

El código completo es el siguiente:
from dataclasses import dataclass
from collections import defaultdict
import re

@dataclass
class Claim():
id: int
x: int
y: int
width: int
height: int

def read_file():
claims = []
with open("input.txt") as f:
lines = f.readlines()
prog = re.compile(r"#([0-9]+) @ ([0-9]+),([0-9]+): ([0-9]+)x([0-9]+)");
for line in lines:
result = prog.match(line.strip())
claim = Claim(
id=int(result.group(1)),
x=int(result.group(2)),
y=int(result.group(3)),
width=int(result.group(4)),
height=int(result.group(5))
)
claims.append(claim)
return claims

def count_overlaps(area):
overlaps = 0
for overlap in area.values():
if overlap > 1:
overlaps += 1
return overlaps

def find_nonoverlaping(claims,area):
for claim in claims:
overlaps = False
for i in range(claim.x,claim.x+claim.width):
for j in range(claim.y,claim.y+claim.height):
if area[(i,j)] > 1:
overlaps = True
if not overlaps:
return claim.id

if __name__ == "__main__":
claims = read_file()
area = defaultdict(lambda: 0)
for claim in claims:
for i in range(claim.x,claim.x+claim.width):
for j in range(claim.y,claim.y+claim.height):
area[(i,j)] += 1
overlaps = count_overlaps(area)
print("Overlaps: %d" % overlaps)
non_overlaping = find_nonoverlaping(claims,area)
print("ID: %d" % non_overlaping)

En esta segunda parte, tengo la intuición de que existe una manera más eficiente de hacerlo, pero todavía no he encontrado esa solución más eficiente.

Día 4


El problema del día 4, aunque aparentemente distinto al anterior, tiene muchas cosas en común y mi forma de resolverlo fue bastante parecida.

Se nos pide que sobre un registro de todas las noches identifiquemos el guardia que duerme más horas, y posteriormente su minuto preferido para quedarse dormido.

En primer lugar, la entrada de texto ya es bastante más compleja, pero con unos simples regex se puede analizar rápidamente, al menos para extraer la información que necesitamos. Aquí conviene prestar atención a que los registros de dormirse y despertarse ya que todos ocurren a la misma hora, luego lo único importante son los minutos. Otro detalle respecto a la entrada es que nos indican que está desordenada, sin embargo el formato de representación de la fecha que nos dan (parecido al ISO), se ordena cronológicamente simplemente con una ordenación alfabética.

La idea es muy similar a la del algoritmo anterior, primero tenemos una tabla hash con todos los guardias. Allí almacenamos otra tabla hash con los minutos de la hora y ponemos un cero si nunca se han dormido en ese minuto. Si usamos defaultdict, como en el código anterior, el código se simplifica bastante. En definitiva estamos usando dos tablas hash en vez de una y aplicar la misma idea de sumar 1, salvo que esta vez con el tiempo en vez del espacio (aunque Einstein vino a decir que eran cosas muy parecidas).
import re
from collections import defaultdict

def read_file():
with open("input.txt") as f:
lines = f.readlines()
lines.sort()
return lines


if __name__ == "__main__":
lines = read_file()
guard_prog = re.compile(r"[* ]+Guard #([0-9]+)")
time_prog = re.compile(r"\[([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+)")
current_guard = 0
start_time = 0
end_time = 0
timetable = defaultdict(lambda: defaultdict(lambda: 0))
for line in lines:
# Hay tres tipos de líneas
# Guardia, Sleep, Wake
a = guard_prog.match(line.split("]")[1])
if a != None:
current_guard = a.group(1)
elif "falls" in line:
t = time_prog.match(line.split("]")[0])
start_time = int(t.group(5))
elif "wakes" in line:
t = time_prog.match(line.split("]")[0])
end_time = int(t.group(5))
for i in range(start_time,end_time):
timetable[current_guard][i] += 1

# Calcular horas dormido
max_guard = ""
max_guard_sleeptime = 0
for guard in timetable:
s = sum(timetable[guard].values())
if s > max_guard_sleeptime:
max_guard_sleeptime = s
max_guard = guard

print("El guardia que más duerme es el %s con %d minutos" % (max_guard,max_guard_sleeptime))

#Calcular minuto ideal
max_minute = 0
max_minute_times = 0
for minute in timetable[max_guard]:
if timetable[max_guard][minute] > max_minute_times:
max_minute = minute
max_minute_times = timetable[max_guard][minute]

print("El guardia duerme más en el minuto %d (%d veces)" % (max_minute,max_minute_times))

print("CHECKSUM %d" % (max_minute*int(max_guard)))

Posteriormente se recorren estas tablas hash para calcular lo pedido.

La segunda parte nos pide algo similar pero no idéntico, el guardia que se ha quedado dormido más veces en el mismo minuto (e indicar que minuto es).

La estructura de datos es exactamente la misma y solamente añadimos otro bucle para que busque este otro dato:
import re
from collections import defaultdict

def read_file():
with open("input.txt") as f:
lines = f.readlines()
lines.sort()
return lines


if __name__ == "__main__":
lines = read_file()
guard_prog = re.compile(r"[* ]+Guard #([0-9]+)")
time_prog = re.compile(r"\[([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+)")
current_guard = 0
start_time = 0
end_time = 0
timetable = defaultdict(lambda: defaultdict(lambda: 0))
for line in lines:
# Hay tres tipos de líneas
# Guardia, Sleep, Wake
a = guard_prog.match(line.split("]")[1])
if a != None:
current_guard = a.group(1)
elif "falls" in line:
t = time_prog.match(line.split("]")[0])
start_time = int(t.group(5))
elif "wakes" in line:
t = time_prog.match(line.split("]")[0])
end_time = int(t.group(5))
for i in range(start_time,end_time):
timetable[current_guard][i] += 1

# Calcular horas dormido
max_guard = ""
max_guard_sleeptime = 0
for guard in timetable:
s = sum(timetable[guard].values())
if s > max_guard_sleeptime:
max_guard_sleeptime = s
max_guard = guard

print("El guardia que más duerme es el %s con %d minutos" % (max_guard,max_guard_sleeptime))

#Calcular minuto ideal
max_minute = 0
max_minute_times = 0
for minute in timetable[max_guard]:
if timetable[max_guard][minute] > max_minute_times:
max_minute = minute
max_minute_times = timetable[max_guard][minute]

print("El guardia duerme más en el minuto %d (%d veces)" % (max_minute,max_minute_times))

print("CHECKSUM %d" % (max_minute*int(max_guard)))

# El guardia que ha estado un minuto concreto mas veces dormido
max_guard = ""
guard_minute = 0
guard_minutes = 0
for guard in timetable:
for minute in timetable[guard]:
if timetable[guard][minute] > guard_minutes:
max_guard = guard
guard_minute = minute
guard_minutes = timetable[guard][minute]
print("El guardia %s se ha dormido en el minuto %d (%d veces)" % (max_guard,guard_minute,guard_minutes))
print("CHECKSUM %d" % (guard_minute*int(max_guard)))

En este caso, la segunda parte apenas ha implicado modificaciones, siendo la estructura de datos subyacente intacta.

Día 5


Este día se nos propone un reto aparentemente sencillo, pero cuya resolución puede ser muy lenta o rápida dependiendo de como lo hagamos. He de decir, que mi solución era muy lenta, extremadamente y tuve que mirar como lo habían hecho otras personas para entender como se podía optimizar.

La primera tarea consiste en reducir unas cadenas de reactivos. La norma es que si hay una letra minúscula y una mayúscula al lado, se pueden quitar. Se nos pide la longitud de la cadena de reactivos después de reducirlo al máximo.
if __name__ == "__main__":

with open("input.txt") as f:
line = f.readline()

line = list(line.strip())
end = False
while not end:
end = True
for i in range(1,len(line)):
if line[i-1] != line[i] and line[i-1].lower() == line[i].lower():
end = False
del line[i-1]
del line[i-1]
break
print("Units: %d" % (len(line)))

La versión original consistía en ir realizando elimaciones sobre la propia lista. Todo ello en un bucle que para cuando en una iteración no se modifica la cadena. Esto es extremadamente ineficiente. Tomando el código de Peter Tseng, existe una opción mejor. Se puede ir haciendo un string nuevo poco a poco comprobando si la nueva letra reacciona con la última del string nuevo. Esto tiene la ventaja de que solo hace falta una iteración para cubrir todas las reacciones. La versión mejorada es la siguiente:
def react(line):
new = list()
for c in line.strip():
if len(new) > 0 and c != new[-1] and c.lower() == new[-1].lower():
del new[-1]
else:
new += c
return new

if __name__ == "__main__":

with open("input.txt") as f:
line = f.readline()
line = react(line)
print("Units: %d" % (len(line)))

Para la segunda parte se nos pida que encontremos la letra, que si eliminamos del compuesto antes de empezar la reacción, genera la cadena más pequeña. Hay que probar con todas las letras, mención especial a string.ascii_lowercase que tiene un iterador con todas las letras minúsculas del alfabeto inglés. Y posteriormente, encontrar la que de resultado inferior. Como no nos pide la letra, solamente la longitud que tendría esa cadena, no tenemos que pasar información extra.
import string

def react(line):
new = list()
for c in line:
if len(new) > 0 and c != new[-1] and c.lower() == new[-1].lower():
del new[-1]
else:
new += c
return new

def min_react(line,letter):
line = [c for c in line if c.lower() != letter]
return len(react(line))

if __name__ == "__main__":

with open("input.txt") as f:
line = f.readline()
l = react(line)

print("Units: %d" % (len(l)))

m = min([min_react(line,char) for char in string.ascii_lowercase])
print("Minimum length: %d" % (m))

Día 6


Esto se empieza a complicar. El día 6 nos pide que sobre una cuadrícula encontremos qué punto de control está más cercano a esa posición. De los puntos de control que un número de cuadrículas cercanas finitas, encontrar cuántas cuadrículas tiene el punto de control con más cuadrículas asociadas. Para saber la distancia de una cuadrícula al punto de control se usa la distancia de Manhattan.

Lo primero es reconocer que puntos de control tienen áreas infinitas, para no tenerlos en cuenta.

Para ello, voy a calcular dos puntos extremos (esquina superior izquierda y esquina inferior derecha), dentro del rectángulo que forman estos puntos están contenidos todos los puntos de control. El objetivo es calcular las distancias de las cuadrículas justo por fuera de este rectángulo. Las cuadrículas que estén más lejos de eso no van a cambiar de punto de control, ya que el más cercano en el borde seguirá siendo el más cercano en el borde + N, ya que no hay más puntos de control fuera.

Posteriormente, empezamos a calcular las distancias de todos los puntos de la cuadrícula. Para almacenar los datos vuelvo a usar una tabla hash (defaultdict de Python), donde la clave es la coordenada X,Y y el valor es el punto de control más cercano a esa cuadrícula. Si dos puntos de control están a la misma distancia o no se ha calculado, se usa -1.

Cuando se ha calculado el punto de control más cercano, se revisa si ese punto estaba fuera del rectángulo que contiene a los puntos de control. Si está fuera, el punto de control pasa a un conjunto de puntos con infinitas cuadrículas cercanas.

Para el conteo de cuántas cuadrículas tiene un punto de control que sabemos que es finito, uso otra tabla hash, inicializada por defecto a 0, cuya clave es el identificador de punto de control y su valor, el número de cuadrículas. Después, de los valores almacenados se calcula el máximo.
from dataclasses import dataclass
from collections import defaultdict
import math

@dataclass
class Punto:
x: int
y: int
owner: int

def distancia(p1,p2):
return abs(p1.x-p2.x)+abs(p1.y-p2.y)

if __name__ == "__main__":
with open("input.txt") as f:
lines = f.readlines()
puntosControl = list()
xlist = list()
ylist = list()
for i,line in enumerate(lines):
l = line.split(",")
xlist.append(int(l[0]))
ylist.append(int(l[1]))
puntosControl.append(Punto(x=int(l[0]),y=int(l[1]),owner=i))
esquinaSuperiorIzquierda = Punto(x=min(xlist),y=min(ylist),owner=-1)
esquinaInferiorDerecha = Punto(x=max(xlist),y=max(ylist),owner=-1)

# Los que están fuera del rango esquinaSuperiorIzquierdaxesquinaInferiorDerecha se excluyen automáticamente
excluidos = set()
world = defaultdict(lambda: -1)
for i in range(esquinaSuperiorIzquierda.x-1,esquinaInferiorDerecha.x+2):
for j in range(esquinaSuperiorIzquierda.y-1,esquinaInferiorDerecha.y+2):
punto = Punto(x=i,y=j,owner=-1)
distanciaMin = math.inf
total = 0
for p in puntosControl:
if distancia(punto,p) == distanciaMin:
punto.owner = -1
if distancia(punto,p) < distanciaMin:
distanciaMin = distancia(punto,p)
punto.owner = p.owner

if i == esquinaSuperiorIzquierda.x-1 or i == esquinaInferiorDerecha.x+1 or j == esquinaSuperiorIzquierda.y-1 or j == esquinaInferiorDerecha.y+1:
excluidos.add(punto.owner)
if punto.owner > -1:
world[(i,j)] = punto.owner
conteo = defaultdict(lambda: 0)
for p in world:
if not world[p] in excluidos:
conteo[world[p]] += 1
print("Maximum finite area: %d" % max(conteo.values()))

En la segunda parte nos dicen que hay una región de puntos cuya suma de distancias a todos los puntos de control es menor a 10000. ¿Cuántos puntos forman esta región? Aquí creo que el enunciado no fue demasiado claro, ya que en un principio pensé que podría haber varias áreas, o que podría haber puntos sueltos, no conectados a la región. Sin embargo eso no pasa. Yo diseñé un algoritmo que iba visitando las celdas adyacentes, pero en realidad no hacía falta, simplemente se puede contar cuantos puntos cumplen la condición. Y se puede hacer en el mismo bucle que la primera parte.
from dataclasses import dataclass
from collections import defaultdict
import math

@dataclass
class Punto:
x: int
y: int
owner: int

def distancia(p1,p2):
return abs(p1.x-p2.x)+abs(p1.y-p2.y)

if __name__ == "__main__":
with open("input.txt") as f:
lines = f.readlines()
puntosControl = list()
xlist = list()
ylist = list()
for i,line in enumerate(lines):
l = line.split(",")
xlist.append(int(l[0]))
ylist.append(int(l[1]))
puntosControl.append(Punto(x=int(l[0]),y=int(l[1]),owner=i))
esquinaSuperiorIzquierda = Punto(x=min(xlist),y=min(ylist),owner=-1)
esquinaInferiorDerecha = Punto(x=max(xlist),y=max(ylist),owner=-1)

# Los que están fuera del rango esquinaSuperiorIzquierdaxesquinaInferiorDerecha se excluyen automáticamente
excluidos = set()
world = defaultdict(lambda: -1)
world_total = 0
for i in range(esquinaSuperiorIzquierda.x-1,esquinaInferiorDerecha.x+2):
for j in range(esquinaSuperiorIzquierda.y-1,esquinaInferiorDerecha.y+2):
punto = Punto(x=i,y=j,owner=-1)
distanciaMin = math.inf
total = 0
for p in puntosControl:
if distancia(punto,p) == distanciaMin:
punto.owner = -1
if distancia(punto,p) < distanciaMin:
distanciaMin = distancia(punto,p)
punto.owner = p.owner
total += distancia(punto,p)
if total < 10000:
world_total += 1

if i == esquinaSuperiorIzquierda.x-1 or i == esquinaInferiorDerecha.x+1 or j == esquinaSuperiorIzquierda.y-1 or j == esquinaInferiorDerecha.y+1:
excluidos.add(punto.owner)
if punto.owner > -1:
world[(i,j)] = punto.owner
conteo = defaultdict(lambda: 0)
for p in world:
if not world[p] in excluidos:
conteo[world[p]] += 1
print("Maximum finite area: %d" % max(conteo.values()))
print("Region size: %d" % world_total)

Día 7


El día 7 se nos propone un reto muy interesante. En primer lugar, tenemos una lista de tareas que hacer en orden. Cada tarea depende de que otras hayan finalizado. Se nos pide el orden en el que se deberán hacer. Para resolver esto vamos a usar una estructura de datos nueva, el grafo dirigido. No voy a implementarlo yo, sino que voy a usar la magnífica librería networkx.

La idea es construir un grafo dirigido, con las tareas. Un nodo dirigido de C a A significa que antes de hacer la tarea A, C tiene que estar completado. Por supuesto puede darse el caso de que antes de hacer A haya que hacer C y otras tareas.

Vamos a realizar una búsqueda primero en anchura (DFS en inglés). Para ello mantenemos una lista con las tareas completadas y otra lista con las tareas que podríamos empezar a hacer. Cuando completamos una tarea vemos si las tareas a las que llevan necesitan todavía más tareas por realizar o en cambio ya pueden ser añadidas a la lista de "listas para empezar". El enunciado nos indica que ante la situación de dos tareas listas para empezar, se usa el orden alfabético para determinar cuál va antes.

Hace falta además una lista de tareas iniciales, que pueden empezar sin esperar. Esto se hace con dos conjuntos según leemos el archivo. Se hace la diferencia entre ellos y esas tareas no tienen prerrequisitos.
import networkx as nx
import re

def read_file():
first = set()
second = set()
G = nx.DiGraph()
prog = re.compile("Step ([A-Z]) must be finished before step ([A-Z]) can begin.")
with open("input.txt") as f:
lines = f.readlines()
for line in lines:
r = prog.match(line.strip())
if not r.group(1) in G:
G.add_node(r.group(1))
if not r.group(2) in G:
G.add_node(r.group(2))
if not G.has_edge(r.group(1),r.group(2)):
G.add_edge(r.group(1),r.group(2))
first.add(r.group(1))
second.add(r.group(2))
return (G,first- second)

if __name__ == "__main__":
G,starter = read_file()
path = list()
to_visit = sorted(starter,reverse=True)

while len(to_visit) > 0:
node = to_visit.pop()
path.append(node)
neighbours = G[node]
for n in neighbours:
if not n in to_visit and not n in path:
allCompleted = True
for u,v in G.in_edges(nbunch=n):
if not u in path:
allCompleted = False
if allCompleted:
to_visit.append(n)
to_visit = sorted(to_visit,reverse=True)
print("".join(path))

La segunda parte también es muy interesante. Se nos indica que las tareas tienen una duración de N segundos, dependiendo del valor alfabético de la letra. Además, ahora existen 5 trabajadores que pueden ir haciendo tareas en paralelo. ¿En cuánto tiempo podemos acabar todas las tareas?
import networkx as nx
import re

def read_file():
first = set()
second = set()
G = nx.DiGraph()
prog = re.compile("Step ([A-Z]) must be finished before step ([A-Z]) can begin.")
with open("input.txt") as f:
lines = f.readlines()
for line in lines:
r = prog.match(line.strip())
if not r.group(1) in G:
G.add_node(r.group(1))
if not r.group(2) in G:
G.add_node(r.group(2))
if not G.has_edge(r.group(1),r.group(2)):
G.add_edge(r.group(1),r.group(2))
first.add(r.group(1))
second.add(r.group(2))
return (G,first- second)

def duration(step):
return 60+ord(step)-64

if __name__ == "__main__":
G,starter = read_file()
path = list()
to_visit = sorted(starter,reverse=True)

while len(to_visit) > 0:
node = to_visit.pop()
path.append(node)
neighbours = G[node]
for n in neighbours:
if not n in to_visit and not n in path:
allCompleted = True
for u,v in G.in_edges(nbunch=n):
if not u in path:
allCompleted = False
if allCompleted:
to_visit.append(n)
to_visit = sorted(to_visit,reverse=True)
print("".join(path))

end_letter = path[-1]
path = list()
to_visit = sorted(starter,reverse=True)

second = 0
workers = list()
# Trabajo Actual, segundo que termina
workers.append(['.',0])
workers.append(['.',0])
workers.append(['.',0])
workers.append(['.',0])
workers.append(['.',0])
def full_workers(workers):
full = True
for w in workers:
if w[0] == ".":
full = False
return full
end = False
while not end:
if len(to_visit) == 0 or full_workers(workers):
second += 1
for i in range(0,len(workers)):
if workers[i][1] <= second:
if workers[i][0] != ".":
path.append(workers[i][0])
neighbours = G[workers[i][0]]
for n in neighbours:
if not n in to_visit and not n in path:
allCompleted = True
for u,v in G.in_edges(nbunch=n):
if not u in path:
allCompleted = False
if allCompleted:
to_visit.append(n)
to_visit = sorted(to_visit,reverse=True)
if workers[i][0] == end_letter:
print("Finish point")
print("Seconds: %d" % second)
end = True
if len(to_visit) > 0:
node = to_visit.pop()
workers[i][1] = second+duration(node)
workers[i][0] = node
else:
workers[i][0] = "."

Bien, partiendo del mismo grafo dirigido ahora vamos a hacer otro tipo de recorrido, también DFS, pero no vamos a añadir nuevos elementos a la lista de forma inmediata, sino cuando hayan sido acabados de procesar. Almacenamos los trabajadores como listas dentro de una lista de trabajadores. Cada trabajador guarda la tarea que estaba haciendo y el segundo en el que acabará. Defino una función para saber si los trabajadores están todos ocupados.

Lo primero a tener en cuenta es que el tiempo no avanza hasta que la lista de tareas que se puede realizar está vacía o los trabajadores están llenos. Luego en cada iteración del bucle, analizamos a los trabajadores. Si no han acabado, no se hace nada. Si ya han acabado y estaban con una tarea, se añade la tarea a la lista de tareas finalizadas, y se analiza si se han desbloqueado nuevas tareas disponibles para realizar. Si la tarea que ha realizado es la última tarea, se acaba el programa.

Por último si hay una tareas disponible para hacer, se la añadimos al trabajador y si no, pues le indicamos que no haga nada (así no añadimos por duplicado la tarea en el futuro).

Salida de debug que saqué en el día 7

Conclusión


Esta ha sido la primera semana del Advent of Code 2018. Como vemos, el nivel de los problemas ha ido aumentado de forma progresiva. La próxima semana comentaré las soluciones correspondientes. Tenéis todo el código hasta ahora aquí.

 ]]>
https://blog.adrianistan.eu/advent-of-code-2018-primera-semana Fri, 7 Dec 2018 00:00:00 +0000
Conclusiones de la visita de Richard Stallman a Valladolid https://blog.adrianistan.eu/conclusiones-de-la-visita-de-richard-stallman-a-valladolid https://blog.adrianistan.eu/conclusiones-de-la-visita-de-richard-stallman-a-valladolid Valladolid. La oportunidad de conocer a tal personaje en primera persona era única, así que no dudé en asistir, con la suerte que tuve de poder estar en primera fila durante la conferencia.

La conferencia no contaba nada nuevo, nada que no supiese cualquier persona que haya leído un poco sobre la idea del software libre, pero se hizo amena. Stallman explica muy bien y las diapositivas están muy bien hechas. Tiene bastantes chistes precocinados pero que causan buen impacto en la audiencia.

Pero Stallman es un personaje. Hablando con la gente que cenó con él el día anterior, algunos me contaban que había sido bastante irrespetuoso, sacando el portátil durante la cena para hacer sus cosas y gritar de vez en cuando que no escuchaba bien.

Durante la charla ha estado bebiendo de su té, descalzo y hasta el momento de empezar ha seguido mandando correos. Le noté bastante envejecido, caminaba medio cojo y se le notaba la marca de la operación en el brazo. Para empezar a puesto su famosa versión de Guantanamera.

La presentación ha seguido explicando las cuatro libertades del software libre, de forma bastante extensa, explicando para todos los públicos por qué son importantes.

Durante la charla también ha hablado del software malévolo, no confundir con privativo. Según él, son dos cosas distintas, pero relacionado. Puede haber software honesto privativo y software malévolo libre, pero son minorías en la práctica. También ha hablado del software privado, que es perfectamente ético, y que hasta él programa software privado. La diferencia es que el software privado nunca se distribuye fuera del propio autor u organización. Bastante parte de la charla se ha centrado en esta parte, tocando el tema de la privacidad a fondo. Para Stallman la recolección de datos personales debería estar prohibida.

Para ilustrar este punto, ha puesto como ejemplo algo que le ha horrorizado, el sistema de parquímetros de Valladolid. Según él son horribles, no porque haya que pagar, que es algo a lo que está dispuesto, sino porque hay que poner la matrícula del vehículo. Poner la matrícula en el parquímetro lo que sirve es para rastrear a la gente. Este tipo de acciones nos acercan cada vez más a la dictadura y a la destrucción de los derechos humanos.

Personalmente el ejemplo me parece bastante exagerado y aunque veo su punto, creo que la recolección de datos personales puede ser necesario en ciertas situaciones, siempre que se traten de forma adecuada.

También ha habido tiempo para hablar de historia. Habló de la historia de GNU, como Linux al principio tenía una licencia no libre (se impedía su redistribución comercial), pero que al poco cambió a GPL 2, siendo el candidato perfecto para el proyecto GNU. Ha comentado que Hurd tiene un diseño muy elegante, moderno pero quizá fue demasiado complicado y que en perspectiva fue un error diseñarlo de esa forma. Ha dicho que Hurd no es usable para nada práctico ahora mismo.

Ha insistido mucho en que el sistema operativo se llama GNU con Linux.

También ha hablado del open source. Y cuando le proclaman padre del open source afirma: "si soy el padre es porque han hecho la reproducción invitro con semen mío robado". Afirma que la gente del open source tiene otra ideología, mucho más pragmática, pero incompleta, ya que no preserva las libertades.

Ha hablado de licencias libres: débiles y la GPL. De entre las débiles recomienda la Apache, aunque por supuesto la mejor es la GPL, en sus distintas versiones. Ha confirmado que nadie está trabajando en la GPL4. Aquí ha aprovechado para criticar a GitHub que ha seguido una muy mala costumbre de no prestar atención a las licencias del software que aleja. Además indica que es necesario poner un comentario en cada archivo con la licencia que sigue. Eso de poner un archivo con la licencia en la carpeta raíz no es suficiente según Stallman.

Esto lo ha enlazado con LibreJS, el complemento que detecta si el código JavaScript de una página es libre o no y lo bloquea o no según esto. Stallman no ha criticado en ningún momento JavaScript, simplemente que tiene que respetar los mismos criterios de los programas nativos para ser software libre.

También ha hablado de distros libres, metiéndose con Ubuntu bastante. Reconoce que estos usuarios están más cerca de la libertad que si usasen Windows o macOS pero que todavía les falta un poco. Y lo peor para Stallman es que mucha gente cree que sistemas como Ubuntu son libres y la gente se queda en ellos.

Por último ha hablado del software libre en la educación, también ha recomendado a la universidad tener una asignatura de ingeniería inversa (o retroingeniería como él lo llama).

Después de esto ha proseguido con el momento más cómico de la charla, se puso su túnica y su aureola y empezó a hablar de la religión de Emacs.



Nos bendijo nuestros ordenadores, y habló de como formar parte de la Iglesia del tan importante Emacs. No es necesario guardar celibato para ser santo pero hay varias cosas, como el peregrinaje de Emacs (saberse todas las combinaciones de teclado de memoria) o los cismas actuales (¿cuál es la tecla más importante en Emacs?). También ha dedicado palabras a los Vimeros como yo. Usar Vi no es pecado, es una penitencia. Y eso a pesar de que VIVIVI es el número de la bestia. También contó como en China le intentaron atacar unos seguidores de Vi, pero tenía sentido porque la violencia empieza por vi.

Después ha subastado un ñu, poniendo caras para que no dejásemos al ñu solo. Además de incidir en que no puede haber pingüinos solos, tiene que haber ñus acompañándolos.

Por último la ronda de preguntas. Mi pregunta ha sido ¿Las redes neuronales pueden ser software libre? Su respuesta ha sido que existen herramientas para alterar los valores numéricos de estas redes, mucho más fácil que la ingeniería inversa. Por tanto no sería técnicamente lo mismo. Creo que lo ha puesto al nivel de una fotografía o un vídeo, donde la edición es más sencilla y no tiene sentido hablar de fotografía libre.

También se ha preguntado por Microsoft y su deriva open source. Stallman celebra que publique cosas con licencias de software libre, pero eso no quita que siga teniendo software privativo que espía como Windows.

Le han preguntado por un teléfono que respete la privacidad. Según él es imposible, aunque se está intentando con un interruptor que permita desconectar la conexión móvil de forma física. El problema es de la tecnología móvil (no importa que sean smartphones o no), que para enrutar los paquetes guarda la ubicación de los dispositivos. El propósito era muy inocente pero se puede usar para espiar. Eso sí, él ha usado teléfonos móviles, siempre cuando necesita llamar pregunata a alguien que haya alrededor si le pueden dejar el teléfono.

Por último, sobre el hardware libre ha dicho que el concepto es erróneo. No puede existir hardware libre. El software libre existe, porque se puede copiar de forma exacta, en el mundo físico eso no pasa. Habría que hablar de hardware con diseños libres, pero ningún objeto podrá ser hardware libre o hardware privativo.

]]>
https://blog.adrianistan.eu/conclusiones-de-la-visita-de-richard-stallman-a-valladolid Tue, 4 Dec 2018 00:00:00 +0000
Alojando una web en IPFS https://blog.adrianistan.eu/alojando-una-web-en-ipfs https://blog.adrianistan.eu/alojando-una-web-en-ipfs primer post vimos como interactuar con IPFS de forma sencilla. Ahora vamos a dar un paso más y vamos a alojar una web en IPFS, aprovechando todas las ventajas de escalabilidad y disponibilidad que nos ofrece la red. Para ello usaremos además otro protocolo llamado IPNS, que sería similar a DNS pero en la red IPFS.

Las páginas ideales para IPFS actualmente son las que siguen el JAMstack, es decir, uso extensivo de JavaScript en el cliente, que podrá conectarse a APIs para obtener/actualizar información. Las APIs no tienen por qué ser centralizadas, ya que JavaScript con WebRTC puede buscar peers, posibilitando APIs descentralizadas.

Generando el contenido estático


El primer paso será generar el contenido estático de la web. Para ello existen herramientas muy conocidas como Jekyll, Hugo o Pelican.

No nos vamos a alargar más en esto, ya que cada herramienta tiene sus pasos. El resultado final será una carpeta con ficheros HTML, CSS, fuentes, imágenes y JavaScript.

Subir a IPFS


Teniendo el nodo IPFS en funcionamiento, subimos la carpeta del modo habitual, en mi caso, voy a subir la página que está en http://adrianistan.eu .
ipfs add -r adrianistan.eu/

Y anotamos el último hash.

Comprobamos que la web es accesible, tanto desde la gateway del nodo, como una externa:

Tanto en el nodo local como en uno externo, la web carga perfectamente con el hash

IPFS tiene direccionamiento por contenido


En el post anterior mencionamos que IPFS direcciona por contenido gracias a los hashes. Esto tiene unas consecuencias interesantes. Por ejemplo, si se añade un archivo duplicado a IPFS, este tiene exactamente la misma dirección, ya que comparten hash. Por otro lado, los documentos no se pueden actualizar, porque entonces su hash cambia. Sin embargo en una web queremos cambiar contenido, ahí entra en acción IPNS.

IPNS, gestión de nombres para IPFS


IPNS es un protocolo parecido en intenciones a DNS que redirige un ID única al hash correspondiente en ese momento. Registramos el hash de la web actual en IPNS.
ipfs name publish HASH

Ahora sí, el hash IPNS puede publicarse por la red, ya que siempre apuntará a la última versión de la web.

Para acceder a recursos a través de IPNS tenemos que cambiar de protocolo, en vez de /ipfs/HASH, tenemos que poner /ipns/HASH. Vale tanto para comandos como para las gateways HTTP.

https://cloudflare-ipfs.com/ipns/QmYDVeoadAzk9ZW6zwJK3E3KHrA1LWLveEdqUv4XAcCjKa/

En cualquier momento podemos comprobar a que dirección IPFS apunta el hash IPNS:
ipfs name resolve QmYDVeoadAzk9ZW6zwJK3E3KHrA1LWLveEdqUv4XAcCjKa

Para actualizar el contenido simplemente se vuelve a repetir el paso de ipfs name publish. IPFS automáticamente modificará la redirección de IPNS.

Los contenidos antiguos no desaparecen, pero pueden no ser accesibles ya que ningún nodo tenga copia de ellos.

DNSLink


Aún así, ir dándole a la gente un hash IPNS es demasiado complicado. Afortunadamente, podemos usar el DNS tradicional para indicar una ruta IPNS y así, como veremos, facilitar bastante las cosas.

Para ello añadimos un campo TXT en los DNS de nuestro dominio. El contenido es el siguiente:
dnslink=/ipns/HASH

Con esto podremos usar /ipns/dominio.com en la red IPFS. Pero todavía hace falta un cliente IPFS.

Afortunadamente, podemos redirigir mediante CNAME a una gateway HTTP de confianza y ¡la web funcionará correctamente! Para ello hay que crear un campo TXT en el subdominio _dnslink con el mismo contenido que el anterior.

Todas las gateways de IPFS soportan DNSLink para que la transición a IPFS sea lo más transparente posible.

Así, finalmente la página carga con un dominio normal y corriente en un navegador normal y corriente.

Fijaos en la URL

De hecho, vosotros mismos podéis acceder:

http://adrianistan.yayeyo.ga

IPFS Companion


Si usamos mucho IPFS, puede que nos interese una extensión que maneje el protocolo ipfs:// . Tanto en Firefox como en Chrome existe IPFS Companion, una extensión que nos permite acceder a contenido IPFS de forma sencilla.

Servicio systemd


Por último, quiero dejar el servicio de systemd necesario para tener el nodo IPFS funcionando constantemente en nuestro ordenador. En este caso, IPFS lo instalé vía Snap.
[Unit]
Description=IPFS daemon

[Service]
ExecStart=/snap/bin/ipfs daemon
Restart=on-failure

[Install]
WantedBy=default.target

sudo cp ipfs.service /etc/systemd/user/

sudo systemctl --user start ipfs.service
sudo systemctl --user enable ipfs.service

Y con esto ya tendríamos suficiente como para jugar con IPFS un buen rato.]]>
https://blog.adrianistan.eu/alojando-una-web-en-ipfs Mon, 24 Sep 2018 00:00:00 +0000
IPFS, el futuro de la web descentralizada https://blog.adrianistan.eu/ipfs-el-futuro-de-la-web-descentralizada https://blog.adrianistan.eu/ipfs-el-futuro-de-la-web-descentralizada IPFS. IPFS son las siglas de InterPlanetary File System y se trata de una red descentralizada de intercambio de archivos. Nos puede recordar a BitTorrent y en efecto, una buena descripción para IPFS es Torrent 2.0. No obstante IPFS implementa bastantes mejoras sobre BitTorrent, mejoras que lo hacen más útil.



En un post próximo explicaré como podemos sacar partido a IPFS ahora mismo, pero antes vamos a ver algunos conceptos fundamentales de IPFS.

Siendo más técnicos, IPFS es una red P2P inspirada en BitTorrent, Git y Kademlia. El protocolo es una red de distribución de contenido (CDN) y define un sistema de archivos donde el identificador es el propio contenido (a través de su hash). En IPFS nada desaparece sino que se versiona.

Vamos a ver como usar IPFS desde la terminal. Existen varios clientes, en diferentes lenguajes, como js-ipfs (que tendrá mucha utilidad en aplicaciones web descentralizadas usando JavaScript), pero de momento el cliente más maduro es go-ipfs. Desde la web puedes descargarlo.

Iniciar IPFS


Una vez IPFS esté instalado hay que ejecutar lo siguiente:
ipfs init
o
ipfs init --profile server

La segunda opción es para centros de datos, ya que reduce el tráfico interno.

Este comando genera las claves RSA del nodo, necesarias para la comunicación con la red IPFS. Además se nos informa de nuestro ID de nodo. Para acceder a este ID en cualquier momento podemos escribir:
ipfs id

Iniciar el nodo IPFS


Para acceder al contenido IPFS necesitamos ejecutar un nodo. Este nodo se inicia con ipfs daemon. Una cosa importante es que por ejecutar el nodo no se va a descargar nada que no pidamos. IPFS es explícito, pero necesita estar activo.
ipfs daemon

Obtener archivos


Ahora vamos a descargar nuestros primeros archivos de la red IPFS.

Para obtener un archivo existen varias formas:
ipfs cat /ipfs/QmS4ustL54uo8FzR9455qaxZwuMiUhyvMcX9Ba8nUH4uVv/readme
o
ipfs cat QmS4ustL54uo8FzR9455qaxZwuMiUhyvMcX9Ba8nUH4uVv/readme

Podemos usar el comando cat de IPFS, que funciona de forma similar a cat de Unix.

Las URL de IPFS todavía no han sido definidas de forma definitiva pero de momento siguen el siguiente formato: /protocolo (omitible)/hash/fichero (si es una carpeta). Es el hash el que identifica el bloque de contenido y es lo que sirve para encontrar el contenido en la red IPFS. Internamente usa una DHT (Distributed Hash Table). IPFS no usa trackers como las primeras versiones de BitTorrent. En cambio usa una DHT basada en Kademlia (bastante rápida).

Para obtener un fichero binario usamos el operador de redirección de Linux.
ipfs cat QmWYhH2Nac5vSgTmc6NULncakvhCXgAci33M5NXHjjBuU8 > Comuneros.jpg

También se puede acceder a IPFS por dos métodos más:
FUSE

Podemos montar un sistema de ficheros en Linux gracias a FUSE que es una puerta a IPFS.
ipfs mount

Y tendrás acceso a carpetas /ipfs/hash/ integradas en tu sistema. Muy útil si lo queremos integrar en aplicaciones.
GATEWAY HTTP

Podemos usar IPFS sin tener instalado nada, usando la puerta de acceso que alguien generosamente proporcione a la red. La puerta se encargará de llevar el contenido IPFS al mundo cliente-servidor de HTTP. Existen varias, de hecho cada nodo tiene un servidor HTTP en localhost para esto mismamente, pero aquí voy a mencionar dos: la de IPFS.io y la de CloudFare.

Esto sirve para llevar IPFS a cualquier ordenador a través de un navegador web tradicional como Chrome, Firefox o Safari. También facilita la tarea de integrar IPFS en aplicaciones que no tengan desarrollada una librería específica.

Añadir archivos


Ahora vamos a poner en la red IPFS nuevos archivos. Para ello usamos ipfs add.

Esta operación genera el hash que identifica al contenido en la red y que tendremos que enviar a las personas que queramos que accedan al contenido.

La red IPFS de este modo es semi-privada, ya que sin saber el hash no podemos acceder al contenido en cuestión.

Y ya estaría, así de simple. También podemos añadir carpetas.

El último hash que se ve en la pantalla hace referencia a la carpeta entera.

Pining


Por defecto, IPFS almacena los archivos propios del nodo (los que hemos añadido con ipfs add). Pero entonces IPFS no supone ninguna ventaja respecto a HTTP. No es distribuido, solo hay una copia de los ficheros en el universo.

Cuando descargamos un fichero, IPFS guarda en caché una copia para ofrecer a la red, haciendo que sea un sistema distribuido de verdad. No obstante si estamos realmente interesados en conservar el fichero en nuestro nodo a disposición de la red IPFS tenemos que hacer pin (ipfs add). Cuando nos deje de interesar conservar el fichero a la red, podemos quitar el pin (ipfs rm).
ipfs pin add QmWYhH2Nac5vSgTmc6NULncakvhCXgAci33M5NXHjjBuU8
ipfs pin rm -r QmWYhH2Nac5vSgTmc6NULncakvhCXgAci33M5NXHjjBuU8

Más cosas


Todavía nos quedan muchas cosas de IPFS que ver, como el versionado, el sistema de nombres IPNS, el sistema de enlazado semántico IPLD y como podemos usar IPFS para páginas y aplicaciones web.

 ]]>
https://blog.adrianistan.eu/ipfs-el-futuro-de-la-web-descentralizada Wed, 19 Sep 2018 00:00:00 +0000
Computación cuántica para torpes: introducción para programadores https://blog.adrianistan.eu/computacion-cuantica-para-torpes-introduccion-para-programadores https://blog.adrianistan.eu/computacion-cuantica-para-torpes-introduccion-para-programadores
En este largo artículo, ideal para el verano, vamos a ver los principios fundamentales de los ordenadores cuánticos más allá de lo que la típica revista o web te podría contar. Veremos qué es un qubit y algunas puertas lógicas interesantes, así como su aplicación.

Los ordenadores cuánticos actuales requieren temperaturas de funcionamiento cercanas al cero absoluto

Notación de Dirac


Lo primero antes de entrar en materia cuántica, será adaptar nuestra notación clásica, a otra más potente. Esta notación que vamos a ver, llamada notación de Dirac o Bra-ket, nos será muy útil ya que los bits clásicos no son más que un caso concreto de qubit en esta notación.

En esta notación tenemos que representar los bits como matrices. Un conjunto de N bits se representa con una matriz de 1 columna y [latex]2^N[/latex] filas. En todas las posiciones existen ceros salvo para la posición que representa la combinación que representa. Veamos algunos ejemplos sencillos:

Un bit con valor 0 se representa así

[latex]
| 0 \rangle = \begin{pmatrix}
1\\
0
\end{pmatrix}[/latex]


Un bit con valor 1 se representa así:

[latex]
| 1 \rangle = \begin{pmatrix}
0\\
1
\end{pmatrix}[/latex]


Aquí contamos como en informática, empezando desde cero. Como ves la posición 0 del vector solo es 1 cuando representa el bit 0. Si la posición que tiene el 1 es la segunda, representa el bit 1.

La parte que va a la izquierda del igual se llama ket. En este caso hemos representado ket 0 y ket 1.

Si tenemos más bits se puede hacer de la misma forma. Vamos a representtar ket 10. 10 en binario es 2, así que estará en la posición tercera.

[latex]
| 10 \rangle = \begin{pmatrix}
0\\
0\\
1\\
0
\end{pmatrix}[/latex]



Puertas lógicas como producto de matrices


¿Recuerdas el producto de matrices de tus clases de álgebra? Resulta que todas las puertas lógicas clásicas pueden representarse como producto de matrices. Por ejemplo, la puerta lógica NOT se puede implementar con esta matriz:

[latex]
\begin{pmatrix}
0 & 1 \\
1 & 0 \\
\end{pmatrix}
[/latex]


Y aquí la vemos en acción

[latex]
\begin{pmatrix}
0 & 1 \\
1 & 0 \\
\end{pmatrix}\begin{pmatrix}
1 \\
0
\end{pmatrix}
=
\begin{pmatrix}
0 \\
1
\end{pmatrix}
[/latex]


También podría hacerse con la puerta AND que toma como entrada dos bits.

[latex]
\begin{pmatrix}
1 & 1 & 1 & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}
\begin{pmatrix}
0 \\
0 \\
0 \\
1
\end{pmatrix}
=
\begin{pmatrix}
0 \\
1
\end{pmatrix}
[/latex]




Un teclado con puertas cuánticas

Juntando bits


Para formar bits más grandes ya sabemos que tenemos que tener una matriz tan grande como combinaciones haya ([latex]2^N[/latex] posiciones, N bits). Existe una forma de calcular automáticamente la posición que hay que marcar y es hacer el producto tensorial. Si no sabes calcularlo no importa mucho, porque apenas lo vamos a usar, pero puedes ver un ejemplo de como funciona. En este ejemplo, queremos juntar los bits 1 y 1 (3 en decimal).

[latex]
\begin{pmatrix}
0 \\
1
\end{pmatrix}
\otimes
\begin{pmatrix}
0 \\
1
\end{pmatrix}
=
\begin{pmatrix}
0 \\
0 \\
0 \\
1
\end{pmatrix}
[/latex]



Qubits


Hasta ahora no hemos visto nada realmente nuevo, solo hemos preparado el terreno para dejar paso a los qubits. Personalmente desconocía que podían usarse matrices para operar con bits y aunque no es tan práctico como otros sistemas, lo cierto es que es muy explícito y elegante.

Los qubits son bits como los que hemos visto antes pero tienen un estado indeterminado. No es que tengan un valor de forma secreta y no sepamos cuál es. Podríamos decir que son los dos valores clásicos a la vez, como el gato de Schrodinger. Cuando realizamos una observación sobre el qubit, este colapsa y podemos ver uno de los dos estados. ¿Cuál vemos? No podemos saberlo a priori, pero hay probabilidades. Los bits clásicos no son más que qubits cuyas probabilidades de colapsar a 0 o 1 es del 100%, por tanto no hay duda y su valor sí que está determinado.

¿Cómo representamos los qubits y sus estados cuánticos? Con números complejos. Si recuerdas, estos eran los que tenían una parte real y una imaginaria. No obstante, voy a tomar solo los números reales para simplificar. Los números reales son números complejos válidos, pero los complejos son mucho más extensos.

La esfera de Bloch permite representar todos los estados cuánticos (números complejos)

Para calcular la probabilidad que tiene un estado cuántico de colapsar a un valor, hacemos el módulo y elevamos al cuadrado: [latex]|a|^2[/latex].

Todo qubit además debe cumplir una propiedad fundamental:

[latex]
\begin{pmatrix}
a \\
b
\end{pmatrix}
\text{ es un qubit}
\Leftrightarrow
|a|^2 + |b|^2 = 1
[/latex]


Y es que la probabilidad de ser 0 y de ser 1 sumadas deben equivaler al suceso seguro, es decir, 1. 100% de probabilidades de que de 0 o 1.

Con esto ya podemos definir algunos qubits.

[latex]
\begin{pmatrix}
\frac{1}{\sqrt{2}} \\
\frac{1}{\sqrt{2}}
\end{pmatrix}
[/latex]


Este es mi qubit preferido. Representa el estado de superposición cuántica. Cada valor tiene el 50% de probabilidades de salir. [latex]|\frac{1}{\sqrt{2}}|^2 = \frac{1}{2}[/latex]. Cuando colapsemos el qubit al observarlo será como lanzar una moneda al aire.

Otro detalle que a veces se nos pasa por alto es que los qubits pueden contener valores negativos. Estos qubits son físicamente diferentes a los positivos, pero tienen las mismas probabilidades de colapsar en los mismos valores que los positivos.

[latex]
\begin{pmatrix}
-1 \\
0
\end{pmatrix}
[/latex]


Es un qubit válido, que colapsa con 100% de probilidad a 0.

¿Cómo se representan los qubits en notación de Dirac? Representando la probabilidad que tiene cada combinación de bits de aparecer. Para un qubit sería algo así:

[latex]
\alpha | 0 \rangle + \beta | 1 \rangle
[/latex]


Siendo [latex]\alpha[/latex] y [latex]\beta[/latex] las probabilidades de colapsar a cada estado.

Puertas cuánticas


Ahora vamos a ver cuáles son las puertas lógicas más importantes del mundo cuántico.


Negación (Pauli X)


Esta es exactamente igual que en el mundo clásico, con la misma matriz que hemos visto antes. Su símbolo es el cuadrado con una X.

Aquí vemos una imagen del simulador IBM Q usando la puerta X cuántica. IBM nos deja ejecutarlo en ordenadores cuánticos reales. Veamos los resultados.

¡Terrible! La mayoría de casos, el ordenador cuántico responde 1, el valor correcto, pero un 13% de los casos no. Teóricamente había una probabilidad del 100% y en la práctica solo es del 86.3%. ¡Y solo es una puerta X! Es por ello que los procesadores cuánticos todavía necesitan mejorar mucho. Google, Microsoft e IBM están investigando de forma independiente en ordenadores cuánticos. Veremos quién consigue tener antes ordenadores cuánticos precisos (aunque hay expertos que piensan que nunca se podrá lograr).

CNOT


Esta puerta es muy interesante. Toma dos qubits, uno de control, que permanece invariable al traspasar la puerta y otro de datos. Al qubit de datos se le aplica la puerta X si el qubit de control está activo. Su matriz es la siguiente:

[latex]
CNOT = \begin{pmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 0 & 1 \\
0 & 0 & 1 & 0
\end{pmatrix}
[/latex]


Se reprensenta de esta forma:

O similar, porque los símbolos de computación cuántica no están todavía muy estandarizados. El punto grande es el qubit de control y el punto pequeño está sobre el qubit de datos.

HADAMARD


Esta puerta es quizá la más famosa del mundo cuántico. Veamos su matriz:

[latex]
\begin{pmatrix}
\frac{1}{\sqrt{2}} & \frac{1}{\sqrt{2}} \\
\frac{1}{\sqrt{2}} & -\frac{1}{\sqrt{2}}
\end{pmatrix}
[/latex]


Esta puerta permite poner un qubit clásico en estado de superposición cuántica. Y también deshacerlo. Es muy usada en algoritmos cuánticos. Se representa con un cuadrado y una H.

Los resultados en el ordenador cuántico real de IBM Q son:

Cuando debería de ser bastante más cercano a 50% en los dos valores.

Con esto ya tenemos las puertas más usadas. Básicamente con Hadamard, X y CNOT se pueden implementar casi todos los circuitos cuánticos. Solo nos faltarían las puertas que operan entran en números complejos para poder implementar todos los circuitos.

Algoritmo de Deutsch-Jozsa


El algoritmo de Deutsch-Jozsa es uno de los algoritmos cuánticos más sencillos de entender y que mejoran drásticamente el rendimiento respecto a un algoritmo clásico.

El planteamiento básico es que tenemos una caja negra que aplica una función sobre un bit. Estas funciones pueden ser: set-to-0, set-to-1 (ambas constantes), identidad (no cambiar nada) y X (ambas dinámicas) . Si queremos saber que función contiene la caja negra, ¿Cuántas veces tenemos que pasar valores? En una CPU clásica tendríamos que hacerlo dos veces para poder determinar que función contiene la caja negra. En una CPU cuántica... también. No hay diferencia. Pero, si cambiamos la pregunta a ¿de qué categoría es la función de la caja negra?, la cosa cambia. En una CPU clásica seguiríamos necesitando 2 pruebas, pero en una CPU cuántica y con un circuito por el exterior, podemos aprovechar la superposición cuántica para realizar una sola prueba y determinar si en el interior hay una función constante o dinámica.

Vamos a ver estas 4 funciones de la caja negra como son:

¿Se te ocurre como puedes crear un circuito fuera de la caja negra que con una sola prueba, ya sepa si estamos ante las funciones Set-0, Set-1 o Identidad, Negación?

El circuito es el siguiente:

Tal y como está diseñado si en q[1] medimos 0, la función es de tipo constante y si medimos 1, es de tipo dinámica. Un desarrollo matemático de los productos de matrices, como el que hay en Wikipedia, te mostrará como siempre es cierto. Este también es un ejemplo de como los ordenadores cuánticos pueden dar resultados totalmente deterministas.

Esta idea, se puede generalizar y extrapolar a otros problemas, generando una colección muy interesante de algoritmos que se ejecutan en tiempo exponencialmente menor que en una CPU clásica.

Algoritmos de Shor y de Grover


Estos dos algoritmos han sido llamados los killer apps de la computación cuántica, ya que son algoritmos que mejoran sustancialmente (uno de forma exponencial, otro de forma cuadrática) los tiempos de problemas reales.

El algoritmo de Shor fue el primero en ser descubierto, en 1994 por Peter Shor. Sirve para factorizar números (es decir, sacar los números primos que multiplicados generan el número original). Lo puede hacer en [latex]O((\log{n})^3)[/latex]. De este modo, los algoritmos tipo RSA que se basan en la factorización de números podrían romperse en tiempo polinómico, por lo cuál RSA ya no serviría como protección. El algoritmo de Shor no da siempre los resultados correctos, pero tiene una probabilidad de éxito superior a la de fracaso, por lo que con repetir múltiples veces la ejecución podríamos estar casi seguros del resultado.

El algoritmo de Grover fue descubierto en 1996 por Lov Grover y permite buscar en una lista no ordenada de datos en [latex]O(\sqrt{n})[/latex] mientras que en una computadora clásica sería [latex]O(n)[/latex].

Estos dos algoritmos sin duda marcan lo que se ha llamada la supremacía cuántica y que ocurrirá cuando los ordenadores cuánticos puedan ejecutar con suficiente precisión estos algoritmos y su utilidad en el mundo real supere a la de los algoritmos clásicos.

Entrelazamiento cuántico


Ya hemos visto las bases de los circuitos cuánticos. Ahora veamos algunas consecuencias de todo lo anterior. Cosas cuánticas que parecen hasta cierto punto fuera de la lógica. ¿Qué ocurre si tenemos varios qubits en un estado como este?

[latex]
\begin{pmatrix}
\frac{1}{\sqrt{2}} \\
0 \\
0 \\
\frac{1}{\sqrt{2}}
\end{pmatrix}
[/latex]


En este estado solo puede colapsar a 00 o a 11. ¿Esto qué significa? Significa que los qubits están entrelazados entre sí, uno depende de otro, si los separamos y uno colapsa a 0, el otro colapsa a 0. Si uno colapsa a 1, el otro colapsa a 1.

Es importante destacar que los qubits pueden separarse en este estados. Los qubits alejados a millones de kilómetros siguen entrelazados y el valor al que deciden colapsar se decide de forma instantánea. Esto quiere decir que se sincronizan a una velocidad superior a la de la luz. El truco es que no se transmite información, por eso el universo lo permite, pero esto permite realizar la teletransportación cuántica.

La forma de entrelazar qubits es muy sencilla, con una puerta Hadamard y una CNOT.

IBM Q todavía tiene que mejorar, pero se aprecia claramente el entrelazamiento cuántico.

Teletransportación cuántica


La teletransportación existe, al menos entre qubits. Y es instantánea (más o menos). La teletransportación cuántica la podemos provocar usando varios qubits entrelazados. Necesitamos 3 qubits. El qubit que va a ser teletransportado, un qubit del emisor y un qubit del receptor. La idea es entrelazar el emisor con el receptor (qubit de destino) y posteriormente el qubit del emisor con el qubit que va a ser transportado.

No he sido capaz de hacer que IBM Q haga una teletransportación, así que aquí viene un esquema distinto. T es el qubit a transportar, A es el emisor y B el receptor. En este ejemplo se usa la puerta Pauli Z, cuya matriz es la indicada.

El truco de la teletransportación instantánea tiene que ver con que A y B tienen que estar entrelazados, por tanto, han tenido que ser transportados a sus respectivos lugares anteriormente a velocidad inferior a la luz.

Esto teletransporta qubits pero no hace copias. Esto es debido al Teorema de No Clonación.

Lenguajes de programación


Mientras esperamos a que los ordenadores cuánticos sean lo suficientemente estables, ya existen lenguajes de programación que podemos usar en simuladores. Quizá el más conocido sea Q# de Microsoft (funciona en Linux, tranquilos), que guarda similitudes con C#. Otro bastante usado es OpenQasm de IBM, algo más parecido a ensamblador.
namespace Quantum.Bell
{
open Microsoft.Quantum.Primitive;
open Microsoft.Quantum.Canon;

operation Set (desired: Result, q1: Qubit) : ()
{
body
{
let current = M(q1);
if (desired != current)
{
X(q1);
}
}
}
operation BellTest (count : Int, initial: Result) : (Int,Int,Int)
{
body
{
mutable numOnes = 0;
mutable agree = 0;
using (qubits = Qubit[0])
{
for (test in 1..count)
{
Set (initial, qubits[0]);
Set (Zero, qubits[1]);

H(qubits[0]);
CNOT(qubits[0],qubits[1]);
let res = M (qubits[0]);

if (M (qubits[1]) == res)
{
set agree = agree + 1;
}

// Count the number of ones we saw:
if (res == One)
{
set numOnes = numOnes + 1;
}
}

Set(Zero, qubits[0]);
}
// Return number of times we saw a |0> and number of times we saw a |1>
return (count-numOnes, numOnes, agree);
}
}
}

Este es un ejemplo de lanzar la moneda con entrelazamiento cuántico en Q#, el lenguaje cuántico de Microsoft.

Referencias


Quantum Computing for Computer Scientists
Cats, Qubits, and Teleportation: The Spooky World of Quantum Algorithms
Microsoft Quantum Development Kit: Introduction and step-by-step demo
Qubit]]>
https://blog.adrianistan.eu/computacion-cuantica-para-torpes-introduccion-para-programadores Tue, 17 Jul 2018 00:00:00 +0000
Usar AVA para tests en una API hecha en Node.js y Express https://blog.adrianistan.eu/usar-ava-para-tests-en-una-api-hecha-en-node-js-y-express https://blog.adrianistan.eu/usar-ava-para-tests-en-una-api-hecha-en-node-js-y-express AVA para crear tests para mis APIs: tanto unitarios como de integración con AVA.



AVA es una herramienta de testing, que nos permite describir los tests de forma muy sencilla. De todas las herramientas que he probado, AVA es muy preferida. Es muy sencilla, ejecuta los tests en paralelo y permite escribir código en ES6 (usa Babel por debajo). Además tiene bastante soporte siendo el framework de testing usando por muchos proyectos ya.

Instalamos AVA de la forma típica:
npm install ava --save-dev

A continuación creamos un fichero cuya terminación sea .test.js, por ejemplo, suma.test.js. El lugar da igual.

Una opción de diseño es poner los test unitarios al lado de las funciones en sí, otra opción es crear una carpeta para todos los tests, ya que los de integración van a ir casi siempre ahí. Para ejecutar los tests, simplemente:
ava

El interior de suma.test.js debe importar la función test de AVA y las funciones que quiera probar.

Los tests se definen como una llamada a la función test con la descripción del test y a continuación un callback (asíncrono si queremos) con el objeto que controla los tests (llamado t normalmente). Veamos un ejemplo simple:
import test from "ava";
import {suma} from "./operaciones";

test("Suma",t => {
t.is(suma(1,2),3);
});

El objeto t soporta múltiples operaciones, siendo is la más básica. Is pide que sus dos argumentos sean iguales entre sí, como Assert.Equal de xUnit.

Veamos que más soporta Ava.

  • t.pass(): Continúa el test (sigue)

  • t.fail(): Falla el test (no sigue)

  • t.truthy(val): Continúa el test si val es verdaderoso (usando la lógica de JavaScript) o falla el test

  • t.true(val): Continúa el test si val es verdadero (más estricto que el anterior) o falla.

  • t.is(val1,val2): Continúa el test si val1 y val2 son iguales (superficialmente) o falla.

  • t.deepEqual(val1,val2): Continúa el test si val1 y val2 son iguales (profundamente) o falla.

  • t.throws(funcion): Ejecuta la función especificada esperando que lance una excepción. Si no lo hace, falla el test. Se puede especificar el tipo de excepción que esperamos en el segundo argumento.

  • t.notThrows(funcion): Exactamente lo contrario que la anterior.


Y algunas más, pero estas son las esenciales.
import test from "ava";

function sum(a,b){
return a+b;
}

function login(username,password){
if(username === null || password === null){
throw new Error("Missing username or password");
}
}

test("Test example: Sum",t => {
t.is(sum(1,2),3);
});

test("Login fail username null", t => {
t.throws(()=>{
login(null,"123456");
});
});
test("Login fail password null", t => {
t.throws(()=>{
login("username",null);
});
});

También podemos definir funciones que se ejecuten antes y después de nuestros tests, y una sola vez o con cada test. Podemos usar test.before, test.beforeEach, test.after y test.afterEach en vez de test. Por ejemplo, si tienes una base de datos que necesita inicialización, puedes definir ese código en test.before y la limpieza en test.after.
import test from "ava";
import db from "../db";

test.before(async () => {
// Iniciar el ORM Sequelize
await db.sync();
});

 

Con esto ya podemos hacer tests unitarios, pero no podemos probar la aplicación web al 100%. Entra en acción supertest que nos permitirá tener un servidor Express simulado para que los tests puedan probar la aplicación al completo.

Supertest


Instalamos supertest
npm install supertest --save-dev

En el fichero de test necesitamos crear un objeto de tipo aplicación de Express. Este objeto puede ser el mismo que usas en tu aplicación real o ser una versión simplificada con lo que quieras probar.
import test from "ava";
import request from "supertest";
import auth from "http-auth";
import express from "express";

function setup(){
const app = express();

let basic = auth.basic({
realm: "Upload Password"
}, function (username, password, callback) {
callback(username === "admin" && password === "123456");
});

app.get("/upload",auth.connect(basic),function(req,res){
res.sendFile("upload.html");
});
return app;
}

test("Página upload requiere autenticación HTTP Basic", async t => {
let res = await request(setup())
.get("/upload")
.send();
t.is(res.status,401);
t.true(res.header["www-authenticate"] !== undefined);
});

Aquí la función de test es asíncrona, AVA es compatible con ambos tipos. Supertest es muy completo y permite probar APIs enteras con su sencilla interfaz que junto con AVA, se convierte en algo casi obligatorio para una aplicación que vaya a producción.]]>
https://blog.adrianistan.eu/usar-ava-para-tests-en-una-api-hecha-en-node-js-y-express Sun, 15 Jul 2018 00:00:00 +0000
La perlificación de Python https://blog.adrianistan.eu/la-perlificacion-de-python https://blog.adrianistan.eu/la-perlificacion-de-python


You know, FOX turned into a hardcore sex channel so gradually I didn't even notice



Marge Simpson, Los Simpson




Recientemente ha salido Python 3.7, con interesantes novedades. También han salido los primeros artículos hablando de las novedades que podrá traer Python 3.8. Como muchos ya conoceréis, y si no es así explico, Python funciona a base de PEPs (Python Enhancement Proposal). Cualquier persona puede abrir un PEP, que es un documento que describe la funcionalidad que se quiere añadir/modificar/eliminar. Estas PEPs se discuten y finalmente Guido, creador de Python, las aprueba y se codifican.

Dentro de las PEP relacionadas con Python 3.8 hay algunas bastante controvertidas que han hecho saltar la voz de alarma. No ha faltado gente que ha opinado que cada vez Python se parece más a Perl. Este proceso habría empezado con Python 3 pero se habría ido haciendo más evidente hasta llegar a hoy. Cada vez con más sintaxis poco utilizada, con más elementos, más cómodo de escribir para el experimentado aunque menos legible si no dominas el lenguaje.

Y resulta curioso, porque Python es en parte una respuesta a la excesiva complejidad que podían tener los programas hechos en Perl. Su popularidad se debe a que es fácil de aprender y eso parece que ya no está tan claro.



Con la introducción de operadores como := o ?? o anotaciones como @dataclass se va, en cierta medida, perdiendo el espíritu original de Python. Y es cierto que otros lenguajes tienen algo similar, pero precisamente Python había sido muy selecto en incluir un conjunto bastante reducido de características, que todo el mundo pudiese dominar. Al final se sacrifica legibilidad y facilidad de aprendizaje por una ergonomía que beneficia a unos pocos en unos casos concretos.

Lenguajes de programación barrocos


Universidad de Valladolid, ejemplo de barroco civil. Foto: https://artevalladolid.blogspot.com

Python lleva un tiempo entrando en un proceso de perlificación pero en mi opinión no es el único lenguaje que ha entrado en una espiral parecida. Cada vez más lenguajes han pasado del renacimiento, donde se buscaba la consistencia, la simetría, la sencillez sin perder la funcionalidad, hacia el barroco, donde los lenguajes son más recargados, con más elementos sintácticos, que cubren mejor casos concretos, pero que de por sí no son tan esenciales, no cambian aspectos fundamentales del lenguaje y normalmente introducen diversas formas de hacer algo en un mismo lenguaje.

Veamos más ejemplos: en C++20 se propuso añadir funcionalidad de dibujado 2D a la librería estándar (propuesta que fue rechazada en una historia bastante larga para este post) y se han propuesto conceptos, módulos, comparación de tres vías, reflexión, metaclases,... En C# 8.0 se han propuesto también bastantes cosas como records, tipos non-nullable, interfaces con métodos ya implementados (traits) y rangos. Y eso sin contar con las características que estos dos lenguajes ya tienen, que son bastante más extensos que Python.

Retablo lateral de la Iglesia de San Miguel y San Julián (Valladolid). Barroco puro. Foto: https://commons.wikimedia.org/wiki/File:San_Miguel_-_retablo_relicario.jpg

Hay un horror vacui, horror al vacío, a funcionalidades. Siempre se añade y casi nunca se elimina. ¿Pero es realmente necesario? Es evidente que durante mucho tiempo, los lenguajes evolucionaban de forma muy lenta y muchos de los cambios que han hecho eran necesarios. Desde hace unos años, se ha aumentado la velocidad de los cambios, pero esto no puede seguir así eternamente, porque el ritmo que llevan muchas veces es superior al de los avances en lenguajes de programación y la retrocompatibilidad impide ir quitando cosas al mismo ritmo que se añaden. De este modo, todos los lenguajes que entran en esta espiral crecen y crecen. No se llegan a pulir, simplemente crecen.
La perfección no se alcanza cuando no hay nada más que añadir, sino cuando no hay nada más que quitar

Antoine de Saint-Exupéry


Uno podría comparar esto con lenguajes naturales, como el español o el inglés. En estos idiomas, pese a existir reglas, existen numerosas excepciones. Es posible que lenguajes como Python se estén viendo influenciados por las mismas mecánicas humanas que rigen los lenguajes naturales y que han hecho que existan excepciones. Tendría bastante sentido que ocurriese así. Pero personalmente, me gustaría que aprender Python no costase tanto como aprender alemán o francés.

Los procesos comunitarios


Para mí, gran parte de esta sobrecarga viene dada por los procesos comunitarios. En un proceso comunitario como PEP, comité de C++ o similares es mucho más fácil añadir que eliminar. En C++ la situación ha llegado a un punto en el que Bjarne Stroustrup, creador de C++, ha pedido que se relajen con las propuestas en Remember the Vasa!, en honor a un bonito barco sueco que se hundió por exceso de carga. No tanto por su peso, sino por su disposición y las reformas que hubo que hacer para que todo encajase.

El Vasa fue recuperado después de su naufragio y se expone en Estocolmo. Foto: https://rachelannsblog.wordpress.com/2016/08/03/set-sail-set-at-the-bottom-of-the-sea/

Es por ello que las personas encargadas de elegir que se añade al lenguaje o no deben de ser muy conscientes de lo que supone, ya que una vez se introduzca, va a ser casi imposible eliminarlo.

No estoy en contra de añadir nuevas características (¡al contrario!) pero se debe respetar la visión de conjunto del lenguaje, que todo cuadre y esté cohesionado. No siempre tener más es mejor.

¿Te ha gustado el artículo?


Si es así, puedes ayudar al blog. Dentro de unos días es el Amazon Prime Day. Como muchos de vosotros seguro que os compraréis algo, no quiero dejar la oportunidad de deciros que este blog tiene enlace de referidos y que por cada cosa que compréis con el enlace, me llevo una pequeña parte (a vosotros no os va a costar más caro).

Enlace Amazon.es

Será muy bien recibido

 ]]>
https://blog.adrianistan.eu/la-perlificacion-de-python Tue, 10 Jul 2018 00:00:00 +0000
¿Cómo funcionan los sistemas basados en inodos? https://blog.adrianistan.eu/como-funcionan-los-sistemas-basados-en-inodos https://blog.adrianistan.eu/como-funcionan-los-sistemas-basados-en-inodos sistemas FAT, saltamos a los sistemas de inodos. Estos se han usado tradicionalmente en sistemas UNIX (UFS, ext2), así que tradicionalmente ha existido una cierta rivalidad  en las redes entre FAT e inodos similar a la de Windows/Linux. Lo cierto es que a nivel técnico cada uno tiene fortalezas y debilidades.

Partición


Tomando la base de FAT, una partición de un sistema basado en inodos también contiene un sector de arranque y un superbloque con metadatos. También es necesario un bloque dedicado al directorio raíz presente en el disco. Además es necesario espacio para almacenar todos los inodos y un mapa de bits de espacio libre que en FAT no hacía falta, ya que la propia tabla nos indicaba que bloques del disco estaban libres.

Los inodos


¿Qué es inodo te podrás preguntar? Es una estructura de datos, su nombre proviene de index node y es que los inodos no son más que índices, que almacenan los números de bloque de las diferentes partes del archivo. Además, contienen metadatos como permisos, propietario, tamaño, fecha de modificación, referencias, tipo de fichero (directorio, archivo, enlace duro, enlace blando,...) salvo el nombre del propio archivo, que en ningún sitio del inodo aparece.



Este sistema tiene una ventaja de rendimiento respecto a FAT en cuanto al acceso aleatorio a los archivos, ya que es mucho más rápido de esta forma que con FAT. En FAT para hacer lo mismo tenemos que ir recorriendo la tabla arriba y abajo siguiendo los números de bloque hasta encontrar el bloque deseado.

Normalmente un inodo tiene un tamaño fijo, lo que implica que el índice no se puede alargar hasta el infinito. Esto hace que haya un número limitado de bloques que puede usar un archivo y por ende, que haya un tamaño máximo de archivo que no es muy elevado. Para solventar este problema hay varias soluciones. El enfoque de UFS y de ext2/3/4 consiste en múltiples niveles de indexado progresivo.

Esto significa que los primeros bloques son números de bloque directos pero los siguientes son números de bloque que llevan a tablas de inodo secundarias que ya sí, hacen referencia al archivo real. Más adelante los números de bloque hacen referencias a tablas de inodo secundarias que a su vez llaman a tablas de inodos terciarias.

Esto provoca algo que en principio puede parecer paradójico y es que es más lento leer una zona final de un archivo que una zona del principio, aunque en una lectura secuencial no se nota demasiado.

Otra solución a esto es enlazar los inodos a modo de lista enlazada añadiendo al final de cada inodo un número de inodo al que continuar la lectura de índices.

Localización de los inodos


Los inodos se crean cuando se formatea el disco y permanecen en un estado libre. No se pueden añadir más inodos ni quitar inodos y no puede haber más archivos y directorios que inodos por esta misma razón. Esto es una desventaja respecto a FAT, ya que en FAT puede haber tantos archivos como bloques haya/entradas tenga la tabla. En sistemas de inodos como ext2/3/4 puede ocurrir que no queden inodos libres pero haya todavía bloques libres, dejando todo ese espacio inutilizado (aunque en realidad lo podrían usar los archivos existentes si creciesen).

Los inodos se pueden ubicar de dos formas distintas. Un enfoque consiste en ponerlos al principio del disco todos juntos. Otro enfoque, el que sigue ext3, consiste en dividir el disco en 4 zonas y ubicar inodos en cada inicio de zona. La idea es que los inodos de esa zona usen bloques de esa zona y de esta forma reducir los desplazamientos de las cabezas lectoras del disco (en unidades SSD esto da completamente igual como podréis suponer).

Gestión del espacio libre


Una de las grandes ventajas de FAT era que la tabla podía mantener a la vez un listado de bloques libres, listos para ser usados. Con inodos no tenemos esa facilidad y tenemos que recurrir a otros tipos de estructura. Aquí hay muchos planteamientos siendo el más común el mapa de bits. El mapa de bits es una estructura que se compone de un listado de bits. Cada bit corresponde a un bloque y dependiendo de si el bit es 1 o 0 podemos saber si el bloque está libre u ocupado.

Como veis, los sistemas basados en inodos tienen que mantener varias estructuras de datos de forma paralela. Esto aumenta las probabilidades de inconsistencias en el sistema. Por este motivo, muchos sistemas más avanzados como ext4 mantienen un journal o diario.

Fragmentación


Veamos como se comportan los sistemas de inodos ante los tres tipos de fragmentación. Respecto a la fragmentación interna, que es aquella que sucede cuando asignamos espacio de más a archivos que no necesitan tanto, tiene los mismos valores que FAT, ya que los bloques siguen pudiendo pertenecer únicamente a un archivo. Realmente, para mejorar la fragmentación interna tenemos que saltar a sistemas como XFS o JFS.

La fragmentación externa de los sistemas basados en inodos es la misma que la de FAT, 0, ya que todo se asigna mediante bloques del mismo tamaño.

Respecto a la fragmentación de datos, históricamente las implementaciones como UFS o ext2/3 se han comportado relativamente bien, aunque a nivel teórico nada impide que se comporten igual de mal que FAT, no existen mecanismos preventivos. Ext4 por contra, sí que tiene mecanismos de prevención (delayed allocation).

Directorios y enlaces duros


En los sistemas basados en inodos los directorios son más sencillos que en FAT, ya que no tienen que almacenar los metadatos del archivo en cuestión. En un fichero de directorio simplemente se almacena el nombre y el número de inodo correspondiente.

Aquí surge un concepto muy interesante, el de enlaces duros. Un enlace duro no es más que otra entrada de directorio que apunta a un mismo inodo. Realmente desde el punto de vista del sistema de archivos no existe un fichero original y su enlace. Simplemente dos entradas diferentes apuntando al mismo inodo. ¿Y cómo reacciona esto a los borrados? Imagina la siguiente situación: tengo un fichero y creo un enlace duro. Borro el fichero original y su inodo se libera. Ahora creo otro archivo y reutiliza ese mismo inodo que estaba libre. ¡Ahora el enlace duro apunta a un contenido totalmente distinto sin darse cuenta! Para prevenir esto, los inodos no se liberan hasta que no quede ninguna entrada de directorio apuntando a ellos. Para eso sirve el campo referencias dentro del inodo, para llevar la cuenta de cuántas veces se hace referencia al inodo en el sistema de archivos. Cuando el valor de referencias llega a cero, se puede liberar sin problema.

Conclusiones


Los sistemas basados en inodos son conceptualmente algo más complejos que los basados en FAT. Comparten además limitaciones comunes, usan más espacio de disco, aunque suelen ser más rápidos.

Actualmente existen sistemas basados en inodos mucho más avanzados y complejos que UFS y ext2/3/4 y que mejoran este sistema de forma sustancial. Por ejemplo en XFS los inodos tienen una estructura totalmente distinta donde se indica un bloque de inicio y una longitud en número de bloques contiguos que pertenecen al archivo. Muchos sistemas además usan estructuras de árbol B+ como pueden ser ZFS o Btrfs.]]>
https://blog.adrianistan.eu/como-funcionan-los-sistemas-basados-en-inodos Thu, 14 Jun 2018 00:00:00 +0000
¿Cómo funcionan los sistemas de archivos basados en FAT? https://blog.adrianistan.eu/como-funcionan-los-sistemas-de-archivos-basados-en-fat https://blog.adrianistan.eu/como-funcionan-los-sistemas-de-archivos-basados-en-fat
En esencia un sistema de archivos es un método ordenado que permite guardar datos sobre un soporte físico para luego poder acceder a ellos. Históricamente ha habido muchos enfoques a este problema: los sistemas más usados usan archivos, directorios y enlaces.

Bloques y sectores: la división del disco


Esta parte es común a muchos sistemas de archivos, tanto FAT como inodos, como otros. A nivel físico los dipositivos están divididos. En el caso del disco duro, el dispositivo de almacenamiento más común, los discos se dividen en sectores de N bytes, según parámetros de la fabricación. Estos sectores cuentan con código de control de errores incorporado y todo ello es manejado por la controladora de disco que opera ajena al sistema operativo. Los sistemas de archivos dividen a su vez el disco en bloques. Es importante dejar claro que bloque no es igual a sector. El bloque es una división que hace el sistema de archivos y los sectores los hace la controladora de disco. Usualmente un bloque equivale a varios sectores, aunque la equivalencia real depende del sistema de archivos en concreto.

Algunos bloques especiales: boot y superbloque y raíz


Antes de entrar en el mecanismo específico de FAT es necesario comentar que en un disco existirán dos bloques de vital importancia ajenos a FAT. El primero es el bloque de boot o arranque (también llamado MBR). Normalmente situado en el sector 0 del disco duro, contiene código para iniciar el sistema operativo. El superbloque suele ser el bloque siguiente, contiene metadatos del sistema de archivos (por ejemplo, puede decir que usamos FAT con bloques de 32KiB, etc). Además en FAT es necesario que exista un fichero siempre, el fichero del directorio raíz.

Directorios


Los directorios o carpetas son archivos como otros cualquiera, solamente que en sus metadatos se indica que es un directorio y no un fichero corriente. Los ficheros de directorios son los encargados de almacenar los metadatos de los ficheros (paras saber si son ficheros, otros directorios, su fecha de modificación, propietario y tamaño entre otros) que se encuentran en el directorio así como una referencia al bloque.

Un fichero en FAT


Vamos al asunto. Supongamos que en un determinado bloque N tenemos un fichero. Este bloque es de 64 KiB. Si un fichero ocupa menos de 64 KiB, perfecto, todos los datos entran en el bloque. Simplemente ajustamos los metadatos de tamaño con el valor correcto y el resto del espacio que queda en el bloque queda inutilizado.

Este espacio perdido se denomina fragmentación interna y dependiendo de los datos que se almacenen en un disco duro, el porcentaje de pérdida puede ser mayor o menor. Evidentemente si tenemos bloques muy grandes y ficheros muy pequeños perderemos mucho espacio debido a la fragmentación interna. Tener bloques muy pequeños y ficheros muy grandes también es problemático pero por otros motivos.
Tipos de fragmentación

En un sistema de archivos existen 3 tipos de fragmentación: interna, externa y de datos. La interna se refiere al espacio perdido en bloques asignados a ficheros que no están llenos por completo. La externa se refiere al espacio que perdemos por no tener un espacio libre contiguo lo suficientemente grande como para guardar el fichero allí. Ningún sistema FAT o de inodos tiene fragmentación externa al usar todos bloques de tamaño predefinido. Por último la fragmentación de datos, o fragmentación a secas, se refiere a que los bloques asignados estén contiguos o no. Esto tiene implicaciones a nivel de rendimiento pero no al número de bytes que se vuelven inútiles como los otros dos tipos de fragmentación.

¿Pero qué pasa si el contenido de nuestro fichero no puede entrar en el tamaño de bloque? Aquí viene la gracia de FAT, la File-Allocation-Table. La FAT es una tabla compuesta por entradas que indican el siguiente bloque del archivo. La tabla está indexada por bloque y además de indicar cuál es el siguiente bloque del archivo también indica si el archivo acaba ahí o si ese bloque está libre y puede usarse para un archivo nuevo.

En la foto el archivo /home/user/hola.txt tiene una longitud menor al tamaño de bloque. Así que miramos en la FAT la entrada 150 y efectivamente comprobamos que no hay bloque siguiente ya que es un End-of-File.



Pongamos un ejemplo con un archivo largo. Cada celda de la tabla correspondiente al índice del bloque actual indica el siguiente bloque a leer. Estos bloques pueden estar en cualquier parte. Si en un disco duro muchos ficheros tienen los bloques muy dispersos, se dice que necesita ser desfragmentado.

Conclusiones


FAT es muy rápido si la tabla FAT consigue ser cargada en memoria, ya que obtener los datos necesarios de ella será muy rápido. Desgraciadamente, dependiendo del tamaño del disco duro y del tamaño de los bloques, esta tabla puede ocupar mucho y ser impráctico.

El mecanismo FAT como vemos es simple pero efectivo. Dispone de una fragmentación interna limitada y carece de fragmentación externa. Un inconveniente es que FAT cuando busca un archivo necesita empezar siempre desde el directorio raíz. Esto implica accesos cada vez más lentos a medida que vayamos haciendo carpetas, aunque con sistemas de caché se puede reducir.

El esquema FAT tampoco impone restricciones propiamente dichas al número archivos de un sistema ni a su tamaño. Sin embargo en la práctica suelen existir límites.

Edito: He explicado mejor que significa la fragmentación y sus tipos]]>
https://blog.adrianistan.eu/como-funcionan-los-sistemas-de-archivos-basados-en-fat Mon, 11 Jun 2018 00:00:00 +0000
Bindings entre Rust y C/C++ con bindgen https://blog.adrianistan.eu/bindings-rust-c-bindgen https://blog.adrianistan.eu/bindings-rust-c-bindgen desde siempre pero dependiendo de la librería podía llegar a ser muy tedioso. Afortunadamente tenemos bindgen, un programa que permite generar estos bindings de forma automática analizando el código de la librería de C o C++.



En este post veremos como usar SQLite desde Rust usando bindgen.

Instalando bindgen


En primer lugar necesitamos tener instalado Clang 3.9 o superior. En Ubuntu o Debian necesitamos estos paquetes:
sudo apt install llvm-3.9-dev libclang-3.9-dev clang-3.9

Para el resto de plataformas puedes descargar el binario desde la página de descargas de LLVM.

Bindgen permite dos modos de uso: línea de comandos o desde el código Rust. El más habitual es desde código Rust pero antes veremos el modo en línea de comandos.

Modo línea de comandos


Para bindings sencillos podemos usar el modo línea de comandos. Instalamos binden con Cargo:
cargo install bindgen

Su uso es muy sencillo:
bindgen /usr/include/sqlite3.h -o sqlite.rs

Simplemente indicamos el fichero de cabecera que queremos traducir y su correspondiente fichero de salida en Rust. Este fichero será el pegamento. Vamos a crear un programa que use este pegamento:
mod sqlite;

use sqlite::{sqlite3_open, sqlite3_exec, sqlite3_close, SQLITE_OK};
use std::ffi::CString;
use std::ptr::{null_mut,null};

fn main(){
let mut db = null_mut();
let database_name = CString::new("test.db").unwrap().into_raw();
let sql = CString::new("
CREATE TABLE contacts (name TEXT, tel TEXT);
INSERT INTO contacts VALUES ('Adrian','555-555-555');").unwrap().into_raw();
let mut error_msg = null_mut();
unsafe{
sqlite3_open(database_name,&mut db);
let rc = sqlite3_exec(db,sql,None,null_mut(),&mut error_msg);
if rc != SQLITE_OK as i32 {
let error = CString::from_raw(error_msg);
println!("ERROR: {}",error.into_string().unwrap());
}
sqlite3_close(db);
}
}

Como se puede apreciar, las llamadas al módulo de pegamento de hacen desde un bloque unsafe ya que se van a usar punteros al estilo C, de forma insegura. Hace tiempo escribí sobre ello así que voy a saltarme esa parte.

Compilamos enlazando de forma manual libsqlite3 de la siguiente forma:
rustc main.rs -lsqlite3

Si todo va bien, compilará aunque con numerosos warnings. En principio no son importantes.

Ahora si ejecutamos el programa resultante debería crear una base de datos nueva con una tabla contacts y los datos insertados.

¡Hemos conseguido llamar a una librería de C desde Rust y no hemos escrito ningún binding!

Build.rs


El sistema anterior funciona, pero no es lo más práctico, además no usa Cargo que es el sistema estándar de construcción de programas y crates un Rust. Lo habitual es dejar este proceso automatizado en el fichero build.rs que se ejecuta con Cargo.

Lo primero es añadir la siguiente línea al fichero Cargo.toml:
[build-requires]
bindgen = "0.26.3"

El siguiente paso consiste en crear un archivo cabecera de C que a su vez haga referencia a todos los archivos de cabecera que necesitamos. En el caso de SQLite es bastante simple.
#include <sqlite3.h>

Y lo llamamos wrapper.h

Ahora viene lo interesante. Dentro de build.rs creamos un programa que gracias a la API de bindgen haga lo mismo que la línea de comandos.
extern crate bindgen;

use std::env;
use std::path::PathBuf;

fn main() {
// indicamos al linker que necesitamos sqlite3
println!("cargo:rustc-link-lib=sqlite3");


let bindings = bindgen::Builder::default()
.header("wrapper.h")
.generate()
.expect("Unable to generate bindings");

// escribir los bindings en $OUT_DIR/bindings.rs
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}

El archivo build.rs debe estar en la misma carpeta que Cargo.toml para que funcione.

Finalmente para hacer accesible nuestros bindings creamos un módulo llamado sqlite.rs con el siguiente contenido.
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

Lo único que hace es desactivar unos warnings molestos e incluir el texto de bindings.rs al completo.

Una vez hecho esto podemos usar desde el programa principal la librería de la misma forma que hemos visto antes.

Ahora podríamos usar estos bindings directamente en nuestro programa o rustizarlos (darles una capa segura alrededor e idiomática) y subirlo a Crates.io.

El código del post está en GitHub]]>
https://blog.adrianistan.eu/bindings-rust-c-bindgen Thu, 7 Jun 2018 00:00:00 +0000
Ayuda a la ciencia desde tu casa con BOINC https://blog.adrianistan.eu/ayuda-a-la-ciencia-desde-tu-casa-con-boinc https://blog.adrianistan.eu/ayuda-a-la-ciencia-desde-tu-casa-con-boinc
El ENIAC, uno de los primeros ordenadores construidos

Este tipo de cálculos se realizan normalmente en supercomputadoras en clúster que trabajan en paralelo. Sin embargo en muchas ocasiones, nada impide que cada nodo esté separado kilómetros de distancia. Ahí nace el proyecto BOINC (siglas de Berkeley Open Infrastructure for Network Computing). BOINC es un sistema de computación distribuida en Grid. Esto quiere decir que el cómputo necesario para un proyecto se divide en paquetes. Cada paquete se manda a un ordenador que forma parte de la red. Cada ordenador se encarga de procesarlo. Cuando los cálculos han finalizado, los envía al servidor central que le entrega a su vez al ordenador un paquete con cómputos nuevos por realizar.

De todos los sistemas de computación distribuida en Grid, BOINC es sin duda el más popular. La potencia de cálculo de BOINC es de media 23 petaflops, algo lejos del mayor supercomputador (Sunway TaihuLight, de China, con 93 petaflops) pero superior a muchos supercomptadores como MareNostrum, el mayor supercomputador de España y que tiene 11 petaflops.

Supercomputador Mare Nostrum 4

Cualquier ordenador del mundo puede colaborar con la red BOINC aportando su potencia a la red y calculando datos muy importantes para las investigaciones científicas.

¿Cómo puedo usar BOINC y ayudar?


Es muy sencillo. BOINC dispone de versiones para Windows, Mac, Linux y Android, así como versiones optimizadas con CUDA (Nvidia), OpenCL y existen versiones de terceros para Solaris, HP-UX, FreeBSD y otros sistemas.

El primer paso es descargar BOINC desde https://boinc.berkeley.edu/ . Si estás en Android puedes ir directamente a Google Play y descargar BOINC desde allí.

El cliente BOINC se divide en dos partes: una que se dedica a calcular y otra que es una interfaz de usuario que nos permite controlar la parte que calcula (BOINC Manager). La ventaja de esto es que son programas independientes y no hace falta tener la interfaz de usuario abierta para seguir calculando.

Proyectos


Lo primero que tenemos que hacer nada más abrir el BOINC Manager es registrarnos en un proyecto para que nos empiece a mandar tareas.

Algunos de los más populares son:

  • SETI@Home. Es el proyecto que busca vida extreterrestre por el universo analizando las señales de radiotelescopios como el de Arecibo. Fue uno de los primeros proyectos de BOINC y uno de los más populares.

  • Rosetta@Home: Este proyecto busca nuevos tipos de proteínas así como su aplicación en enfermedades como la malaria y el alzheimer,.

  • Einstein@Home: Este proyecto busca detectar ondas gravitacionales desde los datos recogidos de observatorios como LIGO.

  • PrimeGrid: Busca nuevos números primos, esenciales en campos como la de la criptografía.

  • ClimatePrediction: Busca modelos climáticos mejorados que permitan mejorar nuestra capacidad de predicción del clima

  • MilkyWay@Home: Investiga modelos tridimensionales sobre nuestra galaxia, la vía láctea

  • LHC@Home: Tareas relacionadas con la investigación en física de partículas con datos generados por el LHC.

  • Asteroids@Home: Amplía nuestro conocimiento sobre los billones de asteroides que hay en el universo, analizando los que podemos ver


Para registrarnos indicamos nuestro correo y nuestra contraseña y automáticamente todo empezará a funcionar. ¡No hay que hacer nada más! BOINC se encargará de todo automáticamente.

Desde la interfaz podemos parar los cálculos en cualquier momento pulsando Suspend o Pausar.

BOINC Credits


En recompensa a nuestro trabajo se nos dan BOINC Credits. La cantidad de BOINC Credits es proporcional a los gigaflops que hayamos aportado. Estos créditos no sirven realmente para nada. Únicamente para fardar ante tus amigos. Y es que hay gente realmente picada y existen equipos de gente que se unen para superar a otros equipos. Los BOINC Credits también sirven para realizar benchmarks de los ordenadores. Puedes consultar tus BOINC Credits desde BOINC Manager o desde webs externas como BOINC Stats.

Mis estadísticas en BOINC]]>
https://blog.adrianistan.eu/ayuda-a-la-ciencia-desde-tu-casa-con-boinc Sat, 26 May 2018 00:00:00 +0000
Fabric, automatiza tareas en remoto https://blog.adrianistan.eu/fabric-automatiza-tareas-en-remoto https://blog.adrianistan.eu/fabric-automatiza-tareas-en-remoto ¡Haz scripts! Sin embargo, hay situaciones donde es más complicada esta situación, como por ejemplo, trabajar con máquinas remotas. Aquí entra Fabric en acción, una librería para Python que permite automatizar procesos en remoto a través de SSH.

Originalmente Fabric, compatible únicamente con Python 2, funcionaba a través de unos archivos llamados Fabfile que se ejecutaban con el comando fab. Este mecanismo ha sido transformado por completo en Fabric 2, que ha salido hace nada. Yo ya he hecho la transición y puedo decir que el nuevo sistema, más enfocado a ser una librería sin más, es mucho más práctico y útil que el anterior.


Instalando Fabric


Voy a usar Pipenv, que es la manera recomendada de gestionar proyectos en Python actualmente.
pipenv install fabric==2.0.1

Y accedemos al virtualenv con:
pipenv shell

Conexiones


El elemento fundamental de Fabric a partir de la versión 2 son las conexiones. Estos objetos representan la conexión con otra máquina y podemos:

  • ejecutar comandos en el shell de la otra máquina, run, sudo.

  • descargar archivos desde la máquina remota a local, get

  • subir archivos de local a remoto, put

  • hacer forwarding, forward_local, forward_remote.


Para iniciar una conexión necesitamos la dirección de la máquina y alguna manera de identificarnos. En todo el tema de la autenticación Fabric delega el trabajo en Paramiko, que soporta gran variedad de opciones, incluida la opción de usar gateways.

Vamos a mostrar un ejemplo, en este caso, de autenticación con contraseña:
from fabric import Connection
from getpass import getpass

password = getpass(prompt="Password for Numancia: ")
numancia = Connection(host="192.168.0.158",user="roma",connect_kwargs={"password" : password})

El objeto creado con Connection ya lo podemos usar. Habitualmente no es necesario cerrar la conexión aunque en casos extremos puede ser necesario, una manera es llamando a close, otra es con un bloque with.

Tareas


Una vez que ya tenemos la conexión podemos pasárselo a diferentes funciones, cada una con una tarea distinta. Veamos algunos ejemplos.

Actualizar con apt


def update(cxn):
cxn.run("sudo apt update")
cxn.sudo("apt upgrade -y")

Los comandos que necesiten sudo pueden necesitar una pseudo-terminal para preguntar la contraseña (si es que la piden, no todos los servidores SSH piden contraseña al hacer sudo). En ese caso añadimos, pty=True.
def update(cxn):
cxn.run("sudo apt update",pty=True)
cxn.run("sudo apt upgrade -y",pty=True)

 

Backup de Mysql/Mariadb y cifrado con gpg


def backup_sql(cxn):
date = time.strftime("%Y%m%d%H%M%S")
filename = "/tmp/blog-backup-"+date+".sql.gz"

cxn.run("sudo mysqldump blog | gzip > "+filename)
local_filename = "backups/"+os.path.basename(filename)
cxn.get(filename,local_filename)
cxn.run("sudo rm "+filename)
os.system("gpg --cipher-algo AES256 -c "+local_filename)
os.remove(local_filename)

Requiere que MySQL/MariaDB esté configurado con la opción de autenticación POSIX (modo por defecto en el último Ubuntu)

Como véis, usar Fabric es muy sencillo, ya que apenas se diferencia de un script local.



Si queremos obtener el resultado del comando, podemos simplemente asignar a una variable la evaluación de los comandos run/sudo.
def isLinux(cxn):
result = cxn.run("uname -s")
return result.stdout.strip() == "Linux"

Grupos


Fabric es muy potente, pero en cuanto tengamos muchas máquinas vamos a hacer muchas veces las mismas tareas. Podemos usar un simple bucle for, pero Fabric nos trae una abstracción llamada Group. Básicamente podemos juntar conexiones en un único grupo y este ejecutas las acciones que pidamos. Existen dos tipos de grupo, SerialGroup, que ejecuta las operaciones secuencialmente y ThreadGroup que las ejecuta en paralelo.
from fabric import ThreadingGroup

def update(cxn):
cxn.run("sudo apt update")


pool = ThreadingGroup("roma@192.168.0.158","madrid@192.168.0.155")
update(pool)

O si tienes ya objetos de conexión creados:
from fabric import ThreadingGroup

def update(cxn):
cxn.run("sudo apt update")

pool = ThreadingGroup.from_connections([roma,madrid])
update(pool)

Con esto ya deberías saber lo básico para manejar Fabric, una fantástica librería para la automatización en remoto. Yo la uso para este blog y otras webs. Existen alternativas como Ansible, aunque a mí nunca me ha terminado de gustar su manera de hacer las cosas, que requiere mucho más aprendizaje.]]>
https://blog.adrianistan.eu/fabric-automatiza-tareas-en-remoto Sat, 19 May 2018 00:00:00 +0000
Natural Language Understanding con Snips NLU en Python https://blog.adrianistan.eu/natural-language-understanding-con-snips-nlu-en-python https://blog.adrianistan.eu/natural-language-understanding-con-snips-nlu-en-python listado de temas.

En aquella época se era bastante optimista con las posibilidades de describir el lenguaje natural (inglés, español, ...) de forma precisa con un conjunto de reglas de forma similar a las matemáticas. Luego se comprobó que esto no era una tarea tan sencilla.

Hoy día, es un campo donde se avanza mucho todos los días, aprovechando las técnicas de machine learning combinadas con heurísticas propias de cada lenguaje.

Natural Language Understanding nos permite saber qué quiere decir una frase que pronuncia o escribe un usuario. Existen diversos servicios que provee de esta funcionalidad: IBM Watson, Microsoft LUIS y también existe software libre, como Snips NLU.

Snips NLU es una librería hecha en Rust y con interfaz en Python que funciona analizando el texto con datos entrenados gracias a machine learning y da como resultado un intent, o significado de la frase y el valor de los slots, que son variables dentro de la frase.
¿Qué tiempo hará mañana en Molina de Aragón?

Y Snips NLU nos devuelve:

  • intent: obtenerTiempo

  • slots:

    • cuando: mañana

    • donde: Molina de Aragón




Pero para esto, antes hay que hacer un par de cosas.

Instalar Snips NLU


Instala Snips NLU con Pipenv (recomendado) o Pip:
pipenv install snips-nlu

pip install snips-nlu

 

Datos de entrenamiento


En primer lugar vamos a crear un listado de frases que todas expresen la intención de obtener el tiempo y lo guardamos en un fichero llamado obtenerTiempo.txt. Así definimos un intent:
¿Qué tiempo hará [cuando:snips/time](mañana) en [donde:localidad](Molina de Aragón)?
¿Qué tal hará [cuando:snips/time](pasado mañana) en [donde:localidad](Ponferrada)?
¿Qué temperatura habrá [cuando:snips/time](mañana) en [donde:localidad](Frías)?

La sintaxis es muy sencilla. Cada frase en una línea. Cuando una palabra forme parte de un slot, se usa la sintaxis [NOMBRE SLOT:TIPO](texto). En el caso de [donde:localidad](Frías). Donde es el nombre del slot, localidad es el tipo de dato que va y Frías es el texto original de la frase. En el caso del slot cuando, hemos configurado el tipo como snips/time que es uno de los predefinidos por Snips NLU.

Creamos también un fichero llamado localidad.txt, con los posibles valores que puede tener localidad. Esto no quiere decir que no capture valores fuera de esta lista, como veremos después, pero tienen prioridad si hubiese más tipos. También se puede configurar a que no admita otros valores, pero no lo vamos a ver aquí.
Burgos
Valladolid
Peñaranda de Bracamonte
Soria
Almazán
Íscar
Portillo
Toro
Fermoselle
Sahagún
Hervás
Oña
Saldaña
Sabiñánigo
Jaca

Ahora generamos un fichero JSON listo para ser entrenado con el comando generate-dataset.
generate-dataset --language es --intent-files obtenerTiempo.txt --entity-files localidad.txt > dataset.json

Entrenamiento


Ya estamos listos para el entrenamiento. Creamos un fichero Python como este y lo ejecutamos:
import io
import json
from snips_nlu import load_resources, SnipsNLUEngine

load_resources("es")

with io.open("dataset.json") as f:
dataset = json.load(f)

engine = SnipsNLUEngine()

engine.fit(dataset)

engine_json = json.dumps(engine.to_dict())
with io.open("trained.json",mode="w") as f:
f.write(engine_json)

El entrenamiento se produce en fit, y esta tarea puede tardar dependiendo del número de datos que metamos. Una vez finalizado, generama un fichero trained.json con el entrenamiento ya realizado.

Hacer preguntas


Ha llegado el momento de hacer preguntas, cargando el fichero de los datos entrenados.
import io
import json
from snips_nlu import SnipsNLUEngine, load_resources

load_resources("es")

with io.open("trained.json") as f:
engine_dict = json.load(f)

engine = SnipsNLUEngine.from_dict(engine_dict)

phrase = input("Pregunta: ")

r = engine.parse(phrase)
print(json.dumps(r, indent=2))

Ahora sería tarea del programador usar el valor del intent y de los slots para dar una respuesta inteligente.

Te animo a que te descargues el proyecto o lo hagas en casa e intentes hacerle preguntas con datos retorcidos a ver qué pasa y si guarda en los slots el valor correcto.]]>
https://blog.adrianistan.eu/natural-language-understanding-con-snips-nlu-en-python Tue, 17 Apr 2018 00:00:00 +0000
Introducción a Prolog, tutorial en español https://blog.adrianistan.eu/introduccion-a-prolog-tutorial-en-espanol https://blog.adrianistan.eu/introduccion-a-prolog-tutorial-en-espanol Prolog es un lenguaje de programación lógico, quizá uno de los más populares de este paradigma ya que fue el primero en implementarlo, en el año 1972 en Francia.

Durante un tiempo, se creyó que PROLOG supondría la revolución de los lenguajes de programación, siendo uno de los estandartes de los lenguajes de programación de quinta generación. Tanto se creía que Borland, la famosa empresa de compiladores para MS-DOS, tenía Turbo Prolog, junto a Turbo C++, Turbo Pascal, Turbo Assembler y Turbo Basic.

Desafortunadamente, Prolog no triunfó como se esperaba y solo fue usado dentro del mundo de la Inteligencia Artificial. Existe un estándar ISO sobre Prolog (al igual que Ada, C++ y otros lenguajes estandarizados) pero no es demasiado influyente. Tampoco ha habido ningún éxito en alguna de las versiones propuestas para dotarle de orientación a objetos al lenguaje.

Prolog sin embargo ha influenciado a algunos lenguajes como Erlang, que toman algunos aspectos de él.

No obstante, Prolog sigue siendo un lenguaje muy interesante, diferente al resto de lenguajes (tanto imperativos, como funcionales), así que pongámonos a ello.

Actualmente existen varios compiladores de Prolog: SWI Prolog, GNU Prolog, Visual Prolog (al mucha gente no lo considera Prolog de verdad), ...

La mejor versión bajo mi punto de vista es SWI Prolog, que tiene una librería de predicados bastante extensa. Es rápido y permite generar ejecutables nativos (realmente no lo son, pero la máquina virtual de Prolog ocupa muy poco y apenas se nota en el tamaño del ejecutable).

Lo podéis descargar gratuitamente desde su página oficial que como curiosiad, está hecha en SWI Prolog. También está en la paquetería de las distribuciones GNU/Linux habituales.

Nuestro primer programa


Para empezar con Prolog voy a tomar un camino distinto de muchos tutoriales y voy a empezar haciendo un programa con Entrada/Salida y que se ejecute como un binario independiente. La potencia de Prolog no está ahí especialmente, pero es un buen punto de partida

Para ello definimos un predicado main de la siguiente forma y lo guardamos en un fichero main.pl.
main :- 
write("Hola Mundo"),
nl,
write("¿Cuál es tu nombre? "),
read_string(user_input,['\n'],[],_,Nombre),
write("Hola "),write(Nombre),nl,
halt.

¿Qué hace el programa? Imprime en pantalla Hola Mundo (write), una nueva línea (nl), lee un string de teclado con el separador \n y lo unifica con la variable Nombre. Esto ya veremos que significa, pero de momento puedes pensar que Nombre es una variable de salida y que a partir de ahí Nombre tiene un valor establecido.

Compilamos con el siguiente comando:
swipl --goal=main --stand_alone=true -o main -c main.pl

Con esto le decimos a SWI Prolog que el objetivo a demostrar es main y que nos genere un fichero stand_alone (independiente).

Y ejecutamos el fichero ejecutable como uno más.

Ahora que ya sabemos como se generan programas compilados, vamos a introducirnos más en lo que hace especial a Prolog.

La terminal de Prolog


Prolog fue diseñado con una terminal interactiva en mente. La idea era que el usuario fuese introduciendo preguntas y el programa en Prolog fuese contestando. Este enfoque, similar a usar un programa desde el REPL de tu lenguaje, no ha acabado cuajando, pero es necesario pasar por él. Más adelante veremos como con SWI Prolog no hace falta usar la terminal. La terminal se abre escribiendo swipl en la línea de comandos:

Vemos un símbolo de interrogación. Eso nos permite saber que estamos en una terminal de Prolog. Podemos escribir write("Hola"). y tener nuestro hola mundo tradicional, aunque no es muy interesante, pero sí es muy interesante que después escribe true. Más adelante veremos por qué.

Los programas Prolog


En Prolog los programas son algo diferentes a lo que estamos acostumbrados. Realmente en Prolog no hay programas sino una base de datos. Un programa se compone de predicados, muy parecidos a los del Cálculo de Predicados. Los predicados aportan información sobre las relaciones entre elementos. Todos los predicados tienen que acabar en punto.

Siguiendo las mismas normas que el cálculo de predicados:

  • Las constantes empiezan por minúscula

  • Las variables empiezan por mayúscula

  • Las funciones son constantes seguidas de N teŕminos. Son funciones estrictamente matemáticas.

  • Los predicados pueden ser atómicos o compuestos, con operadores lógicos (and, or, implica, etc)


Prolog durante la ejecución, va a intentar demostrar que el predicado es cierto, usando el algoritmo de backtracking. Y ahí está la verdadera potencia de Prolog. Veamos unos ejemplos:
fruta(manzana).
fruta(naranja).
fruta(platano).

Guarda ese archivo con extensión .pl y ejecuta swipl comida.pl.

Ahora en la terminal podemos hacer preguntas. ¿Es la manzana una fruta? Prolog responde verdadero. ¿Es la pera una fruta? Prolog responde que falso, porque según el archivo comida.pl, no lo es. Prolog no es inteligente, no sabe que significan las palabras, simplemente actúa siguiendo un conjunto de normas formales.

Hemos dicho que Prolog tiene variables. Una variable en Prolog es un marcador de hueco, es algo que no existe, porque no es ninguna constante en específico. Veamos la potencia de las variables con este otro predicado.

En este caso pedimos demostrar fruta(X).. Prolog buscará la primera solución que demuestra el predicado, que es que X valga manzana. Aquí podemos pulsar ENTER y Prolog se para o pulsar N y Prolog busca otra solución. ¿Potente, verdad? Prolog realiza un proceso interno que se llama unificación, es importante saber como funciona para ver que hace Prolog en realidad.

Unificación


La unificación es un proceso que combina dos predicados en uno que encaja. Para ello buscamos las sustituciones de valores con los que dos predicados son compatibles (realmente solo se pueden modificar las variables). No obstante, Prolog busca siempre el unificador más general, que es aquel que unifica dejando los predicados de forma más genérica posible, es decir, que pueden usarse con más valores.



Espero que esta imagen aclare el concepto de unificación. Básicamente Prolog para intentar demostrar un predicado intentará unificar con otros predicados del programa. Así cuando ponemos por ejemplo comida(manzana) , unifica con comida(X) así que Prolog toma ese predicado para continuar.

Backtracking


Cuando Prolog intenta demostrar un predicado aplica el algoritmo de backtracking. Este algoritmo recorre todas las soluciones posibles pero de forma más inteligente que la fuerza bruta. Backtracking intenta conseguir una solución hasta que un predicado falla, en ese momento, Prolog va hacia atrás y continúa por otra vía que pueda seguir.

Cada predicado es un nodo. Si un predicado falla se vuelve atrás. Esto es muy interesate ya que Prolog técnicamente puede ejecutar código hacia atrás.

Un predicado Prolog sigue esta estructura:


Predicados avanzados


Pero los predicados de Prolog no tienen por qué ser así de simples. Normalmente se usa el operador :- para indicar algo que para que se cumpla la parte de la izquierda, tiene que cumplirse la parte de la derecha antes (en cálculo de predicados es ←).

Por ejemplo, todas las frutas son comidas, así que podemos añadir esto a nuestro archivo.
fruta(manzana).
fruta(naranja).
fruta(platano).

comida(X) :- fruta(X).

Y los predicados en Prolog se pueden repetir y repetir y repetir. Prolog siempre intenta demostrar de arriba a abajo, si falla un predicado, prueba con el siguiente más para abajo y termina cuando no hay más predicados que probar. ¡Así que podemos definir comida en base a más predicados!
fruta(manzana).
fruta(naranja).
fruta(platano).

carne(pollo).
carne(vaca).
carne(cerdo).
carne(caballo).

comida(paella).
comida(pulpo).

comida(X) :- fruta(X).
comida(X) :- carne(X).

Operaciones


En Prolog existen varios operadores importantes:

  • , (coma) AND

  • ; (punto y coma) OR

  • A = B, se intenta unificar A y B. Devuelve true si funciona

  • A \= B es falso si A y B unifican

  • A is B, se evalúa B (es decir, se calcula lo que representa) y se unifica con A

  • A =:= B , evalúa A, evalúa B y los compara. Verdadero si son iguales

  • A =\= B, evalúa A, evalúa B y los compara. Falso si son iguales

  • Y muchos otros como =<, >=, >, < que tienen el comportamiento esperado.

  • Las operaciones matemáticas solo se pueden introducir en expresiones que vayan a ser evaluadas.


Quiero prestar especial atención en símbolo de igual que no es asignación sino unificación, pero puede parecerse. Estas dos líneas son equivalentes:
X = 5
% y
5 = X

Ya que en ambos casos se unifica X con 5.

Veamos un ejemplo de todo esto, ya mucho más realista.
%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Programa restaurante %
%%%%%%%%%%%%%%%%%%%%%%%%%%%

% menu

entrada(paella).
entrada(gazpacho).
entrada(pasta).

carne(filete_de_cerdo).
carne(pollo_asado).

pescado(trucha).
pescado(bacalao).

postre(flan).
postre(nueces_con_miel).
postre(naranja).

% Valor calorico de una racion

calorias(paella, 200).
calorias(gazpacho, 150).
calorias(pasta, 300).
calorias(filete_de_cerdo, 400).
calorias(pollo_asado, 280).
calorias(trucha, 160).
calorias(bacalao, 300).
calorias(flan, 200).
calorias(nueces_con_miel, 500).
calorias(naranja, 50).

% plato_principal(P) P es un plato principal si es carne o pescado

plato_principal(P):- carne(P).
plato_principal(P):- pescado(P).

% comida(Entrada, Principal, Postre)

comida(Entrada, Principal, Postre):-
entrada(Entrada),
plato_principal(Principal),
postre(Postre).

% Valor calorico de una comida

valor(Entrada, Principal, Postre, Valor):-
calorias(Entrada, X),
calorias(Principal, Y),
calorias(Postre, Z),
sumar(X, Y, Z, Valor).

% comida_equilibrada(Entrada, Principal, Postre)

comida_equilibrada(Entrada, Principal, Postre):-
comida(Entrada, Principal, Postre),
valor(Entrada, Principal, Postre, Valor),
menor(Valor, 600).


% Conceptos auxiliares

sumar(X, Y, Z, Res):-
Res is X + Y + Z. % El predicado "is" se satisface si Res se puede unificar
% con el resultado de evaluar la expresion X + Y + Z
menor(X, Y):-
X < Y. % "menor" numerico

dif(X, Y):-
X =\= Y. % desigualdad numerica


% cuantas comidas llevan naranja de postre
%

comidas_con_naranja :-
write("Comidas con naranja: "),
aggregate_all(count,comida(_,_,naranja),X),
write(X),
nl.

Lo cargamos con swipl restaurante.pl y podemos contestar a las siguientes preguntas de forma sencilla:

¿Qué valor calórico tiene la comida de paella, trucha y flan?
valor(paella,trucha,flan,N).

Dime una comidas equilibrada que lleve naranja de postre
comida_equilibrada(X,Y,Z),Z=naranja.

¿Cuántas comidas con naranja hay?
comidas_con_naranja.

Variables anónimas y predicados predefinidos


Si te fijas en el predicado comidas_con_naranja, es diferente al resto. Esta programado de otra forma. La salida por pantalla se hace con write como ya habíamos visto al principio, write es un predicado que siempre es cierto y en caso de backtracking simplemente pasa. aggregate_all es también otro predicado incluido en SWI Prolog, para en este caso, contar cuantas soluciones tiene el predicado comida(_,_,naranja). X unifica en aggregate_all con el valor que toca (que es una constante, los números son constantes) y en las siguientes sentencias (write), X ha sido sustituido por el valor con el que unificó en aggregate_all.

Por otro lado, ¿qué significa la barra baja? Es una variable anónima. Cuando no usamos una variable en más lugares y no nos interesan sus valores podemos simplemente poner barra baja.

Listas en Prolog


Prolog tiene un tipo de dato más avanzado, las listas. Su tamaño es dinámico y es conveniente distinguir la cabeza de la cola. La cabeza es el primer elemento de una lista, la cola el resto de la lista.

Las listas se crean con corchetes:
X = [1,2,3,4,5],
sumlist(X,N).

sumlist es un predicado de SWI que hace la suma de todos los elementos de una lista.

Con la barra podemos separar cabeza y cola de una lista:
[1,2,3,4] = X,
[H|T] = X.

Implementando sumlist


Vamos a ver como se puede implementar sumlist con Prolog de forma sencilla.
sumar([],0).
sumar([H|T],N) :-
sumar(T,X),
N is X+H.

El predicado es sencillo, para un caso base de lista vacía, la suma es 0. Para otros casos más complejos separamos la lista en cabeza H y cola T y la suma es N. Esta suma se define como el resultado de sumar T (queda en X) y la cabeza H.

Assert y retract


¿Podemos crear predicados en tiempo real? Claro. Prolog provee de los predicados especiales assert para añadir y retract para eliminar un predicado.

Operador de corte


Prolog tiene un operador muy controvertido, el operador de corte, !. Se trata de un operador que permite no volver hacia atrás. Hay que intentar no usarlo, pero muchas veces mejora el rendimiento.

Metaprogramación


Prolog tiene un gran soporte para la metaprogramación. Assert y Retract son ya formas de metaprogramación, no obstante Prolog tiene muchas más.

call permite ejecutar código Prolog desde Prolog.

setarg permite modificar un término de un predicado y arg unifica una variable con el término de un predicado que queramos. nb_setarg es la versión de setarg que en caso de backtracking no deshace la operación. Un ejemplo de esto lo podemos encontrar en la definición de aggregate_all en la librería de SWI Prolog:
aggregate_all(count, Goal, Count) :-
!,
aggregate_all(sum(1), Goal, Count).
aggregate_all(sum(X), Goal, Sum) :-
!,
State = state(0),
( call(Goal),
arg(1, State, S0),
S is S0 + X,
nb_setarg(1, State, S),
fail
; arg(1, State, Sum)
).

 

Debug con trace


Prolog posee un predicado muy útil para hacer debugging. Es trace y con el predicado que vaya a continuación podremos inspeccionar todas las operaciones del motor de backtracking.

]]>
https://blog.adrianistan.eu/introduccion-a-prolog-tutorial-en-espanol Thu, 12 Apr 2018 00:00:00 +0000
Mónada ST en Haskell: STRef, STArray https://blog.adrianistan.eu/monada-st-en-haskell-stref-starray https://blog.adrianistan.eu/monada-st-en-haskell-stref-starray ST. ST son las siglas de State Threads. Fueron definidas por Simon Peyton-Jones en un paper de 1994 y son una forma muy curiosa que tiene Haskell de permitirnos escribir código con estructuras de datos mutables. La gracia está en que se garantiza que es imposible que la mutabilidad interna que hay dentro de la mónada ST salga al exterior, de modo que de cara al exterior son funciones puras igualmente. Esta mónada no sirve para comunicarse con el exterior como sí sirve IO pero si podemos usar ST antes que IO, mejor.

STRef


La estructura más fundamental es STRef, que permite tener una variable mutable dentro de Haskell. Las funciones que permiten su manejo son newSTRef, readSTRef, writeSTRef y modifySTRef. Creo que es bastante evidente para qué sirve cada una, pero veamos un ejemplo práctico.

Vamos a hacer una función que sume todos los elementos de una lista. Existen varias formas de hacerlo de forma pura en Haskell:
sumar :: Num a => [a] -> a
sumar [] = 0
sumar (x:xs) = x+sumar xs

Una versión recursiva
sumar :: Num a => [a] -> a
sumar = foldr1 (+)

Una versión más compacta

En un lenguaje de programación imperativo sin embargo posiblemente haríamos algo parecido a esto:
int sumar(int[] nums){
int suma = 0;
for(int i=0;i<nums.length;i++){
suma += nums[i];
}
return suma;
}

Con ST podemos hacer código que se parezca a la versión imperativa, lo cuál es útil en determinados algoritmos y también si queremos ganar eficiencia.
import Control.Monad.ST
import Control.Monad
import Data.STRef

sumar :: Num a => [a] -> a
sumar xs = runST $ do
x <- newSTRef 0

forM_ xs $ \n ->
modifySTRef x $ \x -> x+n

readSTRef x

Lo primero que tenemos que hacemos es ejecutar la mónada ST con runST y a continuación la función. Allí creamos una variable mutable con valor inicial 0. Después lanzamos forM_ que itera sobre cada elemento de la lista xs ejecutando para cada elemento la función que llama a modifySTRef que ejecuta a su vez la función que suma el valor del acumulador con el valor del elemento actual de la lista.

Por último, la función finaliza devolviendo el valor de x.

Como veis, el código no tiene nada que ver a las otras versiones de Haskell y tiene un gran parecido con la versión de Java. No obstante, el código sigue siendo puro, sin efectos colaterales.

STArray


Pero no solo tenemos STRef, también tenemos STArray que es un array de tamaño fijo con estas mismas propiedades. Las funciones básicas son newListArray, readArray, writeArray y getElems.

Por ejemplo, podemos implementar el algoritmo de Bubble Sort de forma imperativa con STArray:


import Control.Monad
import Control.Monad.ST
import qualified Data.Array.ST as ST
import Data.STRef

bubblesort :: [Int] -> [Int]
bubblesort xs = runST $ do
let l = length xs
temp <- newSTRef $ head xs
array <- ST.newListArray (0,l-1) xs :: ST s (ST.STArray s Int Int)

forM_ [0..l] $ \i -> do
forM_ [1..(l-1)] $ \j -> do
prev <- ST.readArray array (j-1)
actual <- ST.readArray array j
if prev > actual then do
writeSTRef temp prev
ST.writeArray array (j-1) actual
t <- readSTRef temp
ST.writeArray array j t
else do
return ()

ST.getElems array

main :: IO ()
main = do
let orden = bubblesort [3,4,1,2]
putStrLn ("Orden: "++(show orden))

La mónada ST junto con las funciones de Control.Monad nos permiten escribir código que parece imperativo y con mutabilidad, sin perder la pureza y la seguridad de Haskell.]]>
https://blog.adrianistan.eu/monada-st-en-haskell-stref-starray Mon, 9 Apr 2018 00:00:00 +0000
Programación dinámica: el problema de knapsack https://blog.adrianistan.eu/programacion-dinamica-el-problema-de-knapsack https://blog.adrianistan.eu/programacion-dinamica-el-problema-de-knapsack programación dinámica. La idea fundamental de la programación dinámica es dividir el problema en subproblemas y posteriormente calcular una única vez cada subproblema. Existen dos acercamientos: top-down y bottom-up. La programación dinámica nos permite resolver problemas más rápidamente que con fuerza bruta, gastando a cambio mucha más memoria del ordenador.

Como explicado así, es un poco abstracto, vamos a intentar resolver un problemas clásico de programación dinámica, el problema de knapsack (o de la mochila).

Problema de Knapsack


Imagina que tenemos una mochila con capacidad para 10 kg. Tenemos 400 elementos para meter en la bolsa, cada uno de ellos con un peso y un valor. ¿Cuál es el valor máximo que podemos llevar en la bolsa?



Este problema parece muy complicado de resolver. Quizá lo único que se nos ocurra es probar todas las combinaciones posibles de elementos, descartar las combinaciones que exceden el peso que puede llevar la mochila y de las restantes quedarnos con el máximo. Funcionaría, pero sería extremadamente lento.

Como cada elemento puede estar o no estar en la mochila, y son 400, las combinaciones son 2⁴⁰⁰=2582249878086908589655919172003011874329705792829223512830659356540647622016841194629645353280137831435903171972747493376. Excesivo.

Otra idea que podría surgir es backtracking. Básicamente recorreríamos todos los elementos posibles en forma de árbol, pero en cuanto veamos que ya no entran más elementos cortamos la búsqueda por esa rama y tomamos otra. Mejoraría el rendimiento, pero no es tampoco lo mejor que se podría hacer.

La solución es usar programación dinámica, vamos a aplicar el enfoque top-down. Lo primero es expresar la solución de forma recursiva.

La función mochila se define como el valor máximo que puede llevar una mochila, con N elementos a elegir y con una capacidad C.
def mochila(n,c):
if n == 0 or c == 0:
# solucion optima para cuando no quedan elementos o la capacidad disponible es 0
return 0
elif datos[n].peso > c:
# no metemos el elemento
return mochila(n-1,c)
else:
#sin meter el elemento
a = mochila(n-1,c)
# metiendo el elemento
b = datos[n].valor + mochila(n-1,c-datos[n].peso)
return max(a,b)

El primer if es la solución óptima, que es trivial si sabemos que ya no quedan elementos por revisar o si la capacidad que queda en la mochila es cero. El elif y else siguientes nos dan las soluciones óptimas también, pero de forma recursiva. En el primer caso, si el elemento es demasiado grande, solo se puede continuar probando con otro elemento, en el otro caso, hacemos los dos cálculos. Miramos si obtenemos más valor metiendo el elemento o sin meterlo y devolvemos el máximo. Este es el primer paso, básicamente es backtracking lo que hemos hecho.

Memoizar


Si bien la solución funcionaba, podemos darnos cuenta de que el ordenador iba a calcular muchas veces lo mismo. En programación dinámica no se calcula nunca nada más de una vez. Una solución sencilla para lograrlo es aplicar memoización, es decir, mantenemos un historial de llamadas a la función con argumentos. Piensa en ello como una caché.

En Python existe desde 3.2 en el módulo functools una caché que cumple lo necesario para memoizar.
import functools

@functools.lru_cache(maxsize=None)
def mochila(n,c):
if n == 0 or c == 0:
# solucion optima para cuando no quedan elementos o la capacidad disponible es 0
return 0
elif datos[n].peso > c:
# no metemos el elemento
return mochila(n-1,c)
else:
#sin meter el elemento
a = mochila(n-1,c)
# metiendo el elemento
b = datos[n].valor + mochila(n-1,c-datos[n].peso)
return max(a,b)

Este ligero cambio mejora drásticamente el rendimiento, haciendo que se calcule de forma casi instantánea (mientras que la otra versión tarda minutos).

Recursividad


Python no es un lenguaje que optimice las llamadas recursivas. Tampoco lo es Java. Ciertamente, en muchos lenguajes hacer demasiadas llamadas recursivas provoca un Stack Overflow. Ante esto se puede reescribir el algoritmo para que sea un bucle (el enfoque bottom-up) o se puede reescribir en un lenguaje que optimice las llamadas recursivas como Haskell o Lisp.

La solución bottom-up de este problema (usando una matriz) sería la siguiente:
V = list()
def mochila_two(n,c):
for x in range(n):
V.append([])
for y in range(c+1):
V[x].append(0)
for i in range(n):
for w in range(c+1):
if datos[i].peso <= w:
V[i][w] = max(V[i-1][w],datos[i].valor+V[i-1][w-datos[i].peso])
else:
V[i][w] = V[i-1][w]

return V[n-1][c]

Esta versión es más rápida y no tiene el problema del límite de recursividad, pero es menos legible.

(en la esquina encontraremos la solución óptima)]]>
https://blog.adrianistan.eu/programacion-dinamica-el-problema-de-knapsack Sun, 11 Mar 2018 00:00:00 +0000
Futures y Tokio: programar de forma asíncrona en Rust https://blog.adrianistan.eu/futures-tokio-programar-forma-asincrona-rust https://blog.adrianistan.eu/futures-tokio-programar-forma-asincrona-rust concurrencia. Sin embargo, si damos un repaso por la librería estándar veremos un par de cosas nada más, algo que puede resultar decepcionante. Esto se debe a que Rust no impone ningún modelo específico de concurrencia como sí lo hacen Go o Erlang. Rust nos provee de los ladrillos de construcción necesarios.

De forma estándar, Rust nos provee de Mutex, atómicos, threads, variables de condición, RwLock y un modelo algo más avanzado de mensajería mediante canales de múltiples productores y un único consumidor (mpsc). En el exterior tenemos crates como Actix que nos proveen del modelo de actores en Rust (dicen que es similar a Akka, yo lo desconozco), modelos de mensajería por canales más flexibles (mpmc) y una cosa muy interesante llamado Futures. Los futures o futuros no son esos contratos que se realizan en bolsa sobre el valor futuro de una acción, sino que son una manera cómoda de manejar valores que existirán en el futuro. Si has usado JavaScript o C# igual te suenan las Promises o Promesas y los Task respectivamente. Los futuros de Rust son exactamente lo mismo.

¿Qué es un futuro?


Un futuro es una variable que representa un dato que todavía no existe. Tenemos la promesa de que ese valor existirá en el futuro. La ventaja es que podemos usarlo aun cuando todavía no tenga un valor. Esto además permite escribir código asíncrono con facilidad.

Una diferencia con respecto a los Promises de JavaScript es que los futuros se basan en poll en vez de en push. Es decir, los futuros van a ir preguntando si el valor ya está disponible. Ante esta pregunta se puede responder con un error, con todavía no está disponible y con ya está disponible, aquí tienes.

Veamos un ejemplo muy tonto pero que puede servirnos para entender algunas cosas.
extern crate futures;

use futures::*;
use std::time;
use std::thread;

fn suma(a: i32, b: i32) -> SumFuture {
SumFuture{
a: a,
b: b
}
}

struct SumFuture{
a: i32,
b: i32
}

impl Future for SumFuture {
type Item = i32;
type Error = String;

fn poll(&mut self) -> Result<Async<i32>,String> {
thread::sleep(time::Duration::from_secs(1));
Ok(Async::Ready(self.a+self.b))
}
}

fn main() {
let c = suma(4,5);
println!("Suma: {}",c.wait().unwrap());
}

Definimos un nuevo futuro, SumFuture, que devuelve el resultado de una suma. El futuro en su función poll duerme el hilo 1 segundo y después devuelve el resultado correcto. En la función main llamamos a suma que devuelve un futuro en vez del resultado. Con el futuro, esperamos con wait a que se resuelva y lo mostramos. Cuando un futuro se ejecuta se convierte en una tarea. Las tareas necesitan ejecutores. Wait ejecuta los futuros en el mismo hilo desde el que se hace la llamada, pero existen otras opciones. El programa, tarda un segundo en imprimir el número.

Pero esto no sirve para nada

Bueno, quizá ahora parezca una tontería, pero en cuanto introduzcamos más elementos, todo tendrá más sentido.

Una característica de los futuros es que se pueden encadenar, tal que así:
fn main() {
let c = suma(4,5)
.and_then(|v|{
suma(v,40)
}).and_then(|v|{
suma(v,40)
}).wait().unwrap();
println!("Suma: {}",c);
}

Ejecutores


Los futuros nos ayudan con la concurrencia, esto es porque se puede esperar a varios futuros a la vez sin problema. Una manera de hacerlo es con CpuPool, un ejecutor que tiene un pool de hilos ya creados. Su uso es muy sencillo, en este ejemplo vemos como hago dos operaciones en paralelo:
fn main() {
let c = suma(4,5)
.and_then(|v|{
suma(v,40)
}).and_then(|v|{
suma(v,40)
});
let d = suma(15,14);

let pool = CpuPool::new_num_cpus();
let c = pool.spawn(c);
let d = pool.spawn(d);
let (c,d) = c.join(d).wait().unwrap();
println!("SUMAS: {},{}",c,d);
}

Con spawn añadimos un nuevo futuro a la ejecución y nos devuelve otro futuro. Con join podemos unir dos futuros en uno solo que se resuelva cuando ambos hayan resuelto. Finalmente hacemos wait en el hilo principal para esperar a que las sumas se hagan e imprimir el resultado. Existen otros ejecutores. Con Tokio usaremos otro.

Tokio




Tokio es una librería que nos permite ejecutar código de entrada/salida de forma asíncrona, usando futuros por debajo. Son especialmente útiles, la parte de red de Tokio y sus ejecutores correspondientes.
extern crate tokio;
extern crate tokio_io;
extern crate futures;

use futures::prelude::*;
use tokio_io::AsyncRead;
use futures::Stream;
use tokio_io::codec::*;
use std::net::*;
use tokio::prelude::*;

fn main(){
let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
let listener = tokio::net::TcpListener::bind(&socket).unwrap();
let server = listener.incoming().for_each(|socket|{
println!("Cliente recibido");
Ok(())
}).map_err(|err|{
println!("error = {:?}",err);
});
tokio::run(server);
}

En este ejemplo tenemos un pequeño servidor TCP que se mantiene a la espera de clientes, imprime un mensaje y cierra la conexión. Este servidor solo utiliza un hilo, no obstante, ya usa futuros. server es un futuro infinito, que nunca acaba, que ejecutamos en el hilo actual con run->spawn. El run solo es necesario para ejecutar el primer futuro, el que mantendrá viva la aplicación. spawn empieza a ejecutar el futuro, que entonces se pasa a denominar tarea. Aquí es Tokio en vez de CpuPool, el planificador de tareas.

Servidor de Echo en Tokio


Ahora vamos a ir con un ejemplo más interesante, ahora el cliente y el servidor estarán conectados durante un tiempo, mandando el cliente un mensaje y el servidor respondiendo. Pero queremos que haya varios clientes simultáneamente. Usando futuros y Tokio podemos hacerlo en un mismo hilo (al estilo Node.js).
extern crate tokio;
extern crate tokio_io;
extern crate futures;

use futures::prelude::*;
use tokio_io::AsyncRead;
use futures::Stream;
use tokio_io::codec::*;
use std::net::*;
use tokio::prelude::*;

fn main(){
let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
let listener = tokio::net::TcpListener::bind(&socket).unwrap();
let server = listener.incoming().for_each(|socket|{
let (writer,reader) = socket.framed(LinesCodec::new()).split();
let action = reader
.map(move |line|{
println!("ECHO: {}",line);
line
})
.forward(writer)
.map(|_|{
})
.map_err(|err|{
println!("error");
});
tokio::spawn(action);
Ok(())
}).map_err(|err|{
println!("error = {:?}",err);
});
tokio::run(server);
}

¿Qué hacemos ahora? Ahora a cada socket de cliente asignamos una tarea, que se encarga, de forma independiente, de ir leyendo línea a línea (gracias a LineCodec).

A partir de aquí se programa en forma de stream, un patrón muy común en la programación con futuros.

En el primer map imprimimos la línea en el servidor y la seguimos pasando por el stream. El siguiente paso es escribir en writer, con forward imprimimos y mandamos esa línea al cliente. Forward a su vez devuelve una tupla con datos (útil para seguir haciendo cosas). Como no los necesitamos, hacemos un map cuya única finalidad sea descartar los valores y finalmente un map_err para capturar posibles errores. Una vez hecho esto tenemos un futuro listo para ser ejecutado. Iniciamos la tarea con spawn y nos olvidamos, pasando a esperar a por un nuevo cliente.

Ahora, en el servidor podemos manejar varios clientes a la vez, cada uno en una tarea distinta, dentro de un mismo hilo.

Cancelando la tarea


Esta tarea que maneja el cliente es infinita, es decir, no se para. La razón es que reader es un lector que genera un futuro infinito esperando a nuevas líneas. Pero, ¿existe algún método para parar la tarea desde el servidor?

Esto no es algo demasiado trivial, pero se puede hacer de manera sencilla usando canales MPSC de Rust y futuros.
extern crate tokio;
extern crate tokio_io;
extern crate futures;

use futures::prelude::*;
use tokio_io::AsyncRead;
use futures::Stream;
use tokio_io::codec::*;
use tokio::prelude::*;

struct Cancellable{
rx: std::sync::mpsc::Receiver<()>,
}

impl Future for Cancellable {
type Item = ();
type Error = std::sync::mpsc::RecvError;

fn poll(&mut self) -> Result<Async<Self::Item>,Self::Error> {
match self.rx.try_recv() {
Ok(_) => Ok(Async::Ready(())),
Err(_) => Ok(Async::NotReady)
}
}
}

fn main() {
let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
let listener = tokio::net::TcpListener::bind(&socket).unwrap();
let server = listener.incoming().for_each(|socket|{
let (writer,reader) = socket.framed(LinesCodec::new()).split();
let (tx,rx) = std::sync::mpsc::channel();
let cancel = Cancellable {
rx: rx,
};
let action = reader
.map(move |line|{
println!("ECHO: {}",line);
if line == "bye"{
println!("BYE");
tx.send(()).unwrap();
}
line
})
.forward(writer)
.select2(cancel)
.map(|_|{

})
.map_err(|err|{
println!("error");
});
tokio::spawn(action);

Ok(())
}).map_err(|err|{
println!("error = {:?}",err);
});
tokio::run(server);
}

La idea aquí es hacer que de alguna manera, el futuro pueda resolverse y así finalizar la tarea. Una función interesante es select2 que devuelve un futuro fusión de dos futuros. Este futuro se resuelve (devuelve valor y acaba la tarea) cuando alguno de ellos lo haga. Como el futuro de reader nunca acabará, entonces basta con poner un futuro que cuando queramos cerrar la conexión resolveremos.

Este futuro es cancel de tipo Cancellable. No viene en la librería, lo he creado arriba y lo que hace es esperar a que el extremo del canal reciba una comunicación. El valor nos da igual, simplemente que se haya producido un ping. Una vez hecho eso, el futuro resuelve y la conexión se cierra.

En el ejemplo, cuando el cliente manda bye la conexión se cierra.



Y esto es una breve introducción a los futuros y a Tokio en Rust. Todavía queda ver como podemos usar async/await para no tener que escribir todo esto en forma de stream.]]>
https://blog.adrianistan.eu/futures-tokio-programar-forma-asincrona-rust Fri, 23 Feb 2018 00:00:00 +0000
Lenguajes de programación que todo buen programador debe conocer https://blog.adrianistan.eu/lenguajes-programacion-buen-programador-conocer https://blog.adrianistan.eu/lenguajes-programacion-buen-programador-conocer debería llamarse un profesional si no conoce al menos 5 lenguajes suficientemente diferentes entre sí. Comparto con él esa afirmación, así que he decidido hacer una lista con esos 5 lenguajes suficientemente diferentes entre sí. La razón de que sean diferentes entre sí es que implementan paradigmas distintos.

Paradigmas de programación


Cada lenguaje está moldeado en base a uno o varios paradigmas de programación. Aunque no hay una teoría con la que todos los autores esten de acuerdo, bajo mi punto de vista existen dos grandes grupos de paradigmas de programación. Imperativos y Declarativos. Los imperativos responden a la pregunta de ¿Cómo se calcula esto? y los declarativos ¿Cuál es el resultado de esto?. Otra manera de verlo es ver al paradigma imperativo como un intento de simplificar la electrónica subyacente. El paradigma declarativo por contra muchas veces se origina de la teoría matemática y posteriormente se aplica al ordenador.

Cada uno de estos paradigmas a su vez tienen más sub-paradigmas y luego existen paradigmas transversales, es decir, que se pueden aplicar (o no) tanto en lenguajes imperativos como en lenguajes declarativos.

Un buen programador necesita conocer estos paradigmas.

Prolog


Prolog es un claro ejemplo de programación lógica. Se trata de un lenguaje declarativo. Diseñado en los años 70 en Francia, Prolog tuvo mucha popularidad en el desarrollo de Inteligencia Artificial debido a sus características lógicas. En esencia, Prolog se basa en la demostración de predicados, similares a los del álgebra de predicados.

Ejemplos de lógica de predicados

Un programa Prolog es en realidad un conjunto de afirmaciones o predicados. En tiempo de ejecución se realizan preguntas sobre predicados. Prolog intenta entonces demostrar la veracidad del predicado, para ello usa el mecanismo de backtracing. Una característica muy interesante de Prolog es el pattern matching, que básicamente permite preguntar para qué valor de una variable se cumple un predicado. Esto permite realizar cosas muy interesantes:
% PROGRAMA EN PROLOG
% PREDICADOS QUE SON VERDADEROS

hijo(bernardo,sonia).
hijo(veronica,sonia).
hijo(bernardo,luis).
hijo(veronica,luis).

varon(bernardo).
varon(luis).
mujer(veronica).
mujer(sonia).

% REGLAS

madre(X,Y) :- hijo(X,Y), mujer(X).

Ahora, para saber quién es la madre de Sonia intentamos demostrar:
?- madre(X,sonia).

Y responderá X = veronica.

En predicados sin variables, Prolog solo devuelve true o false.

Existen varios compiladores/intérpretes de Prolog, siendo el más conocido SWI-Prolog, multiplataforma y con una extensa librería que incluye GUI multiplataforma y framework web. También existe GNU Prolog y Visual Prolog (antiguamente conocido como Turbo Prolog), aunque este último no se le considera Prolog auténtico por ser demasiado diferente al resto.

Haskell


Haskell es un lenguaje declarativo que implementa el paradigma funcional. Es uno de los pocos lenguajes funcionales que son 100% puros. Se entiende por puros como la capacidad de no generar efectos colaterales. Haskell es un lenguaje fuertemente tipado y deriva de la teoría de categorías. Haskell ha sido objeto (hasta cierto punto merecido) de muchas bromas sobre este asunto, ya que para la mayoría de programadores, conocer teoría de categorías no es demasiado práctico.

Otra característica de Haskell es que es perezoso, lo que significa que no calculará nada que no sea estrictamente necesario (esto puede parecer muy raro hasta que lo entiendes en la práctica).
module Main where

import Data.Matrix
import qualified Data.Vector as Vector
import qualified Reader
import qualified Data.Maybe as Maybe

main :: IO ()
main = do
matrix <- Reader.readFile "gosperglidergun.txt"
putStrLn "Iterar cuantas veces?"
n <- getLine
let times = read n :: Int
let finalMatrix = Prelude.iterate Main.iterate matrix
putStrLn $ prettyMatrix $ finalMatrix !! times


iterate :: Matrix Int -> Matrix Int
iterate m =
if hasToGrow then
matrix (nrows m +2) (ncols m +2) (\(i,j) -> builder (i-1,j-1))
else
matrix (nrows m) (ncols m) builder
where
builder (i,j) =
if get (i,j) == 0 then
if hasToBorn (i,j) then
1
else
0
else
if hasToDie (i,j) then
0
else
1
hasToGrow =
Vector.sum (getCol (ncols m) m) > 0 ||
Vector.sum (getRow (nrows m) m) > 0 ||
Vector.sum (getCol 1 m) > 0 ||
Vector.sum (getRow 1 m) > 0
get (i,j) = Maybe.fromMaybe 0 (safeGet i j m)
hasToBorn (i,j) = sumNeighbors (i,j) == 3
hasToDie (i,j) = sumNeighbors (i,j) /= 2 && sumNeighbors (i,j) /= 3
sumNeighbors (i,j) =
get (i-1,j-1) + get (i,j-1) + get (i+1,j-1)
+ get (i-1,j) + get (i+1,j)
+ get (i-1,j+1) + get (i,j+1) + get (i+1,j+1)

Este código pertenece al juego de la vida de Conway que (también) implementé en Haskell, simplemente por curiosidad, ya que no está optimizado. Faltaría el módulo Reader, así que no intentéis compilarlo directamente.

Haskell como tal no tiene variables ni bucles y sus condicionales no son exactamente iguales a los de los lenguajes imperativos (aunque se use if, en el caso de Haskell son bloques que siempre deben devolver un valor).

Aunque siempre ha sido un lenguaje académico (nació en 1994, un año antes que Java), ahora ha alcanzado bastante popularidad y algunas empresas como Facebook lo usan en producción.

El compilador más conocido, capaz de generar código nativo, es GHC. Este dispone de un REPL llamado GHCi y también compila a JavaScript y se está trabajando en WebAssembly. Otro intérprete es Hugs, pero solo es compatible con Haskell98.

La forma recomendada de instalar GHC es con Stack. Usa Stack y ahórrate quebraderos de cabeza.

Otros lenguajes similares a Haskell son ElmPureScriptEta e Idris. Este último compila a JavaScript y pone énfasis en su librería, que es capaz de competir con Angular y React.

Racket (Lisp)


Lisp no puede faltar nunca. Se trata de uno de los primeros lenguajes de programación y sigue siendo tan actual como el primer día, gracias a su simple pero efectivo diseño, inspirado en el cálculo lambda de Alonzo Church. No obstante, Lisp ha evolucionado mucho, si me preguntan que dialecto/lenguaje de Lisp merece la pena aprender ahora diría Racket. Racket es un lenguaje de programación funcional y programación orientada a objetos. Racket no es 100% puro como Haskell (la mayoría de dialectos de Lisp no lo son) pero sigue siendo muy interesante. También, tiene tipado débil en contraposición al tipado fuerte de Haskell.

Racket desciende a su vez de Scheme, que es una de los ramas principales de Lisp, siendo la otra Common Lisp. ¿Por qué estas diferencias? La gente de Scheme prefiere un lenguaje elegante, lo más funcional posible mientras que la gente de Common Lisp prefirieron sacrificar eso a cambio de un lenguaje más práctico en el mundo real.

Racket cuenta con una extensísima librería estándar capaz de realizar todo lo que te imagines sin gran problema. Racket también soporta las famosas y potentes macros.
#lang racket

(require 2htdp/image) ; draw a picture
(let sierpinski ([n 8])
(cond
[(zero? n) (triangle 2 'solid 'red)]
[else (define t (sierpinski (- n 1)))
(freeze (above t (beside t t)))]))

Triángulo de Sierpinski en el IDE DrRacket.

JavaScript


JavaScript está en todas partes. Es uno de los lenguajes con mayor aplicación práctica. Web, servidor, bases de datos, scripting, plugins e incluso IoT. Por eso JavaScript me parece un lenguaje que deba estar en esta lista. Y sin embargo no es fácil categorizarlo correctamente. Ante todo, estamos ante un lenguaje de programación imperativo, con orientación a objetos y buen soporte a la orientación a eventos.

Y antes de que se me venga alguien a comerme, sí, JavaScript está orientado a objetos, aunque no siguen el patrón de clase/herencia que C++ y Java tienen tan acostumbrados a la gente. La orientación a objetos por prototipos no la inventó JavaScript, sino que ya estaba en otros lenguajes como Self y más actualmente Io. Y realmente lenguajes como Python o Ruby no se alejan mucho de esto internamente.

Actualmente, con la versión ES7, tenemos muchas cosas interesantes en programación asíncrona y clases al estilo Java que no son más que azúcar sintáctico sobre el verdadero modelo de JS.

Definitivamente, JavaScript es un lenguaje muy interesante y aunque a algunas personas les pueda parecer un caos, ciertamente es muy productivo y útil. Aprovechar al máximo JavaScript requiere pensar de forma distinta a como lo harías en otros lenguajes imperativos.
async function lastGist(username){
let f = await fetch(`https://api.github.com/users/${username}/gists`);
let data = await f.json();
try{
return data[0].description;
}catch(e){
return undefined;
}
}

let users = ["aarroyoc","DanielBV","hecsaen"];
Promise.all(users.map(lastGist)).then((gists)=>{
gists.forEach((gist)=>{
console.log(`Last gist: ${gist}`);
});
});

C# (o Java)


C# es otro lenguaje bastante complejo, imperativo, orientado a objetos por clases, con partes de programación funcional y programación asíncrona. Pero el sistema de la orientación a objetos aquí es distinto. Aunque Alan Kay, creador de Smalltalk y casi casi de la orientación a objetos, opine que el estilo de C++ y de Java son putas mierdas, lo cierto es que es lo más usado actualmente. Herencia, clases, interfaces, etc

Personalmente prefiero C#, ya que como lenguaje es más potente que Java, pero ambos al final tienen bastantes características comunes entre sí.
using System;

class MainClass {
public static void Main (string[] args) {
var names = new List<String>
{
"Ana",
"Felipe",
"Emilia"
};

foreach (var name in names)
{
Console.WriteLine($"Hello {name}");
}
}
}

Para C# el compilador más usado es Roslyn, actualmente disponible en .NET Framework, .NET Core y Mono.

Otros lenguajes parecidos son Java o si preferimos una sintaxis tipo Pascal: Delphi/Object-Pascal tiene conceptos muy similares.

Conclusión


Con esto ya tendríamos 5 lenguajes suficientemente diferentes. Ahora bien, nos hemos dejado lenguajes muy interesantes, tanto desde el punto de vista práctico como teórico. No puedo dejar de mencionar a Smalltalk por su implementación de los objetos, a C por su ligera capa de abstracción sobre ensamblador, al propio Ensamblador porque es lo que ejecuta la CPU realmente, a Python por su diseño así como su gran uso en ciencia de datos y scripts o a Rust, un lenguaje imperativo con sistema de traits, semántica de movimiento, pattern matching.

Merece también la pena mirar FORTH, SQL (quizá el lenguaje declarativo más usado del mundo) y lenguajes que se adapten bien a la programación reactiva (hay sobre todo librerías, aunque algún lenguaje como Simulink lo implementa). Mathemathica implementa también el paradigma de programación simbólica, muy poco explotado en otros lenguajes.

Estos han sido mis 5 lenguajes, ¿cuáles serían los tuyos?]]>
https://blog.adrianistan.eu/lenguajes-programacion-buen-programador-conocer Sun, 18 Feb 2018 00:00:00 +0000
Tutorial de introducción a Godot 3.0. Juego de Snake en C# https://blog.adrianistan.eu/tutorial-introduccion-godot-3-0-juego-snake-c https://blog.adrianistan.eu/tutorial-introduccion-godot-3-0-juego-snake-c Godot 3.0 salió a la luz. Se trata de una versión con muchas mejoras respecto a Godot 2.x. Se trata de un motor de videojuegos software libre, compatible con la mayoría de sistemas operativos (y consolas a través de una compañía privada). Aprovechando la ocasión voy a explicar como hacer un juego simple, usando C# y el motor 2D de Godot. Este tutorial sirve para familiarizarse con el motor.

Instalando Godot


Lo primero que tenemos que hacer es instalar Godot. Para ello vamos a la página de descarga y descargamos la versión que corresponda a nuestro sistema con Mono.

Es requisito indispensable tener instalado Mono SDK, tanto en Linux como en Windows. El Mono SDK se descarga desde http://www.mono-project.com/download/.

Una vez descargado tendremos un fichero con tres archivos. Descomprímelos en una carpeta. Ahora simplemente puedes ejecutar el fichero ejecutable.

Ventana de proyectos


Nada más arrancar tendremos una ventana de proyectos. Desde ahí podemos abrir proyectos que hayamos creado, descargar demos y plantillas o simplemente crear un proyecto nuevo.

Si le damos a Proyecto nuevo procederemos a la creación de un nuevo proyecto (¡¿qué complicado, verdad?!).

Una vez hecho esto ya estaríamos en el editor propiamente dicho.


Editor


Rápidamente cambiamos al modo 2D y hacemos zoom hacia atrás. Vamos a ajustar la pantalla. Para ello vamos a Proyecto->Ajustes de proyecto. Buscamos el apartado Display->Window y ajustamos la resolución base. Esta será la que usaremos para poner los elementos en pantalla. Después vamos a Stretch y configuramos el modo 2D y mantener el aspect ratio.

Esta configuración sirve para empezar con juegos simples en 2D puedan escalar fácilmente a distintas pantallas sin tener problemas de descuadres.

Ahora hacemos click en el más (+) para añadir un nodo a la escena.

Para nuestro juego, voy a añadir un Node2D, que hereda de CanvasItem y tiene APIs para dibujar rectángulos y círculos..

No lo tocamos y hacemos click derecho en el Nodo. Damos click a Attach Script:

Es importante tener un nombre de script distinto a Node2D. Esto puede dar problemas. Una vez hecho esto, se nos mostrará esta pantalla, que nos permite escribir el código en C#. A partir de ahora voy a usar Visual Studio Code, porque lo considero mejor para escribir código en C# que el editor de Godot. Creamos también dos Sprite hijos del Node2D. Apple y SnakeBody van a llamarse y les creamos scripts de C# también.


Dibujar en pantalla


Abrimos con el VS Code el fichero .cs que hemos creado, en mi caso, Snake.cs.

Dentro veremos una clase con dos funciones, _Ready y _Process. Estas son solo un ejemplo de las funciones que podemos sobreescribir en las clases asociadas a nodos. Otras funciones serían: _Draw, _Input. ¿Cuándo tenemos que usar cada una? Ready es una especie de constructor, ahí inicializamos todo lo que haga falta. Process es llamada constantemente, ahí hacemos las actualizaciones y comprobaciones pertinentes. Draw permite dibujar (si no trabajamos con imágenes es muy útil). Draw no se llama útil. ¿Qué tenemos que hacer en Snake.cs? Bueno, hay que tener un timer para ir generando manzanas, así como comprobar que la serpiente se ha comido la manzana.
using Godot;
using System;
using System.Timers;

public class Snake : Node2D
{
// 640x360
// casillas de 40x40 (mcd 640,360)
private System.Timers.Timer timer; // usamos los timers de .NET
private static readonly Random rnd = new Random(); // random de .NET
private Apple apple; // manzana activa
private SnakeBody body; // serpiente
public override void _Ready()
{
body = GetNode("SnakeBody") as SnakeBody; // obtenemos el nodo SnakeBody
body.Position = new Vector2(0,0); // arriba a la izquierda
timer = new System.Timers.Timer(10000); // cada 10 s
timer.Elapsed += NewApple; // se llama a NewApple
timer.AutoReset = true; // de forma infinita
timer.Start(); // y empezamos a contar ya!
apple = GetNode("Apple") as Apple; // obtenemos el nodo Apple
apple.Position = new Vector2(rnd.Next(15)*40,rnd.Next(8)*40); // posicion aleatoria
}

public void NewApple(object src ,ElapsedEventArgs e)
{
if(apple != null){
RemoveChild(apple); // quita el nodo de la escena si estaba
}
apple = new Apple();
apple.Position = new Vector2(rnd.Next(0,15)*40,rnd.Next(0,8)*40);
AddChild(apple); // anade motor a la escena
}

public override void _Process(float delta)
{
// siempre se comprueba si la serpiente se come la manzana
if(apple != null){
if(body.TryEat(apple)){
RemoveChild(apple);
apple = null;
}
}

}
}

En este código ya vemos cosas interesantes. La primera es que podemos usar cualquier librería de .NET, en este caso he usado clases que forman parte de .NET Standard pero cualquier librería que funcione con Mono debería funcionar. También se puede usar NuGet, al final, Godot tiene un fichero SLN y un CSPROJ, por lo que no es más que un proyecto normal de C#.

La segunda cosa es que hay varias funciones para interactuar con Godot en sí: GetNode (para obtener un nodo, hay que hacer casting), AddChild (para añadir un nodo a una escena) y RemoveChild (para quitar un nodo a una escena).
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;

public class SnakeBody : Sprite
{
private float time = 0;
private enum Direction{
LEFT,
RIGHT,
UP,
DOWN
};
private Direction direction;
private List<Rect2> body;
private bool eat = false;
public override void _Ready()
{
// Called every time the node is added to the scene.
// Initialization here
direction = Direction.RIGHT;
body = new List<Rect2>();
body.Add(new Rect2(0,0,40,40));
body.Add(new Rect2(40,0,40,40));
SetZIndex(1);
}

public override void _Draw()
{
var color = new Color(1,0,0);
foreach(var rect in body){
this.DrawRect(new Rect2(rect.Position.x+2,rect.Position.y+2,36,36),color);
}
}

public bool TryEat(Apple apple)
{
if(body[0].Position.x == apple.Position.x && body[0].Position.y == apple.Position.y){
Console.WriteLine("EAT!");
eat = true;
}

return eat;
}

public bool Crash()
{
return body.Skip(1).Any(t=>{
return t.Position.x == body[0].Position.x && t.Position.y == body[0].Position.y;
});
}

public override void _Process(float delta)
{
// Called every frame. Delta is time since last frame.
// Update game logic here.
time += delta;
if(time > 0.5){
Vector2 translation;
switch(direction){
case Direction.RIGHT: translation=new Vector2(40,0);break;
case Direction.LEFT: translation=new Vector2(-40,0);break;
case Direction.UP: translation = new Vector2(0,-40);break;
default: translation = new Vector2(0,40);break;
}
if(body.Count > 0){
var newRect = new Rect2(body[0].Position,body[0].Size);
newRect.Position += translation;
if(newRect.Position.x < 0){
newRect.Position = new Vector2(600,newRect.Position.y);
}
if(newRect.Position.x > 600){
newRect.Position = new Vector2(0,newRect.Position.y);
}
if(newRect.Position.y < 0){
newRect.Position = new Vector2(newRect.Position.x,320);
}
if(newRect.Position.y > 320){
newRect.Position = new Vector2(newRect.Position.x,0);
}

body.Insert(0,newRect);
if(!eat){
body.RemoveAt(body.Count-1);
}
if(Crash()){
Console.WriteLine("CRASH! Game Over");
}
}
this.Update();
time = 0;
eat = false;
}
}

public override void _Input(InputEvent @event)
{
if(@event.IsAction("move_left") && direction != Direction.RIGHT)
{
direction = Direction.LEFT;
return;
}
if(@event.IsAction("move_right") && direction != Direction.LEFT)
{
direction = Direction.RIGHT;
return;
}
if(@event.IsAction("move_up") && direction != Direction.DOWN)
{
direction = Direction.UP;
return;
}
if(@event.IsAction("move_down") && direction != Direction.UP)
{
direction = Direction.DOWN;
return;
}
}
}

El código de SnakeBody.cs es más largo, pero no más complejo. Como vemos la serpiente la represento como List<Rect2>. Además hay una variable time puesta para que la serpiente vaya a trompicones (como el snake auténtico).

Se puede ver como la función DrawRect nos sirve para dibujar un rectángulo. La API no tiene pérdida y es parecida a la API de Cairo o la Canvas de HTML5.

También se puede ver como se puede usar LINQ para comprobar las intersecciones de la serpiente consigo misma (en realidad se comprueba la cabeza con el resto de trozos, ya que la cabeza es la parte que va a estar presente en todos los golpes).

Con Update se fuerza una nueva llamada a _Draw.

Por último tenemos _Input. En Godot, la entrada se maneja por acciones, una capa de abstracción. Esto quiere decir que no es recomendable comprobar si una tecla ha sido pulsada, simplemente se asignan teclas a acciones desde Godot (o desde el juego en un panel de configuración) y en nuestro código comprobar las acciones.

Crear acciones


Para crear acciones vamos a Proyecto->Ajustes de Proyecto->Mapa de entrada y creamos las acciones que creamos convenientes. Yo las he llamado move_left, move_right, move_down y move_up. Luego las asignamos teclas de forma muy intuitiva.

Con esto ya tendríamos todo para un snake completito. Si le damos a ejecutar, podemos ver el juego en acción.

Todo el código del juego lo tenéis en el GitHub de ejemplos del blog y se puede importar a vuestro Godot.

 

Al juego le faltan muchas cosas, como una pantalla de game over cuando se produce un choque. Y puntuación. Y más detalles. Pero eso ya os lo dejo a vosotros.

 ]]>
https://blog.adrianistan.eu/tutorial-introduccion-godot-3-0-juego-snake-c Tue, 30 Jan 2018 00:00:00 +0000
Generar frases con cadenas de Markov. Machine Learning en Python https://blog.adrianistan.eu/generar-frases-cadenas-markov-machine-learning-python https://blog.adrianistan.eu/generar-frases-cadenas-markov-machine-learning-python
En mi caso voy a usar las frases del presentador argentino afincado en España, Héctor del Mar, porque siempre me han parecido bastante graciosas y tiene unas cuantas.

Héctor del Mar es el de la derecha. Para quien no le conozca, suele comentar los shows de la WWE

¿Qué son las cadenas de Markov?


Las cadenas de Markov es un modelo probabilístico que impone que la probabilidad de que suceda algo solo depende del estado anterior. Aplicado a nuestro caso con palabras, la probabilidad de que una palabra sea la siguiente de la frase solo depende de la palabra anterior. Observemos este grafo:

En él se han introducido dos frases: El coche rojo y El coche verde. La probabilidad de que coche sea la palabra que va después de El es del 100%, pero de que rojo sea la siguiente palabra después de coche es del 50%. Con este ejemplo, parece bastante limitado, pero la cosa cambia cuando metemos muchas frases y muchas palabras se repiten.

Para este ejemplo no obstante, usaré las dos últimas palabras como estado anterior, ya que suele dar resultados mucho más legibles (aunque pueden darse con más probabilidad frases que ya existen).

Obteniendo los datos


El primer paso será tener las frases en un formato óptimo. Para ello usaré requests y BeautifulSoup4. Las frases las voy a sacar de Wikiquote.
from bs4 import BeautifulSoup
import requests

r = requests.get("https://es.wikiquote.org/wiki/H%C3%A9ctor_del_Mar")
soup = BeautifulSoup(r.text,"lxml")
frases = map(lambda x: x.text.replace("\"",""),soup.select(".mw-parser-output li"))
palabras = map(lambda x: str(x).split(" "),frases)

Generando el grafo de Markov


Ahora hay que generar el grafo de Markov. Para ello vamos a usar un diccionario, donde en la clave tendremos el estado anterior, es decir, las dos últimas palabras (en forma de tupla). El contenido será una lista con todas las palabras a las que puede saltar. Al ser una lista, puede haber palabras repetidas, lo que efectivamente hará aumentar su probabilidad.

Aprovechando Python, voy a usar un defaultdict para simplificar el código, ya que con él me voy a asegurar de que todos los accesos al diccionario me van a devolver una lista.
from collections import defaultdict

almacen = defaultdict(lambda: list())

def add_word(prev,next):
global almacen
almacen[prev].append(next)

for frase in palabras:
frase = list(frase)
for i,palabra in enumerate(frase):
if i == 0:
add_word(("START","START"),palabra)
continue
if i == 1:
add_word(("START",frase[0]),palabra)
continue
add_word((frase[i-2],frase[i-1]),palabra)

Generando una frase nueva


Ahora viene el último paso, generrar una frase nueva. Para ello, empezamos con el estado START,START y seguimos el grafo hasta que acabemos. Para elegir la siguiente palabra de la lista usamos random.choice. La frase que se va generando se queda en una lista hasta que finalmente devolvemos un string completo.
import random

def gen_word():
word = list()
state = "START","START"
while True:
w = random.choice(almacen[state])
word.append(w)
state = state[1],w
if w.endswith(".") or w.endswith("!"):
return " ".join(word)

Resultados


Veamos los resultados:

Las frases en rojo son frases que dijo de verdad. Las frases en verde son frases generadas por este machine learning. La tasa de frases nuevas no es muy elevada, pero son más del 50%. Y todas son bastante divertidas.

El código fuente completo de Markov-HectorDelMar está en el repositorio Git del blog: https://github.com/aarroyoc/blog-ejemplos/tree/master/markov-hector-del-mar

Ahora que ya sabes usar cadenas de Markov, ¿por qué no meter como dato de entrada El Quijote?, ¿o los tuits de algún político famoso?, ¿o las entradas de este blog? Las posibilidades son infinitas.

Para despedirme, como diría el Héctor del Mar de verdad:
Aquí estoy porque he venido, porque he venido aquí estoy, si no le gusta mi canto, como he venido, me voy. ¡Nos vamos, don Fernando!
]]>
https://blog.adrianistan.eu/generar-frases-cadenas-markov-machine-learning-python Thu, 25 Jan 2018 00:00:00 +0000
Juego de la Vida de Conway en C# con interfaz gráfica https://blog.adrianistan.eu/juego-la-vida-conway-c-interfaz-grafica https://blog.adrianistan.eu/juego-la-vida-conway-c-interfaz-grafica Daniel Bazaco y yo. Se trata del clásico de juego de la vida, esta vez hecho en C# con .NET Core y Avalonia como librería gráfica. Funciona tanto en Windows como en GNU/Linux. El programa tiene la peculiaridad de que tiene implementados dos algoritmos totalmente distintos para el juego de la vida:

  • El clásico algoritmo de la matriz infinita.

  • Un algoritmo usando Quadtrees y tablas de dispersión optimizadas, que permite tener patrones precalculados.


La velocidad de este segundo algoritmo es muy superior a la del primero, aunque he de confesar que este segundo algoritmo no resulta evidente y tiene una desventaja en el modo gráfico. Este segundo algoritmo avanza a trompicones, por lo que no es posible realizar una animación gráfica idónea, a no ser que lo modifiquemos ligeramente. Este tercer algoritmo que es una modificación del segundo, es más lento, pero permite ser mostrado por la pantalla.

El programa admite ficheros tanto en formato estándar RLE como un formato propio, que hemos llamado Vaca. Puedes pasarte por la wiki del juego de la vida y probar los ficheros RLE que encuentres. No obstante, hay que tener cuidado, pues algunos ficheros RLE no son del juego de la vida, sino de otros juegos con normas ligeramente modificadas.

¿En qué consiste el Juego de la Vida?


El juego de la vida es un autómata celular de dos dimensiones. También se le ha categorizado como juego para cero jugadores.

El juego tiene unas normas sencillas. Cada celda puede estar viva o muerta. En la siguiente evolución, las celdas pueden pasar a vivas o muertas siguiendo este esquema:

  • Una célula muerta con exactamente 3 células vecinas vivas "nace" (es decir, al turno siguiente estará viva).

  • Una célula viva con 2 o 3 células vecinas vivas sigue viva, en otro caso muere o permanece muerta (por "soledad" o "superpoblación").


Unas condiciones de partida determinadas podrán desencaminar comportamientos complejos y emergentes muy interesantes como las pistolas de gliders.

[video width="1364" height="704" mp4="https://files.adrianistan.eu/GosperGliderGun.mp4" webm="https://files.adrianistan.eu/GosperGliderGun.webm"][/video]

Uso


Pantalla de inicio de Conway

Desde aquí podemos dar a Nuevo patrón o Cargar patrón. Si le damos a Nuevo Patrón tendremos una matriz vacía y limpia. Podemos hacer click con el ratón para ir activando/desactivando las casillas. Puedes usar las teclas W, A, S y D o las flechas en pantalla para moverte por el universo infinito de Conway.

Una vez lo tengamos podemos guardarlo para no tener que volver a dibujarlo. Otra opción es cargar un patrón de la lista. Este programa admite formato RLE y Vaca, pero solo guarda archivos en formato Vaca.

Para ejecutar el juego de la vida hay tres botones importantes. El primero es Ejecutar, que ejecuta el juego de la vida indefinidamente. Se para cuando pulsamos Parar (el mismo botón).

El otro es Siguiente, que nos permite avanzar de iteración en iteración manualmente, muy interesante para observar al detalle ciertos patrones. Por otro lado tenemos Iterar N veces, que permite iterar N veces y que sirve para pruebas de rendimiento. Hay que tener en cuenta que tanto Siguiente como Iterar N veces funcionan un poco distinto con el algoritmo Quadtree (el activado por defecto), ya que este algoritmo hace varias evoluciones de golpe, para ser todavía más rápido. La parte mala es que no es posible ver en detalle cada algoritmo.
Algoritmo Matriz

[video width="1008" height="486" mp4="https://files.adrianistan.eu/AlgoritmoMatriz.mp4" webm="https://files.adrianistan.eu/AlgoritmoMatriz.webm"][/video]
Algoritmo Quadtree

[video width="1008" height="486" mp4="https://files.adrianistan.eu/AlgoritmoQuadtree.mp4" webm="https://files.adrianistan.eu/AlgoritmoQuadtree.webm"][/video]

Línea de comandos


Es posible ejecutar el juego de la vida en línea de comandos. Este modo permite cargar un archivo Vaca o RLE y ejecutarlo N iteraciones. Al finalizar se muestran estadísticas y se permite guardar el resultado o mostrarlo por pantalla con caracteres ASCII.

Hay dos parámetros, -i para indicar el fichero de entrada y -n para indicar las iteraciones a calcular.

Algoritmo Quadtree


¿Cómo funciona el algoritmo quadtree que tanto mejora el rendimiento del juego de la vida? Siendo sinceros, no es algoritmo sencillo o evidente. Su nombre más correcto es algoritmo Hashlife y fue descrito por Bill Gosper en los laboratorios de investigación de Xerox Palo Alto.

La idea básica es que muchas veces en el juego de la vida nos encontramos con patrones que se van repitiendo periódicamente y grandes zonas vacías.

Para ello recurre a un almacén de cuadrantes. Y es que ahora el universo ya no es una matriz infinita, sino un cuadrante. Y cada cuadrante tiene cuatro cuadrantes hijos (noroeste, noreste, suroeste y sureste), así hasta llegar al cuadrante mínimo ya no tiene hijos sino que es una celda viva o muerta. Esto evidentemente pone limitaciones al tamaño del universo, que será siempre potencia de dos.

El almacén es una tabla hash, pero no una corriente tipo HashMap de Java o Dictionary de C# sino que toma 4 elementos como índice, cuatro subcuadrantes. Si existe un elemento cuyos cuatro subcuadrantes son iguales (no se comprueba la igualdad exactamente, sería muy lento), se devuelve la siguiente iteración del cuadrante del almacén que cumple esos requisitos. De este modo no hace falta calcular los cuadrantes nada más que la primera vez que el programa se encontró con ellos.

Este sistema consume más memoria, pero mejora de forma sustancial la velocidad de ejecución. El algoritmo luego tiene bastantes más detalles (el diablo está en los detalles), pero esa es la idea principal, no calcular los cuadrantes más que una sola vez.

Descargar Conway


Podéis descargar Conway desde GitHub y compilarlo en Windows y GNU/Linux (Mac no está probado pero en principio funcionaría), con .NET Core 2.0 instalado.

Conway en GitHub


 

 ]]>
https://blog.adrianistan.eu/juego-la-vida-conway-c-interfaz-grafica Sat, 20 Jan 2018 00:00:00 +0000
Cheatsheet de Pandas https://blog.adrianistan.eu/cheatsheet-de-pandas https://blog.adrianistan.eu/cheatsheet-de-pandas Pandas_Cheat_Sheet

Agradecimientos a Irv Lusting que se ha tomado la molestia de hacerla]]>
https://blog.adrianistan.eu/cheatsheet-de-pandas Fri, 19 Jan 2018 00:00:00 +0000
Estadística en Python: ajustar datos a una distribución (parte VII) https://blog.adrianistan.eu/estadistica-python-ajustar-datos-una-distribucion-parte-vii https://blog.adrianistan.eu/estadistica-python-ajustar-datos-una-distribucion-parte-vii
Este tipo de ajuste es muy interesante, ya que nos permite saber si los datos en bruto pueden parecerse a los modelos de Normal u otros y aprovecharlo.

Ajuste de datos a una distribución


Para ajustar datos a una distribución, todas las distribuciones continuas de SciPy cuentan con la función fit. Fit nos devuelve los parámetros con los que ajusta nuestros datos al modelo. ¡Ojo, no nos avisa si es un buen ajuste o no!
Ejemplo: Queremos saber si la altura de los hombres adultos del pueblo de Garray sigue una distribución normal. Para ello tomamos una muestra de 80 alturas de hombres adultos en Garray.


Los datos los tenemos en este CSV, cada altura en una línea:


Altura
180.55743416791302
159.4830930711535
175.54566032406794
149.06378740901533
140.35494067172635
146.65963134242543
171.34024710764376
140.11601629465872
175.6026441151595
158.00860559393507
122.53612034588375
116.10055909040616
152.89225061770068
148.31372767763455
111.17487190927599
160.18952563680827
151.8729737480722
141.50350042949614
165.2379297612276
150.75979657877465
171.5257501059296
157.97922034080895
159.60144363114716
152.52036681430164
172.0678524550487
163.65457704485283
134.9562174388093
189.70206097599245
153.78203142905076
176.1787894042539
190.83025195589502
199.04182673196726
146.97803776211907
174.22118528139467
170.95045320552694
161.2797407784266
190.61061242859464
168.79257731811308
159.87099716863165
136.22823975268153
166.87622973701335
179.58044852016417
172.49583957582817
165.2662334997042
136.6663345224381
161.9352364324168
174.56164027542448
161.62817356012405
167.65579546297906
170.88930983697742
147.22062198310996
151.85737964663497
158.03323614736198
135.77570282853696
161.25435141827515
193.33084953437478
155.43189514766172
155.89204074847055
179.23931091736836
146.485962651657
166.61617663518228
161.70927578953211
164.89798613982495
139.18195138901498
180.30341647946335
162.4811239647979
171.1035005376699
147.01137545913147
187.03282087175134
172.2476631392949
152.9814634955974
174.43159049461713
174.83877117002814
132.66857703218636
173.98029972846837
133.5435543737402
169.62941676289472
166.4887567852903
138.1150540623029
170.52532661450618

Vamos a ajustarlo.
import pandas as pd
import scipy.stats as ss

df = pd.read_csv("alturas.csv")

media, desviacion = ss.norm.fit(df["Altura"])

print(media) # media = 160,37
print(desviacion) # desviacion = 17,41



En este caso nos informa de que estos datos parecen encajar con los de una distribución normal de media 160,37 y desviación típica 17,41.

¿Cómo de bueno es el ajuste? Kolmogorov


Hemos hecho un ajuste, pero no sabemos qué tan bueno es. Existen varios métodos para poner a prueba los ajustes. Existen varios métodos, siendo los más populares Chi-Cuadrado y Kolmogorov-Smirnov.

Chi-Cuadrado no se puede aplicar directamente sobre distribuciones continuas, aún así voy a explicar como se haría. Sin embargo, primero vamos a probar con Kolmogorov-Smirnov, que en SciPy es ktest.
import pandas as pd
import scipy.stats as ss

df = pd.read_csv("altura.csv")
media, desviacion = ss.norm.fit(df["Altura"])
d, pvalor = ss.ktest(df["Altura"],"norm",args=(media,desviacion))
# o alternativamente
d, pvalor = ss.ktest(df["Altura"],lambda x: ss.norm.cdf(x,media,desviacion))

Este test nos devuelve dos valores: D y el p-valor. Yo voy a fijarme en el p-valor. El p-valor es el nivel mínimo de significación para el cual rechazaremos el ajuste. Cuanto más cerca del 1 esté, más confianza hay en el ajuste, cuanto más cerca de cero esté, menos confianza hay en el ajuste. ¿Pero cómo se interpreta exactamente?



Una forma sencillo de entenderlo es hacer obtener el nivel de significación, que es 1 - NivelConfianza/100. Así para una confianza del 95%, este será de 0.05, para una confianza del 99%, el valor se reduce a 0.01. Si el p-valor es inferior a este nivel de significación, el ajuste probablemente no es correcto.
import pandas as pd
import scipy.stats as ss

df = pd.read_csv("altura.csv")
media, desviacion = ss.norm.fit(df["Altura"])
d, pvalor = ss.ktest(df["Altura"],"norm",args=(media,desviacion))

# queremos confianza al 99%
if pvalor < 0.01:
print("No se ajusta a una normal")
else:
print("Se puede ajustar a una normal")

Aquí algún estadístico más serio se tiraría de los pelos, porque en realidad lo único que se puede demostrar con seguridad es en el caso de que no se ajuste. Si salta que se puede ajustar a una normal, simplemente querría decir que no se puede rechazar la hipótesis de partida (que sea normal), pero no confirma que sea normal.

Esto es algo común en el contraste de hipótesis y con los p-valores en particular y ha suscitado crítica, hasta tal punto que en 2016, la American Statistical Association publicó una serie de consejos para tratar con p-valores. El p-valor en este caso solo puede demostrar que no se ajusta a una normal. No obstante, para ir poco a poco, podemos simplificar esto un poco.

Chi-Cuadrado


Para hacer el test de Chi-Cuadrado primero hay que dividir los datos continuos. Para uso podemos usar la función de NumPy, histogram o la de SciPy binned_statistic. Tenemos que construir una lista con las frecuencias esperadas si siguiéramos al 100% la distribución a la que queremos ajustarnos.
def test_chicuadrado(data,N):
n = data.count()
freqs, edges, _ = ss.binned_statistic(data,data,statistic="count")
def ei(i):
return n*(N.cdf(edges[i])- N.cdf(edges[i-1]))
expected = [ei(i) for i in range(1,len(edges))]
return ss.chisquare(freqs,expected)

Una vez tengamos una lista con los valores esperados, simplemente podemos comparar las frecuencias reales con las esperadas con chisquare. Esto nos devolverá C y el p-valor. El significado del p-valor es exactamente igual.

Con esto ya hemos visto como empezar con estadística en Python. No sé si haré más artículos de este estilo, si no es así, espero que os hayan gustado esta serie de artículos. El mundo del data science es inmenso y os invito a seguirlo explorando.

]]>
https://blog.adrianistan.eu/estadistica-python-ajustar-datos-una-distribucion-parte-vii Wed, 17 Jan 2018 00:00:00 +0000
¿Cómo funcionan Meltdown y Spectre? Explicado con InstaStories https://blog.adrianistan.eu/funcionan-meltdown-spectre-explicado-instastories https://blog.adrianistan.eu/funcionan-meltdown-spectre-explicado-instastories ]]> https://blog.adrianistan.eu/funcionan-meltdown-spectre-explicado-instastories Fri, 5 Jan 2018 00:00:00 +0000 Yew, crea webapps al estilo Angular/React en Rust https://blog.adrianistan.eu/yew-frontend-rust-angular-react https://blog.adrianistan.eu/yew-frontend-rust-angular-react WebAssembly de forma nativa. Se trata de Yew, una librería para diseñar frontends, single-page-applications y en general, una librería que es una alternativa a Angular, React, Vue y Elm.

En particular, Yew se basa en The Elm Architecture, por lo que los usuarios de Elm serán los que encuentren más familiar a Yew.

Yew significa tejo. He aquí uno de los tejos más famosos de Valladolid, en la plaza del Viejo Coso

Instalar cargo-web y el soporte a WebAssembly


Antes de empezar a programar necesitaremos instalar un plugin para Cargo, llamado cargo-web, que nos ayudará en el desarrollo web con Rust. Por otro lado, hace falta instalar el soporte de Rust a WebAssembly. Existen tres opciones actualmente: asmjs-unknown-emscripten, wasm32-unknown-emscripten y wasm32-unknown-unknown. Para los primeras opciones hace falta tener instalado Emscripten. Para la última, no hace falta nada, por lo que es mi opción recomendada. Por otro lado, wasm32 significa WebAssembly y asmjs es asm.js, que es simplemente JavaScript y será compatible con más navegadores.
cargo install cargo-web
rustup target add wasm32-unknown-unknown

The Elm Architecture


Los principios de The Elm Architecture se basan en: modelo, mensajes, actualización y vista.

Para este tutorial vamos a hacer la típica aplicación de una lista donde guardamos notas. Nuestro modelo se va a componer de una lista de tareas, para simplificar, pongamos que una tarea es simplemente un String y un ID. Entonces también nos hará falta almacenar un contador para ir poniendo IDs. También, al tener un campo de texto, nos hará falta una variable para ir almacenando temporalmente lo que escribe el usuario.
struct Model {
id: u32,
tasks: Vec<Task>,
input: String,
}

struct Task{
content: String,
id: u32,
}

Lo siguiente es diseñar los mensajes. Los mensajes interactúan con el modelo y desencadenan una actualización de la vista. En esta aplicación solo nos hacen falta dos mensajes: añadir mensaje y borrar mensaje. Pero como tenemos un campo de texto, tenemos que introducir un mensaje Change y siempre viene bien un mensaje que no haga nada.
enum Msg {
Add,
Remove(u32),
Change(String),
None,
}

Una vez hecho esto pasamos a crear la función update, en la que hacemos distintas cosas según el mensaje que recibamos.
fn update(context: &mut Context<Msg>, model: &mut Model, msg: Msg) {
match msg {
Msg::Add => {
let task = Task{
content: model.input.clone(),
id: model.id,
};
model.tasks.push(task);
model.id += 1;
}
Msg::Change(content) => {
model.input = content;
}
Msg::Remove(id) => {
let mut i = 0;
for task in model.tasks.iter(){
if task.id == id{
break;
}
i+=1;
}
model.tasks.remove(i);
}
_ => {

}
}
}

Así pues, si se lanza un mensaje Msg::Add lo que hacemos es copiar el valor de la variable temporal input, crear una nueva tarea con su ID y añadirla a la lista de tareas. Ya está. Yew mágicamente actualizará la página para reflejar que la lista de tareas ha sido modificada. Lo mismo pasa con Remove.

Ahora vamos a las vistas. Una vista es una función que devuelve Html<Msg> y se pueden componer varias funciones así. En nuestro caso, tenemos una vista principal donde se ve un campo de texto y un sitio donde se ejecuta un bucle for con las tareas del modelo. Y a cada tarea se le aplica la vista view_task.
fn view(model: &Model) -> Html<Msg> {
html! {
<div>
<ul>
{ for model.tasks.iter().map(view_task) }
</ul>
<input type="text", value=&model.input, oninput=|e: InputData| Msg::Change(e.value), onkeypress=|e: KeyData|{
if e.key == "Enter" {
Msg::Add
}else{
Msg::None
}
}, />
</div>
}
}

fn view_task(task: &Task) -> Html<Msg>{
let id = task.id;
html!{
<li><span>{&task.content}</span><button onclick=move |_| Msg::Remove(id),>{format!("X")}</button></li>
}
}

La macro html! nos permite escribir HTML directamente en Rust, con algunas diferencias (¡prestad atención a las comas!). También nos permite introducir código Rust (entre llaves) y closures (observad onclick, oninput y onkeypress).

Finalmente en el método main, inicializamos el modelo y llamamos a program, que empieza a ejecutar Yew.

El código final queda así.
#[macro_use]
extern crate yew;

use yew::html::*;

struct Model {
id: u32,
tasks: Vec<Task>,
input: String,
}

struct Task{
content: String,
id: u32,
}

enum Msg {
Add,
Remove(u32),
Change(String),
None,
}

fn update(context: &mut Context<Msg>, model: &mut Model, msg: Msg) {
match msg {
Msg::Add => {
let task = Task{
content: model.input.clone(),
id: model.id,
};
model.tasks.push(task);
model.id += 1;
}
Msg::Change(content) => {
model.input = content;
}
Msg::Remove(id) => {
let mut i = 0;
for task in model.tasks.iter(){
if task.id == id{
break;
}
i+=1;
}
model.tasks.remove(i);
}
_ => {

}
}
}

fn view(model: &Model) -> Html<Msg> {
html! {
<div>
<ul>
{ for model.tasks.iter().map(view_task) }
</ul>
<input type="text", value=&model.input, oninput=|e: InputData| Msg::Change(e.value), onkeypress=|e: KeyData|{
if e.key == "Enter" {
Msg::Add
}else{
Msg::None
}
}, />
</div>
}
}

fn view_task(task: &Task) -> Html<Msg>{
let id = task.id;
html!{
<li><span>{&task.content}</span><button onclick=move |_| Msg::Remove(id),>{format!("X")}</button></li>
}
}

fn main() {
let model = Model {
id: 0,
tasks: vec![],
input: String::from("")
};
program(model, update, view);
}

Ejecutando la aplicación web


Usando cargo web, es muy sencillo generar la aplicación web. Simplemente ejecuta:
cargo web start --target-webasm

El resultado, se montará en un mini servidor web. Si accedes a la URL que indica Cargo con tu navegador web, verás algo similar a esto:

Añade items y bórralos. Observa como la aplicación funciona perfectamente.

Distribuyendo la aplicación


Realmente cargo web ha hecho muchas cosas por nosotros. Si nosotros queremos usar Yew en la vida real, no usaremos cargo web. Para ello, compilamos la aplicación web:
cargo web build --target-webasm

Y accedemos a la carpeta target/wasm32-unknown-unknown/release. Allí encontraremos dos archivos que vamos a necesitar. Uno acabado en .js y otro acabado en .wasm. Ambos ficheros deberemos copiarlos donde queramos usarlos. Por último, será necesario un fichero HTML. En el fichero HTML solo hace falta cargar el fichero JS. Yew hará el resto.

Si quieres saber más, puedes descargarte el código de ejemplo que se encuentra en GitHub.]]>
https://blog.adrianistan.eu/yew-frontend-rust-angular-react Mon, 1 Jan 2018 00:00:00 +0000
Leer y escribir JSON en Rust con Serde https://blog.adrianistan.eu/leer-escribir-json-rust-serde https://blog.adrianistan.eu/leer-escribir-json-rust-serde JSON es un formato muy popular para guardar y transmitir información. En Rust podemos leer y escribir JSON de forma transparente gracias a Serde. Serde es una librería para Rust, que permite transformar las structs nativas de Rust en ficheros JSON, YAML, BSON, MsgPack, XML y viceversa. Serde está compuesto de varios plugins, uno para cada formato de archivo, así que vamos a necesitar el plugin de JSON. Es lo que se dice serializar (guardar) y deserializar (leer).

Deserializando JSON con Serde


Para deserializar necesitamos primero definir el struct en Rust que se corresponde al JSON que vamos a cargar. Además, hay que incluir una cláusula derive e indicar que derivamos Deserializable.
#[derive(Deserialize)]
struct Landmark {
x: i32,
y: i32,
name: String
}

Una vez hecho eso podemos realizar la transformación con Serde. Serde admite varios métodos de entrada. En mi caso voy a usar from_reader, que permite transformar desde cualquier std::io::Read. También se podría usar from_str, que permite leer desde un String.

Así pues un programa que lea Landmarks de un fichero JSON como este:
[{
"x": 42,
"y" : -1,
"name" : "Presa de Tibi"
},
{
"x" : 50,
"y" : 23,
"name" : "Rollo de Villalón de Campos"
}]

Quedaría así:
#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_json;

use std::io::BufReader;
use std::fs::File;

#[derive(Deserialize)]
struct Landmark {
x: i32,
y: i32,
name: String
}

fn main() {
let file = File::open("landmarks.json").unwrap();
let reader = BufReader::new(file);
let landmarks: Vec<Landmark> = serde_json::from_reader(reader).unwrap();
for landmark in landmarks{
println!("Landmark name: {}\tPosition: ({},{})",landmark.name,landmark.x,landmark.y);
}
}

Nótese como usamos Vec<Landmark> para indicar que vamos a cargar un JSON que es un array de Landmarks.

Serializar JSON


Ahora si queremos generar un archivo JSON es muy sencillo. Simplemente marcamos Serialize en la misma estructura que queramos serializar. Posteriormente en Serde podemos utilizar diferentes métodos, como to_writer o to_string.
#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_json;

use std::io::{BufReader,BufWriter};
use std::fs::File;

#[derive(Serialize, Deserialize)]
struct Landmark {
x: i32,
y: i32,
name: String
}

fn main() {
let landmark = Landmark {
x: 67,
y: 23,
name: String::from("Academia de Caballería")
};
let output = File::create("caballeria.json").unwrap();
let writer = BufWriter::new(output);
serde_json::to_writer(writer,&landmark).unwrap();
}

Personalizando la serialización y la deserialización


Serde permite a través de atributos, definir de forma precisa que ocurre con los elementos. En caso de no poner nada, por ejemplo, Serde usará los nombres del struct de Rust en los ficheros y si hay elementos de más, en el fichero al leer, los ignorará.

Por ejemplo, si queremos que en el fichero JSON, en vez de tener name sea nombre, podemos usar rename.
#[derive(Serialize, Deserialize)]
struct Landmark {
x: i32,
y: i32,
#[serde(rename = "nombre")]
name: String
}

Podemos también no querer guardar algún elemento del struct, usamos skip.


#[derive(Serialize, Deserialize)]
struct Landmark {
x: i32,
y: i32,
name: String,
#[serde(skip)]
id: u32
}

Si queremos que Serde falle en caso de que en el JSON haya más campos de los que hemos definido, usamos deny_unkown_fields.
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
struct Landmark {
x: i32,
y: i32,
name: String,
}

Y si queremos que el nombre del struct sea distinto al que crea oportuno Serde, podemos redefinirlo también:
#[derive(Serialize, Deserialize)]
#[serde(rename = "landmark")]
struct Landmark {
x: i32,
y: i32,
name: String,
}

Por último, mencionar que Serde permite serializar/deserializar cosas más complejas, con otros structs, con vectores, con HashMap, entre sus elementos,... Lo único que tendrá que pasar es que esas estructuras a su vez sean serializables/deserializables con Serde (es decir, hayan puesto derive(Serialize,Deserialize)).




]]>
https://blog.adrianistan.eu/leer-escribir-json-rust-serde Sat, 30 Dec 2017 00:00:00 +0000
Gana dos entradas dobles para "Bugs" en el Museo Reina Sofía https://blog.adrianistan.eu/gana-dos-entradas-dobles-bugs-museo-reina-sofia https://blog.adrianistan.eu/gana-dos-entradas-dobles-bugs-museo-reina-sofia Bugs, una exposición dedicada a esas obras de arte que muchas veces pasan inadvertidas, los bugs.

En esta exposición, que se expondrá hasta el 1 de abril de 2018, podremos ver algunos de los máximos exponentes de este estilo artístico surgido a finales del siglo XX y muy popular en el siglo XXI. Como yo mismo he formado parte del comité de selección de obras, tengo el honor de regalar dos entradas dobles para la exposición.

«Queremos reunir en un mismo edificio todas las sensaciones que han provocado a los usuarios. Rabia, indignación, ira y soledad.» dice Sara Echeverría, directora de exposiciones temporales del Reina Sofía y buena amiga.

Algunas de las obras, más destacadas son:

Pantalla azul de Windows sobre monitor Samsung

Pantalla azul de Windows sobre monitor LG y cables desordenados

 

Pantalla azul de Windows rotada PI/2 radianes en aeropuerto

«Las pantallas azules nos transmiten finalidad. Nos hacen ver que todo es efímero. Todo nuestro trabajo, nuestra huella en la humanidad, puede desvanecerse con un simple error de driver. Al final, no somos nada. Una gota más, del océano inmenso.» dice Bill Gates, que fue invitado para preparar la exposición. «Creemos que Bill Gates es uno de los maestros en el arte del bug, su ayuda nos ha sido indispensable para encontrar el enfoque filosófico que buscábamos».

Final de un programa en LISP

«A mí siempre me han atraído los paréntesis de LISP. Era como algo sexual. Sentía orgasmos al descubrir que podía meter más paréntesis todavía.» Y es que en la exposición también se verán obras proclives a bugs como los paréntesis de LISP o la sagrada trinidad de JavaScript.

«JavaScript iba a llenar bastantes salas, así que solamente hemos elegido lo mejor. Ha sido difícil, porque cada día salen nuevos e impresionantes bugs, en cada nueva librería o framework. Habrá que estar atentos a ellos, porque son el futuro de los bugs»

También se ha dejado una sala especial a los ciberataques, que este año han sabido aprovechar con mucha intelegencia bugs de los sistemas operativos más conocidos. ¿Cómo olvidar el WannaCry de Edvard Munch?

WannaCry, de Edvard Munch

Pero no sería  una exposición completa sin contar con bugs como el efecto 2000 o el lanzamiento del cohete Ariane 5 (esas cosas pasan por usar ints de 16 bits en un cohete).





Si queréis participar para ganar una de las dos entradas dobles para ver la exposición, solo tenéis que hacer click aquí.]]>
https://blog.adrianistan.eu/gana-dos-entradas-dobles-bugs-museo-reina-sofia Thu, 28 Dec 2017 00:00:00 +0000
¡Feliz navidad y próspero 2018! (con fractales) https://blog.adrianistan.eu/feliz-navidad-prospero-2018-fractales https://blog.adrianistan.eu/feliz-navidad-prospero-2018-fractales
Comenzamos con In Dulci Jubilo y este cover de Giuliano Ferrace Leanza


El fractal de Koch


Helge von Koch fue un matemático sueco que en 1904 publica un artículo sobre la que desde entonces se llamaría curva de Koch. Se trata de una curva, generada con unas reglas muy simples. La idea fundamental es que se parte de una recta, que se divide en tres trozos iguales. El trozo del medio se sustituye por un triángulo equilátero. Entonces la curva recorre el primer trozo, sube el triángulo, baja el triángulo y continúa recto.

Si ahora en cada uno de los cuatro trozos rectos aplicamos la misma curva de Koch obtenemos esto:

Según la terminología de Mandelbrot, este fractal es un teragón. Si ahora en vez de aplicar esto sobre una recta, lo hacemos sobre un triángulo equilátero con sus tres trozos rectos iguales, ¿qué obtenemos?

Turtle, una librería de Rust para gráficos tortuga


Quizá entre mis lectores haya alguno que aprendió a programar con LOGO. Una de las características de LOGO era que disponía de una tortuga con la que íbamos dibujando según nos movíamos. Esta manera de dibujar, no es óptima en rendimiento puro, pero es ideal para fractales y para enseñar a programar. Python incorpora en su librería estándar el módulo turtle y en Rust está a punto de salir una librería que soporta una API similar llamada turtle.

Lo primero que tenemos que hacer es definir la curva de Koch con turtle.
    fn koch(&mut self, length: f64){
self.forward(length/3.0);
self.left(60.0);
self.forward(length/3.0);
self.right(120.0);
self.forward(length/3.0);
self.left(60.0);
self.forward(length/3.0);
}

Con esto nos valdría, pero no es recursivo. Necesitamos poder aplicar la curva de Koch en nuestras rectas (donde hacemos forward) de forma recursiva. Pero si lo hacemos recursivo de forma infinita no acabará nunca, es por ello que tenemos que indicar cuantos niveles de recursividad queremos.
    fn op(&mut self, length: f64, level: u32) {
if level == 0{
self.forward(length);
}else{
self.koch(length,level);
}
}
fn koch(&mut self, length: f64, level: u32){
self.op(length/3.0,level-1);
self.left(60.0);
self.op(length/3.0,level-1);
self.right(120.0);
self.op(length/3.0,level-1);
self.left(60.0);
self.op(length/3.0,level-1);
}

Esto ya tiene más sentido. Ahora juntemos todo lo necesario:
extern crate turtle;

use turtle::Turtle;
use turtle::Distance;

trait Fractal{
fn koch(&mut self, length: f64, level: u32);
fn op(&mut self, length: f64, level: u32) {
if level == 0{
self.forward(length);
}else{
self.koch(length,level);
}
}
fn forward(&mut self, distance: Distance);
}

impl Fractal for Turtle{
fn koch(&mut self, length: f64, level: u32){
self.op(length/3.0,level-1);
self.left(60.0);
self.op(length/3.0,level-1);
self.right(120.0);
self.op(length/3.0,level-1);
self.left(60.0);
self.op(length/3.0,level-1);
}
fn forward(&mut self, distance: Distance){
self.forward(distance);
}
}

fn main() {
let mut turtle = Turtle::new ();
turtle.set_pen_size(2.0);
turtle.pen_up();
turtle.go_to([-200.0,100.0]);
turtle.pen_down();
turtle.set_speed(0);
turtle.hide();
turtle.right(90.0);
turtle.koch(400.0,5);
turtle.right(120.0);
turtle.koch(400.0,5);
turtle.right(120.0);
turtle.koch(400.0,5);
}

Si compilamos y ejecutamos esto con Cargo:

Y aquí tenemos al copo de nieve de Koch.

Tenéis el proyecto completo en GitHub: https://github.com/aarroyoc/fractal_koch_rust

Fractal (bis)


Otro fractal muy interesante que programé mientras estaba haciendo el copo de nieve fue este otro. Desconozco si tiene nombre. Es algo más complejo de implementar y lo de los colores me dio bastantes dolores de cabeza hasta que encontré una progresión bella.

Su código es (en este caso Python):
from turtle import *
from math import cos, pi

a = 0

def fractal(length=100,level=1,flip=False):
# 3906 llamadas a fractal = 5^5 + 5^4 + 5^3 + 5^2 + 5^1 + 5^0
((r,g,b),_) = color()
global a
a += 1
if a % 7 == 0 and a < 1953:
b += 1
if a % 7 == 0 and a > 1953:
r += 1
b -= 1

r = min(r,255)
g = max(g,0)
b = min(b,255)
b = max(b,0)

color(int(r),int(g),int(b))

if not flip:
left(90)
else:
right(90)
if level == 1:
forward(length)
else:
fractal(length*length/(2*cos(45)*length/2+length),level-1,flip)
if not flip:
right(45)
else:
left(45)
if level == 1:
forward(length/2)
else:
fractal(length*length/(4*cos(45)*length/2+length),level-1,not flip)
if not flip:
right(45)
else:
left(45)
if level == 1:
forward(length)
else:
fractal(length*length/(2*cos(45)*length/2+length),level-1,flip)
if not flip:
right(45)
else:
left(45)
if level == 1:
forward(length/2)
else:
fractal(length*length/(4*cos(45)*length/2+length),level-1,not flip)
if not flip:
right(45)
else:
left(45)
if level == 1:
forward(length)
else:
fractal(length*length/(2*cos(45)*length/2+length),level-1,flip)
if not flip:
left(90)
else:
right(90)

def main():
colormode(255)
penup()
pensize(2.0)
setx(-100)
sety(-200)
pendown()
color(0,255,0)
speed(0)
tracer(False)
fractal(50,6)
done()


main()

 


Vuestra opinión


Ahora os pido que me déis vuestra opinión sobre el blog. Usad los comentarios de debajo y contadme: ¿Cuál es el mayor problema del blog?, ¿crees que los contenidos salen con frecuencia suficiente y necesaria?, ¿los temas son interesantes?, ¿me voy mucho por las ramas?, ... Todo esto lo tendré en cuenta de cara al año que viene.

Aprovecho para recordar que podéis suscribiros a la lista de correo para que os llegue un nuevo correo con cada post, podeís suscribiros al blog por RSS, hay un canal de Telegram y una página en Facebook. En Twitter, GNU Social e Instagram solo tengo perfiles personales, pero si os interesa, allí estoy. Por último en Google+ todavía sigo mandando los artículos.

Si os parece que me merezco una caña, siempre puedes donar usando PayPal, criptodivisas, tu apartamento en Comillas, ...

Sin más, feliz próspero año 2018



 ]]>
https://blog.adrianistan.eu/feliz-navidad-prospero-2018-fractales Fri, 22 Dec 2017 00:00:00 +0000
Estadística en Python: distribución binomial, normal y de Poisson (Parte VI) https://blog.adrianistan.eu/estadistica-python-distribucion-binomial-normal-poisson-parte-vi https://blog.adrianistan.eu/estadistica-python-distribucion-binomial-normal-poisson-parte-vi

  • cdf(x) - Función de distribución F(X)

  • sf(x) = 1 - cdf(x)

  • pmf(x) - Función de probabilidad f(x) (distribuciones discretas)

  • pdf(x) - Función de densidad f(x) (distribuciones continuas)

  • ppf(x) - Función inversa a cdf(x). Nos permite obtener el valor correspondiente a una probabilidad.


Distribución Binomial


Un ensayo de Bernouilli se define como un experimento donde puede darse un éxito o fracaso y donde cada ensayo es independiente del anterior. Por ejemplo, un ensayo de Bernoulli de parámetro 0.5 sería lanzar una moneda a cara o cruz (mitad de posibilidades de cara, mitad de posibilidades de cruz).

Si repetimos N veces los ensayos de Bernouilli tenemos una distribución binomial.

[latex]
X \rightarrow B(N,P)
[/latex]



SciPy nos permite usar binom para trabajar con distribuciones binomiales.


Ejemplo: Un proveedor de DVDs regrabables afirma que solamente el 4 % de los

artículos suministrados son defectuosos. Si un cliente compra un lote de 25

DVDs, ¿cuál es el número esperado de DVDs defectuosos en el lote? Si el cliente

encuentra que 4 de los DVDs comprados son defectuosos, ¿debe dudar de la

afirmación del vendedor?


El número de DVDs defectuosos esperados es el equivalente a decir el número medio de DVDs defectuosos.


import scipy.stats as ss

X = ss.binom(25,0.04)

X.mean() # 1.0

Es decir, de media habría 1 DVD defectuoso en el paquete. mean calcula la media de la distribución.

Para saber si hay que fiarse del vendedor vamos a calcular cuál era la probabilidad de que nos tocasen 4 DVDs defectuosos.
import scipy.stats as ss

X = ss.binom(25,0.04)

pr = X.sf(3) # 0.016521575032415914

Es decir, la probabilidad que ocurriese era del 1%. Podemos sospechar del fabricante. cdf calcula las probabilidades acumuladas. En este caso tenemos que calcular la probabilidad de que hubiese 4 o más fallos, Pr{X>=4}. Una manera fácil de calcularlo es hacer 1-Pr{X<4}. cdf(n) nos permite calcular probabilidades acumuladas hasta N. Otra opción sería simplemente obtener la probabilidad de 0 DVDs defectuosos, 1 DVD defectuoso, de 2 DVDs defectuosos, de 3 DVDs defectuosos, sumarlo y restarlo de 1.
import scipy.stats as ss

X = ss.binom(25,0.04)

pr = 1 - sum(X.pmf(x) for x in range(4))

pmf(n) devuelve la probabilidad de que X=N, Pr{X=N} Esto solo tiene sentido en ciertas distribuciones, las discretas, como es el caso de la binomial.

Podemos calcular la gráfica de esta distribución binomial.
import scipy.stats as ss
import matplotlib.pyplot as plt

X = ss.binom(25,0.04)
x = np.arange(10)

plt.plot(x,X.pmf(x),"bo")
plt.vlines(x,0,X.pmf(x),"b")
plt.show()

En el gráfico también se puede ver que las probabilidades de tener 4 o más DVDs defectuosos son mínimas.


Distribución hipergeométrica


La distribución hipergeométrica es un modelo en el que se considera una población finita de tamaño N en la cual hay M individuos con una determinada característica y se seleccionan n y queremos saber la probabilidad de que haya cierto número de individuos con esa característica en la selección. Para trabajar con estas distribuciones, SciPy trae hypergeom.

Ejemplo: Se formó un jurado de 6 personas de un grupo de 20 posibles miembros de los cuales 8 eran mujeres y 12 hombres. El jurado seelecionó aleatoriamente, pero solamente tenía 1 mujer. ¿Hay motivos para dudarde la aletoriedad de la selección?
import scipy.stats as ss

X = ss.hypergeom(20,6,8)

X.cdf(1) # 0.18730650154798736

La probabilidad de que ocurriese lo que ocurrió es del 18,7%, una probabilidad suficientemente alta como para pensar que no hubo manipulación. Podemos dibujar esta hipergeométrica:

Como se puede observar en la gráfica el caso más probable, con cerca del 35% de posibilidades era que hubiese dos mujeres seleccionadas. Destacar también, que la probabilidad de que haya 7 mujeres en el jurado es cero, porque solo hay 6 plazas en el jurado.

Distribución de Poisson


La distribución de Poisson recoge sucesos independientes que ocurren en un soporte continuo. El número medio de sucesos por unidad de soporte se le conoce como λ y caracteriza la distribución. poisson nos permite crear distribuciones de este tipo.

Algunos ejemplos de distribuciones de Poisson: número de clientes que llegan cada hora a cierto puesto de servicio, número de averías diarias de un sistema informático, número de vehículos que pasan diariamente por un túnel, número de defectos por kilómetro de cable, ...

Ejemplo: La impresora de una pequeña red informática recibe una media de 0.1 peticiones por segundo. Suponiendo que las peticiones a dicha impresora son independientes y a ritmo constante, ¿cuál es la probabilidad de un máximo de 2 peticiones en un segundo? Si la cola de la impresora tiene un comportamiento deficiente cuando recibe más de 10 peticiones en un minuto, ¿cuál es la probabilidad de que ocurra esto?
import scipy.stats as ss

X = ss.poisson(0.1)
X.cdf(2) # 0.99984534692973537

Y = ss.poisson(6)
Y.sf(10) # 0.042620923582537995

Variable Y: número de peticiones a la impresora en un minuto (y la probabilidad de que suceda)

Distribución exponencial


Para modelizar el intervalo entre dos sucesos consecutivos que siguen una distribución de Poisson se usa la distribución exponencial de parámetro λ.

Ejemplo: El proceso de accesos a una página web se produce de una forma estable e independiente, siendo el intervalo entre dos accesos consecutivos una v.a. exponencial. Sabiendo que, de media, se produce un acceso cada minuto,¿cuál es la probabilidad de que no se produzcan accesos en 4 minutos? y ¿cuál esla probabilidad de que el tiempo transcurrido entre dos accesos consecutivos sea inferior a 90 segundos?

Esta distribución en SciPy es un poco rara, ya que no está implementada como podría esperarse.
import scipy.stats as ss

ss.expon.sf(4,loc=0,scale=1) # 0.018315638888734179
ss.expon.cdf(1.5,loc=0,scale=1) # 0.77686983985157021

Distribución normal


Probablemente el modelo de distribución más usado y conocido. Lo usamos para describir variables reales continuas.

Ejemplo: La duración de un determinado componente electrónico, en horas, es una v.a. que se distribuye según una N(2000,40). ¿Cuál es la probabilidad de que la duración de una de esas componentes sea superior a 1900 horas? ¿y de que esté entre 1850 y 1950 horas?
import scipy.stats as ss

X = ss.norm(2000,40)
X.sf(1900) # 0.99379033467422384
X.cdf(1950) - X.cdf(1850) # 0.10556135638165455

Podemos representar esta variable.
import scipy.stats as ss
import numpy as np
import matplotlib.pyplot as plt

X = ss.norm(2000,40)
x = np.arange(X.ppf(0.01),X.ppf(0.99))

plt.plot(x,X.pdf(x),"r")
plt.show()

Estos modelos no son perfectos, pero son lo suficientemente flexibles para ser un buen punto de partida.]]>
https://blog.adrianistan.eu/estadistica-python-distribucion-binomial-normal-poisson-parte-vi Fri, 8 Dec 2017 00:00:00 +0000
Estadística en Python: cálculo de probabilidades (Parte V) https://blog.adrianistan.eu/estadistica-python-calculo-probabilidades-parte-v https://blog.adrianistan.eu/estadistica-python-calculo-probabilidades-parte-v

  • Experimento cualquier proceso de obtención de una observación o medida en el que se suponen fijos ciertos factores. Los experimentos puede ser deterministas si solo es posible un resultado (aunque sea desconocido) y aleatorios. Llamamos azar a los factores que no controlamos de un experimento aleatorio.

  • Probabilidad: la incertidumbre de observar un determinado resultado antes de que se realice el experimento.

  • Suceso: el resultado o conjunto de resultados de un experimento aleatorio

  • Espacio muestral: el conjunto de todos los resultados posibles de un experimento aleatorio

  • Suceso complementario de A: lo que ocurre cuando no ocurre A

  • Suceso seguro: Aquel que ocurre siempre. Se representa con Ω

  • Suceso imposible: Aquel que no forma parte del espacio muestral

  • Sucesos incompatibles: Aquellos que no pueden ocurrir de forma simultánea


El cálculo de probabilidades nos sirve para valorar el riesgo de nuestras decisiones, anticipar eventos y valorar si nuestras hipótesis eran razonables.

Para el cálculo de probabilidades vamos a seguir la axiomática de Kolmogoroff.

Regla de Laplace


La ley fundamental de las probabilidades, define la probabilidad como la razón entre el número de casos favorables y el número de casos totales.

[latex]
Pr\{A\} = \frac{k}{n}
[/latex]


Esto es muy sencillo de utilizar y no voy a poner código Python. Para calcular tanto el número de casos favorables como el de casos totales es recomendable tener nociones de combinatoria, que resulta extremadamente útil en ese tipo de situaciones.

Probabilidad condicionada


¿Qué ocurre si disponemos de información suplementaria? Formulado de otra forma, ¿qué pasa si queremos calcular la probabilidad de A sabiendo B ha ocurrido? ¿Daría el mismo resultado? La respuesta es que no, la probabilidad de A condicionada por B se define de la siguiente forma:

[latex]
Pr\{A | B\} = \frac{Pr\{A \cap B \}}{Pr\{B\}}
[/latex]


¿Cómo se calcula la probabilidad de la intersección de A y B?

[latex]
Pr\{A \cap B\} = Pr\{A\}Pr\{B|A\}
[/latex]


Aquí nos damos cuenta que si A es independiente de B, la probabilidad de su intersección es simplemente el producto de Pr{A} y Pr{B}.

Teorema de Bayes


La generalización de lo anterior es el conocido teorema de Bayes. Podemos usarlo para resolver una gran cantidad de problemas.

En la provincia de Soria, el negocio de acceso a Internet se reparte entre dos operadores, Timofónica y Robafone y dos únicas marcas de routers, Xisco y Nuaweii. En Soria, la cuota de mercado de Timofónica es del 60% y de Robafone el resto. El 70% de los usuarios dispone de router Xisco y el 30% de ambas marcas. Además se sabe que la probabilidad de corte de acceso es 0.1 para usuarios de Timofónica, 0.15 para Robafone y 0.05 para routers Xisco.

¿Cuál es la probabilidad de que a un usuario se le corte el Internet?

Primero vamos a definir un diccionario pr con las probabilidades que nos da el enunciado. Tenemos varias probabilidades relacionadas con un usuario: operador, router, fallos condicionados, ...
pr = dict()
pr["Timofónica"] = 0.6
pr["Robafone"] = 0.4
pr["Xisco"] = 0.7
pr["Xisco Y Nuaweii"] = 0.3
pr["Corte | Timofónica"] = 0.1
pr["Corte | Robafone"] = 0.15
pr["Corte | Xisco"] = 0.05

Para calcular la probabilidad de corte de un usuario hay que sumar la probabilidad de ser usuario de una compañía y tener un corte y de ser de otra compañía y tener un corte.
pr["Corte"] =  pr["Corte | Timofónica"]*pr["Timofónica"] + pr["Corte | Robafone"]*pr["Robafone"]

En este caso Pr{Corte} = 0.12. La probabilidad de que un usuario cualquiera de Soria tenga un corte es del 12%.

Si se sabe que un usuario tiene la línea cortada, ¿cuál es la probabilidad de que tenga router Xisco en casa?

En este caso se pide Pr{Xisco|Corte}. Según el teorema de Bayes, esto es:

[latex]
Pr\{Xisco|Corte\} = \frac{Pr\{Xisco \cap Corte\}}{Pr\{Corte\}} = \frac{Pr\{Xisco\}Pr\{Corte | Xisco\}}{Pr\{Corte\}}
[/latex]



pr["Xisco | Corte"] = pr["Xisco"]*pr["Corte | Xisco"]/pr["Corte"]

Que da una probabilidad de 0,29. Es decir, si el usuario tiene un corte, la probabilidad de que en su casa tenga un router Xisco es del 29%.

¿Cuál es la probabilidad de que se produzca un corte a un usuario que no tiene un router Xisco?

En este caso se pide Pr{Corte | Nuaweii}. Y tenemos un pequeño problema y es que no sabemos la probabilidad de que un usuario tenga en su casa Nuaweii. Con un poco de manipulación matemática podemos obtener una expresión que no depende de Pr{Nuaweii}.

[latex]
Pr\{Corte|Nuaweii\} = \frac{Pr\{Corte \cap Nuaweii\}}{Pr\{Nuaweii\}} \\ = \frac{Pr\{Corte \cap (\Omega-Xisco)\}}{1-Pr\{Xisco\}} = \frac{Pr\{Corte - Corte \cap Xisco\}}{1-Pr\{Xisco\}} \\ = \frac{Pr\{Corte\}-Pr\{Corte \cap Xisco\}}{1-Pr\{Xisco\}}
[/latex]



pr["Corte | Nuaweii"] = (pr["Corte"] - pr["Xisco"]*pr["Corte | Xisco"])/(1-pr["Xisco"])

Funciones asociadas


Función de probabilidad: Una función que devuelve la probabilidad de ser obtenido un valor en un experimento aleatorio. La suma de las funciones de probabilidad de todos los valores que puede tomar la variable es 1.

Función de distribución F(x) Una función que devuelve la probabilidad de obtener un valor igual o menor al valor en un experimento aleatorio. Esta función lo que hace es ir acumulando.

Función de densidad f(x): Como en variables aleatorias continuas no tiene sentido hablar de función de probabilidad (siempre sería 0), se define la función de densidad, como la función que da la probabilidad de que una variable aleatoria esté entre A y B.

Como es lógico es posible pasar entre función de densidad y de distribución mediante integreación y derivación.

Medidas asociadas


Esperanza matemática (μ) o media poblacional

[latex]
\mu = E(x) = \int_{-\inf}^{\inf} xf(x)dx
[/latex]


Mediana: el X que da como resultado 0.5 en la función de densidad, F(X) = 0.5

Varianza:
[latex]
Var(X) = \sigma^2 = E((X-\mu)^2)
[/latex]

Y con esto dejamos este capítulo teórico pero necesario para el siguiente (que será muy útil).

 

 ]]>
https://blog.adrianistan.eu/estadistica-python-calculo-probabilidades-parte-v Sun, 3 Dec 2017 00:00:00 +0000
Easter egg en el comando man de GNU/Linux https://blog.adrianistan.eu/easter-egg-comando-man-gnu-linux https://blog.adrianistan.eu/easter-egg-comando-man-gnu-linux easter egg escondido en el comando man de GNU/Linux. Se trata de una referencia al popular grupo de música sueco, ABBA.



Si conocéis la discografía de ABBA mínimamente seguro que os sonará la canción: Gimme! Gimme! Gimme! (A Man After Midnight)


Pues exactamente ocurre eso. Si ejecutamos man a las 00:30 (after midnight), nos saltará un gimme, gimme, gimme. Sí, es un chsite terrible, pero así es el humor de los programadores de man. Este easter egg fue introducido hace ahora 6 años a raíz de una broma en Twitter.

Ejecutando el easter egg


Existen dos maneras de ver el easter egg. La primera es esperar hasta las 00:30 y entonces ejecutar man. Otra opción es usar faketime para simular en el comando que la hora es 00:30.



Este easter egg ha provocado algún que otro en su interacción con otros programas, por lo que los programadores han considerado quitarlo. No obstante, debido al revuelo causado, se ha reducido su impacto y ahora solamente se muestra si se ejecuta man sin más argumentos.]]>
https://blog.adrianistan.eu/easter-egg-comando-man-gnu-linux Tue, 21 Nov 2017 00:00:00 +0000
Estadística en Python: análisis de datos multidimensionales y regresión lineal (Parte IV) https://blog.adrianistan.eu/estadistica-python-analisis-datos-multidimensionales-regresion-lineal-parte-iv https://blog.adrianistan.eu/estadistica-python-analisis-datos-multidimensionales-regresion-lineal-parte-iv independencia o no). Si existe relación (estan correlacionadas) vamos a construir un modelo de regresión lineal.

Distribución conjunta de frecuencias


En el caso de dos variables, podemos construir una distribución conjunta de frecuencias. Se trata de una tabla de doble entrada donde cada dimensión corresponde a cada variable y el valor de las celdas representa la frecuencia del par. Para ello podemos usar crosstab también (de hecho, su uso original es este).

Ejemplo: En las votaciones a alcalde de la ciudad de Valladolid se presentaban Rafael, Silvia y Olga. Analiza los resultados e informa de quién fue el ganador de las elecciones. ¿Quién fue el candidato favorito en el barrio de La Rondilla?
usuario,voto,distrito
Nerea,Rafael,Centro
Esteban,Olga,Centro
Ismael,Silvia,Centro
Silvia,Rafael,Centro
Susana,Rafael,Centro
Laura,Rafael,Centro
Raquel,Olga,Centro
Eduardo,Olga,La Rondilla
Javier,Silvia,La Rondilla
Saturnino,Rafael,La Rondilla
Segundo,Olga,La Rondilla
Celia,Silvia,La Rondilla
Olga,Rafael,La Rondilla
Casimiro,Silvia,La Rondilla
Rafael,Silvia,La Rondilla

import pandas as pd

df = pd.read_csv("votos.csv")
tab = pd.crosstab(df["voto"],df["distrito"],margins=True) # margins si queremos generar la fila y columna All
total = tab["All"][:-1]
winner = total.idxmax()
print("El ganador de las elecciones fue "+str(winner))

rondilla = tab["La Rondilla"][:-1]
winner_rondilla = rondilla.idxmax()
print("El ganador en el distrito de La Rondilla fue "+str(winner_rondilla))

Como podéis ver, un humando podría haber sacado estas conclusiones observando simplemente la tabla conjunta de frecuencias. ¿Quién tiene más votos en total? Rafael, con 6 en All (la suma de los distritos). ¿Quién ha sacado más votos en La Rondilla? Silvia, con 4 en la columna de La Rondilla. Por último, ¿votó más gente en el Centro o en La Rondilla? Votaron más en La Rondilla (8 votos), que en el Centro (7 votos).

A las frecuencias All se las llama comúnmente distribuciones marginales. Cuando discriminamos las frecuencias a un solo valor de una variable, se habla de distribuciones condicionadas, en este caso hemos usado la distribución de votos condicionada al distrito La Rondilla. Estas distribuciones son univariantes como habréis sospechado.

Gráfico XY o bivariante


Una manera muy útil de observar posibles correlaciones es con el gráfico XY, solamente disponible para distribuciones bivariantes. Cada observación se representa en el plano como un punto. En Matplotlib podemos dibujarlo con scatter.

Ejemplo: Represente el gráfico XY de las variables ingresos y gastos de las familias.
id,ingresos,gastos
1,1000,800
2,1100,885
3,1100,880
4,1200,1000
5,1400,1000
6,900,750
7,600,550
8,2000,1200
9,1200,1000
10,1250,1000
11,3000,2400
12,2200,1700
13,1700,1300
14,1650,1220
15,1825,1500
16,1435,1200
17,980,800
18,1050,800
19,2105,1680
20,1280,1020
21,1590,1272
22,1300,1040
23,1200,1000
24,1110,890
25,850,680

import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv("ingresos_gastos.csv")
plt.scatter(df["ingresos"],df["gastos"])
plt.xlabel("Ingresos")
plt.ylabel("Gastos")
plt.show()

En la imagen podemos ver cada dato representado por un punto. En este ejemplo puede apreciarse como los puntos estan en torno a una línea recta invisible.

Covarianza


Para medir la relación entre dos variables podemos definir la covarianza:

[latex]
cov_{x,y}=\frac{\sum_{i=1}^{N}(x_{i}-\bar{x})(y_{i}-\bar{y})}{N}
[/latex]


Pandas trae el método cov para calcular la matriz de covarianzas. De esta matriz, obtendremos el valor que nos interesa.
import pandas as pd

df = pd.read_csv("ingresos_gastos.csv")

covarianza = df.cov()["ingresos"]["gastos"]

¿Y la covarianza qué nos dice? Por si mismo, bastante poco. Como mucho, si es positivo nos dice que se relacionarían de forma directa y si es negativa de forma inversa. Pero la covarianza está presente en muchas fórmulas.

Coeficiente de correlación lineal de Pearson


[latex]
r_{x,y}=\frac{cov_{x,y}}{s_{x}s_{y}}
[/latex]


Uno de los métodos que usa la covarianza (aunque Pandas lo va a hacer solo) es el coeficiente de correlación lineal de Pearson. Cuanto más se acerque a 1 o -1 más correlacionadas están las variables. Su uso en Pandas es muy similar a la covarianza.
import pandas as pd

df = pd.read_csv("ingresos_gastos.csv")
r = df.corr(method="pearson")["ingresos"]["gastos"]

En este ejemplo concreto, el coeficiente de correlación de Pearson nos da 0.976175. Se trata de un valor lo suficientemente alto como para plantearnos una correlación lineal. Es decir, que pueda ser aproximado por una recta. Si este coeficiente es igual a 1 o -1, se puede decir que una variable es fruto de una transformación lineal de la otra.

Ajuste lineal


Vamos a intentar encontrar un modelo lineal que se ajuste a nuestras observaciones y nos permita hacer predicciones. Esta recta se llamará recta de regresión y se calcula de la siguiente forma:

[latex]
\hat{y}-\bar{y}=\frac{cov_{x,y}}{s_{x}^2}(x-\bar{x})
[/latex]


Usando las funciones de varianza, media y covarianza Pandas no es muy complicado hacer una recta de regresión:
def recta(x):
pendiente = df.cov()["ingresos"]["gastos"]/df["ingresos"].var()
return pendiente*(x-df["ingresos"].mean())+df["gastos"].mean()

Que podemos probar visualmente:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

df = pd.read_csv("ingresos_gastos.csv")

def recta(x):
pendiente = df.cov()["ingresos"]["gastos"]/df["ingresos"].var()
return pendiente*(x-df["ingresos"].mean())+df["gastos"].mean()

line = [recta(x) for x in np.arange(3000)]
plt.scatter(df["ingresos"],df["gastos"])
plt.plot(line)
plt.show()

Sin embargo SciPy ya nos trae un método que calcula la pendiente, la ordenada en el origen, el coeficiente de correlación lineal de Pearson y mucho más en un solo lugar. Es mucho más eficiente, se trata de linregress.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats as ss

df = pd.read_csv("ingresos_gastos.csv")
pendiente, ordenada, pearson, p, error = ss.linregress(df["ingresos"],df["gastos"])

def recta(x):
return pendiente*x + ordenada

recta = np.vectorize(recta)
linea = recta(np.arange(3000))
plt.scatter(df["ingresos"],df["gastos"])
plt.plot(linea)
plt.show()

Además, para calcular los valores del gráfico, he usado vectorize de NumPy, que permite mapear los arrays nativos de NumPy. Más eficiente. Mismo resultado.

La ley de Ohm


¿Quién no ha oído hablar de la Ley de Ohm? Se trata de una ley que relaciona la diferencia de potencial con el amperaje dando lugar a la resistencia. La ley fue enunciada por George Simon Ohm, aunque no exactamente como la conocemos hoy en día. En este ejemplo vamos a deducir de cero la ley de Ohm. Este ejercicio se puede hacer en casa con datos reales si se dispone de un polímetro (dos mejor) y una fuente de alimentación con tensión regulable. Este ejercicio pueden hacerlo niños sin problema.

Olvida todo lo que sepas de la ley de Ohm

Es posible apreciar que en un circuito con una bombilla, si introducimos una pieza cerámica, la intensidad de la bombilla disminuye.

Cuando la corriente no atraviesa la resistencia

Cuando la corriente atraviesa la resistencia

¿Qué ocurre exactamente? ¿Por qué la bombilla tiene menos intensidad en el segundo caso?

Vamos a aislar la resistencia. Ponemos un voltímetro y un amperímetro y vamos cambiando la tensión de entrada. Anotamos la corriente medida en cada caso.


voltaje,intensidad
5,1.01
6,1.19
7,1.4
8,1.62
9,1.81
10,2.01

Podemos intentar hacer un ajuste lineal a estos datos. De modo, que una vez sepamos la intensidad, podamos predecir el voltaje.
import pandas as pd
from scipy import stats as ss

df = pd.read_csv("voltaje_intensidad.csv")
pendiente, ordenada_origen, pearson, p, error = ss.linregress(df["intensidad"],df["voltaje"])

print(pearson) # 0.99969158942375369 es un valor que indica una muy fuerte correlación lineal

Como Pearson nos da un número muy próximo a 1, podemos definir un modelo matemático siguiendo la regresión lineal.

Este modelo matemático se define así:
def voltaje(intensidad):
return intensidad*pendiente + ordenada_origen

Y es lo que se conoce como la Ley de Ohm. En realidad, la ordenada en el origen tiene un valor muy cercano a cero, podemos quitarlo.

Así nos queda un modelo capaz de predecir lel voltaje en base a la intensidad y la pendiente de la recta. Ahora puedes probar cambiando la resistencia y observando que siempre ocurre lo mismo. Tenemos el voltaje por la pendiente del modelo. Este valor de la pendiente es lo que en física se llama resistencia y se mide en ohmios. Así se nos queda entonces la ley de Ohm que todos conocemos:

[latex]
V= IR
[/latex]


En este caso la pendiente equivalía a 4.94 Ω, valor muy cercano a los 5 Ω que dice el fabricante.]]>
https://blog.adrianistan.eu/estadistica-python-analisis-datos-multidimensionales-regresion-lineal-parte-iv Wed, 15 Nov 2017 00:00:00 +0000
Estadística en Python: media, mediana, varianza, percentiles (Parte III) https://blog.adrianistan.eu/estadistica-python-media-mediana-varianza-percentiles-parte-iii https://blog.adrianistan.eu/estadistica-python-media-mediana-varianza-percentiles-parte-iii estadística descriptiva, hoy vamos a ver como calcular ciertas medidas relativas a una variable.


Fichero de ejemplo


alumno,nota
Araceli,9
Manuel,5
Pablo,7
Íñigo,4
Mario,3
Raúl,4
Verónica,6
Darío,10
Laura,4
Silvia,6
Eduardo,2
Susana,8
María,5

Medidas de centralización: media, mediana y moda


La media aritmética se define como la suma de N elementos dividida entre N. Se trata una medida bastante conocida entre la gente, aunque tiene el inconveniente de que es muy susceptible a valores extremos.

La mediana es el valor que dentro del conjunto de datos es menor que el 50% de los datos y mayor que el 50% restante.

La moda es el valor más repetido (solo aplicable a variables discretas).

Para calcular estas medidas, simplemente seleccionamos la variable estadística del DataFrame y usamos los métodos mean, median y mode respectivamente.

Ejemplo: Calcula la media, la mediana y la moda de las notas de los alumnos en el examen
import pandas as pd

df = pd.read_csv("notas.csv")

media = df["nota"].mean()
mediana = df["nota"].median()
moda = df["nota"].mode()
print("""
Media: %d
Mediana: %d
Moda: %d
""" % (media,mediana,moda))

Medidas de posición: cuartiles y percentiles


El concepto es igual al de mediana, salvo que aquí la división ya no es en el 50%. El 25% de las observaciones es menor que el primer cuartil. Los cuartiles abarcan el 25%, 50% y 75% de las observaciones. Los percentiles son una generalización con cualquier porcentaje.

Ejemplo: ¿Cuál es la nota que tiene como mínimo el 10% más nota de la clase?

Este enunciado nos pide calcular el percentil 90.

Usamos quantile y el porcentaje.
p90 = df["nota"].quantile(0.9)

El resultado es que el 10% con más nota de la clase ha sacado un 8,8 como mínimo. Mencionar que existen distintos tipos de interpolación para este cálculo. En la referencia podemos consultar cual nos conviene más.

Medidas de dispersión: desviación típica, rango, IQR, coeficiente de variación


La desviación típica mide la dispersión de los datos respecto a la media. Se trata de la raíz cuadrada de la varianza, que en sí misma no es una medida de dispersión. Para calcular la desviación típica usamos std y var para la varianza. (ddof=0 es necesario si quieres seguir la definición de desviación típica y varianza de algunas bibliografías, la razón es que hay un parámetro de ajuste que Pandas pone a 1, pero otras librerías ponen a 0). En Excel es la diferencia que hay entre DESVEST.M (ddof=1) y DESVEST.P (ddof=0).
std = df["nota"].std(ddof=0)
var = df["nota"].var(ddof=0)
assert(np.sqrt(var) == std)

El rango es la diferencia entre el máximo y el mínimo y el rango intercuartílico o IQR es la diferencia entre el tercer y el primer cuartil.
rango = df["nota"].max() - df["nota"].min()
iqr = df["nota"].quantile(0.75) - df["nota"].quantile(0.25)

El coeficiente de variación es una medida que sirve para comparar entre dos muestras, cuál varía más y cuál es más estable. Es una simple división, de la desviación típica sobre la media, sin embargo, SciPy nos ofrece una función ya preparada.
import pandas as pd
import scipy.stats as ss

df = pd.read_csv("notas.csv")

cv = df["nota"].std(ddof=0) / df["nota"].mean()
cv2 = ss.variation(df["nota"])
assert(cv == cv2)

Medidas de asimetría


Para saber si los datos estan repartidos de forma simétrica existen varios coeficientes: Pearson, Fisher, Bowley-Yule, etc

Para no liarnos demasiado, podemos usar la función skew de SciPy.
asimetria = ss.skew(df["nota"])

Para valores cercanos a 0, la variable es simétrica. Si es positiva tiene cola a la derecha y si es negativa tiene cola a la izquierda.

Y con esto hemos visto los datos que se pueden extraer de una sola variable.]]>
https://blog.adrianistan.eu/estadistica-python-media-mediana-varianza-percentiles-parte-iii Sat, 4 Nov 2017 00:00:00 +0000
Estadística en Python: manipulando datos en Pandas (Parte II) https://blog.adrianistan.eu/estadistica-python-manipulando-datos-pandas-parte-ii https://blog.adrianistan.eu/estadistica-python-manipulando-datos-pandas-parte-ii Pandas. Imaginemos que tenemos una tabla con datos de estatura y peso. Podemos generar una nueva columna con el índice de masa corporal. Veamos como se puede hacer

Fichero de ejemplo


nombre,peso,altura
Basilio,67,1.5
Arturo,80,1.7
Cristina,50,1.4
Alfonso,100,2.0
Nerea,70,1.8

Seleccionado datos


A veces queremos quedarnos con parte de los datos que cumplen una condición. Hay varias maneras de hacerlo.

Ejemplo: Quédate con los datos de Nombre y Altura de los pacientes con peso igual o superior a 70
import pandas as pd

df = pd.read_csv("pesos.csv")

tab = df.loc[df["peso"] >= 70,["nombre","altura"]]

import pandas as pd

df = pd.read_csv("pesos.csv")

tab = df[df["peso"] >= 70][["nombre","altura"]]

import pandas as pd

df = pd.read_csv("pesos.csv")

tab = df.query("peso >= 70")[["nombre","altura"]]

Cualquiera de estos tres métodos pueden usarse indistintamente.


Apply


Apply es una función de DataFrame muy potente que permite aplicar una función a todos las columnas o a todas las filas.

Ejemplo: Calcule el IMC (Índice de Masa Corporal) con los valores de la tabla
import pandas as pd

df = pd.read_csv("pesos.csv")

def imc(x):
return x["peso"]/(x["altura"]**2)

df["imc"] = df.apply(imc,axis=1)
print(df)


Drop


¿Qué pasa si queremos borrar algún dato o columna?

Si queremos borrar columnas:
df.drop(["peso"],axis=1)

Si queremos borrar datos:
df.drop(ELEMENTOS,inplace=True)

# puede haber una condicion compleja
df.drop(df[df["altura"] < 1.6].index,axis=0,inplace=True)

Construyendo el DataFrame a mano


Normalmente los datos los leeremos de algún archivo o base de datos (read_csv, read_json, read_html, read_sql, read_hdf, read_msgpack, read_excel, read_pickle, read_gbq, read_parquet, ...) pero puede darse el caso de que necesitemos ingresar los datos manualmente. El constructor de DataFrame admite diccionarios, arrays de NumPy y arrays de tuplas.
import pandas as pd
import numpy as np

datos_dict = {"peso": [50,60], "altura": [1.6,1.7]}
df = pd.DataFrame(data=datos_dict)

datos_numpy = np.array([[50,1.6],[70,1.7]])
df = pd.DataFrame(data=datos_numpy,columns=("peso","altura"))

datos_tuple = [(50,1.6),(70,1.7)]
df = pd.DataFrame(data=datos_tuple,columns=("peso","altura"))

 

Concatenar DataFrames


Si tenemos varios DataFrames de características similares (columnas iguales) podemos unirlos. Hay que tener cuidado con los índices. Si el tema de los índices te da igual, usa ignore_index.
import pandas as pd

df = pd.read_csv("pesos.csv")

datos1 = [("Emilio",78,1.6),("Rosa",80,1.8)]
df1 = pd.DataFrame(data=datos1,columns=("nombre","peso","altura"))

datos2 = [("Agustín",75,1.6),("Ana",90,1.8)]
df2 = pd.DataFrame(data=datos2,columns=("nombre","peso","altura"))

df = pd.concat([df,df1,df2],ignore_index=True)


Join DataFrames


Si vienes del mundo SQL quizá te suene el tema de los JOIN. En Pandas existe un potente sistema de join, similar al usado en las bases de datos SQL más importantes y con excelente rendimiento. Pandas soporta joins de tipo LEFT, RIGHT, OUTER e INNER.
import pandas as pd

df = pd.read_csv("pesos.csv")

otros_datos = [("Nerea",19),("Irena",21)]
tab_edad = pd.DataFrame(data=otros_datos,columns=("nombre","edad"))

tab_right = pd.merge(df,tab_edad,on="nombre",how="right")
tab_left = pd.merge(df,tab_edad,on="nombre",how="left")
tab_inner = pd.merge(df,tab_edad,on="nombre",how="inner")
tab_outer = pd.merge(df,tab_edad,on="nombre",how="outer")

Con esto ya sabemos lo básico para manejarnos con DataFrames de Pandas

 ]]>
https://blog.adrianistan.eu/estadistica-python-manipulando-datos-pandas-parte-ii Wed, 1 Nov 2017 00:00:00 +0000
Estadística en Python: Pandas, NumPy, SciPy (Parte I) https://blog.adrianistan.eu/estadistica-python-pandas-numpy-scipy-parte-i https://blog.adrianistan.eu/estadistica-python-pandas-numpy-scipy-parte-i Python 3, NumPy, Pandas, SciPy y Matplotlib entre otras.

¿Por qué Python para estadística?


Python se ha convertido en uno de los lenguajes más usados en la comunidad de data science. Se trata de un lenguaje cómodo para los matemáticos, sobre el que es fácil iterar y cuenta con unas librerías muy maduras.

Instalando el stack estadístico en Python


Yo voy a usar Python 3. Tanto si estamos en Windows, macOS o Linux podemos instalar NumPy, SciPy, Pandas y Matplotlib con este simple comando
pip3 install numpy scipy matplotlib pandas --user

¿Para qué sirve cada cosa?


NumPy sirve para realizar cálculos numéricos con matrices de forma sencilla y eficiente y tanto SciPy como Pandas la usan de forma interna. NumPy es la base del stack científico de Python.

SciPy es una colección de módulos dedicados a diversas áreas científicas. Nosotros usaremos principalmente el módulo stats.

Pandas es una librería que permite manipular grandes conjuntos de datos en tablas con facilidad. Además permite importar y exportar esos datos.

Matplotlib permite realizar gráficos y diagramas con facilidad, mostrarlos en pantalla o guardarlos a archivos.

Estadística descriptiva


Vamos a comenzar con la parte de la estadística que trata de darnos un resumen del conjunto de datos.

Para ello vamos a definir el concepto de variable estadística como la magnitud o cualidad de los individuos que queremos medir (estatura, calificaciones en el examen, dinero en la cuenta,...). Las variables pueden ser cualitativas o cuantitativas y dentro de las cuantitativas pueden ser continuas y discretas.

Fichero de ejemplo


En este post voy a usar este fichero para los ejemplos (guardalo como notas.csv).
alumno,nota
Araceli,9
Manuel,5
Pablo,7
Íñigo,4
Mario,3
Raúl,4
Verónica,6
Darío,10
Laura,4
Silvia,6
Eduardo,2
Susana,8
María,5

Cargando datos


Para cargar datos y manipular los datos usamos Pandas. Pandas permite cargar datos de distintos formatos: CSV, JSON, HTML, HDF5, SQL, msgpack, Excel, ...

Para estos ejemplos usaremos CSV, valores separados por comas:
import pandas as pd

df = pd.read_csv("notas.csv")

df es un objeto de tipo DataFrame. Es la base de Pandas y como veremos, se trata de un tipo de dato muy flexible y muy fácil de usar. Si ahora hacemos print a df obtenemos algo así:


Tabla de frecuencias


Si tenemos una variable discreta o cualitativa una cosa habitual que se suele hacer es construir la tabla de frecuencias. Con ella sabemos cuantas veces se repite el valor de la variable. Para crear tablas de frecuencia usamos crosstab. En index indicamos la variable que queremos contar y en columns especificaos el nombre de la columna de salida. crosstab devuelve otro DataFrame independiente.



Ejemplo: ¿Cuántos alumnos han sacado un 5 en el examen?
import pandas as pd

# Leer datos
df = pd.read_csv("notas.csv")

# Generar tabla de frecuencias
tab = pd.crosstab(index=df["nota"],columns="frecuencia")
print(tab)

# Buscar el elemento 5 (el elemento que cumple la condición de que su índice es igual a 5)
fila = tab.loc[tab.index == 5]
# Obtenemos el valor "frecuencia" de la fila
x = fila["frecuencia"]
x = int(x)
print("%d alumnos han sacado un 5" % x)

Ejemplo: ¿Cuántos alumnos han aprobado (sacar 5 o más)?
import pandas as pd

df = pd.read_csv("notas.csv")

tab = pd.crosstab(index=df["nota"],columns="frecuencia")
print(tab)

x = tab.loc[tab.index >= 5]["frecuencia"].sum()
x = int(x)
print("%d alumnos han aprobado el examen" % x)

En estos ejemplo usamos loc para devolver las filas que cumplan la condición descrita entre corchetes. En el último ejemplo como el resultado son varias filas, nos quedamos con la parte de las frecuencias y sumamos todo, para así obtener el resultado final.

Diagrama de sectores


Una forma sencilla de visualizar datos que ya han sido pasados por la tabla de frecuencias es el diagrama de sectores. Usando Matplotlib podemos generar gráficos en los que podemos personalizar todo, pero cuyo uso básico es extremadamente simple.
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv("notas.csv")

tab = pd.crosstab(index=df["nota"],columns="frecuencia")

plt.pie(tab,labels=tab.index)
plt.xlabel("Notas del examen")
plt.savefig("notas.png")

Gráfica generada por Matplotlib

Ejemplo: Haz un diagrama de sectores donde se vea claramente el porcentaje de aprobados frente al de suspensos
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv("notas.csv")

tab = pd.crosstab(index=df["nota"],columns="frecuencia")

aprobados = tab.loc[tab.index >= 5]["frecuencia"].sum()
suspensos = tab.loc[tab.index < 5]["frecuencia"].sum()

data = np.array([aprobados,suspensos])
plt.pie(data,labels=["Aprobados","Suspensos"],autopct="%1.1f%%")
plt.xlabel("Notas del examen")
plt.savefig("notas.png")

Destacar que aquí hemos usado np.array para crear un array de NumPy en vez de una lista nativa de Python.

Diagrama de barras


De forma similar es posible generar un diagrama de barras:

Ejemplo: Genere un diagrama de barras de las notas obtenidas por los alumnos en el examen
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv("notas.csv")

tab = pd.crosstab(index=df["nota"],columns="frecuencia")

plt.bar(tab.index,tab["frecuencia"])
plt.xlabel("Notas del examen")
plt.savefig("notas.png")





 

 ]]>
https://blog.adrianistan.eu/estadistica-python-pandas-numpy-scipy-parte-i Tue, 31 Oct 2017 00:00:00 +0000
Interfaces gráficas multiplataforma en C# con .NET Core y Avalonia https://blog.adrianistan.eu/interfaces-graficas-multiplataformas-c-net-core-avalonia https://blog.adrianistan.eu/interfaces-graficas-multiplataformas-c-net-core-avalonia .NET se volvía multiplataforma, con soporte a macOS y GNU/Linux. Y además se volvía software libre, con licencia MIT y con un desarrollo transparente donde cualquiera puede proponer mejoras, subir parches, etc...

Esto posibilita tener soporte de primer nivel para uno de los lenguajes mejor diseñados actualmente, C#, que hasta entonces tenía un soporte de segunda en Linux, a través de Mono.

Inicialmente el soporte de Microsoft a .NET Core abarca: aplicaciones de consola, aplicaciones web ASP.NET y UWP. Mucha gente se desanimó por no tener WinForms o WPF en .NET Core. Sin embargo eso no quiera decir que no se puedan hacer interfaces gráficas en .NET Core. Aquí os presento la librería Avalonia, que aunque está en beta todavía, funciona sorprendentemente bien y en un futuro dará mucho de que hablar.

Avalonia funciona en Windows, macOS, GNU/Linux, Android e iOS. Usa XAML para las interfaces y su diseño se asemeja a WPF, aunque se han hecho cambios aprovechando características más potentes de C#. Avalonia no es WPF multiplataforma, es mejor.

Creando un proyecto


En primer lugar, tenemos que instalar .NET Core 2.0. Se descarga en la página oficial. A continuación comprobamos que se ha instalado correctamente con:
dotnet --version

Creamos ahora un nuevo proyecto de tipo consola con new
dotnet new console -o GitHubRepos

Si todo va bien nos saldrá algo parecido a esto:

Se nos habrán generado una carpeta obj y dos archivos: Program.cs y GitHubRepos.csproj. El primero es el punto de entrada de nuestro programa, el otro es el fichero de proyecto de C#.

Vamos a probar que todo está en orden compilando el proyecto.
dotnet run

Añadiendo Avalonia


Vamos a añadir ahora Avalonia. Instalar dependencias antes en C# era algo complicado. Ahora es muy sencillo, gracias a la integración de dotnet con NuGet.
dotnet add package Avalonia
dotnet add package Avalonia.Win32
dotnet add package Avalonia.Skia.Desktop
dotnet add package Avalonia.Gtk3

¡Ya no tenemos que hacer nada más! Nuestra aplicación será compatible ya con Windows y GNU/Linux. Para el resto de plataformas, hay más paquetes disponibles en NuGet, sin embargo desconozco su grado de funcionamiento. Solo he probado la librería en Windows 7, Windows 10, Ubuntu y Debian.

Program.cs


Program.cs define el punto de entrada a la aplicación. Aquí en una aplicación Avalonia podemos dejarlo tan simple como esto:
using System;
using Avalonia;

namespace GitHubRepos
{
class Program
{
static void Main(string[] args)
{
AppBuilder
.Configure<App>()
.UsePlatformDetect()
.Start<MainWindow>();
}
}
}

Básicamente viene a decir que arranque una aplicación Avalonia definida en la clase App con la ventana MainWindow. A continuación vamos a definir esas clases.

App.cs y App.xaml


En Avalonia tenemos que definir una clase que represente a la aplicación, normalmente la llamaremos App. Crea un fichero llamado App.cs en la misma carpeta de Program.cs, con un contenido tan simple como este:
using Avalonia;
using Avalonia.Markup.Xaml;

namespace GitHubRepos
{
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
}
}

Lo que hacemos aquí es pedir al intérprete de XAML que lea App.xaml simplemente y por lo demás, es una mera clase hija de Application.

El fichero App.xaml contiene definiciones XAML que se aplican a todo el programa, como el estilo visual:
<Application xmlns="https://github.com/avaloniaui">
<Application.Styles>
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
</Application.Styles>
</Application>

MainWindow.cs y MainWindow.xaml


Ahora nos toca definir una clase para la ventana principal de la aplicación. El código para empezar es extremadamente simple también:
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;

namespace GitHubRepos
{
public class MainWindow : Window
{
public MainWindow()
{
Initialize();
}
private void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
}
}

Aquí hacemos lo mismo que con App.cs, mandamos cargar el fichero XAML. Este fichero XAML contiene los widgets que va a llevar la ventana. Parecido a HTML, QML de Qt, Glade de GTK o FXML de Java o XUL de Mozilla.
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GitHub Repositories">
<StackPanel HorizontalAlignment="Center">
<Button Content="¡Hola mundo!"></Button>
</StackPanel>
</Window>

GitHubRepos.csproj


Antes de poder compilar es necesario modificar el fichero de proyecto para incluir los ficheros XAML en el binario. Se trata de añadir un nuevo ItemGroup y dentro de él un EmbeddedResource.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.5.1" />
<PackageReference Include="Avalonia.Skia.Desktop" Version="0.5.1" />
<PackageReference Include="Avalonia.Win32" Version="0.5.1" />
</ItemGroup>
<!-- HE AÑADIDO ESTO -->
<ItemGroup>
<EmbeddedResource Include="**\*.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<!-- HASTA AQUÍ -->
</Project>

Ahora ya podemos compilar con dotnet run.

Nos deberá salir algo como esto:

Añadiendo clicks


Vamos a darle vidilla a la aplicación añadiendo eventos. Para ello primero hay que darle un nombre al botón, un ID. Usamos Name en XAML. También usaremos un TextBlock para representar texto.
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GitHub Repositories">
<StackPanel HorizontalAlignment="Center">
<Button Name="lanzar" Content="Lanzar dado"></Button>
<TextBlock Name="resultado">No has lanzado el dado todavía</TextBlock>
</StackPanel>
</Window>

Ahora en MainWindow.cs, en el constructor, podemos obtener referencia a los objetos XAML con Find.
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using System;

namespace GitHubRepos
{
public class MainWindow : Window
{
Button lanzar;
TextBlock resultado;
public MainWindow()
{
Initialize();

lanzar = this.Find<Button>("lanzar");
lanzar.Click += LanzarDado;

resultado = this.Find<TextBlock>("resultado");
}
private void Initialize()
{
AvaloniaXamlLoader.Load(this);
}

private void LanzarDado(object sender, RoutedEventArgs e)
{
var r = new Random().Next(0,6) + 1;
resultado.Text = $"Dado: {r}";
}
}
}

Y ya estaría. También comentar que la función LanzarDado puede ser async si nos conviene. A partir de ahora ya puedes sumergirte en el código de Avalonia (¡porque documentación todavía no hay!) y experimentar por tu cuenta.

Un ejemplo real, GitHubRepos


Ahora os voy a enseñar un ejemplo real de la comodidad que supone usar Avalononia con .NET Core. He aquí un pequeño programa que obtiene la lista de repositorios de un usuario y los muestra por pantalla.
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Collections.Generic;
using System.Runtime.Serialization.Json;
using System.Linq;

namespace GitHubRepos
{
public class MainWindow : Window
{
Button refresh;
TextBox username;
TextBlock status;
ListBox repos;
public MainWindow()
{
Initialize();

refresh = this.Find<Button>("refresh");
refresh.Click += RefreshList;

username = this.Find<TextBox>("username");
status = this.Find<TextBlock>("status");
repos = this.Find<ListBox>("repos");
}
private void Initialize()
{
AvaloniaXamlLoader.Load(this);
}

private async void RefreshList(object sender, RoutedEventArgs e)
{
var user = username.Text;
status.Text = $"Obteniendo repositorios de {user}";
var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
client.DefaultRequestHeaders.Add("User-Agent", "GitHubRepos - Avalonia");
try{
var downloadTask = client.GetStreamAsync($"https://api.github.com/users/{user}/repos");

var serializer = new DataContractJsonSerializer(typeof(List<GitHubRepo>));
var repoList = serializer.ReadObject(await downloadTask) as List<GitHubRepo>;

repos.Items = repoList.OrderByDescending(t => t.Stars).Select(repo => {
var item = new ListBoxItem();
item.Content=$"{repo.Name} - {repo.Language} - {repo.Stars}";
return item;
});
status.Text = $"Repositorios de {user} cargados";
}catch(HttpRequestException){
status.Text = "Hubo un error en la petición HTTP";
}catch(System.Runtime.Serialization.SerializationException){
status.Text = "El fichero JSON es incorrecto";
}
}
}
}

Y este es su correspondiente XAML:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GitHub Repositories"
Width="300"
Height="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition />
</Grid.RowDefinitions>

<StackPanel Grid.Row="0" HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal">
<TextBox Name="username">aarroyoc</TextBox>
<Button Name="refresh" Content="Actualizar"></Button>
</StackPanel>
<TextBlock Name="status"></TextBlock>
</StackPanel>
<ListBox Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Visible" SelectionMode="Single" Name="repos"></ListBox>
</Grid>
</Window>

Adicionalmente, he usado una clase extra para serializar el JSON.
using System.Runtime.Serialization;

namespace GitHubRepos{
[DataContract(Name="repo")]
public class GitHubRepo{

[DataMember(Name="name")]
public string Name;

[DataMember(Name="language")]
public string Language;
[DataMember(Name="stargazers_count")]
public int Stars;
}
}

El resultado es bastante satisfactorio:



 ]]>
https://blog.adrianistan.eu/interfaces-graficas-multiplataformas-c-net-core-avalonia Fri, 13 Oct 2017 00:00:00 +0000
Diversión con punteros en Rust: bloques unsafe https://blog.adrianistan.eu/diversion-punteros-rust-bloques-unsafe https://blog.adrianistan.eu/diversion-punteros-rust-bloques-unsafe Diversión con Punteros.

Hoy vamos a hablar de un tema apasionante. Los bloques unsafe de Rust así como de los raw pointers. ¿Has programado en C? Si es así, los raw pointers de Rust son exactamente iguales a los punteros de C. Si no sabes lo que es un puntero, te lo explico.

¿Qué es un puntero?


Un puntero es un tipo de variable que en vez de almacenar el dato, almacena la posición en memoria donde se encuentra el dato.

En lenguajes en lo que todo es un objeto (como Python), nunca trabajamos con los datos reales, sino siempre con punteros, pero el lenguaje lo gestiona de forma automática. En lenguajes más cercanos al metal por contra sí que suele dejarse esta opción.

Nuestro puntero es la variable que contiene 0x00ffbea0 y que apunta a la dirección de memoria donde se encuentra el dato

Rust tiene distintos tipos de punteros: Box, Rc, Arc, Vec, ... Estos punteros son transparentes al usuario y muchas veces no tenemos que preocuparnos de su funcionamiento. Sin embargo, muchas veces queremos tener un control más fino del ordenador. Esto lo lograremos con los raw pointers. Se trata de punteros con los que podemos operar y desreferenciar.

Crear raw pointers no supone ningún problema, pero acceder al valor al que apuntan en memoria sí. Podría darse el caso de que no existiera valor alguno o hubiese sido modificado. En los punteros normales, el compilador de Rust se encarga de que no ocurra, pero en los raw pointers el compilador no lo puede saber. Es por ello, que para acceder al valor de un raw_pointer necesitas usar bloques de código unsafe, código inseguro en Rust.

Creando un raw pointer


Lo primero que hay que saber es que existen dos tipos de raw pointers en Rust, los mutables y los inmutables.

Los punteros inmutables tienen el tipo *const T y los mutables el tipo *mut T.
fn main(){ 
let numero = 5;
let puntero = &numero as *const i32;
println!("Address: 0x{:x}", puntero as usize);
println!("Value: {}",numero);
}

 

En este ejemplo, creamos una variable con valor 5 y le creamos un puntero, que contiene la dirección de memoria donde está el dato. Para representar la dirección de memoria se suele usar la notación hexadecimal. Antes debemos hacer un cast a usize. usize es un tipo en Rust cuyo tamaño depende de la máquina en cuestión (32 bits en máquinas de 32 bits, 64 bits en máquinas de 64 bits), siendo usado para representar direcciones de memoria, puesto que tiene el tamaño exacto para almacenarlas.

Hasta ahora no hemos usado unsafe. Esto es porque no hemos probado a acceder al valor. Para acceder a un valor, o deferrenciar, usamos el operador *.


fn main(){
let numero = 5;
let puntero = &numero as *const i32;
println!("Address: 0x{:x}", puntero as usize);
println!("Value: {}",numero);
unsafe{
println!("Value: {}",*puntero);
}
}


Ambos prints imprimen 5. Hasta aquí no hemos hecho nada interesante con punteros. Todo esto era más fácil hacerlo sin punteros. Veamos alguna aplicación práctica de los punteros.

Modificar datos sin control


Si te pongo este código, ¿me puedes decir que salida dará?


fn main(){
let mut numero = 5;
let puntero = &mut numero as *mut i32;
println!("Address: 0x{:x}", puntero as usize);
unsafe{
scary_things(puntero);
}
println!("Value: {}",numero);
}


Uno podría pensar que como en ningún sitio reasignamos numero, y numero es una variable de tipo i32, que implementa Copy, es imposible modificarle el valor. Y eso es correcto en las reglas de Rust normales, pero en unsafe, podemos pasar el puntero hacia otras funciones (los punteros también son Copy, ocupan el tamaño de un usize). Y esas funciones pueden modificar los datos en memoria a su antojo. Así, pues, la respuesta correcta es indeterminado. Hacer esto es una mala práctica, pero en ocasiones se puede ganar rendimiento o interactuar con una librería de C usando estos métodos.


unsafe fn scary_things(p: *mut i32) {
*p = 12;
}

fn main(){
let mut numero = 5;
let puntero = &mut numero as *mut i32;
println!("Address: 0x{:x}", puntero as usize);
unsafe{
scary_things(puntero);
}
println!("Value: {}",numero);
}


Esta sería la versión completa del programa.

Aritmética de punteros


Una vez tenemos acceso a memoria podemos acceder a cualquier parte de memoria (en sistemas operativos modernos, memoria que esté asignada a nuestro programa). En C simplemente podíamos operar con el puntero como si fuese un número, con sumas, restas, multiplicaciones y divisiones. Estas operaciones eran un poco traicioneras porque eran relativas a la máquina. Sumar 1 a un puntero de int equivalía en realidad a sumar 4 al puntero en una máquina de 32 bits. En Rust esto no se permite, pero a cambio tenemos métodos que nos permiten hacer lo mismo. El más importante es offset. El offset nos permite desplazarnos por la memoria hacia delante y hacia atrás.


fn main(){
let mut numero = 5;
let b = 35;
let c = 42;
let puntero = &mut numero as *mut i32;
println!("Address: 0x{:x}", puntero as usize);
unsafe{
*puntero.offset(1) = 120;
}
println!("Value: {}",numero);
println!("Value: {}",b);
println!("Value: {}",c);
}


Este programa parte de una suposición para funcionar. Y es que numero, b y c están contiguos en memoria y en el mismo orden que como los que he declarado. En el puntero tenemos la dirección a numero, es decir, a 5. Sin embargo, si avanzamos en la memoria una posición llegaremos a al 35, y si avanzamos dos, llegamos a 42. Entonces podemos editar el contenido de esa memoria. Al acabar el programa b vale 120. Hemos modificado el valor y ni siquiera b se había declarado como mut. Esto os recuerdo, usadlo solo en casos excepcionales.

Reservar memoria al estilo C


Estas cosas empiezan a tener utilidad en cuanto podemos usar memoria dinámica al estilo C, es decir, con malloc, free, calloc y compañía. El equivalente a malloc en Rust suele ser Box o Vec y es lo que debemos usar. Box sabe que espacio en memoria tiene que reservar de antemano y Vec ya está preparado para ir creciendo de forma segura.


extern crate libc;

use libc::{malloc,free};
use std::mem::size_of;

fn main(){
unsafe {
let puntero = malloc(10*size_of::<i32>()) as *mut i32;
for i in 0..10 {
*puntero.offset(i) = 42;
}
for i in 0..10{
println!("{}",*puntero.offset(i));
}
free(puntero as *mut libc::c_void);
}
}


En este caso usamos malloc como en C para generar un array de forma dinámica con espacio suficiente para almacenar 10 elementos de tamaño i32.

Con esto ya hemos visto el lado oscuro de Rust, la parte unsafe. No hemos visto como llamar a funciones de C directamente, algo que también require usar bloques unsafe.

Como vemos, Rust no nos limita a la hora de hacer cualquier cosa que queramos, solo que nos reduce a los bloques unsafe, para que nosotros mismos tengamos mejor control de lo que hagamos.]]>
https://blog.adrianistan.eu/diversion-punteros-rust-bloques-unsafe Thu, 12 Oct 2017 00:00:00 +0000
Cheatsheet de contenedores de Rust https://blog.adrianistan.eu/cheatsheet-contenedores-rust https://blog.adrianistan.eu/cheatsheet-contenedores-rust cheatsheet donde podemos ver como funciona cada contenedor de datos en Rust. Es un recurso muy interesante bajo licencia Creative Commons 4.0 BY, que si bien no es necesario para poder programar en Rust, puede sernos de utilidad cuando trabajemos a bajo nivel con el lenguaje.

Enlace original]]>
https://blog.adrianistan.eu/cheatsheet-contenedores-rust Tue, 10 Oct 2017 00:00:00 +0000
Crear ventanas y botones en Rust con GTK https://blog.adrianistan.eu/crear-ventanas-botones-rust-gtk https://blog.adrianistan.eu/crear-ventanas-botones-rust-gtk
Hoy vamos a introducir una manera de crear interfaces gráficas de usuario (GUI) con Rust. Para ello usaremos GTK. GTK funciona sobre GNU/Linux, macOS y Windows (aunque es un poco más complicado de lo que debiera).

Para ello usaremos la fantástica crate gtk del proyecto Gtk-rs.

Instalando gtk


Añade al fichero Cargo.toml las siguiente líneas:


[dependencies]
gtk = "0.2.0"


Y ejecuta cargo build.

Creando una ventana


Lo primero que vamos a hacer es crear una ventana.

Importamos gtk e iniciamos GTK.


extern crate gtk;

use gtk::prelude::*;

fn main() {
if gtk::init().is_err() {
println!("Failed to initialize GTK.");
return;
}


Ahora podemos crear una ventana. GtkWindow es un struct con métodos asociados, por lo que podemos ajustar ciertos parámetros. En este código parece que estamos usando orientación a objetos, pero recuerdo que Rust técnicamente no tiene clases ni herencia.


let window = gtk::Window::new(gtk::WindowType::Toplevel);

window.set_title("Adrianistan - GTK - Rust");
window.set_border_width(10);
window.set_position(gtk::WindowPosition::Center);
window.set_default_size(350, 70);


Ahora vamos a añadir un evento para que cuando se pulse la X en nuestra ventana, se cierre el programa. Para ello tenemos que entender como funciona GTK. Cuando programamos en GTK lo que hacemos es configurar la aplicación y posteriormente ceder la ejecución a una función gtk_main que procesa todos los eventos según haya sido configurado. Para configurar los eventos usaremos callbacks, que en Rust se pueden implementar con closures. Las funciones que en GTK nos permiten conectar eventos siempre tienen el prefijo connect.


window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(false)
});


Ahora vamos a mostrar la ventana. Por defecto en GTK todos los widgets (todo lo que se muestra en pantalla) está oculto. Para mostrar todos los widgets que se han añadido a la ventana se suele usar show_all, que va a haciendo show de forma recursiva. Por último, le damos el control de la aplicación a gtk::main.


window.show_all();
gtk::main();
}


Una vez hecho esto, si compilamos con cargo run ya deberíamos ver una preciosa ventana GTK.

Y por supuesto, si pulsamos la X, la aplicación se cierra.

Layouts y botones en GTK


Vamos ahora a añadir dos cosas: un botón y un label que nos de un número del dado. Para poner varios elementos en una aplicación GTK es recomendable usar un layout. Voy a usar el layout Box, que permite agrupar los widgets de forma vertical u horizontal. Para los números aleatorios voy a usar la crate rand. El código final sería así:


extern crate gtk;
extern crate rand;

use gtk::prelude::*;
use rand::distributions::{IndependentSample, Range};

fn pick(a: i32, b: i32) -> i32 {
let between = Range::new(a, b);
let mut rng = rand::thread_rng();
between.ind_sample(&mut rng)
}

fn main() {
if gtk::init().is_err() {
println!("Failed to initialize GTK.");
return;
}

let window = gtk::Window::new(gtk::WindowType::Toplevel);

window.set_title("Adrianistán - GTK - Rust");
window.set_border_width(10);
window.set_position(gtk::WindowPosition::Center);
window.set_default_size(350, 70);

window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(false)
});

let vbox = gtk::Box::new(gtk::Orientation::Vertical,10);

let button = gtk::Button::new_with_label("Tirar el dado");

let label = gtk::Label::new("No has tirado el dado todavía");

let l = label.clone();
button.connect_clicked(move |_| {
let dado = pick(1,7);
let text: String = format!("Dado: {}",dado);
l.set_text(text.as_str());
});

vbox.add(&button);
vbox.add(&label);
window.add(&vbox);

window.show_all();
gtk::main();
}


Aquí pasan varias cosas interesantes. La primera es que usamos add para ir añadiendo de forma jerárquica los widgets. Debajo de Window está Box y debajo de Box tenemos Button y Label.

Por otra parte, vemos que la función asociada al evento del click de los botones es connect_clicked. Bien, en este ejemplo he introducido algo importante. Estoy modificando un widget desde un evento relacionado a otro widget. ¿Esto como se lleva con las reglas de propiedad/ownership de Rust? Bastante mal. Rust no puede saber si cuando se ejecuta el evento tenemos acceso al widget en cuestión que vamos a modificar. Afortunadamente, la API de gtk-rs ha sido diseñada con esto en cuenta y simplemente podemos hacer una llamada a clone para obtener otra referencia al objeto, que podemos pasar al closure (con move). Este clonado no lo es tal, sino que hace uso de Rc. Simplemente se nos presenta de forma transparente.

El ejemplo final: dibujando con Cairo


En este último ejemplo voy a añadir un widget donde se podrá ver la cara del dado que ha salido. Para ello uso un GtkDrawingArea, que permite usar la API de Cairo.


extern crate gtk;
extern crate rand;

use gtk::prelude::*;
use rand::distributions::{IndependentSample, Range};
use std::rc::Rc;
use std::cell::Cell;

fn pick(a: i32, b: i32) -> i32 {
let between = Range::new(a, b);
let mut rng = rand::thread_rng();
between.ind_sample(&mut rng)
}

fn main() {
if gtk::init().is_err() {
println!("Failed to initialize GTK.");
return;
}

let window = gtk::Window::new(gtk::WindowType::Toplevel);

window.set_title("Adrianistán - GTK - Rust");
window.set_border_width(10);
window.set_position(gtk::WindowPosition::Center);
window.set_default_size(350, 70);

window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(false)
});

let vbox = gtk::Box::new(gtk::Orientation::Vertical,10);

let button = gtk::Button::new_with_label("Tirar el dado");

let label = gtk::Label::new("No has tirado el dado todavía");

let r = Rc::new(Cell::new(0));

let random = r.clone();
let drawingarea = gtk::DrawingArea::new();
drawingarea.set_size_request(300,300);
drawingarea.connect_draw(move |widget,cr|{
let width: f64 = widget.get_allocated_width() as f64;
let height: f64 = widget.get_allocated_height() as f64;
cr.rectangle(0.0,0.0,width,height);
cr.set_source_rgb(1.0,1.0,1.0);
cr.fill();

cr.set_source_rgb(0.,0.,0.);
let random = random.get();
if random == 1 || random == 3 || random == 5{
cr.arc(width/2.0,height/2.,height/10.,0.0,2.0*std::f64::consts::PI);
}
cr.fill();
if random == 2 || random == 3 || random == 4 || random == 5 || random == 6 {
cr.arc(width/4.,height/4.,height/10.,0.0,2.0*std::f64::consts::PI);
cr.arc(3.*width/4.,3.*height/4.,height/10.,0.0,2.0*std::f64::consts::PI);
}
cr.fill();

if random == 4 || random == 5 || random == 6 {
cr.arc(3.*width/4.,height/4.,height/10.,0.0,2.0*std::f64::consts::PI);
cr.arc(width/4.,3.*height/4.,height/10.,0.0,2.0*std::f64::consts::PI);
}
cr.fill();

if random == 6 {
cr.arc(width/2.,height/4.,height/10.,0.0,2.0*std::f64::consts::PI);
cr.arc(width/2.,3.*height/4.,height/10.,0.0,2.0*std::f64::consts::PI);
}
cr.fill();
Inhibit(false)
});

let l = label.clone();
let dado = r.clone();
let da = drawingarea.clone();
button.connect_clicked(move |_| {
dado.set(pick(1,7));
let text: String = format!("Dado: {}",dado.get());
l.set_text(text.as_str());
da.queue_draw();
});

vbox.add(&button);
vbox.add(&label);
vbox.add(&drawingarea);
window.add(&vbox);

window.show_all();
gtk::main();
}


Con esto tenemos lo básico para empezar a diseñar GUIs con GTK y Rust.

 ]]>
https://blog.adrianistan.eu/crear-ventanas-botones-rust-gtk Fri, 29 Sep 2017 00:00:00 +0000
Leer de teclado en Rust https://blog.adrianistan.eu/leer-teclado-rust https://blog.adrianistan.eu/leer-teclado-rust Python o Ruby, lo cierto es que sí se quiere hacer bien es complicado. Funciones como scanf de C son consideradas inseguras y en Java, hasta la llegada de la clase java.util.Scanner era un dolor de cabeza. En Rust no es distinto, es por ello que muchos tutoriales de Rust obvian esta parte. No obstante, leer de teclado no es tan difícil, como veremos a continuación.


read_line


El método principal para leer de teclado es read_line, que nos lee una línea como String. Para acceder a read_line primero necesitamos tener on objeto stdin. La manera más fácil de hacerlo es usar el módulo std::io.

El procedimiento es el siguiente, en primer lugar creamos una variable de tipo String vacía y mutable donde se va a alojar el resultado, posteriormente leemos y tratamos el resultado.


use std::io;

fn main() {
println!("Dime tu nombre: ");
let mut input = String::new();
io::stdin().read_line(&mut input);
println!("Tu nombre es {}",input.trim());
}


Como vemos, al leer la línea también se nos guarda el salto de línea. Si queremos quitarlo podemos usar trim.

Este código sin embargo generará una advertencia por el compilador y es que read_line genera devuelve un valor, concretamente un Result, que como vimos, sirven para el manejo de errores en Rust. Si no queremos tratar este Result con especial interés, podemos usar ok y opcionalmente especificar un mensaje de error con expect.


use std::io;

fn main() {
println!("Dime tu nombre: ");
let mut input = String::new();
io::stdin().read_line(&mut input).ok().expect("Error al leer de teclado");
println!("Tu nombre es {}",input.trim());
}


Si quieres tratar el error mejor puedes, pero read_line no suele fallar.

Leyendo enteros


Hasta aquí todo sencillo, porque leíamos String, en lo que entra todo lo que el usuario puede meter. Pero, ¿y si queremos leer un número de teclado? La cosa se complica. Normalmente se lee de teclado como String y luego se intenta pasar a número. Veamos como.


use std::io;
use std::str::FromStr;

fn main() {
println!("Dime tu edad: ");
let mut input = String::new();
io::stdin().read_line(&mut input).ok().expect("Error al leer de teclado");
let edad: u32 = u32::from_str(&input.trim()).unwrap();
let frase = if edad >= 18 {
"Mayor de edad"
}else{
"Menor de edad"
};
println!("{}",frase);
}


Como vemos, hay que importar std::str::FromStr para tener disponible las operaciones from_str en los tipos elementales. También se observa que hemos hecho un unwrap, porque from_str devuelve un Result. Este error sin embargo conviene que lo tratemos con más cuidado, pues es bastante probable que salte.

Un ejemplo ideal


En este código vamos a ver como pedir un entero, asegurándonos de que el usuario realmente introduce un entero e insistiendo hasta que finalmente introduce un entero válido.


use std::io;
use std::io::Write;
use std::str::FromStr;
use std::num::ParseIntError;

fn read_input() -> Result<u32,ParseIntError> {
print!("Dime tu edad: ");
io::stdout().flush().ok();
let mut input = String::new();
io::stdin().read_line(&mut input).ok().expect("Error al leer de teclado");
let input = input.trim();
let edad: u32 = u32::from_str(&input)?;
Ok(edad)
}

fn main() {
let edad;
loop {
if let Ok(e) = read_input(){
edad = e;
break;
}else{
println!("Introduce un número, por favor");
}
}
let frase = if edad >= 18 {
"Mayor de edad"
}else{
"Menor de edad"
};
println!("{}",frase);
}


He decidido separar la parte de pedir el número a otra función que devuelve Result para así poder usar el operador ?. También he usado print! y io::stdout().flush() en vez de println! para que tanto el mensaje como la entrada se realice en la misma línea y quede más bonito.



 ]]>
https://blog.adrianistan.eu/leer-teclado-rust Fri, 15 Sep 2017 00:00:00 +0000
Autómatas celulares unidimensionales en Python https://blog.adrianistan.eu/automatas-celulares-unidimensionales-python https://blog.adrianistan.eu/automatas-celulares-unidimensionales-python Think Complexity cuando en un capítulo empezó a hablar de los autómatas celulares unidimensionales. El tema me interesó y por eso esta entrada. Veamos primero a qué nos referimos cuando hablamos de esto.

Cuando hablamos a autómatas celulares, nos referimos a pequeñas entidades independientes pero que interaccionan entre sí. Celulares porque son la unidad elemental del universo donde van a existir y autómatas porque deciden por ellas mismas, basadas en un conjunto de reglas predefinido, cuando el tiempo avanza de forma discreta (es decir, a pasos).

Este concepto abstracto puede visualizarse con facilidad si nos imaginamos una rejilla. Cada celda es una célula capaz de cambiar su estado según su entorno.

Los autómatas celulares fueron objeto de estudio de Stephen Wolfram, matemático conocido por haber diseñado el programa Mathemathica y Wolfram Alpha.



Los autómatas celulares unidimensionales son aquellos que forman parte de un universo unidimensional. Es decir, cada célula tiene una vecina a su izquierda y a su derecha. En los bordes se pueden definir varios comportamientos pero el concepto no varía. Pensemos en ello como una tira de celdas.



El estudio de estos autómatas es interesante, ya que pueden generarse patrones/situaciones muy complejas en base a unas reglas sencillas.

¿Cómo se definen las reglas?


Wolfram usó un sistema para definir las reglas de estos autómatas que hoy conocemos como Wolfram Code. Se basa en definir una tabla con los estados presentes de la célula y sus vecinas así como el valor que deberá adoptar en esa situación la célula. Como Wolfram usó células con solo dos estados, todo está en binario, y la parte baja de la tabla es un número de 8 bits. Este número se suele pasar a decimal y así se identifica.

























Estados presentes111110101100011010001000
Estado futuro00110010

Esta tabla representa la Regla 50, porque 00110010 en binario es 50.

¿Cómo se representan?


Una manera muy interesante de representar estos autómatas es poner cada paso en una fila distinta dentro de una imagen.

Una vez que sabemos esto vamos a hacer un programa en Python que nos permita observar la evolución de estos autómatas.

Usaremos el procedimiento original, que es empezar con todos los estados de los autómatas en 0 salvo el del autómata central, que será 1.

La clase Automata


La clase autómata va a contener las reglas, así como un ID y el estado que posee. Además, por cuestiones técnicas conviene guardar el estado anterior que tuvo.


class Automata(object):

rules = list()

def __init__(self,idx=0):
self.idx = idx
self.state = False
self.statePrev = False


Como podéis ver, rules no lleva self, es decir, va a ser una variable compartida entre todas las instancias de Automata. Esto es porque las reglas son idénticas a todos los autómatas.

La clase World


Ahora vamos a definir el universo donde residen estos autómatas. Este universo almacena una lista con los autómatas, se encarga de actualizarlos según las normas y de dibujarlos usando PIL. También he insertado el código que codifica las normas según el número en decimal.


class World(object):
def __init__(self,rule=50):
self.rule = rule
self.im = Image.new("L",(WIDTH,HEIGHT))
self.data = np.zeros(WIDTH*HEIGHT,dtype=np.uint8)
b = bin(rule)[2:].zfill(8)
Automata.rules = [True if c == "1" else False for c in b]
self.list = list()
self.step = 0

def add(self):
automata = Automata(len(self.list))
self.list.append(automata)

def update(self):
for automata in self.list:

automata.statePrev = automata.state
p = self.list[automata.idx - 1].statePrev if automata.idx > 0 else False
n = self.list[automata.idx + 1].state if automata.idx < len(self.list)-1 else False
s = automata.state

if p and s and n:
automata.state = automata.rules[0]
elif p and s and not n:
automata.state = automata.rules[1]
elif p and not s and n:
automata.state = automata.rules[2]
elif p and not s and not n:
automata.state = automata.rules[3]
elif not p and s and n:
automata.state = automata.rules[4]
elif not p and s and not n:
automata.state = automata.rules[5]
elif not p and not s and n:
automata.state = automata.rules[6]
elif not p and not s and not n:
automata.state = automata.rules[7]


def draw_row(self):
if self.step == 0:
middle = len(self.list) // 2
self.list[middle].state = True
for i,automata in enumerate(self.list):
if automata.state:
self.data[self.step*HEIGHT+i] = 255
self.step += 1
def save(self):
self.im.putdata(self.data)
self.im.save("RULE-%d.png" % self.rule)


Con esto ya lo tenemos casi todo. Ahora faltaría poner en marcha todo. La idea es simplemente crear una instancia de World, hacer unas cuantas llamadas a add, y después ir haciendo el ciclo update/draw_row. Una vez hayamos acabado, hacemos save y obtendremos un PNG con la imagen.

Código completo




import numpy as np
from PIL import Image

WIDTH = 5001
HEIGHT = 5001

class Automata(object):

rules = list()

def __init__(self,idx=0):
self.idx = idx
self.state = False
self.statePrev = False

class World(object):
def __init__(self,rule=50):
self.rule = rule
self.im = Image.new("L",(WIDTH,HEIGHT))
self.data = np.zeros(WIDTH*HEIGHT,dtype=np.uint8)
b = bin(rule)[2:].zfill(8)
Automata.rules = [True if c == "1" else False for c in b]
print(Automata.rules)
self.list = list()
self.step = 0

def add(self):
automata = Automata(len(self.list))
self.list.append(automata)

def update(self):
for automata in self.list:

automata.statePrev = automata.state
p = self.list[automata.idx - 1].statePrev if automata.idx > 0 else False
n = self.list[automata.idx + 1].state if automata.idx < len(self.list)-1 else False
s = automata.state

if p and s and n:
automata.state = automata.rules[0]
elif p and s and not n:
automata.state = automata.rules[1]
elif p and not s and n:
automata.state = automata.rules[2]
elif p and not s and not n:
automata.state = automata.rules[3]
elif not p and s and n:
automata.state = automata.rules[4]
elif not p and s and not n:
automata.state = automata.rules[5]
elif not p and not s and n:
automata.state = automata.rules[6]
elif not p and not s and not n:
automata.state = automata.rules[7]


def draw_row(self):
if self.step == 0:
middle = (len(self.list) // 2)
self.list[middle].state = True
for i,automata in enumerate(self.list):
if automata.state:
self.data[self.step*HEIGHT+i] = 255
self.step += 1
def save(self):
self.im.putdata(self.data)
self.im.save("RULE-%d.png" % self.rule)
def __str__(self):
s = str()
for l in self.list:
s += "T" if l.state else "F"
return s

def world_run(rule):
world = World(rule)
for _ in range(WIDTH):
world.add()

for _ in range(HEIGHT):
world.draw_row()
world.update()
world.save()

def main():
rule = input("Rule: ")
try:
rule = int(rule)
if 255 >= rule >= 0:
world_run(rule)
print("Check for the generated RULE-%d.png file" % rule)
else:
raise ValueError
except ValueError:
print("Please, insert a number between 0 and 255")
main()

if __name__ == "__main__":
main()


Algunas reglas importantes


Regla 30


Una de las más importantes a nivel matemático. Ha sido objeto de mucho estudio, sin embargo no vamos a entrar en detalles más allá de su aspecto visual.



Vista ampliada

Regla 110


Esta regla es también muy interesante. ¡Se demostró que era Turing completa!



Vista en detalle

Regla 126


Esta regla no es tan importante, pero personalmente me parece muy bonita.



Vista ampliada

Reglas 57 y 99


Son dos reglas isomorfas. Es decir, son en realidad la misma regla pero aplicada a lados distintos. Elijo estas dos porque se aprecia muy bien el isomorfismo.

Regla 57

Regla 99

Regla 169




Vista en detalle

Regla 129


Regla 90


Es el famoso triángulo de Sierpinski.

Regla 150


Regla 105


Esta regla no tiene isomorfo.

En este artículo no he querido entrar en las complejidades matemáticas de todo esto. Es algo que todavía no entiendo así que no sería sincero por mi parte exponerlo.

Bonus: Richard Feynman y Steve Jobs


Quien me conoce sabe de sobra que uno de los personajes de la historia que más ha influido en mi vida es Richard Feynman. Debo reconocer que entré en un estado de éxtasis al descubrir que Feynman y Wolfram no solo trabajaron juntos, sino que lo hicieron alrededor de la regla 30 antes mostrada. También me sorprendió que Steve Jobs y Wolfram resultasen ser amigos de toda la vida. No dejo de sorprenderme de los contactos de ciertos personajes históricos entre sí.

Feynman a la izquierda, Wolfram a la derecha]]>
https://blog.adrianistan.eu/automatas-celulares-unidimensionales-python Mon, 28 Aug 2017 00:00:00 +0000
Todo lo que debes sobre las tarjetas de crédito, débito y prepago https://blog.adrianistan.eu/lo-debes-las-tarjetas-credito-debito-prepago https://blog.adrianistan.eu/lo-debes-las-tarjetas-credito-debito-prepago Este artículo es bastante extenso. Si te interesan los detalles más técnicos puedes ir al final.


Nacieron en la primera mitad del siglo XX. Originalmente, estas primeras tarjetas no eran más que una línea de crédito de un determinado establecimiento. En 1924 por ejemplo, General Petroleum Corporation emite una tarjeta para poder adquirir gasolina. En realidad, lo que sucedía al usar esa tarjeta era que contraíamos una deuda con la compañía, que posteriormente había que pagar en efectivo. De este modo, funcionaban de forma similar a como cuando dejamos que nos apunten algo en la cuenta del bar.

Esto cambió en 1949, cuando Franck McNamara estaba cenando con unos amigos y discutiendo precisamente sobre este tipo de tarjetas. Finalmente al llegar a la hora de pagar, McNamara pudo comprobar que se había dejado la cartera en casa. Tuvo que llamar a su mujer para que le llevase el dinero. Pasó tal vergüenza que se propuso acabar para siempre con este tipo de situaciones. Así es como nació Diners Club, la primera compañía de tarjetas de crédito del mundo.

Frank X. McNamara

Una de las primeras tarjetas Diners Club

Otros dicen que no llamó a su mujer sino que salió del aprieto dando su tarjeta de visita y anotando la cantidad debida. Sea cual sea la historia verdadera, lo cierto es que el sistema de funcionamiento de una tarjeta de crédito es ese. Nosotros cuando pagamos con una tarjeta de este tipo estamos emitiendo deuda, que a final de mes o cuando más nos convenga, pagamos íntegramente o a plazos. Diners cobraba comisiones de mantenimiento a los poseedores de las tarjetas y comisiones por transacción a los establecimientos que las admitían. Esta es la principal diferencia respecto a las tarjetas de débito y las tarjetas prepago.

Logotipos en un establecimiento de Master Charge (actual MasterCard) y BankAmericard (actual VISA)

A partir de ese momento empiezan a surgir más compañías, Bank AmeriCard o Master Charge (VISA y MasterCard actualmente). En España se aceptaron tarjetas de crédito por primera vez en el año 1954 y por aquel entonces eran simples tarjetas de cartón con un número y su titular, que los hoteles y restaurantes de la época anotaban para posteriormente reclamar el pago a Diners. La primera tarjeta emitida por un banco español no llegaría hasta 1978, por el Banco de Bilbao y se trataba de una AmeriCard. Las tarjetas de crédito son el tipo de tarjetas más admitidas en el mundo y suelen llevar asociadas ventajas tales como seguros, programas de puntos, ... Al pasarnos una factura a final de mes es más fácil saber cuanto hemos gastado en un mes. Sin embargo estas tarjetas tienen un gran peligro. Nos dejan a nuestra disposición una gran cantidad de dinero, y si no somos capaces de pagar las cuotas a tiempo podemos enfrentarnos a unos intereses de devolución en la mayoría de los casos de dos cifras.

Las primeras tarjetas de crédito emitidas en España fueron las BankAmericard del Banco de Bilbao

Como vemos, las tarjetas de crédito no necesitaban ninguna tecnología en especial para funcionar, las de débito y las prepago, sí que lo necesitan. No obstante, todas las tarjetas de crédito hoy en día usan tecnología, de hecho existen tarjetas que operan en varios modos, crédito y débito. ¿Cómo funcionan las tarjetas de débito?

Las tarjetas de débito son lo más parecido a pagar en efectivo. Cuando pagamos, estamos pagando con nuestro dinero. En algunas se nos permite caer en números rojos (VISA Debit) mientras que en otros se comprueba la existencia de fondos (VISA Electron). Inmediatamente a la compra, el dinero se descuenta de la cuenta bancaria asociada. Estas tarjetas suelen tener gastos de mantenimiento menores (en España los bancos las suelen dar gratis) y son prácticamente igual de admitidas que las de créditos en cajeros automáticos, establecimientos y compras por Internet. No obstante, las tarjeta de débito no son aceptadas en todos los sitios, por dos motivos:

    • Las tarjetas de débito (como VISA Electron) requieren conexión a la cuenta bancaria de tu banco. Este proceso es más complicado que simplemente anotar que debes 50€ a fulanito. Otras tarjetas como VISA Debit pueden funcionar en puntos de pago offline ya que pueden dejarte en números rojos (por tanto, el comerciante se asegura que siempre va a recibir el dinero, aún sin saber cuanto dinero tienes).

 

    • Las tarjetas de débito no dejan gastar más dinero del que hay en cuenta (o pueden dejar, pero no demasiado).



Por estos dos motivos, puede ser que una tarjeta de débito no llegue a funcionar en algún país extranjero (aunque es cada vez más raro) o simplemente no se admitan para poder así cobrarte gastos imprevistos (alquileres de coches, hoteles, ...). Si bien en esto las tarjetas de débito han ido mejorando y en muchas existe la posibilidad de reservar una cantidad de dinero de la tarjeta. Eso quiere decir que al hacerel primer pago o la entrada, se nos cobra una parte y se nos reserva otra. Esa cantidad reservada a efectos prácticos no podemos usarla para nada pero técnicamente sigue siendo nuestro. Cuando se acaba el plazo y no ha hecho falta recurrir a ese dinero de reserva, el comerciante libera ese dinero y ya lo podemos usar en nuestra tarjeta.

Las tarjetas prepago funcionan con tecnología similar a las de débito, salvo por el detalle de que no tienen asociada una cuenta bancaria como tal. En vez de eso, las tarjetas prepago tienen una cuenta propia, que debemos recargar antes de efectuar pagos o retirar dinero. Las tarjetas prepago se han vuelto muy populares para menores de edad y compras por Internet, donde solo se recarga la tarjeta con la cantidad máxima que consideremos necesario.

La tarjeta Antes de BBVA es una tarjeta prepago

Por último, existe otro tipo de tarjeta, aunque apenas usadas en compras, se trata de las tarjetas monedero. Su diferencia principal respecto a las tarjetas prepago reside en como se realiza el pago. En una tarjeta prepago, se realiza una conexión al emisor de la tarjeta, como con las de débito, mientras que en una monedero no. En una tarjeta monedero el dinero se almacena en la propia tarjeta, de forma cifrada. Este sistema no es tan popular y en España que yo sepa ningún banco emite tarjetas de este tipo. Son usadas sin embargo en algunos sistemas de transporte público o de empresas, donde se puede descontar el dinero instantáneamente, aunque no haya conexión con un servidor central. La forma de recargar estas tarjetas es acudir con la propia tarjeta a un punto de recarga, no pudiendo realizarse por Internet o sin la tarjeta.

Tarjeta monedero emitida por Caja Duero para el transporte urbano de Valladolid

Mencionar también las tarjetas ATM. Estas tarjetas únicamente pueden usarse en los cajeros para sacar dinero, no pudiéndose usar en las compras. De este modo funcionan similar a algunas libretas de ahorro. Sin embargo, he de reconocer que personalmente no he visto ninguna tarjeta ATM.

Redes bancarias


Si nos centramos en tarjetas de crédito, débito y prepago tenemos que tener en cuenta las redes interbancarias. Estas permiten las conexiones entre las redes de los distintos bancos y permiten por ejemplo que podamos sacar dinero en un cajero de EspañaDuero cuando nuestra tarjeta la ha emitido N26 Bank o que podamos pagar en el extranjero. En el sistema bancario, no existe nada parecido a Internet (la red de redes) y cada red tiene sus propios puntos de acceso. Así pues, no todos los cajeros tienen por qué poder conectarse a todas las redes existentes, con los consiguientes problemas (no poder sacar dinero, no poder pagar,...). En tarjetas de débito esto es más problemático. En España existen tres redes locales aunque funcionan también redes internacionales.

    • Servired, la mayor red intranacional de España, con el BBVA, Bankinter, Deutsche Bank, Sabadell, Bankia, CaixaBank, Cajamar, las cajas rurales que están unidas por el Banco Cooperativo Español, Triodos Bank, Self Bank, Banco Mediolanum, Caixa Geral, Laboral Kutxa, Abanca y otros bancos más pequeños.

 

    • 4B, originalmente para Banco Santander, Banco Popular, Banco Pastor y Banesto, ahora también ING, Targobank, Openbank, Banca March, Cetelem, Inversis, Andbank y alguno más.

 

    • Euro6000, usada antiguamente por las cajas de ahorros, hoy en día de sostiene gracias a Unicaja, EspañaDuero, Ibercaja, Kutxabank, Liberbank y EVO Banco.



Cajero perteneciente a la red intranacional Servired. También aparecen logos de PLUS, Cirrus, Tarjeta 6000, Eurocheque, Eurocard,...

Indicativo del Banco Pastor y Telebanco 4B

IberCaja, con sede en Zaragoza, es una de las entidades que sigue dentro de Euro 6000

Estas redes se comunican con otras redes similares de otros países o bien pueden recurrir a una red internacional, como Cirrus. Aunque después de la crisis de 2008 muchos bancos y cajas se han fusionado entre sí, parece que las tres redes siguen operando de forma independiente.

Logo actual de la red interbancaria Cirrus

Tarjetas


Vamos ahora a ver las principales empresas que se dedican a las tarjetas:

VISA (anteriormente Bank AmeriCard) es quizá la tarjeta más conocida del mundo. Es la tarjeta líder en España. A nivel global es la segunda red más importante (solo superada por UnionPay). Las tarjetas VISA operan a través de la red interbancaria PLUS. VISA dispone de varios tipos de tarjeta: VISA, VISA Debit, VISA Electron y V Pay. VISA es de crédito, VISA Debit es la versión más libre de las de débito. Permite dejarte en números rojos ya que al hacer un pago no se comprueba la disponibilidad de fondos. Es por ello que la VISA Debit puede usarse en algunos sitios donde solo se admiten tarjetas de crédito por ese mismo motivo, el comerciante se puede asegurar que va a recibir su dinero aunque no sepa si tu cuenta tiene fondos. VISA Electron por contra, verifica que haya fondos suficientes en la cuenta, así que necesita que exista una conexión con el banco. Desde hace no mucho existe V Pay, una tarjeta de débito mucho más moderna, emitidas solo con chip y que solo funcionan en SEPA (Single Euro Payments Area). Han sido diseñadas para ser usadas en transacciones pequeñas y por lo general funcionan de forma similar a VISA Electron.

Logo antiguo de VISA Electron

PLUS es la red interbancaria de VISA

V Pay es la gama más moderna de VISA, sólo válida en territorios SEPA

MasterCard, la gran competidora de VISA en España. MasterCard entro en el mercado europeo con las adquisiciones de Eurocard y Europay. Su red interbancaria es Cirrus. Posee varias tarjetas: MasterCard, Debit MasterCard y Maestro. MasterCard es la línea de tarjetas de crédito, Debit MasterCard es una línea de tarjetas de débito compatibles con puntos offline y números rojos (como VISA Debit) y Maestro es comparable a VISA Electron, ya que requiere comprobación de fondos. No obstante, Maestro también puede funcionar como tarjeta prepago y las tarjetas Maestro son distintas al resto de tarjetas MasterCard y VISA. En ciertos países, Maestro funciona como Debit MasterCard, por lo que no requiere autorización electrónica. Las tarjetas Maestro también son compartidas en algunos países con algún proveedor local de tarjetas, como Girocard en Alemania. Maestro fue de las primeras tarjetas donde no había que firmar, sino que se introducía un PIN al comprar, tanto si se compra usando chip como con banda magnética. Muchas Maestro que no pueden usarse en el extranjero e Internet. En España, las tarjetas Maestro nunca han llegado a ser populares.

Logos antiguos de MasterCard, Cirrus y Maestro

American Express es la tercera entidad de tarjetas en España. Sin embargo, debido a sus altas comisiones, es fácil encontrar tiendas que no la admitan.



Discover y Diners Club son bastante usadas en Estados Unidos y otros países como Croacia. En España son bastante difíciles de encontrar y se suelen aceptar únicamente en establecimientos turísticos. Hasta hace años eran empresas con tarjetas independientes, no obstante, Discover adquirió Diners Club hace años. Discover se centrará en el mercado doméstico estadounidense y Diners Club, que era más usada internacionalmente, seguirá con la expansión internacional.



JCB, se trata de una entidad japonesa de tarjetas. Es usada sobretodo por japoneses y coreanos.



UnionPay, es la compañía de tarjetas más grande del mundo, aunque la mayor parte de sus transacciones se concentran en China. Solo las emiten bancos chinos.

UnionPay es la tarjeta más usada del mundo desde 2015

RuPay, son unas tarjetas de amplio uso creadas con intervención del gobierno de la India con el deseo de que todos los habitantes de la India pudieran disponer de esta forma de pago.



Otras tarjetas pueden ser Bancomat (Italia), Elo (Brasil) o Dankort (Dinamarca). Este tipo de tarjetas, debido a su poca popularidad suelen ser compatibles con VISA y MasterCard en el extranjero, mientras que en sus países de origen usan su red interna.

Elo es muy usada dentro de Brasil

¿Cómo funcionan las tarjetas?


Ahora vamos a ver como se puede pagar con las tarjetas de crédito, débito y prepago. En primer lugar comentemos algo sobre el número de las tarjetas. Estas tienen normalmente 16 dígitos, aunque pueden llegar a 19 dígitos. Se numeran por lotes, siendo los primeros dígitos los correspondientes a la empresa. Es posible reconocer la empresa detrás de la tarjeta según su número o PAN.

Los PAN siguen una estructura, que aunque presenta longitud variable, está bien definida.



El primer dígito corresponde al Major Industry Identifier (MII). Indica a que sector pertenece la empresa de la que es la tarjeta. Los sectores son:

Dígito Sector
0 Uso interno
1 Aerolíneas
2 Aerolíneas y otros
3 Viajes, entretenimiento, finanzas
4 Finanzas
5 Finanzas
6 Finanzas
7 Petroleras
8 Salud y telecomunicaciones
9 Sin asignar


Aunque actualmente, la mayoría de tarjetas son de finanzas. El MII y los siguientes 5 dígitos forman el BIN o Bank Identification Number y sirven para idenficar al banco emisor de la tarjeta y enrutarlo. Los siguientes dígitos excepto el último forman parte del identificador de tarjeta personal, asignado por el banco a sus clientes. El último dígito es de comprobación y se calcula con el algoritmo de Luhn.

Número(s) de inicio Longitud número de tarjeta Empresa
22 a 27 16 MasterCard
30 14 Diners Club
34 15 American Express
35 16 JCB
36 14 Diners Club
37 15 American Express
38, 39 14 Diners Club
4 16 VISA
5018, 502, 503, 506 12 a 16 Maestro
5019 16 Dankort
51 a 55 16 MasterCard
56 a 58 12 a 19 Maestro
60 16 Discover
62 16 a 19 UnionPay
622, 64, 65 16 Discover
639, 6220 12 a 19 Maestro
88 16 a 19 UnionPay


Esta tabla no es exhaustiva pero puede servir de referencia rápida. Por ejemplo, aunque VISA empieza siempre por 4, no todas las que empiezan por 4 son VISA siempre, ya que existen tarjetas Elo que también empiezan por 4. No obstante, se trata de rangos tan pequeños que la lista sería muy larga si fuese exhaustiva.

Banda magnética y firma


Estas tarjetas, mejorando las de cartón, agilizan el proceso de pago pero ni introducen ninguna medida de seguridad extra. Se definen según el estándar ISO/IEC 7813 e ISO/IEC 7811. Incluyen una banda magnética desde la que se puede leer el número de la tarjeta, la fecha de caducidad, los nombres del titular y algunos datos técnicos. Las tarjetas tienen 3 bandas o tracks.

Los 3 tracks de las bandas magnéticas y su posición

El primer y segundo track llevan información redudante, de forma que si una banda está dañada puede leerse la otra, sin embargo no siguen el mismo formato.

Nombre del campo Longitud Uso
Start Sentinel (SS) 1 caracter Indica que comienza el track 1, siempre es %
Format Code (FC) 1 caracter Indica el tipo de tarjeta, B para crédito/débito
Primary Account Number (PAN) Hasta 19 digitos Normalmente, el número de la tarjeta que está impreso
Field Separato]]> https://blog.adrianistan.eu/lo-debes-las-tarjetas-credito-debito-prepago Thu, 27 Jul 2017 00:00:00 +0000 Timers en systemd, reemplazo a cron https://blog.adrianistan.eu/timers-systemd-reemplazo-cron https://blog.adrianistan.eu/timers-systemd-reemplazo-cron systemd se ha impuesto como el init por excelencia del mundo Linux. Las distros más populares como Debian, Ubuntu, Fedora, openSUSE y Arch Linux usan systemd por defecto. Hoy vengo a hablaros de una característica interesante de systemd que son son los timers. Se trata de poder definir que arranquen programas/servicios cada cierto tiempo o a una hora, su versatilidad es tal que pueden sustituir a cron en la mayoría de casos. Veamos como usar los timers.

Creando un servicio


Para crear un timer primero necesitamos crear un servicio simple que ejecute el comando que queramos al ser iniciado.

Crea el archivo miapp.service y ponlo en /etc/systemd/system/.


[Unit]
Description=Cat Mania GIF

[Service]
ExecStart=/home/pi/charlatan/catmania.py


 

Creando el timer


Para el timer es necesario un archivo con extensión timer en /etc/systemd/system/. Se suele usar el mismo nombre que el que tiene el servicio para escenarios simples. El archivo miapp.timer quedaría así:


[Unit]
Description=Run Cat Mania daily

[Timer]
OnCalendar=daily
RandomizedDelaySec=5min
Unit=catmania.service

[Install]
WantedBy=timers.target


 

Aquí es donde podemos realizar toda la magia de los timers. Los timers de systemd tienen dos modos de funcionamiento. En el primer modo, systemd ejecutará el timer cada cierto tiempo desde el arranque del sistema ( o algo más tarde si no queremos saturar el sistema). El otro modo es el modo calendario donde podemos especificar fechas y horas con wildcards y patrones. Además, es posible modificar la hora exacta de ejecución del timer con las propiedades de Randomize y que evitan que si hay varios timers configurados a la vez, no lo hagan exactamente a la vez (pero siempre dentro de un margen). Otras opciones para OnCalendar pueden ser:

OnCalendar=Wed,Sun *-*-10..15 12:00:00


En ese caso, el timer se activaría los miércoles y domingos que cayesen entre el 10 y el 15 de todos los meses a las 12 horas.

Activando el timer


Una vez estén copiados los archivos service y timer ejecutamos:


systemctl start miapp.timer
systemctl enable miapp.timer


Ya solo queda esperar a que systemd lance el servicio en la fecha y hora especificados.

Monitorización


Los timers están integrados con systemd, por lo que los logs se registran con journalctl. Podemos inspeccionarlos de forma sencilla:


journalctl -f -u miapp.service
journalctl -f -u miapp.timer


Para revisar los timers instalados y activos en el sistema puedes usar el comando:


systemtctl list-timers


Y ya estaría. De este modo podemos usar systemd como reemplazo de cron. Yo personalmente lo prefiero, ya que me parece una sintaxis más clara.]]>
https://blog.adrianistan.eu/timers-systemd-reemplazo-cron Tue, 25 Jul 2017 00:00:00 +0000
Formulario de registro en Elm https://blog.adrianistan.eu/formulario-registro-elm https://blog.adrianistan.eu/formulario-registro-elm Elm, un lenguaje funcional puro y a la vez framework de backend. De momento he programado ya he visto unas cuántas cosas y puedo decir que me gusta bastante, que es un soplo de aire fresco en el mundo web y que sinceramente no creo que vaya a triunfar algún día. Sin embargo para proyectos personales, lo veo como alternativa superior a Angular, React y Vue.

Uno de los ejercicios que puede venir bien para entender Elm es implementar un formulario de registro con normas para la contraseña. Según vayamos escribiendo se nos informa sobre qué errores tiene la contraseña para ser segura y cuando lo sea mostramos el botón de registro.

Es un código un poco tonto pero me apetecía compartirlo.


import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
import Char
import String
import List

main : Program Never Model Msg
main = Html.program {init = init, view = view, update = update, subscriptions = subscriptions}

type alias Model =
{
name : String,
password : String,
passwordAgain : String
}

init : (Model, Cmd Msg)
init = (Model "" "" "", Cmd.none)

subscriptions : Model -> Sub Msg
subscriptions model = Sub.none

type Msg = Name String | Password String | PasswordAgain String

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Name name -> ({ model | name = name},Cmd.none)
Password pass -> ({model | password = pass},Cmd.none)
PasswordAgain pass -> ({model | passwordAgain = pass},Cmd.none)

view : Model -> Html Msg
view model =
div []
[
input[type_ "text", placeholder "Name", onInput Name][],
input[type_ "password", placeholder "Password", onInput Password][],
input[type_ "password", placeholder "Confirm", onInput PasswordAgain][],
viewValidate model
]

viewValidate : Model -> Html Msg
viewValidate model =
let list = checkMinLength model :: checkPassword model :: checkUpper model :: checkLower model :: checkDigit model :: []
wrongList = List.filter (\(x,y) -> not x) list
in
if List.length wrongList == 0 then
div [] [button[][text "Submit"]]
else
div [] (List.map showError wrongList )

showError : (Bool,String) -> Html Msg
showError (_,error) =
div [style[("color","red")]] [text error]

checkPassword : Model -> (Bool, String)
checkPassword model =
if model.password == model.passwordAgain then
(True,"")
else
(False,"Passwords do not match")

checkMinLength : Model -> (Bool,String)
checkMinLength model =
if String.length model.password > 7 then
(True,"OK")
else
(False,"Password must have a minimum length of 8 characters")

checkUpper : Model -> (Bool,String)
checkUpper model =
let isUp = String.any Char.isUpper model.password
in
if isUp then
(True,"")
else
(False,"The password must contain an upper letter")

checkLower : Model -> (Bool,String)
checkLower model =
let isDown = String.any Char.isLower model.password
in
if isDown then
(True,"")
else
(False,"The password must contain a lower letter")

checkDigit : Model -> (Bool,String)
checkDigit model =
let isDig = String.any Char.isDigit model.password
in
if isDig then
(True,"")
else
(False,"The password must contain a digit")


 ]]>
https://blog.adrianistan.eu/formulario-registro-elm Sat, 15 Jul 2017 00:00:00 +0000
Box, Rc y RefCell, punteros inteligentes en Rust https://blog.adrianistan.eu/box-rc-refcell-punteros-inteligentes-rust https://blog.adrianistan.eu/box-rc-refcell-punteros-inteligentes-rust String o Vec. Sin embargo, ¿si queremos implementar algo similar a String como lo haríamos? Aquí entran en juego, los punteros inteligentes, punteros con capacidades extra. Los más importantes son Box, Rc y RefCell.

Box, reservar memoria en el heap


Box es parecido a malloc de C. Reservan memoria en la parte alta de la memoria. El uso principal de Box en Rust es el de implementar estructuras de datos cíclicas o recursivas.


fn main() {
let n = Box::new(42);
println!("n = {}", n);
}


No es muy útil usarlo así. Solo compensa usarlo en situaciones donde es necesario que la variable ocupe un tamaño fijo, que a priori es indeterminado.

Rc, ¡viva la multipropiedad!


Rc es un puntero inteligente algo más interesante. Permite que un dato sea propiedad de varias variables a la vez. Funciona con un mecanismo de recolector de basura. La clave está en que cuando hagamos clone de un Rc no obtendremos una copia exacta, sino una referencia más al dato original. Esto permite ahorrar memoria. Veamos en un ejemplo, como Regex se comparte entre varias variables. Se trata del mismo Regex, en ningún momento ocurre una duplicidad en memoria.


extern crate regex;

use regex::Regex;
use std::rc::Rc;


fn main() {
let r = Rc::new(Regex::new(r"([A-Za-z0-9])([.]|[_]|[A-Za-z0-9])+@gmail.com").unwrap());
if r.is_match("pucela_segunda@gmail.com") {
println!("Correo válido de Gmail");
}else{
println!("Correo no válido de Gmail");
}

let puntero = r.clone();
println!("Reference Count: {}",Rc::strong_count(&puntero));
puntero.is_match("perro@gmail.com");
r.is_match("_pepe@gmail.com");
}


La línea Reference Count nos dice cuantas variables tienen ahora mismo acceso a ese dato. Rc funciona a la perfección en un solo hilo, pero si quieres hacer lo mismo entre hilos debes usar Arc. Rc por sí mismo, solo permite lecturas, es cuando lo juntamos con RefCell cuando obtenemos soporte de escritura.

RefCell, saltándonos las normas


En primer lugar, RefCell es muy interesante y poderoso, pero no es recomendable usarlo si se pueden usar otros métodos. Digamos que RefCell permite llevar las normas del compilador de Rust sobre préstamos y dueños al runtime. Esto puede provocar crasheos así que normalmente se usa con Rc que previene estas situaciones.


use std::rc::Rc;
use std::cell::RefCell;

fn main(){
let n = Rc::new(RefCell::new(42));
let x = n.clone();
let y = n.clone();
*x.borrow_mut() += 10;
*y.borrow_mut() += 10;
println!("N: {:?}",n.borrow());
}


El resultado de este código es 62. Por tanto, hemos conseguido que distintas variables pudiesen mutar el dato.

Con esto ya hemos visto los punteros inteligentes más importantes de Rust. Existe alguno más como Cell que sin embargo, no es tan usado.

 ]]>
https://blog.adrianistan.eu/box-rc-refcell-punteros-inteligentes-rust Mon, 3 Jul 2017 00:00:00 +0000
Funciones y closures en Rust https://blog.adrianistan.eu/funciones-closures-rust https://blog.adrianistan.eu/funciones-closures-rust

Funciones simples


Las funciones se definen con la palabra reservada fn. Posteriormente indicamos el nombre de la función y los argumentos de entrada. Si la función devuelve un valor se debe poner una flecha y el tipo del valor de devolución. Para devolver un valor se puede usar return o se puede dejar la última línea sin punto y coma. Funciona de forma similar a como vimos con let y las asignaciones complejas.


fn suma(a: i32, b: i32) -> i32{
a+b
}

fn main(){
let a = 5;
let b = 42;
let c = suma(a,b);
println!("{}",c);
}


Rust como tal no admite devolver varios valores a la vez, pero es posible usar tuplas y simularlo.


fn string_length_and_lines(txt: &String) -> (usize,usize) {
(txt.len(),txt.lines().count())
}

fn main(){
let s = String::from("Europe's Skies - Alexander Rybak\nSuper Strut - Deodato\nEl Cóndor Pasa - Uña Ramos");
let (length,lines) = string_length_and_lines(&s);
println!("La lista de canciones tiene una longitud de {} caracteres y {} líneas",length,lines);
}


Funciones de primer nivel


En Rust las funciones son elementos de primer nivel, lo que quiere decir que pueden pasarse por argumentos entre funciones. Veamos un ejemplo. En este caso uso un for-in que todavía no hemos visto, pero la idea es comprobar como se puede pasar una función a través de un argumento.


fn ladrar(){
println!("Guau")
}

fn hacer_n_veces(f: fn(), n: i64){
for _ in 0..n{
f();
}
}

fn main(){
hacer_n_veces(ladrar,42);
}


 

Genericidad


Las funciones en Rust admiten genericidad, lo que quiere decir que la misma función es compatible con distintos tipos, siempre respetando las normas. Usualmente, para denominar al tipo genérico se usa T. T se sustituye en cada caso por el tipo en cuestión. Entonces si en el primer parámetro de la función hemos puesto un i32 y T también está en el segundo, este también tiene que ser i32.


fn mayor<T: PartialOrd + Copy>(n: &Vec<T>) -> T{
let mut max: T = n[0];
for &i in n.iter(){
if i > max{
max = i;
}
}
max
}

fn main(){
let v: Vec<i32> = vec![1,2,7,8,123,4];
let max = mayor(&v);
println!("{}",max);
}


En este ejemplo además vemos genericidad restringida, la cuál es la más habitual. Básicamente T puede ser cualquier tipo pero debe de implementar las traits PartialOrd (para hacer la comparación) y Copy (elementos que se copian, en vez de moverse). La genericidad en Rust es un campo muy extenso, no obstante para entenderla bien, tendremos que entrar en las traits, ya en otro capítulo.

Closures


Los closures o funciones anónimas son funciones sin nombre que se definen y se consumen. En lenguajes como Python existen bajo la palabra reservada lambda. En Rust se definen con barras verticales.


fn main(){
let potencia = |x| x*x;
let pot_5 = potencia(5);
println!("{}",pot_5);
}


Como es de imaginar podemos usar expresiones más complejas entre llaves.


fn main(){
let potencia_sup = |x| {
let y = x+1;
y*y
};
let pot_5 = potencia_sup(5);
println!("{}",pot_5);
}


Con esto sabemos lo suficiente de funciones y genericidad para avanzar a otros temas.]]>
https://blog.adrianistan.eu/funciones-closures-rust Mon, 3 Jul 2017 00:00:00 +0000
Concurrencia en Rust https://blog.adrianistan.eu/concurrencia-en-rust https://blog.adrianistan.eu/concurrencia-en-rust

Crear threads


Todas las funciones relacionadas con threads están en std::thread así que primero hemos de importarlo con use. Para crear un thread usamos thread::spawn, que toma una función como argumento. Esta operación devuelve un JoinHandler que puede ser usado para esperar a que finalicen los hilos de ejecución.


use std::thread;

fn main(){
let handle = thread::spawn(||{
for i in 1..100{
println!("Attens ou va t'en - France Gall");
}
});
handle.join();
println!("Hilo finalizado");
}


Aquí ya se presenta una cuestión interesante, ¿qué pasa si queremos llevar datos del hilo principal al nuevo thread? Si lo intentamos hacer, Rust se quejará. En realidad, tenemos que activar la closure con move. Lo que hace es transferir la propiedad del dato a la closure. Con eso podemos procesarlo tranquilamente en el hilo nuevo, siempre y cuando no intentemos acceder a él desde el hilo principal.


use std::thread;

fn main(){
let v = vec!["Attens ou va t'en - Paul Mauriat","Attens ou va t'en - France Gall"];
let handle = thread::spawn(move ||{
for i in v{
println!("{}",i);
}
});
handle.join();
println!("Hilo finalizado");
}


Mensajes


Una de las opciones que permite Rust para concurrencia es el paso de mensajes.


use std::thread;
use std::sync::mpsc;
use std::time::Duration;


fn main(){
let (tx,rx) = mpsc::channel();

let h = thread::spawn(move ||{
thread::sleep(Duration::new(5,0));
let val = String::from("Attens ou va t'en - France Gall");
tx.send(val).unwrap();
});

loop {
if let Ok(msg) = rx.try_recv() {
println!("{}",msg);
break;
}
}
// Opción síncrona
//let msg = rx.recv().unwrap();
//println!("{}",msg);

// Opción Iterable
// for msg in rx{
// println!("{}",msg);
// }
}


Tx es el objeto usado para transmitir y Rx para recibir. Tx lo puede tener cualquier hilo para mandar mensajes mientras que Rx está limitado al hilo principal. Existen varias formas de recibir los mensajes. try_recv no bloquea el hilo de ejecución, por lo que puede usarse en un bucle con if-let. recv es síncrono y bloquea el hilo de ejecución si hace falta. Tx también es Iterable así que podemos leer los mensajes en un bucle for-in.

Mutex y Arc


Cuando queremos compartir memoria en Rust tenemos que recurrir a una combinación de Mutex y Arc.

Mutex provee de gestión del acceso a memoria. Para acceder al dato es necesario hacer lock. No es estrictamente necesario hacer unlock una vez hayamos modificado el dato, pues cuando Rust libere memoria, también hará unlock. Sin embargo, Mutex por sí solo no es suficiente.

Arc (Atomic Reference Counter) permite tener datos con múltiples dueños. Esto funciona con una especie de recolector de basura y es útil porque cuando hablamos de hilos podría darse la situación de que el hilo dueño muriese y un hilo con los datos prestados todavía siguiese vivo. Con clone conseguimos una copia para mover libremente que en realidad referencia al mismo dato en memoria.


use std::thread;
use std::sync::{Mutex,Arc};

fn main(){
let counter = Arc::new(Mutex::new(42));
let mut handles = vec![];

for _ in 0..10 {
let counter = counter.clone();
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();

*num += 1;
});
handles.push(handle);
}

for handle in handles {
handle.join().unwrap();
}

println!("Result: {}", *counter.lock().unwrap());
}


Con esto ya tendremos lo suficiente para manejar concurrencia en Rust. Por supuesto, esto es más complejo que esto y si de verdad quieres aprovechar el potencial al máximo quizá debas revisar la documentación de las traits Send y Sync.

 

 ]]>
https://blog.adrianistan.eu/concurrencia-en-rust Mon, 3 Jul 2017 00:00:00 +0000
Estructuras de control en Rust https://blog.adrianistan.eu/estructuras-control-rust https://blog.adrianistan.eu/estructuras-control-rust if-else y for-in. Ahora veamos la lista entera junto con un operador muy importante en Rust, match.

Condicionales, if, if-else, if-let


En Rust if-else funciona de forma parecida a otros lenguajes, quizá lo único destacable sea que no hacen falta los paréntesis.


fn main(){
let ano = 1998;
if ano > 1975{
println!("Deberías escuchar a Ennio Morricone");
}else if ano > 1600{
println!("Deberías escuchar a Bach");
}else{
println!("Deberías escuchar a tu monje benedictino más próximo");
}
}


En realidad la sintaxis del if no es tan simple, ya que existe una versión vitaminada, pensada para tratar con Option y Result. Se trata de if-let, una construcción tomada de Swift y que sirve para ejecutar una porción de código sólo si el valor existe (en el caso de Option) o no ha habido fallos (con Result).


fn main(){
let url = Some("https://blog.adrianistan.eu");

if let Some(url) = url {
println!("{}",url);
}

let magic_box: Result<String,String> = Ok(String::from("Aquí no hay nada"));
if let Ok(magic_box) = magic_box {
println!("{}",magic_box);
}

let url: Option<&'static str> = None;
if let Some(url) = url {
println!("{}",url);
}

let magic_box: Result<String,String> = Err(String::from("No tienes la llave de la caja"));
if let Ok(magic_box) = magic_box {
println!("{}",magic_box);
}

}


Condicionales, match


match es un potente condicional en Rust basado en concordancia de patrones. Podemos pensar en ello como un switch vitaminado, pero realmente es capaz de hacer muchas más cosas. Como curiosidad, mencionar que ciertos lenguajes de programación como Haskell usan la concordancia de patrones muy a menudo y consiguen obtener un código muy claro.

Para demostrar el uso del match primero vamos a construir una tupla con cuatro valores.


fn buscar(t: (bool,&'static str,&'static str,i32)) -> &'static str{
match t {
(true,"Mike Oldfield","The Bell",1992) => "Gran canción",
(true,"Mike Oldfield",_,1992) => "Tubular Bells II probablemente",
(true,autor,_,_) => autor,
(false,..) => "Disco no existente",
_ => "¿Qué me has pasado exactamente?"
}
}

fn main(){
let tupla = (true,"Mike Oldfield","The Bell",1992);

let busqueda = buscar(tupla);
println!("{}",busqueda);

}


match realiza comparaciones de arriba a abajo y devuelve la expresión después de la flecha. Como os habréis imaginado, al ser una expresión directamente no se pone punto y coma y no hace falta hacer return en la función de ningún tipo.

La primera condición del match es clara: si es exactamente igual la tupla a la descrita se devuelve Gran canción. En la segundo vemos una barra baja. Ya hemos hablado de él, pero vamos a recordarlo. Se trata del operador que usamos cuando algo nos da igual. Similar a un asterisco en la terminal de Linux.

El tercer caso es interesante pues nos permite comprobar concordancia en el primer campo y a su vez nos permite extraer el valor del segundo campo, que queda en la variable autor.

El cuarto caso es para los perezosos, simplemente con que cumpla una condición, el resto ni nos molestamos en escribir. Usando los dos puntos hacemos que todos los false vayan al mismo sitio.

Por último pero no menos importante, tenemos la barra baja. Cualquier tupla que no haya caído antes en otro caso caerá en este. (En este ejemplo no puede llegar a funcionar, pues con los casos anteriores ya hemos cubierto el espectro. El compilador de Rust nos avisa de que no hay posibilidad de que alguna vez se alcance ese código).

Existen más opciones, una barra horizontal | nos servirá para hacer un OR. Si trabajamos con números, 1..10 representa un intervalo en el que caen los valores admitidos.

Por descontando, match no solo funciona en tuplas, sino que tiene una gran variedad de usos.

Bucles, loop y while


Estos bucles funcionan prácticamente igual que en otros lenguajes. loop es un bucle infinito que solo se rompe con break. while tiene una condición que se comprueba al inicio.


fn main(){
let mut n = 0;
loop {
if n > 100{
break;
}
println!("No soy pesado");
n+=1;
}

while n < 200{
println!("No soy pesado");
n+=1;
}
}


Es conveniente usar los bucles while cuando tenemos una bandera o una condición inesperada, pero nunca para recorrer elementos de una lista o similares.

Bucles, for-in


En Rust se toma el ejemplo de lenguajes como Python y se deja atrás el bucle for de C. El bucle for-in recorre todos los elementos de un Iterable. Un Iterable es cualquier cosa que implemente la trait Iterable, y que como en lenguajes como Python y JavaScript con los generadores, puede no ser necesariamente una lista, simplemente algo sobre lo que se pueda iterar. Estos elementos son perezosos y solo se calculan cuando son necesarios, abriendo la posibilidad a listas infinitas. Los vectores son Iterables, veamos como usarlos.


fn main(){
let v = vec!["Haskell","Elm","Python","C++","JavaScript","Rust","Java"];
for s in v{
println!("{}",s);
}
}


Como vemos, es bastante simple. Este programa imprime cada lenguaje de programación por pantalla. El código no es muy complicado.

¿Y si resulta que quiero iterar N veces y no tengo un Iterable a mano? Similar al range de Python, existe una sintaxis compacta y clara.


fn main(){
let n = 100;
for i in 0..n{
println!("{}",i);
}
}


La sintaxis de dos puntos permite iterar en un rango en Z cerrado por la izquierda y abierto por la derecha. Muchas cosas de la librería estándar son iterables y cualquier array o vector puede serlo, así que este será probablemente el bucle que más uses.

Con esto acabamos las estructuras de control, listos para entrar en temas más avanzados.

 ]]>
https://blog.adrianistan.eu/estructuras-control-rust Mon, 3 Jul 2017 00:00:00 +0000
Structs, traits y POO en Rust https://blog.adrianistan.eu/structs-traits-poo-rust https://blog.adrianistan.eu/structs-traits-poo-rust

¿Es Rust orientado a objetos?


La respuesta no es unánime. Bajo ciertas definiciones lo es, bajo otras no. Lo cierto es que aquí vamos a suponer que lo es, aunque no implementa clases y herencia.

Structs en Rust


Antes de avanzar a los objetos, vamos a ir con los structs o estructuras, similares a las que podemos tener en C. Las estructuras son datos agrupados por clave-valor.


struct Punto{
x: i32,
y: i32
}

struct Rectangulo{
origen: Punto,
ancho: i32,
alto: i32
}

fn main(){
let p = Punto {x: 50, y: 50};
println!("Punto X: {}",p.x);
}


Se crean con la sintaxis de llave. Hasta aquí todo nada raro. Pero en Rust, las estructuras pueden y deben llevar asociado métodos. Estos métodos se definen en las traits. Piensa en ello como si fuesen interfaces. No obstante, algunas traits pueden tener implementación automática. Estas traits las llamaremos derivables. Usando la anotación #[derive()] podemos indicarle a una estructura que derive de una trait. ¿Recuerdas las traits Clone y Copy? Son de este tipo y salvo que queramos cosas más especiales, con hacer una anotación nos bastará. ¿No te parece que Punto es una estructura que debe ser copiada en vez de movida? Pues simplemente añadimos las traits correspondientes.


#[derive(Copy,Clone)]
struct Punto{
x: i32,
y: i32
}

struct Rectangulo{
origen: Punto,
ancho: i32,
alto: i32
}

fn main(){
let p = Punto {x: 50, y: 50};
println!("Punto X: {}",p.x);
}


Así, hemos modificado el comportamiento del struct. Además, hemos ganado métodos en la estructura. Ahora podemos hacer p.clone() y obtener una copia. Esto ha sido gracias a la trait Clone, la cuál es necesaria para los elementos que quieran ser Copy. Esto además significa que podremos pasar la estructura a todas las funciones con genericidad cuya única condición sea que implementemos Copy.

Métodos asociados a la estructura


Hemos visto que Clone ha añadido un método a la estructura, como si fuese una clase de la que hubiérmamos heredado. Nosotros también podemos definir métodos asociados a la estructura. Se hace con la palabra reservada impl.


#[derive(Copy,Clone)]
struct Punto{
x: i32,
y: i32
}

struct Rectangulo{
origen: Punto,
ancho: i32,
alto: i32
}

impl Rectangulo{
pub fn area(&self) -> i32{
self.ancho*self.alto
}
}

fn main(){
let p = Punto {x: 50, y: 50};
println!("Punto X: {}",p.x);
let rectangulo = Rectangulo {origen: p,ancho: 20, alto: 20};
println!("Área: {}",rectangulo.area());
}


Aquí hemos definido el método area asociado a Rectangulo. Es importante destacar que aunque parezca una definición de clase, aquí no hay herencia posible y tampoco hay constructores. El primer argumento de la función area es self, o lo que es lo mismo, una referencia a la propia estructura.

Además vemos pub, lo que indica que es un método público.

Como hemos dicho, hay ciertas traits que se autoimplementan, pero otras requieren de código de integración. Vamos a implementar dos traits en Rectangulo, PartialOrd para las comparaciones y std::fmt::Display para definir que se debe mostrar cuando hagamos println! a Rectangulo. PartialOrd a su vez requiere PartialEq presente.


use std::cmp::Ordering;

#[derive(Copy,Clone)]
struct Punto{
x: i32,
y: i32
}

struct Rectangulo{
origen: Punto,
ancho: i32,
alto: i32
}

impl Rectangulo{
pub fn area(&self) -> i32{
self.ancho*self.alto
}
}

impl PartialEq for Rectangulo{
fn eq(&self,other: &Rectangulo) -> bool{
self.area() == other.area()
}
}

impl PartialOrd for Rectangulo{
fn partial_cmp(&self, other: &Rectangulo) -> Option<Ordering>{
if self.area() == other.area() {
Some(Ordering::Equal)
}else if self.area() > other.area() {
Some(Ordering::Greater)
}else{
Some(Ordering::Less)
}
}
}

impl std::fmt::Display for Rectangulo{
fn fmt(&self,f: &mut std::fmt::Formatter) -> std::fmt::Result{
write!(f,"Origen: ({},{}) - Area: {}",self.origen.x,self.origen.y,self.area())
}

}

fn main(){
let p = Punto {x: 50, y: 50};
println!("Punto X: {}",p.x);
let r1 = Rectangulo {origen: p,ancho: 20, alto: 20};
println!("{}",r1);
let r2 = Rectangulo {origen: Punto{x: 3, y: 4}, ancho: 30, alto: 30};
println!("{}",r2);
if r1 < r2 {
println!("r2 es más grande");
}

}


Aquí ya estamos viendo código Rust en estado puro. Sin embargo todavía no hemos visto como crear traits nosotros mismos.

Traits


Una trait es similar a una interfaz y deben de ser usadas mediante la composición, no la herencia. Piensa como si el objeto estuviese hecho con piezas de puzzle. Unas le dan una funcionalidad, otras otra y junt con código propio, se consigue crear un objeto completo. Así funciona la POO en Rust.

Imagina que existe la trait Pintable. Podremos crear métodos que admitan mediante genericidad restringida solo a objetos que implementen Pintable. Y si queremos que Rectangulo sea uno de ellos solo hay que implementar las funciones de Pintable sin definir. Pero Pintable puede incluir métodos ya hecho que funcionan con independencia del objeto en cuestión. Esos métodos pueden ser reescritos si creemos conveniente.


impl Pintable for Rectangulo{
fn pintar(&self) {
// pintar rectangulo
}
}

trait Pintable{
fn pintar(&self);
fn limpiar(){
// limpiar pantalla
}
}


Con esto acabamos un capítulo muy importante del tutorial. En el siguiente hablaremos de algo que ya se ha dejado ver como es Option y Result y trataremos la gestión de errores.

 ]]>
https://blog.adrianistan.eu/structs-traits-poo-rust Mon, 3 Jul 2017 00:00:00 +0000
Gestión de errores en Rust, Option y Result https://blog.adrianistan.eu/gestion-errores-rust-option-result https://blog.adrianistan.eu/gestion-errores-rust-option-result
El uso de NULL puede traer muchas consecuencias negativas, pero como el propio Hoare dijo: era tan fácil de implementar que no pude resistirme. Además, usarlo es fácil. Existen lenguajes sin NULL desde hace tiempo pero su uso no es muy frecuente y ciertamente, añaden complejidad a la programación. Rust soluciona esto con dos objetos muy sencillos de utilizar, Option y Result.

Option y Result


Ambos son objetos que sirven para llevar cualquier otro valor. Hemos de desempapelarlos para sacar el verdadero valor, si es que existe. Option en realidad es un enum con dos posibles valores: Some y None. Result también es un enum con dos posibles valores: Ok y Err.

¿Cuándo usar Option y cuándo usar Result?


Esta duda es muy frecuente ya que funcionan de manera muy similar. La regla general es que Result tiene que usarse con errores y si no se tratan, cerrar el programa, mientras que Option lleva cosas que pueden estar o no, pero su inexistencia no conlleva un error.

Uso


Option es algo más sencilla de usar.


let opt: Option<i32> = Some(42);
let opt: Option<i32> = None;


En Result hay que indicar el tipo del valor correcto y del valor de error que se comunique.


let operacion_peligrosa: Result<i32,String> = Ok(42);
let operacion_peligrosa: Result<i32,String> = Err(String::from("La operación ha fallado"));


if-let


Option y Result pueden integrarse con estructuras de control para un manejo idomático de estas situaciones. Un ejemplo es if-let. Imagina un perfil de una aplicación web. Hay campos obligatorios, como usuario y contraseña, pero otros no, como la página web.


struct Perfil{
username: String,
password: String,
url: Option<String>
}

impl Perfil{
fn new(u: String, p: String) -> Self{
Perfil {username: u, password: p, url: None}
}
}

fn main(){
let mut p1 = Perfil::new(String::from("aarroyoc"),String::from("1234"));
let mut p2 = Perfil::new(String::from("The42"),String::from("incorrect"));

p1.url = Some(String::from("https://blog.adrianistan.eu"));

for perfil in [p1,p2].iter() {
let url = perfil.url.clone();
if let Some(url) = url{
println!("URL: {}",url);
}
}

}


try!


try! es una macro que permite manejar con mayor claridad las operaciones que puedan generar muchos errores. try! solo se puede usar en funciones que devuelvan Result. Básicamente, try! devuelve inmediatamente el Err correspondiente si la operación resultante no ha sido exitosa, permitiendo una salida antes de tiempo y un código mucho más limpio.


fn peligro() -> Result<i32,String>{
Err(String::from("Operación inválida"))
}

fn funcion_error() -> Result<i32,String>{
let n = try!(peligro()); // aquí ya se sale de la función
Ok(n)
}

fn main(){
let n = funcion_error().unwrap();

}


La macro try! es tan usada dentro de Rust que dispone de azúcar sintáctico, el símbolo de interrogación ?.


fn peligro() -> Result<i32,String>{
Err(String::from("Operación inválida"))
}

fn funcion_error() -> Result<i32,String>{
let n = peligro()?;
Ok(n)
}

fn main(){
let n = funcion_error().unwrap();
}


 

Unwrap


Vamos a observar las operaciones de desempapelado que existen para Option y Result. La más básica es unwrap. Intenta desempapelar el valor, si resulta que el Result/Option era un Err/None, el programa crashea.

unwrap_or intenta desempapelar y si el valor no existe, se sustituye por el valor indicado en el or.

unwrap_or_else es similar a unwrap_or pero toma una función como parámetro. Así es posible crear una cadena de intentos.


fn main(){
// let n = function_error().unwrap(); crash
let n = function_error().unwrap_or(7);
let n = funcion_error().unwrap_or_else(|t|{
7
});
println!("{}",n);
}


Existen muchas más opciones, como and, expect o iterar sobre un Option/Result.

panic!


Si nos gustan los crasheos (!) podemos forzarlos con la macro panic!. Crashea el programa con el mensaje que indiquemos


fn main(){
panic!("Adiós muy buenas");
}


Y con esto hemos visto lo suficiente del manejo de errores en Rust.]]>
https://blog.adrianistan.eu/gestion-errores-rust-option-result Mon, 3 Jul 2017 00:00:00 +0000
Referencias y préstamos en Rust https://blog.adrianistan.eu/referencias-prestamos-en-rust https://blog.adrianistan.eu/referencias-prestamos-en-rust Rust 101, tutorial de Rust en español vamos a adentrarnos en un concepto clave para entender y programar en Rust. Rust es un lenguaje seguro como hemos dicho, esto implica que no puede haber NULLs ni tampoco fugas de memoria. Es decir, tiene que gestionar la memoria de forma eficiente pero dejarla directamente al programador, ya que son una fuente de bugs peligrosa. Algunos lenguajes disponen de recolector de basura (GC, Garbage Collector), que reduce la eficiencia del lenguaje. Go, Swift o Java usan GC. Luego existen lenguajes que dejan la memoria al descubierto, como C y C++. Rust toma un enfoque diferente, ya que no deja la memoria al descubierto ni usa GC. Para ello el compilador realiza una tarea de dueños y préstamos que veremos a continuación.

Las reglas



  1. Cada valor en Rust tiene una variable que es su dueña

  2. Un valor solo puede tener un dueño a la vez

  3. Cuando el dueño desaparece, el valor lo hace a su vez, de forma automática


Así que cuando una variable desaparece del entorno, el dato del que era dueño es liberado. Estas reglas vistas así permiten poca flexibilidad en la programación. Es por ello que los dueños pueden prestar los valores.

La trait Copy y la trait Clone


En primer lugar hay que distinguir distinto comportamiento según el tipo de un valor. Si implementan la trait Copy (la mayoría de tipos primitivos), entonces su comportamiento por defecto es de copia. Son datos en los que la copia es barata y rápida y no influye que existan varias copias de lo mismo. Cualquier cosa que no requería malloc en C puede ser Copy en Rust. Se trata de valores que se almacenan en el stack.

Otra trait es Clone, que permite hacer copias de datos más complejos, por ejemplo de un vector. Veamos un ejemplo de un código sin Copy pero con Clone.


fn main(){
let s1 = String::from("Adios - Xavier Cugat");
let s2 = s1;
println!("{}",s1); // ERRROR

}


Este código da error porque el tipo String no implementa Copy. Entonces la línea let s2 = s1; lo que ha hecho en realidad ha sido mover el valor. Mover significa que le ha transferido el ser dueño del valor de la cadena de texto a s2. Por tanto s1 ya no es dueña del valor y no puede operar con él. Esto pasa en los tipos que no implementan Copy, que transfieren la propiedad a otra variable. Si queremos hacer una copia real, tendremos que recurrir al clonado. El tipo String implementa Clone así que es posible generar otro dato String exactamente igual pero independiente al original.


fn main(){
let s1 = String::from("Adios - Xavier Cugat");
let s2 = s1.clone();
println!("{}",s1);

}


Implicaciones


Esto tiene una implicación sorprendente y es que pasar una variable tal cual a una función si no es del tipo Copy implica que ¡perdemos el acceso a ese valor! Pongamos un ejemplo:


fn main(){
let s1 = String::from("Bolero - Maurice Ravel ");
f(s1);
println!("{}",s1);
}


Este código da error. Al hacer la llamada a la función f hemos transferido la propiedad del valor de s1 a f. Por ello, cuando intentamos hacer el print no vamos a poder ya que s1 ya no es dueña de la cadena de texto. Para solucionar estos problemas tenemos los préstamos.

Prestando en Rust


Rust permite prestar los valores de los que una variable es dueña de dos maneras: solo lectura o con escritura. Siempre ha de cumplirse la norma de que solo puede haber una con permiso de escritura pero puede haber infinidad con permiso de lectura. Y nunca se pueden dar las dos cosas a la vez.

El préstamo de lectura se realiza con el operador &, que lo que hace es una referencia de lectura al valor. La variable sigue siendo sueña así del valor, solo lo ha prestado y entrega una referencia.


fn main(){
let s1 = String::from("Bolero - Maurice Ravel ");
f(&s1);
println!("{}",s1);
}


Este código ya sí funciona y podemos ver en pantalla Bolero - Maurice Ravel.

Si queremos hacer el préstamo en modo escritura, hemos de usar &mut. Sin entrar muchos detalles, así funcionaría un préstamo de escritura.


fn f(s: &mut String){
s.push_str(" & Adios - Xavier Cugat");
}

fn main(){
let mut s1 = String::from("Bolero - Maurice Ravel");
f(&mut s1);
println!("{}",s1);
}


Así tenemos por pantalla la frase Bolero - Maurice Ravel & Adios - Xavier Cugat.

Este tipo de sintaxis, a priori compleja, nos ayudará mucho con la concurrencia pero de momento ya hemos visto suficiente.

 ]]>
https://blog.adrianistan.eu/referencias-prestamos-en-rust Mon, 3 Jul 2017 00:00:00 +0000
Tests en Rust https://blog.adrianistan.eu/tests-en-rust https://blog.adrianistan.eu/tests-en-rust #[test].

Definiendo una función de test


Una función de test es cualquiera que lleve la directiva #[test]. Normalmente, estos tests se dejan dentro de un módulo con la directiva #[cfg(test)].


fn suma(a: i32,b: i32) -> i32{
a+b
}

#[cfg(test)]
mod test{

#[test]
fn suma(){
assert_eq!(super::suma(4,5),9);
}
}


La macro assert_eq! provocará un panic! si sus argumentos no coinciden. También es posible hacer fallar los tests llamando a panic! manualmente. ¿Cómo se ejecutan estos test te preguntarás? Sencillo, con cargo test. Automáticamente Cargo selecciona las funciones de test y las ejecuta todas, generando un informe de tests existosos y tests que han fallado.

Obviamente, existen más opciones dentro de los tests. assert! comprobará que una expresión sea true. #[should_panic] se deberá indicar en aquellas funciones de test en lo que lo correcto sea que ocurra un panic!. Por otro lado, la trait Debug es interesante.

Es posible ejecutar tests de forma manual, con cargo test NOMBRE_TEST.

 ]]>
https://blog.adrianistan.eu/tests-en-rust Mon, 3 Jul 2017 00:00:00 +0000
Documentación con rustdoc https://blog.adrianistan.eu/documentacion-con-rustdoc https://blog.adrianistan.eu/documentacion-con-rustdoc rustdoc.

Comentarios en Markdown


Los comentarios de rustdoc empiezan con 3 barras y pueden llevar Markdown. Se escriben encima de la función, estructura, etc que describamos.


/// Perfil almacena los datos del perfil de un usuario en nuestra webapp
struct Perfil{
username: String,
password: String,
url: Option<String>
}

impl Perfil{
/// Genera un nuevo Perfil
/// # Ejemplo
/// ```
/// let user = Perfil::new("The42","1234");
/// ```
pub fn new(u: &str, p: &str) -> Perfil{
Perfil {username: String::from(u), password: String::from(p), url: None}
}
}


Mencionar que el código del comentario es sometido a tests también por cargo test. Para generar la documentación basta con escribir cargo doc y Cargo generará la documentación en formato HTML.

Consultando documentación


El mejor sitio para leer la documentación de Rust es Docs.rs. Docs.rs ejecuta cargo doc a todas las crates de Crates.io y las expone al público sin costo.



 ]]>
https://blog.adrianistan.eu/documentacion-con-rustdoc Mon, 3 Jul 2017 00:00:00 +0000
Variables y tipos de datos en Rust https://blog.adrianistan.eu/variables-tipos-datos-rust https://blog.adrianistan.eu/variables-tipos-datos-rust Rust 101, el tutorial de Rust en español, y ahora vamos a ver como trabaja Rust con las variables. En este aspecto no se distingue demasiado de otros lenguajes imperativos, aunque vamos a ver que la inferencia de tipos nos va a ayudar. También vamos a ver tipos de datos más complejos.

Declaración de variables


En Rust las variables de declaran usando la palabra reservada let. El tipo de la variable puede inferirse pero en ocasiones podemos ayudar al compilador indicando el tipo del que queramos que sea la variable.


fn main(){
let x = 42;
let url = "http://adrianistan.eu";

println!("{}",url);
}


Sin embargo hay una diferencia importante respecto a otros lenguajes y es que aquí las variables serán por defecto inmutables. Tratar de hacer esto:


x += 5;


causará un error de compilación. No obstante, esta limitación puede saltarse aplicando el modificador mut. Aunque las variables puedan volverse mutables esto ya nos avisa de que Rust tiene muy presente conceptos de programación funcional más pura (como podemos tener en Haskell, Elm, F# u OCaml).


fn main(){
let mut x = 42;
x += 5;
println!("{}",x);
}

Para espeficicar un tipo usamos una notación de dos puntos después de <strong>let</strong>.


fn main(){
let x: i32 = 5; // int 32 bits
let y: f64 = 5; // float 64 bits
}


Arrays y vectores


Hasta ahora hemos podido ver en acción variables de tipos de datos más o menos primitivos. Ahora vamos a ver dos estructuras de datos similares, aunque diferentes. Digamos para simplificar, que los arrays tienen tamaño fijo y los vectores no.


fn main() {
let mut notas_array: [i32;5] = [0;5];
notas_array[0] = 1;
notas_array[1] = 6;

let mut notas_vec: Vec<i32> = vec!();
notas_vec.push(1);
notas_vec.push(6);

println!("Nota 2: {}",notas_array[1]);
println!("Nota 2: {}",notas_vec[1]);
}


En este caso inicializamos un array todo a cero de 5 posiciones y también un vector. He dejado las marcas de los tipos, aunque no son necesarias.

Constantes, let shadowing y casting


Rust soporta constantes con la palabra reservada const. La convención es usar SCREAMING_SNAKE_CASE para las constantes. El valor de una constante es inmutable y a diferencia de let, no se puede reescribir. ¿Espera, las variables con let se pueden reescribir? En efecto, es posible definir una variable posteriormente, de tipo totalmente distinto con el mismo nombre. En este ejemplo vamos a ver el uso de const y el uso de let shadowing.


const PI: f64 = 3.14;

fn main(){
let x = 42;
let x = (x as f64) + PI;
println!("{}",x);

}


Además podemos apreciar como se realiza casting en Rust. Se usa la palabra reservada as y se indica el tipo al que queremos hacer cast. En este ejemplo de let shadowing podemos apreciar como no solo hemos redefinido x, sino que lo hemos hecho de otro tipo. En la primera línea, x es de tipo i32, en la segunda pasa a ser f64.

Tuplas


Las tuplas pueden resultar algo novedoso a quien venga de ciertos lenguajes, pero no son elementos realmente nuevos. Podría entenderse una tupla como una estructura sin nombre. Se trata de una especie de array donde cada elemento puede ser de un tipo, pero espeificado de antemano. Lo veremos mejor con ejemplos.


fn main(){
let tupla = (42,"Adrianistán",true);
let (random,country,has_beers) = tupla;
println!("{}",random);
let (_,country,_) = tupla;
println!("{}",country);
let has_beers = tupla.2;

}


Aquí podemos observar como se forma una tupla, compuesta por un entero, un texto y un booleano. La sentencia con let desestructura la tupla y se obtienen 3 variables con los valores de los 3 campos de la tupla. Es posible que en algunas situaciones no nos importe algún campo. Para ignorar algún campo, dejamos una barra baja. También existe la sintaxis punto para acceder a elementos de la tupla. Para ello indicamos el orden dentro de la tupla del elemento que queremos. No obstante, no es tan claro como desestructurar la tupla.

Expresiones avanzadas con let


let admite cualquier expresión. Esto es una práctica habitual en muchos lenguajes imperativos, sin embargo, las expresiones en Rust son más amplias de lo que vemos a simple vista.


fn main(){
let age = 65;
let x = if age > 17 {
"Mayor de edad"
}else{
"Menor de edad"
};
println!("{}",x);

}


Por ejemplo, una estructura condicional if-else es una expresión. Un ojo avizado podría decir que este código está mal, que falta el punto y coma. Realmente no es así, en Rust si algo no lleva punto y coma se vuelve una expresión. Más bien, al no llevar punto y coma no se vuelve un statement. Esto puede aplicarse a cualquier tipo de código que no deja de ser una expresión.


fn main(){
let age = 65;
let a = 10;
let b = 25;
let x = {
let u = a*b;
u+age
};
println!("{}",x);

}


Y como véis, se puede complicar hasta niveles muy elevados. Al principio puede no ser muy natural, pero es bastante cómodo en muchas situaciones.

Con esto acabamos nuestra visita a las variables.]]> https://blog.adrianistan.eu/variables-tipos-datos-rust Mon, 3 Jul 2017 00:00:00 +0000 Cargo y módulos en Rust https://blog.adrianistan.eu/cargo-modulos-rust https://blog.adrianistan.eu/cargo-modulos-rust Cargo nos ayuda a gestionar las dependencias del proyecto.

Módulos


Los módulos se definen con la palabra reservada mod y por defecto todo es privado. Es necesario indicar pub para hacer esos campos públicos. Con use podemos acortar la manera con la que hacemos referencia a los componentes del módulo. Así pues, use es similar a using namespace de C++.


mod network{
pub fn version() -> String{
String::from("Network 1.0.0")
}
pub mod server{
fn private_thing(){

}
pub fn public_thing(){

}
}
}

fn main() {
println!("{}",network::version());
{
use network::server::public_thing;
public_thing();
}
}


Los módulos resultan una opción más interesante si tenemos múltiples archivos. Por norma general, si un módulo se define en el fichero de entrada del compilador, este se busca en dos sitios:

  • Un fichero nombre_del_modulo.rs

  • Un fichero nombre_del_modulo/mod.rs (opción recomendada para módulos de varios archivos)


Por lo que el código anterior puede sustituirse por esto en el fichero principal:


mod network;

fn main() {
println!("{}",network::version());
{
use network::server::public_thing;
public_thing();
}
}


Y esto en un fichero network.rs


pub fn version() -> String{
String::from("Network 1.0.0")
}
pub mod server{
fn private_thing(){

}
pub fn public_thing(){

}
}


Podemos usar use-as para modificar el nombre con el que se importa una función.


mod network;

fn main() {
println!("{}",network::version());
{
use network::server::public_thing as super_thing;
super_thing();
}
}


 

Crates


El código en Rust se organiza en crates, que son colecciones de módulos que se distribuyen. Piensa en ello como si fuesen gemas de Ruby o librerías de C++. Las crates se pueden compartir con la gente. El mayor repositorio de crates de la comunidad Rust es crates.io.

Las crates se pueden generar con rustc o con cargo, aunque es habitual usar la segunda opción. Vamos a ver primero como usaríamos una crate externa. En este caso voy a usar regex. Sé que en esta parte no vas a saber como descargar regex y usarla, pero voy a explicar el código.


extern crate regex;

use regex::Regex;

fn main() {
let r = Regex::new(r"([A-Za-z0-9])([.]|[_]|[A-Za-z0-9])+@gmail.com").unwrap();
if r.is_match("pucela_segunda@gmail.com") {
println!("Correo válido de Gmail");
}else{
println!("Correo no válido de Gmail");
}
}


Básicamente se usa extern crate para importar una crate externa, en este caso regex. Con use lo único que hacemos es acortar la manera de llamar a las funciones. Si no estuviese podríamos poner perfectamente regex::Regex::new en vez de Regex::new y funcionaría.

Cargo


Cargo es una herramienta que cumple dos funciones dentro del ecosistema Rust. Por un lado es un gestor de dependencias y por otro lado gestiona también las compilaciones.

En su parte de gestor de dependencias nos permite especificar que crates necesita el proyecto para compilar, su versión y si la crate lo soporta, con qué características activadas.

En su parte de gestor de compilaciones, realiza la compilación con rustc y añade las flags pertinentes al compilador. También se le pueden especificar cosas más complejas, aunque en Rust no suele ser habitual tener configuraciones de compilación excesivamente complejas.

Todo lo relativo a Cargo se define en el archivo Cargo.toml. Que tiene una estructura similar a esta:


[package]
name = "super_app"
version = "0.1.0"
authors = ["Adrián Arroyo Calle"]

[dependencies]
regex = "0.2.2"


En la sección dependencies puedes añadir línea por línea la crate que te haga falta. Consulta la documentación de cada crate para esto, pues puede haber variaciones.

Comandos básicos de Cargo


Crear un proyecto con Cargo


cargo new --bin mi_proyecto # si queremos que sea ejecutable

cargo new mi_crate # si queremos que sea una crate

Compilar y ejecutar


cargo run

cargo build # solo compilar

cargo build --release # compilar en modo Release

Cargo automáticamente se encarga de obtener las dependencias en la fase de compilación.

Instalar aplicaciones


cargo install APLICACION

Muchas herramientas de pueden distribuir a través de Cargo. Por ejemplo, Racer, un programa que sirve de autocompletado para IDEs como Eclipse o Visual Studio.

Ejecutar tests


cargo test

Generar documentación


cargo doc

Plugins de Cargo


Cargo es extensible gracias a plugins. Algunos interesantes son Clippy, cargo-audit, rustfmt, ...]]>
https://blog.adrianistan.eu/cargo-modulos-rust Mon, 3 Jul 2017 00:00:00 +0000
Tutorial de CouchDB https://blog.adrianistan.eu/tutorial-couchdb https://blog.adrianistan.eu/tutorial-couchdb CouchDB, una base de datos que no es precisamente nueva ni es precisamente famosa, pero que en mi opinión tiene unas características únicas.



En primer lugar vayamos con la teoría. Apache CouchDB es una base de datos NoSQL que almacena documentos JSON y tiene una potente API REST. Está programada en Erlang y es un proyecto Apache desde 2008. Usa JavaScript para la operativa interna. Visto este resumen uno podría pensar que estamos ante un clon de MongoDB (a pesar de que CouchDB es mucho más antigua) pero no. CouchDB parte de una filosofía distinta, ser la base de datos de la era de la web y se nota mucho como toma decisiones que la alejan de las bases de datos relacionales y de las bases de datos NoSQL más populares.

La API REST


Una parte fundamental de CouchDB es la API REST, que es la única API que dispone. CouchDB de este modo puede funcionar de dos formas:

  • En un modelo tradicional, con una servidor que haga las peticiones. Este servidor puede estar programado en cualquier cosa (Java, PHP, Node.js, C#, Python, Ruby, Elixir,...) y que no es muy distinto a como nos conectamos por ejemplo a MongoDB o a MySQL.

  • En un modelo innovador donde la base de datos está conectada directamente a Internet y no hace falta ningún servidor que gestione la comunicación. Este modelo da lugar a las CouchApps, aplicaciones CRUD pero sin servidor propiamente dicho, solo base de datos y un alojamiento estático de archivos. A través de JavaScript en el lado del cliente podemos obtener la información, conectándose directamente el navegador con la base de datos.


La posibilidad de hacer esto último es una de las mayores ventajas de CouchDB en mi opinión, ya que según el proyecto, esto puede ahorrar mucho tiempo y prevenir muchos posibles bugs en operaciones tontas. El desafío de este artículo será crear una aplicación en CouchDB que pudiera simular a una aplicación CRUD cualquiera. Esta aplicación reducirá costes respecto a una tradicional ya que eliminamos la parte del servidor, solo mantenemos la base de datos (y un proxy).

Instalando CouchDB


CouchDB está en casi todas las distros Linux. Es posible que no esté disponible la última versión, pero para lo que vamos a hacer no es necesario. Yo voy a usar CouchDB 1.4, una versión bastante antigua, pero es la que tiene Debian en sus repos. En Ubuntu tenéis CouchDB 1.6 y en la página oficial está la versión 2.0

Creando una base de datos


En CouchDB las bases de datos son cubos sin fondo. Podemos arrojar documentos JSON sin control.

Vamos a usar la API REST para crear la base de datos "supermercado".


curl -X PUT http://127.0.0.1:5984/supermercado


Podemos comprobar que la base de datos ha sido creada con GET


curl -X GET http://127.0.0.1:5984/supermercado


En CouchDB ciertas rutas que comienzan por barra baja son especiales. Por ejemplo si queremos pedir una lista de todas las bases de datos podemos hacer GET a _all_dbs.


curl -X GET http://127.0.0.1:5984/_all_dbs




Para borrar la base de datos usaríamos DELETE


curl -X DELETE http://127.0.0.1:5984/supermercado


Pero no lo hagas todavía. Si has pensado en lo que hemos hecho te estarás alarmando. Hemos creado una base de datos, por HTTP y no se nos ha pedido ninguna autorización ni nada. Por defecto CouchDB es inseguro ya que arranca en un modo llamado Admin Party, pero rápidamente veremos que si vamos a exponer CouchDB a Internet vamos a necesitar seguridad, y CouchDB la trae. Pero antes, vamos a trabajar con documentos.

Insertando un documento


Insertar un documento es tan fácil como tirar documentos a la papelera (bueno, quizá no tan fácil).


curl -X PUT http://127.0.0.1:5984/supermercado/UUID -d '{"nombre": "Manzana", "tipo" : "fruta", "precio" : 5}'


Donde UUID es un UUID. Si no tienes un UUID a mano, CouchDB te ofrece uno a través de _uuids.


curl -X GET http://127.0.0.1:5984/_uuids?count=10


En realidad no es estrictamente necesario poner un UUID, pero es la convención habitual. Una vez hagamos el PUT obtendremos de respuesta un JSON con tres campos muy importantes. Ok para decirnos si la operación fue bien o no, _id, con el ID del documento (es el UUID de antes) y el _rev. Este último campo, nos indica con que versión del documento hemos interactuado, en nuestro caso, era la primera así que hay un 1 delante. Esto es muy importante en CouchDB y tiene que ver con como gestiona la concurrencia. CouchDB es eventualmente consistente. Quiere decir que en una situación de escritura y lectura simultáneas, la base de datos no se bloquea sino que puede mandar distintas versione de los documentos. Cuando editemos un documentos tendremos que indicar  sobre que versión hacemos la modificación, y si algún otro cliente se nos adelantó y modificó el documento antes, tendremos que basarnos en su revisión para que sea aceptada por la base de datos. Hay que tener que CouchDB no es un sistema de control de versiones, las revisiones antiguas pueden desaparecer sin previo aviso. Esta característica, la de ser eventualmente consistente, también facilita mucho su desempeño en clústeres, pero no vamos a entrar en ello. De momento hay que saber que en CouchDB, a diferencia de otras bases de datos, para actualizar un documento necesitaremos su ID y su revisión.

Para ver el documento conociendo su ID hacemos un simple GET


curl -X GET http://127.0.0.1:5984/supermercado/UUID


Nos devuelve el documento entero junto con las propiedades _id y _rev.

Actualizando un documento


El precio de las manzanas ha cambiado, vamos a actualizarlo. El proceso es muy simple, es como insertar un documento pero añadimos la revisión sobre la que queremos hacerlo. Es importante mencionar que CouchDB no preserva nada en las actualizaciones. Si quieres dejar campos sin tocar, tendrás que volver a subirlos.


curl -X PUT http://127.0.0.1:5984/supermercado/98c003b03bc8aa87cb05983d1c000713 -d '{"_rev": "1-eba25568090eb2dfffad770b55147a67","nombre": "Manzana", "tipo" : "fruta", "precio" : 4}'


Para borrar documentos necesitas usar DELETE indicando el número de revisión.


curl -X DELETE http://127.0.0.1:5984/supermercado/98c003b03bc8aa87cb05983d1c000713?rev=2-298fdb46385be60609b242b3e5cc3566


(pero no lo hagas)

Vistas


Todo muy bonito, pero, ¿cómo accedo a la información? En SQL usaríamos SELECT, en bases como MongoDB lanzaríamos un find. En CouchDB hay que usar vistas, que se programan en JavaScript y siguen un esquema MapReduce (que recordemos, funciona muy bien en paralelo). Esto se puede hacer de varias formas. Se puede usar Fauxton, una interfaz web de CouchDB, se puede usar Erica, una herramienta para facilitar la creación y mantenimiento de CouchApps o se puede hacer con la API REST, ya que las vistas se definen en documentos JSON también.

Tenemos que crear un Design Doc. Los design doc son muy útiles y permiten por ejemplo validar documentos que vayan a entrar en la base de datos (podemos definir schemas así) o definir las vistas. Un design doc simple es así. Definimos una vista llamada all con una operación map.

 


{
"views" : {
"all" : {
"map" : "function(doc){ emit(null,doc); }"
}
}
}


Si lo tenemos guardado en un archivo JSON


curl -H "Content-Type: application/json" --data @super.json -X PUT http://127.0.0.1:5984/supermercado/_design/super


Ahora vamos a ejecutar la vista


curl -X GET http://127.0.0.1:5984/supermercado/_design/super/_view/all


Obtenemos un objeto JSON con todos los documentos que contiene la base de datos supermercado. Por supuesto es posible crear distintos documentos de diseño, con distintas vistas cada uno.

Las vistas solo las debería poder crear el administrador de la base de datos, por lo que al contrario que en otras bases de datos, el cliente no podrá realizar operaciones que no hayan sido definidas antes. En realidad, no es del todo cierto, ya que en CouchDB 2.0 se añadió Mango, un lenguaje de consulta declarativo que permite realizar cierta operativa sin usar las vistas, pero no es tan potente.

Otro ejemplo:


{
"views" : {
"by-price" : {
"map" : "function(doc){ emit(doc.precio,doc); }"
}
}
}


Esta vista puede ser llamada con parámetros


curl -X GET http://127.0.0.1:5984/supermercado/_design/super/_view/by-price?descending=true&amp;limit=1


CouchDB realiza la operación de ordenado por su cuenta con el valor del key que devolvemos.

La base de datos en el salvaje oeste


Ahora vamos a ver como exponer CouchDB al salvaje Internet sin comprometer seguridad. En primer lugar toca hablar de los usuarios. Como hemos dicho, CouchDB arranca en el modo Admin Party. Este modo será desactivado en cuanto creemos un usuario como admin. CouchDB soporta además usuarios tradicionales, una característica muy útil que veremos después como usar.

Para crear un usuario como admin lanzamos esta petición:


curl -X PUT http://127.0.0.1:5984/_config/admins/aarroyoc -d '"MiPassword"'


A partir de ahora ya no estamos en una admin party y si intentamos crear una base de datos veremos que CouchDB nos deniega el acceso. Sin embargo, otras tareas como subir documentos a bases de datos ya existentes todavía funcionan.

Para protegernos mejor debemos saber los tipos de autorización que soporta CouchDB. En primer lugar soporta HTTP Basic Auth, un método en el que mandamos el usuario y la contraseña en texto plano en cada petición. Esto no es para nada seguro, pero combinado con SSL no es mala opción. Sin embargo sigue sin ser la mejor solución para los expertos en seguridad. CouchDB también acepta autenticación por cookies. Las cookies duran 10 minutos por defecto y se generan haciendo una petición a _session con formato de formulario HTTP.


curl -vX POST http://127.0.0.1:5984/_session -H "Content-Type:application/x-www-form-urlencoded" -d "name=aarroyoc&amp;password=MiPassword"


Que nos devuelve una cookie válida para operar en la base de datos

Existen plugins que permiten gestionar desde CouchDB autorizaciones más diversas, como por ejemplo OpenID o OAuth2, pero pueden complicarnos mucho el desarrollo.

Los usuarios normales se guardan en la base de datos especial _users y se les puede asignar roles, con permisos para cada base de datos. Por defecto, se pueden crear cuentas de usuario nuevas de forma tan simple como así:


curl -X PUT http://127.0.0.1:5984/_users/org.couchdb.user:USUARIO -d '{"name" : "USUARIO", "password" : "MiPassword", "type" : "user", "roles" : []}'


Es importante seguir el esquema org.couchdb.user:USUARIO para los documentos. Los roles solo los puede ajustar una cuenta de admin, así que en las peticiones anónimas deberá ir vacío.

Si en algún momento quieres saber cuántos usuarios tiene la base de datos, puedes usar la vista predefinida _all_docs.


curl -X GET http://usuarioadmin:contraseñaadmin@127.0.0.1:5984/_users/_all_docs


Estos documentos de usuarios por defecto son privados pero podemos mostrar cierta información para el resto de usuarios, sobretodo si añadimos campos extra a los documentos


curl -X PUT http://usuarioadmin:contraseñaadmin@127.0.0.1:5984/_config/couch_httpd_auth/public_fields -d '"name"'


Hará visible el campo name a todas las peticiones GET anónimas a ese documento de usuario.

Accesos a la base de datos


Ahora vamos a ver como controlar las lecturas y escrituras de una base de datos en concreto. Aquí creo que CouchDB tiene limitaciones y que debe ser algo en lo que enfocarse en futuras versiones pues es un control muy limitado. Si fuese una base de datos interna no sería mucho problema, pero teniendo en cuenta que va a estar expuesta a Internet sería necesario algo más potente. Eso no quiere decir que no se puedan hacer cosas, pero vamos a tener que tirar de funciones JavaScript internas.

Por un lado, tenemos un documento especia en cada base de datos llamado _security. Este contiene listas de admins y miembros. Las listas pueden estar vacías, contener usuarios o contener roles. En caso de que la lista de miembros este vacía se considera que todos los usuarios son miembros (incluido los anónimos). Los miembros pueden leer y escribir todos los archivos. Esto es muy insuficiente. Por poner un caso completo, yo necesitaba:

  • Que todos los usuarios pudieran leer los documentos (incluido anónimos)

  • Que todos los usuarios registrados pudieran crear documentos

  • Que solo se pudiesen modificar los documentos creados por cada usuario


Esto se puede hacer si obligamos a los documentos a que especifiquen un dueño. Todo lo podemos hacer en esta función:


function (new_doc, old_doc, userCtx){
if(!userCtx.name)
throw({forbidden: "Not authorized"});

if(!new_doc.owner)
throw({forbidden: "Plase, set an owner"});

if(new_doc.owner != userCtx.name)
throw({forbidden: "Owner in document should be the same as user"})

if(old_doc!=null)
if(old_doc.owner != userCtx.name && userCtx.roles.indexOf("_admin") < 0)
throw({forbidden: "Not your document"});
return;
}


Esta función puede ser subida a CouchDB con la API HTTP dentro de un design doc. El design doc quedaría así:


{
"_rev" : "7-670f7428b5a5afb25ec61382024f0733",
"views" : {
"all" : {
"map" : "function(doc){ emit(doc.name,doc); }"
},
"by-price" : {
"map" : "function(doc){ emit(doc.precio,doc); }"
}
},
"validate_doc_update":"function (new_doc, old_doc, userCtx){\n if(!userCtx.name)\n throw({forbidden: \"Not authorized\"});\n\n if(!new_doc.owner)\n\tthrow({forbidden: \"Plase, set an owner\"});\n\n if(new_doc.owner != userCtx.name)\n throw({forbidden: \"Owner in document should be the same as user\"})\n\n if(old_doc!=null)\n if(old_doc.owner != userCtx.name && userCtx.roles.indexOf(\"_admin\") < 0)\n throw({forbidden: \"Not your document\"});\n return;\n} \n"
}



Por supuesto, en _rev irá la revisión que toque. Este sistema es más potente de lo que parece ya que podemos controlar todos los detalles de la operación. Es por ello que muchas veces la función validate_doc_update es la más compleja de los documentos de diseño. Para ayudarme un poco, estoy usando Node.js para leer archivos JavaScript y pasarlos a una cadena JSON válida.

CORS y SSL


Vamos a lanzar CouchDB a Internet. En primer lugar, un par de detalles. CouchDB idealmente vivirá en un dominio separado a la web estática. Para que las peticiones JavaScript funcionen hay que activar CORS. También vamos a proteger los datos así como los usuarios y contraseñas transmitidos. Todo esto podemos hacerlo del tirón con nginx:

 


server {
listen 443 ssl http2;
ssl_certificate /etc/letsencrypt/live/alejandria.adrianistan.eu/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/alejandria.adrianistan.eu/privkey.pem;
server_name alejandria.adrianistan.eu;
location / {
add_header Access-Control-Allow-Origin *;
proxy_pass http://localhost:5984;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Ssl on;
}

location /_utils/ {
return 404;
}
}


¡Listo! Ya tenemos una instancia de CouchDB directamente conectada a Internet. Como podéis comprobar es una alternativa muy interesante en muchos casos. Sin embargo, ciertas cosas son un poco diferentes y más complejas de realizar. Mencionar que CouchDB también es posible usarse con un servidor de por medio en un esquema tradicional. En este caso, podemos usar la cuenta de admin para crear bases de datos personales para cada usuario. Este enfoque es necesario si necesitamos que los datos sean privados o queremos afinar más que roles tienen acceso a una base datos. CouchDB anima a crear tantas bases de datos como necesitemos, varias por usuario si es necesario, para satisfacer nuestros requerimientos. Esto no tiene gran impacto en el rendimiento tal y como está diseñado.

Puede hacerse de forma ligera, con una pequeña app que gestione el alta de usuarios y una vez creada la base de datos y ajustados los permisos se puede usar la API REST de CouchDB desde el lado del cliente con total normalidad, con protecciones de lectura y escritura más precisas.

Este modelo que hemos presentado es ideal para aplicaciones cuya base de datos principal sea pública y accesible y con unos pequeños ajustes puede adaptarse a muchas situaciones. Espero que os haya gustado el artículo. Nos hemos dejado cosas como la replicación entre nodos, los ficheros binarios o attachments ydos complementos a las vistas llamados show y lists, que permiten renderizar HTML o XML entre otras cosas, con los datos de salida de una vista si es necesario.

 ]]>
https://blog.adrianistan.eu/tutorial-couchdb Thu, 15 Jun 2017 00:00:00 +0000
WebAssembly para tontos (usando Rust) https://blog.adrianistan.eu/webassembly-tontos-usando-rust https://blog.adrianistan.eu/webassembly-tontos-usando-rust Este post usa Emscripten para generar WebAssembly con Rust. Hoy día Emscripten no es necesario, pero no he podido actualizar el tutorial
Una de las cosas que más me han sorprendido del mundo web en estos años fue el proyecto WebAssembly. Un proyecto que pretendía traer un bytecode unificado para navegadores. Un proyecto que permitiría compilar prácticamente cualquier lenguaje a la web sin necesidad de tocar JavaScript.

El proyecto surgía de iniciativas fracasadas de Google (PNaCl) y de Mozilla (asm.js). Pero a este proyecto se unieron Microsoft y Apple, por lo que la compatibilidad estaba asegurada.

WebAssembly es un bytecode (como el de Java o el de .NET) que puede ser ejecutado por un navegador, cada navegador implementa su máquina virtual. También es posible usarlo en otros entornos relacionados con el mundo JavaScript como Node.js. Sin embargo entre los objetivos de WebAssembly está no estar atado a JavaScript, por lo que la especificación puede ser implementada por cualquier otro tipo de entorno. Actualmente WebAssembly no tiene recolector de basura y no tiene acceso directo a las Web API. No obstante, sigue siendo un proyecto interesante. Vamos a ver como usar WebAssembly con Rust.

Instalando Rust y Emscripten


Instala Rust, la versión estable es compatible con lo que queremos. Recomiendo usar Rustup.
curl https://sh.rustup.rs -sSf | sh

El paso clave es instalar un nuevo target, el target de WASM32 (WebAssembly de 32 bits).
rustup target add wasm32-unknown-emscripten

Por supuesto también hace falta instalar Emscripten.

Descarga la versión portable de Emscripten aquí. Descomprime y ejecuta
source ./emsdk_env.sh
emsdk update
emsdk install latest
emsdk activate latest
source ./emsdk_env.sh
emcc -v (para comprobar)

Emscripten ya estará instalado junto con Clang y las piezas claves de LLVM necesarias.

Escribiendo el programa en Rust


Vamos a escribir un programa simple. Un hola mundo.

 
fn main(){
println!("Hola mundo - WebAssembly + Rust");
}

Compilamos con rustc

 
rustc --target=wasm32-unknown-emscripten main.rs -o main.html

Esto genera diversos archivos: main.html, main.js, main.wasm y main.asm.js (para compatibilidad con navegadores que no tienen WebAssembly). El fichero .wasm contiene el bytecode, si intentas abrirlo verás que es ilegible. Sin embargo, Chrome, Firefox, Edge, Safari y Node.js entenderán ese archivo. Probamos el fichero main.html en Firefox (cuya última versión soporta WebAssembly por defecto):


Usando este sistema compilamos aplicaciones enteras. Si se ajustan ciertos parámetros de Emscripten y se usa una crate adecuada en Rust puede usarse para generar juegos 3D usando WebGL escritos 100% en Rust.

Cargas librerías en Rust desde JavaScript


En el paso anterior vimos como compilar a WASM aplicaciones enteras. Ahora vamos a compilar el código de Rust a una librería y vamos a cargarlo con JavaScript.

La librería va a ser muy simple:
#[no_mangle]
pub fn random_number() -> i32 {
42
}

fn main() {

}


Ahora compilamos el fichero a WebAssembly
rustc --target=wasm32-unknown-emscripten random.rs

Ahora vamos a cargar el fichero random.wasm. Para ello usaremos la ayuda de random.js, que contiene el código necesario para cargar el fichero WASM así como definir los imports que el código Rust espera (variables de entorno, funciones globales, etc).

 
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<script>
var Module = {
wasmBinaryFile: "random.wasm",
onRuntimeInitialized: main,
};
function main() {
var random_number = Module.cwrap("random_number","number",[]);
alert(random_number());
}
</script>
<script src="random.js"></script>
</body>
</html>

Usando este sistema, podemos ir añadiendo código WASM poco a poco en nuestras webs.

 

Conclusión


Como vemos, ya es posible hoy día usar WebAssembly en nuestros proyectos. Para crear el código WebAssembly podemos usar Rust. El código WASM puede interactuar con el código JavaScript existente. ¿Qué opináis de WebAssembly? ¿Creéis que puede suponer un antes y un después en el mundo web o se seguirá usando JavaScript de forma masiva?

 ]]>
https://blog.adrianistan.eu/webassembly-tontos-usando-rust Fri, 2 Jun 2017 00:00:00 +0000
Triángulo de Sierpinski en JavaScript https://blog.adrianistan.eu/triangulo-sierpinski-javascript https://blog.adrianistan.eu/triangulo-sierpinski-javascript Chaos Game. El vídeo es bastante interesante y habla de como de una aparente aleatoriedad es posible sacar fractales y patrones regulares. Esta misma mañana al llegar a casa y antes de comer me he picado y me he puesto a implementar el algoritmo en JavaScript usando el Canvas de HTML5. El resultado lo tenéis aquí:


http://adrianistan.eu/sierpinski/


Y el código que lleva es el siguiente:


const COLOR_LIST = ["red","green","yellow","pink","brown","purple","cyan","blue","orange"];

function punto(x,y){
var p = {
x:x,
y:y
};
return p;
}

function puntoMedio(p,q){
var m = {
x: Math.round((p.x+q.x)/2),
y: Math.round((p.y+q.y)/2)
};
return m;
}

function getRandomColor(){
return COLOR_LIST[Math.floor(COLOR_LIST.length * Math.random())];
}

function dibujarPunto(ctx,p,size){
ctx.fillStyle = getRandomColor();
ctx.fillRect(p.x,p.y,size,size);
}

function $(id){
return document.getElementById(id);
}

function get(id){
return Math.round(document.getElementById(id).value);
}

function main(){
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

var interval;

$("start").addEventListener("click",function(){

var size = get("size");
var vertex = [punto(get("a-x"),get("a-y")),punto(get("b-x"),get("b-y")),punto(get("c-x"),get("c-y"))];

let p = punto(get("s-x"),get("s-y"));

dibujarPunto(ctx,p,size);

interval = setInterval(function(){
var q = vertex[Math.floor(3*Math.random())];
p = puntoMedio(p,q);
dibujarPunto(ctx,p,size);
},get("speed"));
});

$("stop").addEventListener("click",function(){
clearInterval(interval);
});

$("reset").addEventListener("click",function(){
ctx.fillStyle = "white";
ctx.fillRect(0,0,600,600);
});
}

window.addEventListener("DOMContentLoaded",main);


Con distinto número de vértices existen otros fractales, también muy chulos. Incluso en el vídeo de Numberphile realizan un fractal con un gran parecido a una hoja de helecho, usando dos triángulos y una ligera modificación del algoritmo.

Un saludo y soñad con fractales.
]]>
https://blog.adrianistan.eu/triangulo-sierpinski-javascript Wed, 24 May 2017 00:00:00 +0000
¿Nos vemos en la LechazoConf? https://blog.adrianistan.eu/nos-vemos-la-lechazoconf https://blog.adrianistan.eu/nos-vemos-la-lechazoconf LechazoConf. Uno de los mayores eventos de informática de la región.



Este evento cuenta con ponencias muy interesantes sobre el éxito y el fracaso (o al menos interesantes sobre el papel). Además, se podrá comer lechazo.

Yo estaré por allí, gracias a una invitación que tengo, así que si quieres: hablarme, comentarme, explicarme, insultarme, agradecerme, increparme, consolarme, abrazarme, besarme, matarme, vacilarme, comer unas patatas juntos, pasear juntos, debatir sobre metafísica, hacer travesuras por la ciudad o simplemente explicarte como conseguir la clave del WiFi o explicarte como instalar Windows sin que parezca que estés haciendo el vago, no dudes en buscarme.

PD: Me llamo Adrián Arroyo, no creo que me lo cambie para cuando sea la Lechazo.

PD2: Aunque nos hagamos muy buenos amigos, recuerda que mi lechazo es mi lechazo y el tuyo el tuyo]]>
https://blog.adrianistan.eu/nos-vemos-la-lechazoconf Mon, 22 May 2017 00:00:00 +0000
Anrokku, un videojuego tipo puzzle https://blog.adrianistan.eu/anrokku-videojuego-tipo-puzzle https://blog.adrianistan.eu/anrokku-videojuego-tipo-puzzle Anrokku es un juego de puzles que he programado estas semanas. Las reglas son simples, somos una ambulancia y tenemos que salir del parking debido a una emergencia. Desafortunadamente el parking es un caos y los coches bloquean la salida. Tu labor es ir moviendo los coches para lograr que la ambulancia salga del parking. Y cuantos menos movimientos hagas mejor.



En el menú principal podremos seleccionar a cuál de los 20 niveles queremos jugar. No podremos jugar a todos a la vez, es necesario habernos pasado los niveles anteriores para desbloquear el siguiente.



Ya en el juego tenemos que ir arrastrando los coches con el ratón para que la ambulancia pueda irse por la derecha. El juego está programando en Python 2.7 usando GTK. Anrokku ahora mismo funciona con Python 3 y GTK 3. El renderizado está hecho en un GtkDrawingArea donde he usado Cairo. ¿Quiéres jugar? El juego completo es gratuito y open source bajo la liencia MIT en GitHub. No obstante, puedes descargar el archivo ZIP con el juego desde este enlace.

Descargar Anrokku


Después de descargar y descomprimir el archivo. Ejecuta el fichero main.py haciendo doble click o desde la terminal:


python3 main.py


¡Quiero ver muchas pantallas como esta!



 El juego almacena los récords obtenidos en cada nivel. Puede ser interesante repetir los niveles para hacerlo en el menor número posible de movimientos. En el primer nivel he conseguido ganar en 32 movimientos, ¿alguien se atreve a superarme?

]]>
https://blog.adrianistan.eu/anrokku-videojuego-tipo-puzzle Mon, 15 May 2017 00:00:00 +0000
Mandando paquetes ICMP ECHO personalizados con Python https://blog.adrianistan.eu/mandando-paquetes-icmp-echo-personalizados-python https://blog.adrianistan.eu/mandando-paquetes-icmp-echo-personalizados-python

ICMP ECHO


La prueba la he hecho con un ICMP ECHO pero podría hacerse con otros protocolos también, como DHCP.

Un ICMP ECHO es lo que hace el famoso comando pingping6 (en IPv6). Se trata de un protocolo de la capa de red (capa 3 en TCP/IP). ICMP dispone de varias operaciones para funcionar correctamente (Internet Control Message Protocol, controlar la red) pero la mayoría de ellas no se utilizan. Una que sí se usa es el ECHO, que permite mandar una información a un host y el host nos tiene que devolver lo mismo.

¿Cómo sabe a donde tiene que enviar la respuesta? A la IP de origen del primer paquete claro, pero ¿y si mentimos? ¿Y si le ponemos que la IP de origen es la IP de broadcast? Supuestamente, enviaría la respuesta a todos los demás hosts de la subred.



Script en Python


Para poder hacer los datagramas IP personalizados y poder mentir, usé raw sockets. Estos solo me han funcionado en Linux. El código es muy simple, la parte que más me costó fue el checksum pues las operaciones con bytes en Python son un poco curiosas, no existiendo el tipo byte pero si bytes.


import socket
import struct
import random
import binascii
import sys
import functools

ICMP_CODE = socket.getprotobyname("icmp")
ICMP_ECHO = 8
IP_SRC = "192.168.0.255"
IP_DST = "192.168.0.255"

def main():
s = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket.IPPROTO_ICMP)
s.setsockopt(socket.SOL_IP,socket.IP_HDRINCL,1)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
packet_id = int((100000* random.random()) % 65535)
packet = create_packet(packet_id)
#print ("Size of packet: "+str(sys.getsizeof(packet)))
s.sendto(packet,(IP_DST,0))

def create_header(data):
version = 4 # IP version
ihl = 5 # Numero de Bytes
DF = 0
Tlen = sys.getsizeof(data) + 20 # Longitud del datagrama
ID = 1774 # ID de paquete
Flag = 0 # Opciones
Fragment = 0 # Numero de fragmento
TTL = 128 # Tiempo de vida
Proto = socket.IPPROTO_ICMP # Protocolo de los datos
ip_checksum = checksum(data) # Checksum de los datos
SIP = socket.inet_aton(IP_SRC) # Source IP address
DIP = socket.inet_aton(IP_DST) # Destination IP address
ver_ihl = (version << 4) + ihl
f_f = (Flag << 13) + Fragment
ip_hdr = struct.pack("!BBHHHBBH4s4s", ver_ihl,DF,Tlen,ID,f_f,TTL,Proto,ip_checksum,SIP,DIP)
return ip_hdr

def checksum(msg):
flag = False
suma = 0
for byte in msg:
if not flag:
suma += (byte << 8)
else:
suma += byte
flag = not flag
resto = suma & 0xFFFF0000
resto = resto >> 8
suma += resto
check = (~suma) & 0x0000FFFF
return check

def create_data(id):
header = struct.pack("bbHHh", ICMP_ECHO, 0, 0, id, 1)
data = b"42"
header = struct.pack("bbHHh", ICMP_ECHO, 0, socket.htons(checksum(header+data)), id, 1)
return header + data

def create_packet(packet_id):
data = create_data(packet_id)
header = create_header(data)
return header+data
while True:
main()



Espero que a alguien le resulte interesante el código. Es un ejemplo de como generar datagramas sin la ayuda del sistema operativo. Además es importante saber que este tipo de programas solo pueden ejecutarse como root. Yo, por las pruebas que pude hacer, creo que los sistemas operativos actuales no son tontos y este tipo de ataques ya son conocidos. No pude capturar con Wireshark ninguno que fuese respuesta a broadcast, mientras que si la IP, aun siendo falsa, parecía real, sí que podía capturarla.
 ]]>
https://blog.adrianistan.eu/mandando-paquetes-icmp-echo-personalizados-python Wed, 10 May 2017 00:00:00 +0000
La belleza de MIPS https://blog.adrianistan.eu/la-belleza-mips https://blog.adrianistan.eu/la-belleza-mips

MIPS, reinventemos la rueda... pero bien


Los orígenes de la arquitectura MIPS se remontan a 1981 cuando John L. Hennessy y su equipo de la Universidad de Stanford buscan implementar un procesador lo más simple posible. Al contrario que en las arquitecturas de tipo CISC lo que se busca en una de tipo de RISC como MIPS es definir las instrucciones más simples posibles y optimizar estas. ARM es otro ejemplo de arquitectura RISC. Para optimizar aún más, MIPS hace un uso intensivo de la segmentación.

Los procesadores MIPS ganaron popularidad rápidamente. Fueron usados por Silicon Graphics (SGI) para sus workstations con sistema operativo IRIX. Allí fueron usadas en estudios de Hollywood, centros científicos, etc En su máximo esplendor estas máquinas acogieron grandes avances, como la invención de OpenGL y del sistema de archivos XFS.

Esta relación de MIPS con el mundo multimedia le hizo popular entre las consolas. La Nintendo 64, la PlayStation 2 o la PSP llevan procesador MIPS.



Finalmente MIPS ha entrado en cierta decadencia. SGI cerró y las consolas decidieron apostar por otro tipo de procesadores. Sin embargo MIPS sigue vivo. Es usado en routers y algunos móviles. Existe Loongson, una variación de la aquitectura MIPS usada en China. Incluso ha habido placas estilo Raspberry Pi con MIPS, como por ejemplo, la Creator CI20.

Cuatro principios del diseño


Estos principios se aplican en MIPS.

  • La simplicidad favorece la regularidad

    • La regularidad facilita la implementación

    • La simplicidad mejora el rendimiento a menor coste



  • Cuanto más pequeño más rápido

  • Mejorar en lo posible los casos más frecuentes

  • Un buen diseño requiere buenas soluciones de compromiso


Tres tipos de instrucciones


La belleza de MIPS se basa en su diseño, claro, conciso y con pocas excepciones. Empecemos por lo básico, en MIPS todas las instrucciones son de 32 bits y son de una de estas tres categorías:

  • R, operan con tres registros

  • I, operan con dos registros y una constante

  • J, operan con una dirección de memoria


32 registros de 32 bits


En MIPS existen 32 registros. Bastantes si los comparamos con x86 de Intel. ¿Qué es un registro? Es donde se guardan los datos que se van a necesitar en las operaciones. Como si fueran cajas donde podemos guardar cosas, las cajas de la CPU. Salvo un par de ellos especiales, en realidad todos son iguales, no obstante, por convenio ciertos registros se usan para unas cosas y otros para otras.

Tomado de Wikipedia

Operaciones básicas


Las operaciones básicas son la suma, la resta, la suma inmediata, los desplazamientos, las operaciones lógicas and, or y nor, las operaciones de carga y guardado en memoria, el comparador menor que, los branch y los saltos. Y ya esta. Todo lo demás se implementa mediante pseudoinstrucciones, es decir, instrucciones que el ensamblador convierte en varias instrucciones básicas. Muchos se habrán sorprendido que no haya operación de multiplicar. O que no haya move. O que solo se pueda comparar con menor que. Todas esas cosas las podemos hacer pero debemos ser conscientes de que son pseudoinstrucciones y MIPS en realidad no tiene esas operaciones registradas. Veamos un ejemplo de código MIPS. Primero tenemos el código en C equivalente, y después el código MIPS.


a = b + c + 5




# a en $s0, b en $s1, c en $s2
add $s0, $s1, $s2 # Sumamos $s1 + $s2 y ponemos el resultado en $s0
addi $s0, $s0, 5 # Sumamos a $s0 la constante 5 y dejamos el resultado en $s0


Por norma general en MIPS cuando queramos usar una constante en vez de un registro bastará con poner una i (de inmmediato) al final. Así en vez de usar una instrucción de tipo R usaremos una de tipo I.

Branch y jump


La magia de los computadores reside en que son capaces de tomar decisiones. Pueden ejecutar bucles y evaluar condiciones. En ensamblador este tipo de cosas se hacen con saltos. Sin embargo MIPS incluye dos tipos distintos de saltos.

Los branch evalúan condiciones y saltan en caso de que se cumpla a una posición del código N bytes por arriba o por debajo de la dirección de memoria actualmente almacenada en $ra.

Los jump saltan a una dirección de memoria de forma absoluta. Como MIPS es de 32 bits y las instrucciones también son de 32 bits resulta evidente que no es posible saltar a cualquier dirección de memoria, no al menos en una simple instrucción. Los bits que faltan, los más significativos se dejan a los mismos que había en donde se hizo el jump.

Esto parece muy complicado pero veamos que no lo es tanto en la práctica. Como recomendación, usa jumps para saltar a subrutinas y usa branch para hacer ifs y bucles.


int x = 5;
do{
x--;
}while(x!=0);




# x en $s0
.text
main:
addi $s0, $zero, 5 # Cargamos 5 a $s0
# es equivalente a la pseudoinstrucción li $s0, 5
bucle:
addi $s0, $s0, -1 # las constantes también pueden ser negativas
bne $s0, $zero, bucle # branch on not equal. Si $s0 != $zero, se salta a bucle. Si no, se sigue para abajo




int double(int x){
return x*2;
}
...
int y = 5;
y = double(y);




.text
main:
addi $s0, $zero, 5
add $a0, $s0, $zero #podriamos usar la pseudoinstruccion move
jal double
move $s0, $v0 #pseudoinstruccion para copiar de $v0 a $s0
li $v0, 10
syscall # ejecuta una syscall, con código el que hay en $v0 (10 vamos a suponer que es salir del programa)
double:
sll $v0, $a0, 1
jr $ra


¿Qué hace jal? Llama a la subrutina double ejecutando un salto y poniendo $ra a la siguiente dirección (para retomar el ciclo de ejecución normal cuando salgamos de la subrutina con jr $ra).

Cargando datos


Hasta ahora hemos trabajado siempre con datos que ya estaban en los registros. ¿Qué pasa si queremos recorrer un array por ejemplo? Un array es un tipo de dato más complejo, que no entra en un registro. Este ejemplo suma los números del array.


int* x = [1,2,3,4,5];
int suma = 0;
for(int i =0;i<4;i++){
suma = suma + x[i]
}




.data
x: .word 1,2,3,4,5
.text
main:
li $s0, 0
li $t9, 5
la $t0, x
bucle:
lw $t1, 0($t0)
add $s0, $s0, $t1
addi $t0, $t0, 4
addi $t9, $t9, -1
bne $zero, $t9, bucle
li $v0, 10
syscall


Usamos la cabecera data para registrar datos en el stack que el sistema operativo repartirá como pueda. Le llamamos x y contiene WORDS, que en MIPS son 4 bytes, es decir, 32 bits.

la es una pseudoinstrucción (usa lui y ori internamente) que sirve para cargar direcciones de 32 bits en un registro. En este caso cargamos la posición de donde empieza el vector x en $t0. $t0 es un puntero ahora.

lw es la instrucción importante aquí, significa load word y permite cargar palabras de la memoria a un registro. Le indicamos donde queremos que se guarde ($t1), y donde está el dato en memoria ($t0). Adicionalmente le indicamos el desplazamiento, que digamos es una constante que podemos aplicar para cargar unos bytes antes o después de lo que indique el registro. Esto es muy útil, pero en este ejemplo no tiene sentido usarse y lo he dejado a 0.

¿Por qué sumamos 4 a $t0 en cada pasada? Hemos dicho que los WORD en MIPS ocupan 32 bits, 4 bytes. Pues en esa operación estamos moviendo el puntero 4 bytes más en la memoria, para pasar al siguiente elemento del vector. Esto es aritmética de punteros. ¡Chachi pistachi!

El emulador Mars


¿Ya has visto por qué MIPS es tan bonito? A diferencia de otros ensambladores, la sintaxis de MIPS es muy regular, con comportamientos predecibles aunque no conozcamos exactamente la instrucción. Si quieres probar tus destrezas en MIPS existen varios simuladores. El que uso ahora mismo se llama Mars, está hecho en Java y es bastante completo. Es posible inspeccionar la memoria al completo, ir paso a paso en cada instrucción, insertar breakpoints y observar el valor de los registros en cada momento.

Descargar Mars

También si nos vemos con fuerzas podremos pasar a hardware real con Linux. OpenWrt, presente en routers, o Debian, en la Creator CI20 pueden ser buenas opciones.

Conclusión


Seguro que si habías programado con anterioridad en otro ensamblador esto te ha parecido muy sencillo. Y es que MIPS no es especialmente complejo.]]>
https://blog.adrianistan.eu/la-belleza-mips Wed, 3 May 2017 00:00:00 +0000
¿Qué pasa con Elixir? https://blog.adrianistan.eu/que-pasa-con-elixir https://blog.adrianistan.eu/que-pasa-con-elixir Elixir. Elixir es un lenguaje de programación de propósito general que vio la luz en 2011. Con bastantes aspectos de un lenguaje de programación funcional y dinámico, está especificamente diseñado para manejar la concurrencia. Se ejecuta sobre la máquina virtual BEAM, originalmente diseñada para Erlang y es capaz de ejecutar código métodos escritos en Erlang sin ninguna complicación extra. Además dispone de un sistema de compilación llamado mix, un gestor de paquetes llamado hex y la forma de escribir documentación es similar a Python (existe help(método), ¡aleluya!). Elixir además dispone de hot swapping, es decir, permite cambiar el código en ejecución sin necesidad de parar el programa. También le caracteriza tener un buen soporte a operaciones asíncronas.

¿Es Elixir el nuevo Ruby?


Mucha gente ha querido llamar a Elixir el Erlang escrito bonito. Muchos lo comparan con Ruby pues sí que comparten alguna característica común en su sintaxis y una filosofía en general de simplicidad. Además Elixir es mucho más rápido y está mucho mejor diseñado para concurrencia y aplicaciones tolerantes a fallos (como su hermano mayor Erlang).

Sin embargo, ¿es justo comparar Ruby con Elixir?

Entra Phoenix en acción


Mucha de la popularidad de Ruby se la debe a su framework Rails. Usado por una gran cantidad de sitios en la web, supuso para muchos su primer contacto con Ruby y la razón de que se quedasen aprendiendo el lenguaje.

Curiosamente, Elixir también tiene un framework web que atrae a desarrolladores. Se trata de Phoenix.

Phoenix se presenta como un framework para la nueva web. Aplicaciones realtime, tolerantes a fallos y de alto rendimiento sin perder las facilidades y comodidades de frameworks anteriores. La gente que lo ha usado cuenta maravillas. ¿Has usado Phoenix? Cuéntanos tu experiencia en los comentarios.

Probando Elixir con su REPL


Elixir viene con un REPL, es decir, una terminal donde línea a línea podemos ir introduciendo código Elixir. Se ejecuta con el comando iex.

Podemos hacer un hola mundo con IO.puts.

Podemos probar a definir un módulo de suma:


defmodule Adder do
def add(a,b) do
a+b
end
end

Adder.add 42,15


Lo que se ve en amarillo es el bytecode compilado para BEAM del módulo Adder

Aunque llegado el momento quizá nos interese crear alguna función anónima.


add = fn a,b -> a+b end
x = 42
IO.puts add.(x,15)


Es fácil encontrar documentación en español sobre Elixir si deseas investigar más sobre el tema.

Sin duda Elixir parece interesante. ¿Será un mero hype o dentro de unos años Elixir será uno de los lenguajes más populares? Ciertas webs han empezado a recomendar echar un vistazo a Elixir y Phoenix si queremos crear una nueva aplicación web.]]>
https://blog.adrianistan.eu/que-pasa-con-elixir Sun, 30 Apr 2017 00:00:00 +0000
Novedades de C++17 https://blog.adrianistan.eu/novedades-de-c17 https://blog.adrianistan.eu/novedades-de-c17


Repasemos las noveades que incorpora C++17 respecto a C++14

if-init


Ahora podemos incluir una sentencia de inicialización antes de la condición en sentencias if y switch. Esto es particularmente útil si queremos operar con un objeto y desconocemos su validez.


// ANTES
Device dev = get_device();
if(dev.isOk()){
dev.hacerCosas();
}

// AHORA
if(Device dev = get_device(); dev.isOk()){
dev.hacerCosas();
}


Declaraciones de descomposición



Azúcar sintántico que permite mejorar la legibiliad en ciertas situaciones. Por ejemplo, en el caso de las tuplas, su uso se vuelve trivial.


// FUNCIÓN QUE DEVUELVE TUPLA
std::tuple<int, std::string> funcion();

// C++14
auto tup = funcion();
int i = std::get<0>(tup);
std::string s = std::get<1>(tup);

// C++17
auto [i,s] = funcion();



Esto funciona para multitud de estructuras de datos, como estructuras, arrays, std::array, std::map,...


std::map m = ...;
for(auto && [key, value] : m){

}


Deduction Guides



Ahora es menos necesario que nunca indicar los tipos en ciertas expresiones. Por ejemplo, al crear pares y tuplas:


// ANTES
auto p = std::pair<int,std::string>(42,"Adrianistan");

// AHORA
auto p = std::pair(42,"Adrianistan");


Esto por supuesto también sirve para estructuras y otras construcciones:


template<typename T>
struct Thingy
{
T t;
};

// Observa
Thingy(const char *) -> Thingy<std::string>;

Thingy thing{"A String"}; // thing.t es de tipo std::string


template auto




// ANTES
template <typename T, T v>
struct integral_constant
{
static constexpr T value = v;
};
integral_constant<int, 2048>::value
integral_constant<char, 'a'>::value

// AHORA
template <auto v>
struct integral_constant
{
static constexpr auto value = v;
};
integral_constant<2048>::value
integral_constant<'a'>::value


Fold expressions



Imagina que quieres hacer una función suma, que admita un número ilimitado de parámetros. En C++17 no se necesita apenas código.


template <typename... Args>
auto sum(Args&&... args) {
return (args + ... + 0);
}


Namespaces anidados



Bastante autoexplicativo


// ANTES

namespace A{
namespace B {
bool check();
}
}

// AHORA

namespace A::B {
bool check();
}


Algunos [[atributos]] nuevos



[[maybe_unused]]


Se usa para suprimir la advertencia del compilador de que no estamos usando una determinada variable.


int x = 5;
[[maybe_unused]] bool azar = true;
x = x + 10


[[fallthrough]]



Permite usar los switch en cascada sin advertencias del compilador.

switch (device.status())
{
case sleep:
device.wake();
[[fallthrough]];
case ready:
device.run();
break;
case bad:
handle_error();
break;
}


Variables inline



Ahora es posible definir variables en múltiples sitios con el mismo nombre y que compartan una misma instancia. Es recomendable definirlas en un fichero de cabecera para luego reutilizarlas en ficheros fuente.


// ANTES
// en una cabecera para que la usasen los demás
extern int x;

// solo en un fichero fuente, para inicializarla
int x = 42;
// AHORA

// en la cabecera
inline int x = 42;



if constexpr



Ahora es posible introducir condicionales en tiempo de compilación (similar a las macros #IFDEF pero mejor hecho). Estas expresiones con constexpr, lo que quiere decir que son código C++ que se evalúa en tiempo de compilación, no de ejecución.


template<class T>
void f (T x)
{
if constexpr(std:: is_integral <T>::value) {
implA(x);
}
else if constexpr(std:: floating_point <T>::value) {
implB(x);
}
else
{
implC(x);
}
}


std::optional



Tomado de la programación funcional, se incorpora el tipo optional, que representa un valor que puede existir o no. Este tipo ya existe en Rust bajo el nombre de Option y en Haskell como Maybe.


std::optional opt = f();
if(opt)
g(*opt);

// otra opción de uso si queremos proveer de un reemplazo
std::optional opt = f();
std::cout << opt.value_or(0) << std::endl;


std::variant



Descritas como las unions pero bien hechas. Pueden contener variables de los tipos que nosotros indiquemos.


std::variant<int, double, std::vector> precio; // precio puede ser un int, un double o un std::vector

// comprobar si el valor en un variant es de un determinado tipo
if(std::holds_alternative<double>(precio))
double x = std::get<double>(precio);


std::any



Si con std::variant restringimos los posibles tipos de la variable a los indicados, con std::any admitimos cualquier cosa.


std::any v = ...;
if (v.type() == typeid(int)) {
int i = any_cast<int>(v);
}


std::filesystem


Se añade a la librería estándar este namespace con el tipo path y métodos para iterar y operar con directorios. Dile adiós a las funciones POSIX o Win32 equivalentes.


#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;

void main(){
fs::path dir = "/";
dir /= "sandbox";
fs::path p = dir / "foobar.txt";
std::cout << p.filename() << "\n";
fs::copy(dir, "/copy", fs::copy_options::recursive);
}


Algoritmos en paralelo



Muchos de los algoritmos de STL ahora pueden ejecutarse en paralelo bajo demanda. Con std::execution::par indicamos que queremos que el algoritmo se ejecute en paralelo.


std::sort(std::execution::par, first, last);


¿Qué novedades se esperan en C++20?


Ya hemos visto lo que trae C++17. Ahora veremos que se espera que traiga C++20 en 2020.

  • Módulos. Reemplazar el sistema de includes

  • Corrutinas. Mejorar la programación asíncrona

  • Contratos. Mejorar la calidad del código

  • Conceptos. Mejorar la programación genérica

  • Redes. Estandarizar la parte de red en C++ tal y como se ha hecho con std::filesystem

  • Rangos. Nuevos contenedores



Referencias:





 ]]>
https://blog.adrianistan.eu/novedades-de-c17 Wed, 19 Apr 2017 00:00:00 +0000
Tutorial de Rocket, echa a volar tus webapps con Rust https://blog.adrianistan.eu/tutorial-rocket-echa-volar-tus-webapps-rust https://blog.adrianistan.eu/tutorial-rocket-echa-volar-tus-webapps-rust Iron como un web framework para Rust. Sin embargo desde que escribí ese post ha surgido otra librería que ha ganado mucha popularidad en poco tiempo. Se trata de Rocket. Un web framework que propone usar el rendimiento que ofrece Rust sin sacrificar la facilidad de uso de otros lenguajes.

Rocket lleva las pilas cargadas


A diferencia de Iron, Rocket incluye bastantes prestaciones por defecto con soporte para:

  • Plantillas

  • Cookies

  • Formularios

  • JSON

  • Soporte para rutas dinámicas


Un "Hola Mundo"


Rocket necesita una versión nightly del compilador de Rust. Una vez lo tengas creamos una aplicación con Cargo.
cargo new --bin rocket_app

Ahora modificamos el fichero Cargo.toml generado en la carpeta rocket_app para añadir las siguientes dependencias:
rocket = "0.2.4"
rocket_codegen = "0.2.4"
rocket_contrib = "*"

Editamos el archivo src/main.rs para que se parezca algo a esto:
#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate rocket;

#[get("/")]
fn index() -> &'static str {
"El cohete ha despegado"
}

fn main() {
rocket::ignite().mount("/",routes![index]).launch();
}

Con esto iniciamos Rocket y dejamos a la función index que gestione las peticiones GET encaminadas a /. El servidor devolverá El cohete ha despegado.

Ahora si ejecutamos cargo run veremos algo similar a esto:

Vemos que el servidor ya está escuchando en el puerto 8000 y está usando todos los cores (en mi caso 4) del ordenador.

Configurar Rocket


Rocket dispone de varias configuraciones predeterminadas que afectan a su funcionamiento. Para alternar entre las configuraciones debemos usar variables de entorno y para modificar las configuraciones en sí debemos usar un fichero llamado Rocket.toml.

Las configuraciones por defecto son: dev (development), stage (staging) y prod (production). Si no indicamos nada, Rocket se inicia con la configuración dev. Para arrancar con la configuración de producción modificamos el valor de ROCKET_ENV.
ROCKET_ENV=prod cargo run --release

Sería el comando para arrancar Rocket en modo producción. En el archivo Rocket.toml se puede modificar cada configuración, estableciendo el puerto, el número de workers y parámetros extra pero no vamos a entrar en ello.

Rutas dinámicas


Rocket soporta rutas dinámicas. Por ejemplo, si hacemos GET  /pelicula/Intocable podemos definir que la parte del nombre de la película sea un parámetro. Esto hará que la función encargada de /pelicula/Intocable y de /pelicula/Ratatouille sea la misma.
#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate rocket;

#[get("/pelicula/<pelicula>")]
fn pelicula(pelicula: &str) -> String {
format!("Veo que te gusta {}, a mi también!",pelicula)
}

#[get("/")]
fn index() -> &'static str {
"El cohete ha despegado"
}

fn main() {
rocket::ignite().mount("/",routes![index,pelicula]).launch();
}

Los argumentos de la función son los parámetros de la petición GET. ¿Qué pasa si no concuerda el tipo de la función con lo que se pasa por HTTP? Nada. Sencillamente Rocket ignora esa petición, busca otra ruta (puede haber sobrecarga de rutas) y si encuentra otra que si satisfaga los parámetros será esa la escogida. Para especificar el orden en el que se hace la sobrecarga de rutas puede usarse rank. En caso de no encontrarse nada, se devuelve un error 404.

POST, subir JSON y formularios


Rocket se integra con Serde para lograr una serialización/deserialización con JSON inocua. Si añadimos las dependencias serde, serde_json y serde_derive al fichero Cargo.toml podemos tener un método que acepete una petición POST solo para mensajes del tipo application/json con deserialización incorporada.
#![feature(plugin)]
#![plugin(rocket_codegen)]

#[macro_use] extern crate rocket_contrib;
#[macro_use] extern crate serde_derive;
extern crate serde_json;
extern crate rocket;

use rocket_contrib::{JSON, Value};

#[derive(Serialize,Deserialize)]
struct User{
name: String,
email: String
}

#[post("/upload", format="application/json", data="<user>")]
fn upload_user(user: JSON<User>) -> JSON<Value> {
JSON(json!({
"status" : 200,
"message" : format!("Usuario {} registrado con éxito",user.email)
}))
}

fn main() {
rocket::ignite().mount("/",routes![upload_user]).launch();
}

Si el JSON no se ajusta a la estructura User simplemente se descarta devolviendo un error 400.

Lo mismo que es posible hacer con JSON puede hacerse con formularios usando el trait FromForm.
#![feature(plugin,custom_derive)]
#![plugin(rocket_codegen)]

#[macro_use] extern crate rocket_contrib;
#[macro_use] extern crate serde_derive;
extern crate serde_json;
extern crate rocket;

use rocket_contrib::{JSON, Value};
use rocket::request::{FromForm, Form};

#[derive(FromForm)]
struct User{
name: String,
email: String
}

#[post("/upload", data="<user>")]
fn upload_user(user: Form<User>) -> String {
format!("Hola {}",user.get().name)
}

fn main() {
rocket::ignite().mount("/",routes![upload_user]).launch();
}

Errores


En Rocket, como es lógico, es posible crear páginas personalizadas para cada error.
#![feature(plugin,custom_derive)]
#![plugin(rocket_codegen)]

#[get("/")]
fn index() -> &'static str {
"El cohete ha despegado"
}

#[error(404)]
fn not_found() -> &'static str {
"La página no ha podido ser encontrada"
}

fn main() {
rocket::ignite().mount("/",routes![index]).catch(errors![not_found]).launch();
}

La lista de métodos que manejan errores hay que pasarla en el método catch de rocket::ignite

Respuestas


Rocket nos permite devolver cualquier cosa que implemente el trait Responder. Algunos tipos ya lo llevan como String, File, JSON, Option y Result. Pero nada nos impide que nuestros propios tipos implementen Responder. Con Responder tenemos el contenido y el código de error (que en la mayoría de casos será 200). En el caso de Result es muy interesante, pues si Err contiene algo que implementa Responder, se devolverá la salida que implemente también, pudiendo así hacer mejores respuestas de error, mientras que si no lo hacen se llamará al método que implemente el error 500 de forma genérica. Con Option, si el valor es Some se devolverá el contenido, si es None se generará un error 404.
#![feature(plugin,custom_derive)]
#![plugin(rocket_codegen)]

#[macro_use] extern crate rocket_contrib;
extern crate rocket;

use rocket::response::{self, Responder, Response};
use std::io::Cursor;
use rocket::http::ContentType;

struct Pelicula{
nombre: &'static str,
pais: &'static str
}

impl<'r> Responder<'r> for Pelicula{
fn respond(self) -> response::Result<'r> {
Response::build()
.sized_body(Cursor::new(format!("La película {} se hizo en {}",self.nombre,self.pais)))
.header(ContentType::new("text","plain"))
.ok()
}
}

#[get("/pelicula/<pelicula>")]
fn pelicula(pelicula: &str) -> Result<Pelicula,String> {
let intocable = Pelicula{
nombre: "Intocable",
pais: "Francia"
};
let madMax = Pelicula{
nombre: "Mad Max",
pais: "Estados Unidos"
};
match pelicula {
"Intocable" => Ok(intocable),
"Mad Max" => Ok(madMax),
_ => Err(format!("No existe esa película en nuestra base de datos"))
}
}

#[get("/")]
fn index() -> Result<String,String> {
Err(format!("No implementado"))
}

#[error(404)]
fn not_found() -> &'static str {
"La página no ha podido ser encontrada"
}

fn main() {
rocket::ignite().mount("/",routes![index,pelicula]).catch(errors![not_found]).launch();
}

Este ejemplo para /pelicula/Intocable devolverá: La película Intocable se hizo en Francia mientras que para /pelicula/Ratatouille dirá No existe esa película en nuestra base de datos.

También es posible devolver plantillas. Rocket se integra por defecto con Handlebars y Tera, aunque no es muy costoso añadir cualquier otra como Maud.

Conclusión


Rocket es un prometedor web framework para Rust, bastante idiomático, que se integra muy bien con el lenguaje. Espero con ansia las nuevas veriones. Es posible que la API cambie bastante hasta que salga la versión 1.0, no obstante así es como ahora mismo funciona.]]>
https://blog.adrianistan.eu/tutorial-rocket-echa-volar-tus-webapps-rust Sat, 15 Apr 2017 00:00:00 +0000
Rust en 5 minutos - #PicnicCode2017 https://blog.adrianistan.eu/rust-5-minutos-picniccode2017 https://blog.adrianistan.eu/rust-5-minutos-picniccode2017
El pasado 17 de marzo fue el Picnic Code en la Universidad de Valladolid. En el evento, organizado por el GUI y Cylicon Valley, tuve el honor de dar una Lightning Talk. Se trata de una charla de 5 minutos muy rápidos para exponer una idea. Mi Lightning Talk titulada Rust en 5 minutos iba dirigida a enseñar, sin entrar en muchos detalles, aquellas características que hacen de Rust un lenguaje seguro. No estaba nervioso hasta que subí al escenario... ¡y entonces ya empecé a estarlo! Hay algunos fallos frutos de los nervios y las diapositivas... bueno, podían haber funcionado mejor.

En cualquier caso, estáis invitados a ver Rust en 5 minutos.]]>
https://blog.adrianistan.eu/rust-5-minutos-picniccode2017 Tue, 28 Mar 2017 00:00:00 +0000
Tutorial de Maud, motor de plantillas HTML para Rust https://blog.adrianistan.eu/tutorial-maud-motor-plantillas-html-rust https://blog.adrianistan.eu/tutorial-maud-motor-plantillas-html-rust Iron, Piston y Neon. Hoy veremos Maud, un potente motor de plantillas que se centra en la eficiencia. Maud se compara a otras soluciones como Razor, ERB, Liquid,  Handlebars o Jade pero esta vez escribiremos nuestro HTML en Rust. ¿Locura? No, y de hecho funciona de forma bastante transparente. Vamos a verlo en acción

Comparativa de velocidad de diferentes motores. Maud es el más rápido (menos es mejor)

Instalando Maud


Maud usa plugins del compilador, una característica que a día de hoy no está activado ni en el canal estable ni el canal beta, solamente en el canal nightly. Para obtener una copia de Rust nightly lo ideal es usar Rustup.

Una vez hecho eso, creamos un nuevo proyecto y añadimos las dependencias de Maud al fichero Cargo.toml.


maud = "*"
maud_macros = "*"


Una simple plantilla


Ahora abrimos el archivo src/main.rs y vamos a empezar a usar Maud.


#![feature(plugin)]
#![plugin(maud_macros)]

extern crate maud;

fn main(){
let name = "Adrián";
let markup = html!{
p { "Hola, soy " (name) " y estoy usando Maud"}
};
println!("{}", markup.into_string());
}



La potencia de Maud se ve en la mega-macro html!. En esta macro escribiremos la plantilla que será compilada de forma nativa a Rust, lo que nos asegura una velocidad de ejecución excepcional. En este caso la salida será una etiqueta P de párrafo con la variable interpolada.

Simple, ¿no?

PreEscaped y otros elementos básicos


Por defecto en Maud todos el texto se convierte a HTML seguro. Es decir, no se pueden introducir etiquetas nuevas en el texto. Si por alguna razón necesitásemos añadir etiquetas nuevas podemos usar PreEscaped, que no realiza esta transformación de seguridad. Veamos el siguiente código:


#![feature(plugin)]
#![plugin(maud_macros)]

extern crate maud;

use maud::PreEscaped;

fn main(){
let name = "Adrián";
let markup = html!{
p { "Hola, soy " (name) " y estoy usando Maud" }
p { "<h5>Esto no funcionará</h5>" }
p { (PreEscaped("<h5>Esto sí funcionará</h5>")) }
};
println!("{}", markup.into_string());
}



El primer H5 se convertirá a código HTML seguro, es decir, no añadirá la etiqueta, en cambio se verá h5 en la web. Por contra con PreEscaped se añadirá la etiqueta h5 tal cual.

Los elementos son muy fáciles de añadir en Maud y por lo general no deberías usar PreEscaped salvo contadas ocasiones. Veamos como añadir más etiquetas.


#![feature(plugin)]
#![plugin(maud_macros)]

extern crate maud;

fn main(){
let name = "Adrián";
let markup = html!{
p { "Hola, soy " (name) " y estoy usando Maud" }
p {
"Si la montaña no viene a Mahoma"
br /
"Mahoma va la montaña"
small em "Atribuido a Francis Bacon"
}
};
println!("{}", markup.into_string());
}



En este ejemplo vemos como las etiquetas que no llevan texto como BR o INPUT debemos cerrarlas con una barra. Por otro lado es posible aglutinar varios niveles de etiquetas en una sola línea ( SMALL->EM->Texto).

Atributos, clases e IDs


En Maud es posible asignar atributos también, usando literales o variables. Para los atributos de texto la sintaxis es muy parecida a HTML.


#![feature(plugin)]
#![plugin(maud_macros)]

extern crate maud;

fn main(){
let name = "Adrián";
let amazon = "http://www.amazon.com";
let markup = html!{
p { "Hola, soy " (name) " y estoy usando Maud" }
p {
"Este texto contiene enlaces a "
a href="http://www.google.com" "Google"
" y a "
a href=(amazon) "Amazon"
}
};
println!("{}", markup.into_string());
}


Además existen en HTML atributos vacíos. Es decir, atributos que con su sola presencia basta y normalmente no llevan valor asignado.


#![feature(plugin)]
#![plugin(maud_macros)]

extern crate maud;

fn main(){
let name = "Adrián";
let allow_editing = true;
let markup = html!{
p { "Hola, soy " (name) " y estoy usando Maud" }
p contenteditable?[allow_editing] {
}
};
println!("{}", markup.into_string());
}


En este caso el atributo contenteditable se añade si la variable allow_editing es true. Si queremos añadir atributos vacíos en cualquier circunstancia podemos simplemente quitar [allow_editing] y dejar p contenteditable? {}.

Los IDs y las clases se añaden usando la sintaxis esperable, puntos y almohadillas.


#![feature(plugin)]
#![plugin(maud_macros)]

extern crate maud;

fn main(){
let name = "Adrián";
let markup = html!{
p { "Hola, soy " (name) " y estoy usando Maud" }
p.red.font-big#editor contenteditable? {
}
};
println!("{}", markup.into_string());
}


Estructuras de control


Maud soporta las estructuras de control básicas de Rust, if/else, if/let, for in y match.


#![feature(plugin)]
#![plugin(maud_macros)]

extern crate maud;

fn main(){
let loggedIn = false;
let email = Some("mariscal@example.com");
let fonts = ["Arial","Times New Roman","Verdana"];
let markup = html!{
@if loggedIn {
h1 { "Has iniciado sesión" }
} @else {
h1 { "Por favor, inicia sesión primero" }
}

@if let Some(mail) = email {
p { "Su email es " (mail) }
}
ol {
@for font in &fonts {
li (font)
}
}

};
println!("{}", markup.into_string());
}


Como vemos, Maud posee suficientes características para ser interesante. Maud además permite extender el motor para representar cualquier tipo de dato. Por defecto Maud mirá si está implementado std::fmt::Display pero si queremos añadir etiquetas extra este método no funcionará. En cambio si se implementa la trait maud::Render tendrás control total sobre como va a mostrar Maud las variables de ese tipo. En la crate maud_extras se encuentran implementaciones por ejemplo de Markdown para Maud.

Maud se integra además con web frameworks de Rust, en especial con Iron y con Rocket. Sin duda, una de mis crates favoritas.]]>
https://blog.adrianistan.eu/tutorial-maud-motor-plantillas-html-rust Tue, 31 Jan 2017 00:00:00 +0000
Tutorial de Neon - Combina Node.js con Rust https://blog.adrianistan.eu/tutorial-neon-combina-node-js-rust https://blog.adrianistan.eu/tutorial-neon-combina-node-js-rust Neon.

Con Neon podemos generar módulos para Node.js que son escritos y compilados en Rust con las ventajas que supone desde un punto de vista de rendimiento y con la certeza de que en Rust la seguridad está garantizada.

Usando Neon puedes desarrollar tu aplicación en Node.js y si alguna parte tiene problemas de rendimiento sustituirla por su equivalente en Rust. Para el ejemplo voy a hacer un módulo de Markdown.

Instalando Neon


En primer lugar instalamos la herramienta de Neon desde npm.


npm install -g neon-cli


Una vez esté instalado podemos usar la herramienta de Neon para construir un esqueleto de módulo. Este esqueleto tendrá dos partes, un punto de entrada para Node.js y otro para Rust.


neon new PROYECTO


Hacemos un npm install como nos indica. Esto no solo obtendrá dependencias de Node.js sino que también se encargará de compilar el código nativo en Rust.

El código Node.js


Nuestro módulo contiene un archivo de Node.js que sirve de punto de entrada. Allí lo único que se debe hacer es cargar el módulo en Rust y hacer de pegamento. Puede ser algo tan simple como esto:


var addon = require("../native");

module.exports = addon; // se exportan todos los métodos del módulo nativo


Aunque si queremos añadir un tratamiento específico también es posible.


var addon = require("../native");

module.exports = {
render: function(str){
return addon.render(str);
}
}


El código en Rust


Nos dirigimos ahora al archivo native/src/lib.rs. Ahí definimos los métodos nativos que va a tener el módulo. Lo hacemos a través de la macro register_module!.


register_module!(m,{
m.export("render",render)
});


Ahora vamos a implementar la función render, que toma el texto en Markdown y lo devuelve en HTML.


fn render(call: Call) -> JsResult<JsString> {
let scope = call.scope; // obtener el contexto
let md: Handle<JsString> = try!(try!(call.arguments.require(scope,0)).check::<JsString>()); // obtener primer argumento como JsString. aquí puede hacerse tratamiento de fallos
let string = md.value(); // Pasar JsString a String
let html: String = markdown::to_html(&string); // usamos la crate markdown para renderizar a html
Ok(JsString::new(scope, &html).unwrap()) // devolvemos un JsString con el contenido del HTML
}



Las funciones que interactuan con Node deben devolver un JsResult de un tipo JsXXX, por ejemplo, JsString, JsUndefined o JsInteger. Siempre aceptan un argumento llamado de tipo Call que nos da toda la información necesaria y que podemos usar para sacar argumentos. El scope o contexto es muy importante y lo deberemos usar en las funciones que interactúen con Node.

Código completo del fichero Rust




#[macro_use]
extern crate neon;
extern crate markdown;

use neon::vm::{Call, JsResult};
use neon::js::JsString;
use neon::mem::Handle;

fn render(call: Call) -> JsResult<JsString> {
let scope = call.scope;
let md: Handle<JsString> = try!(try!(call.arguments.require(scope,0)).check::<JsString>());
let string = md.value();
let html: String = markdown::to_html(&string);
Ok(JsString::new(scope, &html).unwrap())
}

register_module!(m, {
m.export("render", render)
});


Y no te olvides de añadir la dependencia markdown al fichero Cargo.toml.

Probándolo


Es muy fácil probarlo. Con el REPL de Node podemos probar partes del módulo antes de publicarlo a npm.

Para abrir el REPL ejecuta Node sin argumentos


node


E introduce línea por línea lo que quieras probar:


var md = require("./");
md.render("__Esto es Markdown__");


Verás el resultado por la pantalla:



Ahora que ya sabemos que funciona podemos publicarlo a npm si queremos con:


npm publish


Aunque recuerda revisar antes el fichero package.json para especificar la licencia y la descripción. Una vez esté publicado su uso en un nuevo proyecto será muy sencillo y de forma transparente se compilará el código nativo.


var md = require("rust-markdown");
var http = require('http');
var fs = require("fs");

var server = http.createServer((req, res) => {
fs.readFile("index.md","utf-8",function(err,data){
var html = md.render(data);
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end(html);
});
});

server.listen(8080, "127.0.0.1", () => {
console.log("Server running");
});




 

 ]]>
https://blog.adrianistan.eu/tutorial-neon-combina-node-js-rust Fri, 27 Jan 2017 00:00:00 +0000
Tutorial de Piston, programa juegos en Rust https://blog.adrianistan.eu/tutorial-piston-programa-juegos-rust https://blog.adrianistan.eu/tutorial-piston-programa-juegos-rust tutorial de Iron, que os recomiendo ver si os interesa el tema del desarrollo web backend.

Hoy vamos a hablar de Piston. Piston es una de las librerías más antiguas del ecosistema Rust. Surgida cuando todavía no existía Cargo, esta librería está pensada para el desarrollo de juegos. No es la única que existe en Rust pero sí la más conocida. Piston es una librería que te enseñará Rust de la mejor forma. Y ahora quiero disculparme, porque Piston no es una librería, son un montón, pero eso lo veremos enseguida. En primer lugar creamos un proyecto nuevo con Cargo.
cargo new --bin ejemplo_piston
cd ejemplo_piston

Ahora abrimos el archivo Cargo.toml, vamos a añadir las dependencias necesarias. Las dependencias en Piston son un poco complicadas, veamos:

  • Existen las dependencias core, implementan la API fundamental pero no pueden usarse por separado, son window, input y event_loop. Se usan a través de piston.

  • Los backends de window, existen actualmente 3 backends: glutin, glfw, sdl2. Se importan manualmente.

  • Graphics, una API 2D, no presente en core, pero al igual que las dependencias core necesita un backend.

  • Los backends de graphics son varios: opengl, gfx y glium.

  • Existe una dependencia que nos deja todo montado, piston_window. Esta trae por defecto el core de Piston, glutin, graphics y gfx.

  • Luego existen dependencias extra, como por ejemplo para cargar texturas, estas las podremos ir añadiendo según las necesite el proyecto.


Para simplificar añadimos piston_window únicamente:

 


[package]
name = "piston_example"
version = "0.1.0"
authors = ["Adrián Arroyo Calle"]

[dependencies]
piston_window = "0.59.0"


 

Ahora abrimos el archivo main.rs. Añadimos la crate de piston_window y los módulos que vamos a usar.


extern crate piston_window;

use piston_window::*;
use std::path::Path;


 

Así mismo definimos un par de cosas para el resto del programa, la versión de OpenGL que usará Piston internamente y una estructura para guardar los movimientos de teclado.


const OPENGL: OpenGL = OpenGL::V3_1;

struct Movement{
up: bool,
down: bool,
left: bool,
right: bool
}


 

En la función main podemos crear la ventana, especificando título y tamaño. Más opciones como V-Sync, pantalla completa y demás también están disponibles.


fn main() {

let mut window: PistonWindow = WindowSettings::new("Piston - Adrianistan",[640,480])
.exit_on_esc(true)
.opengl(OPENGL)
.build()
.unwrap();


 

Ahora cargamos la tipografía Sinkin Sans, que vamos a usar para dibujar texto en pantalla. Como hay dos posibles localizaciones comprobamos esos dos lugares antes de salir del programa si no se consigue cargar la fuente.


let mut glyphs = Glyphs::new(Path::new("SinkinSans.ttf"),window.factory.clone()).unwrap_or_else(|_|{
let glyphs = Glyphs::new(Path::new("target/debug/SinkinSans.ttf"),window.factory.clone()).unwrap_or_else(|_|{
panic!("Failed to open the font file. Check that SinkinSans.tff is in the folder");
});
glyphs
});


 

Inicializamos la estructura de movimientos, generamos las dimensiones iniciales del rectángulo (que será un cuadrado en este caso), su color y la posición del ratón.


let mut mov = Movement{
up: false,
down: false,
left: false,
right: false
};

let mut dims = rectangle::square(50.0,50.0,100.0);
let mut rect_color = color::BLACK;

let mut mc: [f64; 2] = [0.0,0.0];


 

Ahora viene la parte importante, el bucle de eventos. El bucle va a funcionar infinitamente generando eventos por el camino (pueden ser eventos de inactividad también). Usamos la función draw_2d para dibujar en 2D. Hay dos maneras de dibujar un rectángulo, en primer lugar tenemos la forma abreviada y en segundo lugar una más completa que permite más opciones. Por último dibujamos el texto usando la fuente y realizando una transformación para que no quede el texto en la posición 0,0.


while let Some(e) = window.next() {
window.draw_2d(&e, |c, g| {
clear([0.5, 0.5, 0.5, 1.0], g);
rectangle([1.0, 0.0, 0.0, 1.0], // color rojo, rgba
[0.0, 0.0, 100.0, 100.0], // dimensiones
c.transform, g); // transormacion y donde se va a dibujar

let rect = Rectangle::new(rect_color);
rect.draw(dims,&c.draw_state,c.transform,g);
text(color::BLACK,18,"¡Saludos desde Piston!",&mut glyphs,c.transform.trans(100.0,200.0),g); // aplicamos una transormacion, movemos las X 100 y las Y 200
});


 

A continuación vamos a tratar cada evento de forma independiente, como todos los métodos devuelven Option, hemos de usar esta sintaxis con Some. En primer lugar tenemos un UpdateEvent, que básicamente nos informa del tiempo delta transcurrido. Recomiendo usar este evento para realizar los cambios en las geometrías, en este caso para mover el rectángulo.


if let Some(upd_args) = e.update_args() {
let dt = upd_args.dt;

if mov.right {
dims[0] += dt*100.0;
}
if mov.left {
dims[0] -= dt*100.0;
}
if mov.up {
dims[1] -= dt*100.0;
}
if mov.down {
dims[1] += dt*100.0;
}
}


Los siguientes dos eventos son opuestos, uno se activa cuando pulsamos una tecla y el otro cuando la soltamos. Comprobamos la tecla y modificamos la estructura movement en consecuencia.


if let Some(Button::Keyboard(key)) = e.press_args() {
if key == Key::W {
mov.up = true;
}
if key == Key::S {
mov.down = true;
}
if key == Key::A {
mov.left = true;
}
if key == Key::D {
mov.right = true;
}
};
if let Some(Button::Keyboard(key)) = e.release_args() {
if key == Key::W {
mov.up = false;
}
if key == Key::S {
mov.down = false;
}
if key == Key::A {
mov.left = false;
}
if key == Key::D {
mov.right = false;
}
};


Por último, si queremos comprobar clicks del ratón hacemos algo similar. He añadido código para que cambio el color del rectángulo si pulsamos sobre él.


if let Some(Button::Mouse(mb)) = e.release_args() {
if mb == MouseButton::Left {
let x = mc[0];
let y = mc[1];
if x > dims[0] && x < dims[0] + dims[2] { if y > dims[1] && y < dims[1] + dims[3] {
rect_color = if rect_color == [1.0,0.0,0.0,0.7]{
[0.0,1.0,0.0,0.7]
} else if rect_color == [0.0,1.0,0.0,0.7] {
[0.0,0.0,1.0,0.7]
} else{
[1.0,0.0,0.0,0.7]
}
}
}

}
}


A continuación un pequeño evento que guarda la última posición del ratón.


if let Some(mouse_cursor) = e.mouse_cursor_args() {
mc = mouse_cursor;
}
}
}


 

Y con esto ya tenemos hecho un ejemplo en Piston.



Si quieres tener un ejecutable para Windows sin que se muestre primero la consola debes compilar la versión que vas a distribuir con unos parámetros especiales. Si usas Rust con GCC usarás:
cargo rustc --release -- -Clink-args="-Wl,--subsystem,windows"

Si por el contrario usas Visual C++:
cargo rustc --release -- -Clink-args="/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup"

 

Piston todavía se encuentra en fuerte desarrollo, en la API estan documentados todos los métodos pero aun así muchas veces no se sabe como hacer ciertas cosas. Piston soporta además 3D, contando con una librería especializada en vóxels. Veremos como evoluciona esta librería.]]>
https://blog.adrianistan.eu/tutorial-piston-programa-juegos-rust Tue, 17 Jan 2017 00:00:00 +0000
9front, un fork de Plan9 desarrollado por dementes https://blog.adrianistan.eu/9front-fork-plan9-desarrollado-dementes https://blog.adrianistan.eu/9front-fork-plan9-desarrollado-dementes 9front, derivado de Plan9. En principio pensé que sería un fork normal y corriente. Cuando entré en su página descubrí sin embargo que era algo completamente distinto.

La 9community, formada por Henry Kissinger y Hillary Clinton, promotores del nuevo orden mundial

Su página web está cargada de minimalismo y memes, la gran mayoría de ellos inspirados en la guerra fría. Al entrar respiramos la esencia de 9front. Por ejemplo, el servidor web de la página es werc. Werc se define a sí mismo como el anti-framework anti-web. Sin bases de datos, solo ficheros y ¡programado en rc! Rc es un componente fundamental de 9front, una piedra angular. Se trata del lenguaje de shell de 9front. El equivalente en Linux sería Bash.

Werc es solo una de las cosas que acoge bajo su paraguas la organización Cat-V (de System-V, marcándose un chiste Unix de cuidado). El lema de Cat-V, Considered harmful (considerado peligroso) nos alerta de que encontraremos contenido que ha sido ajeno para la mayoría de la población. ¿Realmente estamos entrando en Cat-V en la iluminación de Internet? ¿Es entrar en Cat-V un equivalente moderno a salir de la cueva de Platón?

Algunos proyectos de Cat-V son:

  • 9P, un sistema de archivos distribuido. Es usado por 9front.

  • Acme, un editor de texto hecho por Rob Pike

  • Sam, otro editor de texto, también de Rob Pike

  • Fortune, una colección de archivos fortune de varios sistemas UNIX. Parémonos ante este fortune etiquetado dentro de OpenBSD:



(1) Alexander the Great was a great general.
(2) Great generals are forewarned.
(3) Forewarned is forearmed.
(4) Four is an even number.
(5) Four is certainly an odd number of arms for a man to have.
(6) The only number that is both even and odd is infinity.

Therefore, Alexander the Great had an infinite number of arms.


Corramos un tupido velo y sigamos.

¡La web oficial de Glenda!

Por si te lo preguntas, sí, la mascota de Go se encuentra inspirada en Glenda

El subdominio harmful está reservado para pensamientos y para expresar opiniones.

Se habla de economía, películas, periodismo, relaciones personales (de tratar con estúpidos, un artículo muy bueno), de lo políticamente correcto, de ciencia, de la seguridad post 11S, de sociedad (y para mi sorpresa contiene una opinión que yo también tengo pero que apenas he visto, el matrimonio debe ser eliminado del sistema legal de los países para pasar a ser algo cultural/religioso, similar al bautizo), se habla de software, de estándares y de palabras. Pretenden hablar de: SQL, fútbol, la Matrix, svn, postmodernismo, gnu, religión, vi, educación, apache, respeto, teoría de cuerdas, complejidad, comida 'orgánica', etanol, igualdad, metadata, lógica de "sentirse bien", teoría del trabajo, ...

HTTP 0.2, un intento de simplificar HTTP.

NineTimes, la web de noticias del mundo Plan9, Inferno y 9front.

Y por supuesto, un archivo de citas célebres.

Volvamos a 9front


Continué en la página de 9front, vi que tenía un Código de Conducta (algo que han introducido algunos proyectos open source pero que bajo mi punto de vista es innecesario y en algunos casos peligroso). Sin embargo en 9front no hay un código de conducta, hay varios. Cada vez que te metes en la página sale uno distinto. Y todos son muy random. Desde el credo en latín hasta una especie de receta para crear desarrolladores químicamente. La mitad son cánticos de alabanza a 9front. Yo veo en esto una sátira perfecta de lo que son algunos Códigos de Conducta.

Luego vemos para descargar las ISO, pero a estas alturas es lo que menos importa. A continuación encontramos una recopilación de webs alternativas para 9front. Hay una basada en 4chan, pero esta me gusta más:

Y llegamos a mi sección favorita, Propaganda.

Porque el boot de 9front es explosivo, nuclearmente explosivo

La comunidad 9front



Los altos mandos de 9front

The front fell off



Man es un comando muy propenso a chistes

Mothra es el navegador web de 9front

Orwell dirige NineTimes

El comando timesync no es más que una modernización de Stonehenge

Posiblemente haga una entrada seria hablando de 9front, pero es que la comunidad 9front tiene mucha tela que cortar.

Y donde dije dementes, quise decir genios. Son absolutos genios.]]>
https://blog.adrianistan.eu/9front-fork-plan9-desarrollado-dementes Mon, 16 Jan 2017 00:00:00 +0000
Un nuevo lenguaje de programación para juegos https://blog.adrianistan.eu/nuevo-lenguaje-programacion-juegos https://blog.adrianistan.eu/nuevo-lenguaje-programacion-juegos Ideas about a new programming language for games. En el vídeo, Blow analiza los problemas que presenta C++ para el desarrollo de juegos y por qué según él ni Go ni D ni Rust consiguen mejorar la situación. El lenguaje de programación perfecto para juegos debería tener las siguientes características:

  • Poca fricción

  • Placer por programar

  • Rendimiento

  • Simplicidad

  • Diseñado para buenos programadores


Con poca fricción se refiere a que la tarea de programar no debe añadir mucha complejidad para solucionar problemas que tendríamos si programásemos de la forma más simple posible. Fricción es para él RAII en C++. Fricción es la gestión de errores en Rust. Fricción se entiende como código que no añade significativamente nada pero que es necesario para un correcto funcionamiento. Fricción es rellenar papeleo de Hacienda. Muchos defensores de estas posturas argumentan que en realidad esa productividad perdida se recupera con el tiempo al reducir el número de bugs que pueden ocurrir. Blow dice que según su experiencia en juegos AAA realmente no compensa. Tardas más tiempo solventado bugs potenciales que bugs reales. Su solución no es evitar al 100% este tipo de bugs (como hace Rust) sino habilitar unas herramientas potentes que ayuden a solucionar estos bugs si alguna vez suceden.

Esto se relaciona con el placer por programar. Un lenguaje que te invite a programar, a experimentar, donde te sientas a gusto. Muchos lenguajes han perdido esa esencia. Con el tiempo muchos lenguajes se han ido complicando de forma innecesaria y se han ido volviendo pesadillas. Ruby sería el caso contrario, un lenguaje que conserva ese placer. Pero Ruby no entra en juego por razones obvias de rendimiento.

Con rendimiento básicamente dice que cualquier lenguaje que disponga de GC (recolector de basura) no es válido. Incluso Go, que tiene un GC mucho mejor que Java o la plataforma .NET no lo considera correcto.

Con simplicidad se busca legibilidad y potencia. El lenguaje debe ser uniforme, con cohesión en todos sus elementos.

Y con diseñado para buenos programadores se refiere a que el lenguaje no debe suponer que el programador es idiota e intentar corregir sus errores. Debe permitir hacer virguerías si así lo desea el programador. Rust está bien pues permite tener código unsafe. Justo lo que se necesita para hacer virguerías. Pero hace falta más trabajo en este aspecto pues supone un cambio de mentalidad.

La idea detrás de RAII es incorrecta


Mucha gente opina que RAII es una de las mejores cosas que han pasado en la programación. Muchos lenguajes presuponen RAII. D por ejemplo considera que RAII es la manera correcta de programar. Resource Acquisition Is Initialization consiste en que cada vez que vamos a acceder a un recurso tenemos que codificarlo en una clase, inicializar el recurso en un constructor y liberar el recurso en un destructor. Añades operadores para permitir copia, ... Este sistema presenta una elevada fricción. Y además no funciona bien, en el sentido de que todo se acaba sobrecomplicando. Alejándose de esa simplicidad que estamos buscando.

Uno de los principales problemas de este patrón de diseño es que no existe un recurso. Es una generalización errónea de varios conceptos. Un recurso puede ser memoria, otro recurso puede ser un archivo, una entrada de teclado, etc El problema es que estos recursos son demasiado diferentes como para ser tratados con un mismo patrón de forma óptima. Mientras RAII puede ser interesante hablando de archivos, es una muy mala opción si hablamos de memoria. Porque la memoria es el recurso más importante para un programador. Se podría simplificar diciendo que un programador lo único que hace es modificar la memoria constantemente.

Pero muchos de los usos de RAII tienen que ver con las excepciones. Y a Blow tampoco le gustan las excepciones. La gestión de errores en C es pésima pero las excepciones son muy complejas. Una de las cosas más complejas que implementan los lenguajes de programación que disponen de ellas. Y la implementación de C++ más todavía. Blow se lamenta de que haya gente que siga pensando que es una buena idea. Reduce la claridad del código, complica el flujo del programa. RAII en C++ ayuda a que en caso de que se de una excepción los recursos puedan ser liberados.

No solo lo dice él, sino que enlaza el siguiente vídeo: Systematic Error Handling in C++ por Andrei Alexandrescu.



Un mejor sistema que las excepciones


Go implementa múltiples valores de retorno (al contrario que la mayoría de lenguajes derivados de C donde solo de devuelve una cosa). Go lo soporta de forma nativa. Pero Matt Newport le responde como podría hacer eso en C++11 con std::tie.


#include <iostream>;
#include <tuple>;
#include <functional>;

std::tuple<int, int> f()
{
int x = 5;
return std::make_tuple(x, 7); // return {x,7}; en C++17
}

int main()
{
int a, b;
std::tie(a, b) = f();
std::cout << a << " " << b << "\n";
}


Rust, como Go, soporta esto de forma nativa:


fn f() -> (i32,i32) {
(4,7)
}

fn main() -> () {
let (a,b) = f();
println!("A es {}, B es {}",a,b);
}


Aunque no es la manera en la que Rust maneja los errores. En su lugar posee Option y Result que en C++17 también van a ser implementados en std::optional y que es en realidad un concepto presente en Haskell.

Sintaxis exasperante


En la charla Blow sigue hablando y comenta que desde un punto de visto purista y correcto la sintaxis de punteros de C++11 es incorrecta. Que std::unique_ptr<Vector3[]> implica que quieres un Unique Ptr basado en Vector3 pero en realidad la idea correcta sería quiero un Vector3 con características de Unique Ptr. Lo mismo es aplicable para std::shared_ptr. Este tipo de punteros no deberían estar expresados de esta forma sino que deberían entrar en la sintaxis del lenguaje, por su utilidad práctica.

En Rust, el equivalente a std::unique_ptr sería Box que es el puntero más usado. El equivalente a std::shared_ptr sería Rc, no tan usado pero disponible.

Blow sigue hablando en este vídeo y en el siguiente de más errores que tiene C++, aunque de menor importancia. En todo caso, Blow sigue en el desarrollo de su compilador de Jai. C++ ha avanzado un montón y me ha llegado a sorprender que existiesen cosas como constexpr y los módulos de C++, una solución a los archivos de cabecera que son tan tediosos de escribir.

Si tenéis tiempo y sabéis inglés miráos el vídeo original. Creo que esta mucho mejor explicado que esto. Y también la respuesta de Matt Newport en la que dice que C++ SÍ es válido para todo lo que dice Blow.
]]>
https://blog.adrianistan.eu/nuevo-lenguaje-programacion-juegos Sun, 8 Jan 2017 00:00:00 +0000
Mozilla desiste, Rust será sustituido por Jai https://blog.adrianistan.eu/mozilla-desiste-rust-sera-sustituido-jai https://blog.adrianistan.eu/mozilla-desiste-rust-sera-sustituido-jai
Rust

Muchos conocerán a Jonathan Blow por ser el creador de juegos como Braid o The Witness, pero además, lleva trabajando en Jai desde 2014.



Jai actualmente es un lenguaje que transpila a C++, para posteriormente ser compilado por un compilador normal de C++. La idea detrás de Jai es ser el lenguaje de programación de videojuegos. Tras haber trabajado muchos años con C++, Blow ha diseñado un lenguaje que mejore las partes malas de C, conservando sus partes buenas. Blow criticó al lenguaje Rust asegurando que usar Rust era como la casa que tiene sus esquinas plastificadas pero luego te deja una pistola cargada sobre la mesa.

Brendan Eich, creador de JavaScript ha declarado:
En el fondo sabíamos que podría pasar. En Mozilla solo sabemos diseñar lenguajes horribles. De hecho a mí me dejaron hacer JavaScript y ya sabemos lo que pasó

Ahora Mozilla se ha acercado a Blow para aprender más sobre Jai, un lenguaje mucho más práctico. Jai permite al programador hacer lo que quiera, pero opcionalmente y a través de syntactic sugar es optimizado y analizado para llegar a mejorar en rendimiento incluso a C.

James Gosling, creador de Java ha dicho:
Nosotros diseñamos Java para que fuese muy difícil escribir código malo. Cualquier mejora sobre eso se merece mis aplausos, lástima que no se oigan porque la JVM todavía está arrancando.

A día de hoy no existen compiladores de Jai disponibles al público y la única copia se cree que está en manos de Jonathan Blow. La única documentación existente se encuentra recopilada en GitHub por fans de Jai.

Definitivamente este año no ha sido muy bueno para Mozilla, que ya ha tenido que abandonar otros proyectos como Firefox OS o Matchstick, que prometían innovación pero que por A o por B no han sabido llegar a buen puerto.

ACTUALIZACIÓN: ¡Feliz día de los inocentes!]]>
https://blog.adrianistan.eu/mozilla-desiste-rust-sera-sustituido-jai Wed, 28 Dec 2016 00:00:00 +0000
Crónica de un vídeo de citas célebres https://blog.adrianistan.eu/cronica-video-citas-celebres https://blog.adrianistan.eu/cronica-video-citas-celebres
Como muchos sabréis, este año me pasé el videojuego The Witness. Es un juego muy interesante y que os recomiendo. El caso es que nunca llegué a escuchar todas las citas célebres del juego, y en YouTube solo encontré vídeos sueltos con subtítulos en... francés. Así que me propuse hacer un vídeo que recogiese todas las citas célebres del juego, con subtítulos en Español. Antes esta situación hay dos opciones:

  • Lo que una persona normal haría sería buscar en la guía las localizaciones de las citas e ir grabándolas.

  • Lo que haría un perturbado sería meterse en los archivos del juego, decodificar los archivos e implementar un complejo sistema para generar el vídeo.


Por supuesto, hice lo último.

thewitnessquotes

Encontrando archivos del juego


Los archivos del juego no están escondidos, se encuentran en un archivo llamado data-pc.zip. Hasta ahí fácil. Una vez dentro encontramos cientos de archivos con unas extensiones peculiares que nos informan de lo que hay dentro (.texture, .lightmap,...). Sin embargo los archivos de sonido no los encontramos de manera sencilla. Necesitan una conversión. Estaba ya buscando soluciones (sospecho que tiene que ver con Audiokinetic Wwise) cuando encontré en reddit a un buen samaritano que había subido, ya decodificados, los archivos de sonido a Mega.

Al ver los archivos observé cantidad de ficheros .WAV y muy poquitos .OGG. Eso ya nos da una pista, pues las citas célebres han tenido que ser codificadas como Ogg, ya que si fuesen con WAV el fichero sería demasiado grande. Extraigo los archivos y borro todos los WAV, pues sé que ahí no están.

Pero no hemos acabado. Los Ogg no solo eran de citas célebres, también había efectos de sonido largos. Afortunadamente, los archivos de efectos de sonido solían llevar un prefijo común (amb_, spec_,...).

Los subtítulos


Tenemos los ficheros de audio de las citas en formato Ogg. Ahora hacen falta los subtítulos. Están fuera de ese fichero ZIP gigante y no es difícil encontrarlos. En concreto el archivo es_ES.subtitles. Sin embargo, una vez lo abres descubres la primera sorpresa. Es un formato del que desconocía su existencia. Os pongo un poco para ver si alguien es capaz de saber el formato:

 


: tagore_voyage

= 1.000000
Creía que mi viaje había llegado al final
al estar al límite de mis fuerzas...

= 6.700000
que el camino ante mí estaba cerrado,
las provisiones agotadas
y que había llegado la hora de refugiarse en la silenciosa oscuridad.

= 17.299999
Pero encuentro que la voluntad no se me acaba,

= 21.299999
y cuando las palabras se apagan en la lengua,
surgen nuevas melodías del corazón;

= 27.200001
y cuando se pierde el antiguo camino,
una nueva tierra revela sus maravillas.

= 32.700001

= 32.709999
Rabindranath Tagore, 1910


: tagore_end

= 1.000000
En mi vida te he buscado con mis canciones.
Fueron ellas las que me guiaron de puerta en puerta,

= 8.800000
y con ellas me he sentido a mí,
buscando y tocando mi mundo.

= 14.400000

= 14.900000
Fueron mis canciones las que me enseñaron
todas las lecciones que he aprendido

= 18.900000
me mostraban caminos secretos,
condujeron mi vista hacia más de una estrella
en el horizonte de mi corazón.

= 25.500000

= 26.100000
Me guiaron todo el día
hacia los misterios del país del placer y el dolor

= 32.500000
y, finalmente,
¿a las puertas de qué palacio me han
guiado en el atardecer al final de mi viaje?

= 39.299999


: tagore_boast

= 1.000000
Presumí ante los hombres de conocerte.

= 4.300000
Ven tus imágenes en mis trabajos.
Vienen y me preguntaban "¿Quién es él?"

= 10.900000
No tengo respuesta para ellos.
Les digo "No sabría decirlo".

= 16.799999
Me culpan y se marchan con desprecio.
Y tú te sientas ahí, sonriendo.

= 23.500000

= 24.700001
Mis historias sobre ti quedan en canciones duraderas.
El secreto sale a borbotones de mi corazón.

= 31.000000
Vienen y me piden
"Cuéntame todo lo que significan".

= 34.900002
No tengo respuesta para ellos.
Les digo "Ah, ¡quién sabrá!"

= 40.500000

= 41.000000
Sonríen y se marchan con desprecio absoluto.
Y tú te sientas ahí, sonriendo.

= 48.000000

= 49.500000
- Rabindranath Tagore, 1910


Pero no me iba a detener. Así que empecé a diseñar un programa que permitiese traducir este archivo a un archivo SRT normal y corriente. Para ello usaría Regex a saco (me leí el libro, para algo me tendría que servir).

regexp

Para hacer el programa usé Node.js. Sí, se que para este tipo de cosas el mejor lenguaje es Perl, o un derivado como Ruby pero todavía no he aprendido lo suficiente de Ruby como para plantearmelo. JavaScript cuenta de forma estándar (tanto Node.js como navegador) con la clase RegExp, que permite ejecutar expresiones regulares y esa es la que he usado.

Finalmente conseguí hacer un script de Node.js, sin dependencias externas, que traduciese este archivo subtitles en un SRT.

Generando un vídeo para cada cita


Ya tenemos el audio, tenemos los subtítulos en un formato conocido. Vamos ahora a generar un vídeo. Primero necesitaremos una imagen de fondo. Pillo una cualquiera de Internet y empiezo a hacer pruebas con ffmpeg. El formato de salida va a ser MP4 codificado con H264 porque realmente es el formato que más rápido se codifica en mi ordenador.

Nada más empezar empiezo a ver que los subtítulos no están funcionando, no se fusionan con la imagen y el audio. Al parecer es un problema que involucra a fontconfig, ffmpeg y Windows. Sí, estaba usando Windows hasta ahora.

Me muevo a Debian y ahora ya funciona bien el fusionado de subtítulos.

Ahora intento unir dos vídeos con ffmpeg también. Fracaso. Lo vuelvo a intentar, FRACASO. Si os digo que la mayor parte del tiempo que me ha llevado este proyecto ha sido encontrar como concatenar varios MP4 en ffmpeg sin que me diese errores extraños quizá no os lo creeríais, pero es verídico. No me creeríais porque la wiki de ffmpeg lo explica correctamente y si buscáis por Internet os van a decir lo mismo. ¿Qué era lo que pasaba?

  1. Las dimensiones de los vídeos no cuadraban

    1. Esto fue obvio y fue lo primero que pensé. ffmpeg tiene un filtro de escalado, pero por alguna razón no funcionaba. La razón era que estaba usando dos veces la opción "-vf" (filtro de vídeo), una con los subtítulos y otra con el escalado. ffmpeg no admite nos veces la opción, si quieres aplicar dos filtros de vídeo tienes que usar una coma entre ellos.



  2. Formato de píxeles

    1. Este era el verdadero problema. Normalmente no suele pasar, pero como las imágenes de los dos vídeos venían de fuentes distintas, ffmpeg usó un formato de píxeles distinto en cada una. Forzando a ffmpeg a usar siempre "yuv420p" funcionó y la concatenación se pudo realizar.




Probé también con mkvmerge, pero me decía que la longitud de los códecs era distinta. No entendí el error hasta que no me enteré que había sido el formato de píxeles, cada vídeo usaba uno distinto en su codificación.

El comando necesario para generar cada vídeo fue entonces:


ffmpeg -loop 1 -i imagen.jpg -i audio.ogg -c:v libx264 -tune stillimage -pix_fmt yuv420p -s 1280x720 -vf scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2,subtitles=subtitulos.srt video.mp4


Concatenar los vídeos


Para concatenar los vídeos es necesario tener un archivo de texto donde se indiquen los archivos y su orden, siguiendo este formato:


# Archivo de concatenacion ffmpeg
file 'video1.mp4'
file 'video2.mp4'
file 'video3.mp4'


Luego, su uso es bastante sencillo:


ffmpeg -f concat -i videos.txt -c:v copy video.mp4


El script final


Ahora solo hacía falta convertir todos los archivos de audio en vídeo con sus subtítulos. Usando un script de bash se puede hacer esto:


for f in *.ogg; do
node main.js "${f%.*}"
done


Y el código de main.js es el siguiente. main.js se encarga de traducir los ficheros subtitles a SRT, de llamar a ffmpeg y de añadir el vídeo a la lista de videos.txt para la posterior concatenación.


var fs = require("fs");
var spawn = require("child_process").spawn;

var ZONE = process.argv[2];
var IMG = process.argv[2]+".jpg";

function timeFormat(SEC,MILISEC){
var date = new Date(SEC * 1000);
var regex = new RegExp("([0-9]+:[0-9]+:[0-9]+)");
var str = regex.exec(date.toUTCString())[1] + "," + MILISEC.substring(0,3);
return str;
}

var witness = fs.readFileSync("es_ES.subtitles","utf-8");
var srt = fs.createWriteStream(ZONE+".srt");

var regex = ": "+ZONE+"\n\n= ([0-9]+).([0-9]+)\n";

var LINE = 1;

while(!(new RegExp(regex + "\n: ","g").test(witness))){
var start = new RegExp(regex,"g");
var match = start.exec(witness);

var START_TIME_SEC = parseInt(match[match.length - 2]);
var START_TIME_MILISEC = match[match.length - 1];
var TEXT = "";

do{
var text = new RegExp(regex + "(.+)\n","g");
var exec = text.exec(witness);
if(exec!=null){
TEXT = TEXT + "\n" + exec[exec.length - 1];
regex = regex + "(.+)\n";
}
}while(exec != null);

regex = regex + "\n= ([0-9]+).([0-9]+)\n";
var time = new RegExp(regex,"g");
var end_time = time.exec(witness);
if(end_time != null){
var END_TIME_SEC = parseInt(end_time[end_time.length - 2]);
var END_TIME_MILISEC = end_time[end_time.length - 1];
srt.write(LINE + "\n");
srt.write(timeFormat(START_TIME_SEC,START_TIME_MILISEC)+" --&amp;amp;amp;gt; "+timeFormat(END_TIME_SEC,END_TIME_MILISEC));
srt.write(TEXT);
srt.write("\n\n");
}else{
srt.write(LINE + "\n");
srt.write(timeFormat(START_TIME_SEC,START_TIME_MILISEC)+" --&amp;amp;amp;gt; "+timeFormat(5*60,"000000"));
srt.write(TEXT);
srt.write("\n\n");
break;
}
LINE++;
}

srt.end();

var ffmpeg = spawn("ffmpeg",["-loop","1","-i",IMG,"-i",ZONE+".ogg","-c:v","libx264","-tune","stillimage","-pix_fmt","yuv420p","-s","1280x720","-shortest","-vf","scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2,subtitles="+ZONE+".srt",ZONE+".mp4"]);

ffmpeg.stderr.on('data', (data) =&amp;amp;amp;gt; {
console.log(`stderr: ${data}`);
});

fs.appendFileSync("video.txt","file '"+ZONE+".mp4'\n");


Se trata de un programa que hice deprisa y corriendo y aunque el código es feo (o eso me parece a mi), la verdad es que me ha servido.

Y el resultado...



 ]]>
https://blog.adrianistan.eu/cronica-video-citas-celebres Sun, 18 Dec 2016 00:00:00 +0000
Las 12 Uvas Game Jam https://blog.adrianistan.eu/las-12-uvas-game-jam https://blog.adrianistan.eu/las-12-uvas-game-jam
las12uvasEl foro Gamedev Hispano presenta su primera game jam, un evento donde tendrás que desarrollar un juego de cero, teniéndolo que acabar antes de que termine el año.

Se trata de las 12 uvas game jam y cualquiera puede participar tanto de forma individual como por equipos. Esta competición tiene pocas reglas y es ideal si es tu primera game jam, aunque seguro que si has participado en otras también te gustará. El tema elegido es: Hasta la vista 2016.

Se puede programar el juego como quieras (Unity, Phaser, libGDX, Corona, LUA,...) siempre que sea capaz de funcionar al menos en Windows, Linux, Mozilla Firefox (HTML5) o Android. Se valorará creatividad y diversión, pero también se puntuará el uso que le dan los participantes al foro.

¿A qué esperas? Tienes toda la información en el foro.

 ]]>
https://blog.adrianistan.eu/las-12-uvas-game-jam Tue, 13 Dec 2016 00:00:00 +0000
Nace el foro Gamedev Hispano https://blog.adrianistan.eu/nace-foro-gamedev-hispano https://blog.adrianistan.eu/nace-foro-gamedev-hispano Gamedev Hispano.

Se trata de un punto de encuentro para preguntar, debatir y descubrir sobre desarrollo de juegos en la lengua de Cervantes. Inicialmente enfocado al desarrollo de juegos HTML5 (Phaser y Babylon), el foro huye de ser "otro más" de Unity/Unreal y se centra en un desarrollo de juegos quizá para algunos más artesanal.

screenshot-gamedevhispano-com-2016-12-05-15-00-18

Aunque Phaser y Babylon hayan sido los frameworks elegidos para el foro, nadie está excluido. Existen subforos de SDL, Ogre3D, PyGame, libGDX y Godot y foros de temática general como ¡Enseña tu juego! y Multimedia.

Os animo a todos a registraros en el foro y a crear un hilo. Que la gente participe y colabore para ayudarse mutuamente.

Gamedev Hispano

]]>
https://blog.adrianistan.eu/nace-foro-gamedev-hispano Mon, 5 Dec 2016 00:00:00 +0000
Usando Iron, un web framework para Rust https://blog.adrianistan.eu/usando-iron-web-framework-rust https://blog.adrianistan.eu/usando-iron-web-framework-rust Actualmente, si quiéres hacer una aplicación web con Rust, te recomiendo Rocket, es mucho más sencillo de usar, más potente, más rápido y tiene más usuarios.
Rust cada día atrae a más desarrolladores. Es eficiente y es robusto. Mozilla ha sido la principal impulsora de este lenguaje para ser usado en entornos tan complejos como el propio Firefox.

Hoy vamos a introducirnos en el mundo del desarrollo web con Rust. Cuando la gente oye desarrollo web normalmente se piensa en lenguajes como PHP, Python, Ruby o JavaScript. Estos son lenguajes con los que es rápido desarrollar algo, aunque son mucho menos eficientes y es más fácil cometer errores debido a que son interpretados directamente. Un paso por encima tenemos a Java y C#, que cubren en parte las carencias de los otros lenguajes mencionados. Ha llegado la hora de hablar de Rust. Si bien es cierto que hay web frameworks en C++, nunca han sido muy populares. ¿Será Rust la opción que finalmente nos permita tener aplicaciones web con una eficiencia nativa?

Existen varios web frameworks en Rust, para este tutorial vamos a usar Iron, el más popular según Crates.io. Quizá te interese echar un vistazo también a Rocket, mi preferido.

ironframework

Crear proyecto e instalar Iron


Lo primero que hay que hacer es crear un nuevo proyecto en Rust, lo hacemos gracias a Cargo.
cargo new --bin MundoRust

cd MundoRust

Ahora editamos el fichero Cargo.toml para añadir las dependencias que vamos a usar.
[package]
name = "MundoRust"
version = "0.1.0"
authors = ["Adrián Arroyo Calle"]

[dependencies]
iron = "0.4.0"
router = "0.4.0"
staticfile = "0.3.1"
mount = "0.2.1"


Ahora obtenemos las dependencias especificadas con Cargo.
cargo run

Hola Mundo con Iron


Vamos a empezar a programar en Rust. Vamos a hacer una simple aplicación que devuelva "Hola Rustáceos" por HTTP.

Editamos el archivo src/main.rs
extern crate iron;

use iron::prelude::*;

fn hola(_: &mut Request) -> IronResult<Response> {
Ok(Response::with((iron::status::Ok, "Hola Rustaceos")))
}

fn main() {
Iron::new(hola).http("0.0.0.0:80").unwrap();
}

holarustaceos

Usando router para el enrutamiento


Hemos hecho un pequeño servidor HTTP con Iron. Pero nos falta algo, que sea capaz de manejar rutas. Que miweb.com/hola no sea lo mismo que miweb.com/adios. Iron por defecto no trae enrutador, pero es muy habitual usar Router, que ya hemos instalado antes por conveniencia.
extern crate iron;
extern crate router;

use iron::prelude::*;
use router::Router;

fn get_page(r: &mut Request) -> IronResult<Response>{
let path = r.url.path();
Ok(Response::with((iron::status::Ok, format!("Hola, peticion GET {}",path[0]))))
}

fn submit(_: &mut Request) -> IronResult<Response>{
Ok(Response::with((iron::status::Ok, "Peticion POST")))
}

fn main() {
let mut router = Router::new();

router.get("/:page", get_page, "page");
router.post("/submit", submit, "subdmit");

Iron::new(router).http("0.0.0.0:80").unwrap();

}

getiron

Archivos estáticos


Para gestionar los ficheros estáticos vamos a usar staticfile y mount, otras dos librerías para Iron.
extern crate iron;
extern crate router;
extern crate staticfile;
extern crate mount;

use iron::prelude::*;
use router::Router;
use staticfile::Static;
use mount::Mount;

fn get_page(r: &mut Request) -> IronResult<Response>{
let path = r.url.path();
Ok(Response::with((iron::status::Ok, format!("Hola, peticion GET {}",path[0]))))
}

fn main() {
let mut router = Router::new();

router.get("/:page", get_page, "page");

let mut mount = Mount::new();
mount.mount("/public",Static::new("static/"));
mount.mount("/",router);

Iron::new(mount).http("0.0.0.0:80").unwrap();
}

ironstaticfile

 

Hemos dado nuestros primeros pasos en Iron, un web framework para Rust. Iron es completamente modular y soporta muchas más cosas que aquí no hemos visto. Gran parte de su funcionalidad se implementa a través de middleware, como en otros web frameworks populares.]]>
https://blog.adrianistan.eu/usando-iron-web-framework-rust Sun, 20 Nov 2016 00:00:00 +0000
Tutorial de WebExtensions (II) - Popup y Tabs https://blog.adrianistan.eu/tutorial-webextensions-ii-popup-tabs https://blog.adrianistan.eu/tutorial-webextensions-ii-popup-tabs

¿Qué es el popup?


El popup es un panel desplegable que se acciona al pulsar un botón de acción. Este panel es una página HTML que puede contener código JavaScript.

popup

El popup es el elemento que permite al usuario un mayor control sobre la extensión y es ideal tanto para mostrar información como para contener acciones. De hecho, WebExtensions limita el número de botones de acción que puede crear una extensión a uno. Si queremos simular tener más botones deberemos usar un popup que despliegue un pequeño menú.

Definir el popup en manifest.json


Para definir el popup tenemos que indicar la ubicación del fichero HTML correspondiente al popup.


"browser_action" : {
"default_icon" : "icons/icono_guay.png",
"default_title" : "Mi PopUp guay",
"default_popup" : "popup/index.html"
},


Con eso ya tenemos definido el popup. Se mostrará el botón en la barra superior y al hacer click se abrirá el popup.

El PopUp en sí


El PopUp es un fichero HTML sin el mayor misterio. Para definir el tamaño del PopUp usaremos CSS:


body{
width: 400px;
height: 400px;
}


Y el navegador adaptará el PopUp a esas dimensiones. A ese archivo HTML le podemos añadir archivos JavaScript mediante el procedimiento habitual en la web, vía una etiqueta script. Lo interesante viene en estos archivos de JavaScript pues cuentan con más privilegios. Desde ellos podemos acceder a las APIs retringidas a extensiones así como librarnos de limitaciones impuestas a las webs (como la política que bloquea los XMLHttpRequests ejecutados a dominios ajenos a nuestra página).

Para usar las APIs restringidas debemos pedir permiso en el fichero manifest.json. Por ejemplo, vamos a jugar con las pestañas. Para ello usamos los permisos tabs y activeTab.


"permissions" : ["tabs","activeTab"],


Tabs (pestañas) en WebExtensions


La API se encuentra en chrome.tabs y dispone de varios métodos y eventos. Vamos a ver los más importantes:

  • chrome.tabs.query - Obtener información de las pestañas

    • Este método es muy flexible, lo podemos comparar a hacer un SELECT en SQL, pues obtiene una lista de pestañas que cumplen los parámetros que hemos indicado. Por ejemplo para comprobar la pestaña actual, usaremos la propiedad active:


      chrome.tabs.query({"active": true},function(tabs){
      var tab = tabs[0];
      // tab es la pestaña actualmente activa
      });


      Otra opción muy interesante es para averiguar que pestañas tienen abierta una URL que cumple un patrón. Además los parámetros de búsqueda se pueden combinar:


      chrome.tabs.query({"url" : "*://*.adrianistan.eu/*", "status" : "loading", "muted" : true},function(tabs){
      // tabs (que puede estar vacío) contiene todas las URL que estan en un adrianistan.eu (subdominios incluidos),
      // se encuentran cargando
      // y el usuario ha silenciado su sonido
      });





  • chrome.tabs.create - Permite crear nuevas pestañas

    • Su uso básico es muy simple


      chrome.tabs.create({"url" : "https://blog.adrianistan.eu/"},function(tab){
      // tab es la pestaña creada
      });





  • chrome.tabs.executeScript - Esta función permite inyectar a la pestaña un script de contenido (Content Script).

    • Ahora no vamos a entrar en como se realizaría la comunicación entre los scripts Background y los Content. El código se puede pasar por una cadena de texto o por un archivo, siendo preferible esta última opción. Se puede especificar además cuando queremos que se ejecute dentro del ciclo de carga de la pestaña.


      chrome.tabs.executeScript(tab.id (opcional), {"code" : "alert('Hola');"},function(){

      });

      O

      chrome.tabs.executeScript({"file" : "miarchivo.js", "runAt" : "document_start"},function(){

      });





  • chrome.tabs.insertCSS - Permite inyectar CSS en la pestaña

    • Su uso es exactamente igual al de chrome.tabs.executeScript


      chrome.tabs.insertCSS({"file" : "misestilos.css"},function(){

      });





  • chrome.tabs.sendMessage - Enviar un mensaje a la pestaña

    • Hasta ahora no hemos visto la separación entre background y content scripts. Esta función permite enviar datos desde los scripts background a los content, que tendrán una función de escucha. La menciono, pero lo veremos más adelante en profundidad.




Por último, veamos algunos de los eventos que puede generar la API.

  • chrome.tabs.onUpdated - Es llamado cada vez que una pestaña sufre una modificación en sus datos (cambio de URL, quitar el sonido, finalizar una carga, etc)

    • Es muy posible que necesitemos filtrar el contenido que nos da, para fijarnos si se ha producido en una URL que nos interesa o en alguna condición relevante. Eso no obstante, es responsabilidad del programador.


      chrome.tabs.onUpdated.addListener(function(tabId,changeInfo,tab){
      // el objeto changeInfo presenta una estructura similar al de búsqueda con chrome.tabs.query
      // este objeto solo contiene valores para aquellos parámetros que han cambiado
      // con tab podemos acceder a la pestaña al completo
      });






Con esto ya podemos hacer extensiones interesantes, la APIs de PopUp y de Tabs son de las más usadas en WebExtensions.]]>
https://blog.adrianistan.eu/tutorial-webextensions-ii-popup-tabs Thu, 3 Nov 2016 00:00:00 +0000
Tutorial de WebExtensions (I) - manifest.json y conceptos https://blog.adrianistan.eu/tutorial-webextensions-i-manifest-json-conceptos https://blog.adrianistan.eu/tutorial-webextensions-i-manifest-json-conceptos
WebExtensions Tomada de http://tenfourfox.blogspot.com.es/2015/08/okay-you-want-webextensions-api.html

Posteriormente Firefox respondería con Jetpack y Addon SDK, un sistema similar pero escrito encima de XUL y XPCOM, incompatible pero que no necesitaba reinicios y tampoco perdía potencia pues las APIs de XPCOM seguían allí.

Luego Opera decide abandonar Presto y usar Blink, el motor de Chromium. Con respecto a su sistema de extensiones, planean ser 100% compatibles con Chrome.

Firefox también ha de realizar cambios internos, concretamente se inició el trabajo (electrolysis) de hacer Firefix multiproceso. Esto rompía gran parte de las APIs internas de Firefox. Todos los complementos necesitarían ser reescritos. Ya de paso, teniendo en cuenta que hay que diseñar una nueva API para extensiones, se toma la de Chrome y se renombra WebExtensions. Posteriormente Microsoft afirma que Edge será compatible con extensiones de Chrome o WebExtensions. Esta es la historia de como 4 navegadores se pusieron de acuerdo en estandarizar la API de desarrollo de extensiones para navegadores.

En esta serie de tutoriales vamos a ver como programar nuestra extensión usando WebExtensions.

manifest.json


El fichero manifest.json de una extensión es muy importante y es obligatorio. Contiene metadatos de la extensión así como permisos de los que dispone la extensión y las acciones que define.

En primer lugar vamos a distinguir entre distintos tipos de scripts distintos.

  • Background scripts: estos scripts se ejecutan en el fondo del navegador. Se ejecutan al iniciar el navegador. Podemos configurar eventos y entonces se mantendrán a la escucha durante toda la sesión. Estos scripts no pueden acceder a las páginas web.

  • Content scripts: estos scripts se añaden a determinadas páginas según un patrón en el archivo manifest.json o desde un background script. Estos scripts interactúan directamente con las páginas en los que han sido adjuntados.


Veamos ahora un fichero manifest.json

 


{
"manifest_version" : 2,
"name" : "Bc.vc shortener",
"version" : "2.0.0",
"description" : "Shorthener for Bc.vc",
"homepage_url" : "http://adrianistan.eu/norax/",
"icons" : {
"16" : "icon-16.png",
"48" : "icon-48.png",
"64" : "icon-64.png",
"96" : "icon-96.png",
"128" : "icon-128.png"
},
"applications" : {
"gecko" : {
"id" : "@bc-vc",
"strict_min_version" : "45.*"
}
},
"permissions" : ["notifications","contextMenus","clipboardWrite","activeTab","storage","http://bc.vc/"],
"browser_action" : {
"default_title" : "Bc.vc shortener",
"default_icon" : "icon-64.png"
},
"background" : {
"scripts" : ["background/install.js","background/main.js"]
},
"options_ui" : {
"page" : "options/options.html",
"chrome_style" : true
}
}


manifest_version siempre es 2. Al principio rellenamos metadatos sencillos, name, description, version, homepage_url,... Posteriormente debemos añadir los iconos. Dependiendo de lo que queramos no hacen falta todos los iconos. Por ejemplo, el icono de 16x16 solo es necesario si nuestra extensión define un menú contextual del botón derecho. La sección applications ha sido añadida por Mozilla y originalmente no está en Chrome. Define la compatibilidad entre distintos navegadores y versiones, sin embargo, por ahora solo Firefox tiene en cuenta esto.

A continuación se definen los permisos. Una extensión necesita pedir permiso para acceder a ciertas APIs más privilegiadas. Por ejemplo, para mostrar notificaciones o para realizar peticiones HTTP a servidores remotos.

A continuación vemos la acción del navegador. A diferencia de otros sistemas de extensiones, WebExtensiones solo permite un botón en la interfaz del navegador. Es una manera excelente de separar funcionalidades, aunque si vienes de Firefox verás como limita a muchas extensiones, algunas de las cuáles modificaban la interfaz por completo. La acción del navegador puede hacer dos cosas: abrir un panel popup o generar un evento en el background script. Dependiendo de como sea la experiencia de usuario que prefieras te convendrá una opción u otra (en este caso, como no hemos definido un popup html default_popup, tendremos que escuchar el evento browserAction en un background script). Similar a browser_action existe page_action, la única diferencia es el lugar donde aparece el botón.

Page Action a la izquierda

Browser Action a la derecha

Después podemos asignar archivos de background scripts. Por último options_ui es la manera adecuada de crear una página de opciones. Será una página HTML, pero de eso hablaremos en otro tutorial.

Por último vamos a ver los content scripts


"content_scripts": [
{
"matches": ["*://*.mozilla.org/*"],
"js": ["borderify.js"],
"css" : ["style.css"],
"all_frames" : false,
"run_at" : "document_end"
}
]


Una manera de añadir content scripts es desde el manifest.json, especificando un patrón en matches. Podemos añadir JavaScript y CSS.

Otra opción es web_accesible_resources. Se trata de elementos de la extensión que pueden ser cargados desde una página web. Por ejemplo, si un content script quiere mostrar una imagen en una web, esta deberá estar indicada en web_accesible_resources.


"web_accesible_resources" : ["imagen/zorro.png"]

...

chrome.extension.getURL("imagen/zorro.png"); // desde el content script


Este fichero es el más importante de una WebExtension. En el siguiente tutorial veremos los background scripts combinados con browser actions.]]>
https://blog.adrianistan.eu/tutorial-webextensions-i-manifest-json-conceptos Sat, 1 Oct 2016 00:00:00 +0000
Phaser.js Hispano, aprende a hacer videojuegos en HTML5 https://blog.adrianistan.eu/phaser-js-hispano-aprende-videojuegos-html5 https://blog.adrianistan.eu/phaser-js-hispano-aprende-videojuegos-html5 Phaser.js Hispano es un sitio web, de mi propia creación, donde escribo tutoriales para aprender a usar Phaser, una de las librerías más populares para crear juegos 2D con JavaScript.

Phaser

Su éxito se basa en su simplicidad. No trata de reinventar la rueda con conceptos extraños e innovadores. Simplemente hace lo que muchas otras librerías hacen, de un modo claro, sin complicaciones. Fue creado por Richard Davey, alias photonstorm. Comenzó como una librería privada suya, pues él realiza juegos de navegador como trabajo. Con el tiempo fue mejorando, se hizo opensource y ahora cuenta con muchos usuarios aunque gran parte del desarrollo lo sigue realizando Richard. El motor ha sido usado con éxito en infinidad de juegos y a día de hoy me parece la opción más madura y efectiva de realizar un juego HTML5 con JavaScript.

Phaser usa Pixi.js como motor de renderizado, lo que permite a los juegos usar WebGL o Canvas 2D indistintamente. Además si manejas Pixi verás que algunas partes de la API de Phaser son similares. Los juegos funcionan en cualquier dispositivo que soporte HTML5, desde ordenadores de mesa (Windows/Mac/Linux) hasta televisores y consolas (WiiU, Xbox One, Chromecast,...) pasando por los omnipresentes móviles y tablets, que a día de hoy son las plataformas que prefieren los jugadores para jugar a juegos sencillos, de poca profundidad.

¿Estas listo para probar Phaser? He realizado una lista con, al menos, los elementos de los que me gustaría tratar en la web Phaser.js Hispano. La Gran Guía de Phaser en Español, úsalo como índice para tus consultas. Si tienes alguna duda no dudes en expresarla. ¿Quiéres tratar algún tema en particular? ¿Conocías la librería antes?]]>
https://blog.adrianistan.eu/phaser-js-hispano-aprende-videojuegos-html5 Thu, 1 Sep 2016 00:00:00 +0000
La Criptonovela del verano: una historia en tres capítulos (Capítulo 3) https://blog.adrianistan.eu/la-criptonovela-del-verano-una-historia-tres-capitulos-capitulo-3 https://blog.adrianistan.eu/la-criptonovela-del-verano-una-historia-tres-capitulos-capitulo-3

Capítulo 3: El robo de Bitfinex


Bitfinex es una casa de intercambio, donde la gente puede depositar sus bitcoins y conseguir otra moneda y viceversa. En el caso de Bitfinex el soporte al trading está muy presente.

Bitfinex

El 3 de agosto saltó a noticia, incluso la prensa generalista de hizo eco, había habido un robo en la plataforma. Al poco se aclaró que el problema no era un fallo de seguridad de Bitcoin sino de la propia plataforma de Bitfinex. La cantidad sustraída ascendió a más de 65 millones de dólares al cambio. Ese mismo día la cotización de Bitcoin cayó un 20%. A estas alturas el caso podría recordarnos al de Mt. Gox, pero aquí acaban las similitudes.

CEO de Mt. Gox en los buenos tiempos CEO de Mt. Gox en los buenos tiempos

Bitfinex bloqueó la plataforma y decidió que quitaría el 36% a todos los que tuviesen depositado dinero en la plataforma. No importa que tuviesen dólares, Litecoins, Ethereum, ... les afecta a todos. Bitfinex además tuvo una curiosa idea, compensó a sus usuarios con BFX, una moneda especial. Esta moneda representa el dinero sustraído, con valor nominal de 1$. Es decir, si por la quita del 36% perdiste 500$, Bitfinex te recompensa con 500 BFX.

Ante esta curiosa respuesta quedan dos posibilidades:

  • Cuando Bitfinex logre recuperar el dinero sustraído fruto de su actividad recompre los BFX a valor nominal. Esto implicaría que Bitfinex pagaría con sus bolsillos las pérdidas del robo y los usuarios recuperarían su dinero íntegramente.

  • Intercambiar los BFX por otra moneda, aquí uno puede asumir las pérdidas (pues el BFX siempre valdrá menos que un dólar a nivel de mercado, ya que la devolución no es algo garantizado) o comprar todavía más BFX con la esperanza de que suban.


Nada más salir BFX, su valor experimentó un descenso pronunciado, llegando a caer hasta un valor de 0,3 $.

Sin embargo esta solución no ha gustado a muchos usuarios que plantean emprender acciones legales contra la compañía.

A modo de comparación, si recordáis, con Ethereum ante un robo de una cantidad inferior se llegó al hard fork, sin embargo en este caso tal situación no se ha planteado, pues se considera que el fallo no lo tuvo en ningún caso Bitcoin sino Bitfinex.

¿Te parece correcta la solución de Bitfinex ante el robo?

Este es el último capítulo de la serie La Criptonovela del verano: una historia en 3 capítulos. Podéis dejar en los comentarios si os ha gustado o no y por qué.]]>
https://blog.adrianistan.eu/la-criptonovela-del-verano-una-historia-tres-capitulos-capitulo-3 Tue, 30 Aug 2016 00:00:00 +0000
La Criptonovela del verano: una historia en tres capítulos (Capítulo 2) https://blog.adrianistan.eu/la-criptonovela-del-verano-una-historia-tres-capitulos-capitulo-2 https://blog.adrianistan.eu/la-criptonovela-del-verano-una-historia-tres-capitulos-capitulo-2 En el capítulo anterior vimos los intentos de tomar el control de Bitcoin por parte de Blockstream y la disputa por el tamaño de bloque con Core, Classic y Unlimited luchando por hacerse un hueco. Veamos la situación en Ethereum.

Capítulo 2: Ethereum y la DAO, o el debate de si el código es la ley


Ethereum es una plataforma basada en la cadena de bloques. Normalmente cuando se habla de cadena de bloques la gente piensa en Bitcoin y piensa que la única aplicación de esta tecnología son las criptodivisas. ¿Pero qué pasa si en vez de dinero transmitimos datos? ¿Y si esos datos tienen un procesado dentro de la propia plataforma? Pues eso es Ethereum, donde pueden funcionar aplicaciones descentralizadas en su máquina virtual, con la verificación entre nodos que nos da la cadena de bloques. Esto son los contratos inteligentes, para más información visita la entrada que escribí sobre Ethereum y Smart Contracts.

El crecimiento de Ethereum ha sido impresionante, hasta tal punto de que la moneda propia de Ethereum, el Ether tiene niveles de capitalización de mercado y volumen que cualquier criptodivisa desearía y que solo Bitcoin es capaz de lograr.

EthereumMarketCap

Los desarrolladores de Ethereum deciden crear el 30 de abril de 2016 una organización autónoma, un organismo regido por el código sin trabajadores y a la vez fondo de inversión para otras empresas y organizaciones basadas en Ethereum que repartía beneficios a sus inversores. Su nombre fue la DAO (siglas de Decentralized Autonomous Organization). Algunas de sus características eran:

  • Funcionamiento sobre la plataforma Ethereum sin jefes ni junta directiva

  • Totalmente autónoma

  • Opensource, programada en Solidity

  • Opera sin la regulación de ninguna nación del mundo


La DAO fue financiada gracias a financiación colectiva (crowdfunding) el 28 de mayo de 2016, con un éxito rotundo. La DAO batió récords y se convirtió en la campaña de crowdfunding más exitosa de la historia, recaudando 160 millones de dólares en monedas de Ethereum, Ether. Superaba así al mayor proyecto hasta la fecha que era el videojuego Star Citizen.

DAO DAO, en chino, "el camino"

Se calculó que el 14% de todo el Ether minado en Ethereum se encontraba en la DAO. A partir del 28 de mayo las participaciones en la DAO podían ser intercambiadas como si se tratara de una criptodivisa más.

Al poco tiempo llegan los problemas, varias personas revisan el código de la DAO y encuentran vulnerabilidades graves que piden que sean corregidas.

Ya el 17 de junio, un hacker aprovechó una combinación de las vulnerabilidades descubiertas en la DAO previamente para sustraer un tercio de la cantidad depositada en la DAO. Estas vulnerabilidades no se creían explotables hast que el hacker encontró que las mismas se encontraban en otra parte del código y le dejarían replicar la DAO, pero bajo su control. Se intentó parar el ataque mandando SPAM a la red Ethereum. Al poco se lanzó un soft fork en Ethereum que limitaba la la cuenta DAO hija gastar ese dinero hasta que no hubiesen transcurrido 27 días, tiempo en el que se decidiría que hacer. Al cambio la cantidad del robo fue de 50 millones de dólares. El precio del Ether se desplomó. Esto suscitó un gran debate.

¿Era una brecha de seguridad? ¿O simplemente un método legal pero poco ético de cumplir las disposiciones del contrato inteligente? En Ethereum la norma era que el código es la ley, lo que se programa se cumple siempre sin excepción. El hacker cumplió los términos del contrato inteligente. ¿Era fallo del hacker o del desarrollador que escribió el contrato de forma pésima? ¿Se podía considerar un robo? Si recordamos, la DAO no estaba sujeta a ninguna legislación nacional.

La gente se dividió en dos bandos:

  • El primero consideraba que el hacker, aunque de forma poco ética, había cumplido los términos y disposiciones y por ello legalmente le pertenecería ese dinero.

  • Otros que consideraban que esto debía de ser considerado una excepción y que había que encontrar una forma de devolver el dinero a la gente, incumpliendo el mandato de que el código es la ley irrefutable.


DAOButton

Los desarrolladores de Ethereum, que eran también algunos de los grandes inversores de la DAO, prefirieron la segunda opción. Ethereum fue programado para añadir la característica de devolver el dinero sustraído a partir de un determinado bloque que entraría en la cadena a partir del 17 de julio. Obviamente los usuarios del primer grupo sintieron que los principios de Ethereum se estaban viendo traicionados y anunciaron sus intenciones de proseguir su trabajo en Ethereum Classic.

Empresas como Coinbase o Uphold apoyaron a Ethereum y se comprometieron a usar en sus nodos la versión que incluiría la devolución. Al igual que en Bitcoin, en Ethereum tenía que haber un consenso de la fuerza de cómputo suficiente para lograr un hard fork y dividir la cadena en dos. Se produjo el hard fork, en ese momento los nodos que no actualizaron su versión Ethereum pasaron a Classic.

Etc

Ahora ambas plataformas conviven, con cadenas de bloques separadas. En Ethereum Classic prefieren código irreversible, resistente a censuras, con los ideales de que Ethereum es ese ordenador que nunca se apaga y que siempre ejecuta tus contratos. Se calcula que un 22% de los usuarios de Ethereum apoyan las pretensiones de Classic y que lo ocurrido con la DAO sienta un terrible precedente que podría desembocar en censura.

¿Cuál triunfará? Posiblemente ambas cadenas se mantengan, pero una de las dos tendrá que ser la mayoritaria. Ethereum tiene a su favor que la fundación Ethereum va a seguir programando tal y como tenía planeado, con nuevas actualizaciones que podrían beneficiar al ecosistema. Por otra parte el cambio de un algoritmo PoW a un algoritmo PoS que quiere realizar Ethereum podría quitarle las ganas a ciertos mineros. Estos se trasladarían a Classic, aunque aquí la opinión es que sería algo temporal ya que mantener el algoritmo PoW a Classic le podría pasar factura al largo plazo. Classic tiene a su favor su reputación de realmente inmutable, ajena a cualquier situación, una libertad anárquica, algo de lo que Ethereum ya no puede presumir.

¿Cuál es tu opinión al respecto? ¿Cuál fue la decisión correcta, la de Ethereum o la de Ethereum Classic?

En el siguiente capítulo veremos otro robo, en este caso a Bitcoin, a través de Bitfinex.]]>
https://blog.adrianistan.eu/la-criptonovela-del-verano-una-historia-tres-capitulos-capitulo-2 Sun, 28 Aug 2016 00:00:00 +0000
La Criptonovela del verano: una historia en tres capítulos (Capítulo 1) https://blog.adrianistan.eu/la-criptonovela-del-verano-una-historia-tres-capitulos-capitulo-1 https://blog.adrianistan.eu/la-criptonovela-del-verano-una-historia-tres-capitulos-capitulo-1

Capítulo 1: Bitcoin y su escalabilidad, tamaño del bloque


Esto es una auténtica guerra. Bandos muy definidos, hostilidad, el debate comenzó hace ya tiempo (abril de 2015, puede que antes) pero sigue sin finalizar. ¿Debería Bitcoin aumentar el tamaño de los bloques? Y más inquietante, ¿cómo debería hacerse?

Guerra

El protocolo actual de Bitcoin permite ejecutar un máximo de 7 transacciones por segundo (3 según otras fuentes). Este número era suficiente cuando Bitcoin surgió y solo lo usaban cuatro gatos pero ahora empieza a haber problemas que se agravarían aún más si se consigue popularizar Bitcoin. Por ello, para aumentar el número de transacciones simultáneas se hace preciso aumentar el tamaño del bloque (actualmente 1 MB) que contiene las transacciones pendientes de verificar.

TamañoDelBloque

El límite en el tamaño de los bloques fue una solución temporal creada por Satoshi Nakamoto hasta que se pudieran usar clientes ligeros, sin embargo nunca se ha modificado.

Esta cuestión no ha contado con el consenso habitual que se lograba para implementar otras características. Hay intereses en ambas direcciones. Gavin Andresen y Mike Hearn publicaron ya a finales de 2015 un fork, denominado Bitcoin XT, que implementaba la proposición BIP 101. Además añadía algunas mejoras de seguridad y usabilidad. El objetivo era lograr que el 11 de enero de 2016, al menos el 75% de los nodos de la red Bitcoin funcionasen con Bitcoin XT. Si lo conseguían, conseguirían imponer sus normas y Bitcoin Core (el Bitcoin original) se bifurcaría (un hard fork). Los mineros de cada red seguirían minando pero ya no funcionarían en la misma red, habría dos.

Gavin Andresen, fue el designado por Satoshi Nakamoto para desarrollar Bitcoin Gavin Andresen, fue el designado por Satoshi Nakamoto para desarrollar Bitcoin

Sin embargo esto generó mucha crítica en la comunidad. Por una parte tener dos cadenas de bloques podría suponer un peligro de doble gasto y la credibilidad de Bitcoin se vería afectada por haber "dos bitcoines". Otra crítica tuvo que ver con la privacidad. Bitcoin XT recogía las IP, también de usuarios que usaban Tor. Las críticas llegan al modelo de gobernación de Bitcoin pues en última estancia Bitcoin XT proponía cambiar la manera de tomar decisiones.

Bitcoin XT guardaba las IP de los usuarios rompiendo con la privacidad característica de Bitcoin Bitcoin XT guardaba las IP de los usuarios rompiendo con la privacidad característica de Bitcoin

Más tarde, Satoshi Nakamoto (o alguien que se hacía pasar por él) afirmaba que él veía la necesidad de cambios en Bitcoin Core, pero que Bitcoin XT le parecía peligroso. Finalmente Bitcoin XT no logró sus objetivos.

En este punto vamos a revisar los motivos que exponen aquellos que no quieren aumentar el tamaño del bloque.

  • Las tarifas de transacción son muy bajas y no subirán hasta que el espacio en el bloque sea escaso. Necesitamos un límite de tamaño por bloque para asegurar la escasez y por lo tanto ponerle un precio a las transacciones.

  • ¿Qué pasaría si el mercado fijase un tamaño de bloque tan grande que sólo Google se pudiera permitir mantener funcionando nodos completos?

  • ¿Y si las tarifas de transacción fijadas por el mercado no pagan lo suficiente como para mantener un poder computacional que proteja a la red de otros adversarios económicamente fuertes?

  • ¿Y si la competencia resulta en que la rentabilidad del minado es tan baja que saca del juego a los pools más pequeños y la minería Bitcoin acaba siendo un monopolio?


Argumentos tomados de ElBitcoin.org

Estos argumentos son muy parecidos entre sí y tienen que ver con la minería. Las objeciones al límite del tamaño del bloque tienen que ver con las recompensas por cómputo. Si el tamaño del bloque aumenta, los mineros van a tener que realizar más trabajo para recibir la recompensa por bloque minado (25 BTC). La otra recompensa, basada en las comisiones por transacción sigue siendo tan baja que sigue siendo preferible minar para conseguir un bloque entero. Los mineros van a preferir bloques pequeños. Además se expone la posibilidad de que los bloques sean tan grandes que Bitcoin acabe centralizándose. Esto último de hecho es algo que ya ocurre. Los grandes nodos chinos copan gran parte del mercado.

Sigamos con la historia. En febrero de 2016 una encuesta a usuarios de Bitcoin revelaba que el 90% de ellos querían aumentar el tamaño del bloque hasta por lo menos 2MB. Entonces aparece Bitcoin Classic. Bitcoin Classic, también diseñado por Gavin Andresen, propone aumentar el tamaño de bloque a 2MB. No realiza ningún cambio más sobre Bitcoin Core. El anuncio de este hard fork fue eliminado de Reddit, lo que hizo pensar a muchos que cierta parte del mundo Bitcoin censuraba a Classic.

Classic obtuvo el apoyo de mucha más gente en mucho menos tiempo. Empresas como Coinbase, Bitcoin.com, Xapo, Blockchain.info,... apoyaron Classic desde el principio, algo que evidenciaba el alejamiento de los desarrolladores de Core con parte de la comunidad. Para que Classic se imponga tiene que cumplirse la misma condición que con Bitcoin XT, 75% de la potencia de la red ha de funcionar usando Classic.

BitcoinClassic

Bitcoin Core no ha sido ajena y ha propuesto sus soft forks, pequeñas modificaciones, más conservadoras, que no eliminan la compatibilidad, proponiendo un modelo de "testigos segregados", que no aumenta el tamaño del bloque sino que reduce la información de cada transacción de manera que entran más transacciones en un bloque del mismo tamaño. La postura de Core fue apoyada por OneName, GreenAddress,...

Los testigos segregados han sido criticados ya que según parte de la comunidad solo resolverían el problema a corto plazo. Esta solución no obstante parece ser también del agrado de Classic, que añadiría ambas cosas: testigos segregados y aumento del tamaño de bloque. Este aumento primero sería fijo, subiendo a 2MB, posteriormente se trabajaría según la propuesta de Stephen Pair, CEO de BitPay, en un modelo de crecimiento dinámico. Sin embargo BitPay ya se ha adelantado y actualmente están desarrollando un fork de Bitcoin Core según sus propuestas, esta versión es BitPay Core y es experimental.

La cosa se complica más pues tenemos Bitcoin Unlimited. Se trata de un sistema donde cada nodo especificaría el tamaño de bloque que le gustase. Concretamente empezaría en un 1MB y el tamaño podría verse aumentado según el minero. Este proyecto sin embargo no ha atraído la atención suficiente y aunque sigue en activo, no cuenta con un apoyo suficiente.

Blockstream

Ya en 2015 entra en juego también Blockstream, una compañía dedicada a sidechains o cadenas laterales (una cadena lateral es un producto paralelo pero que toma referencia en Bitcoin, similar a las monedas de distintos países con un patrón oro). Aprovechan la potencia de Bitcoin con otros propósitos. Su producto principal es Liquid. Las transacciones en Liquid no son procesadas por los mineros. Para usar la red lateral Liquid es necesario pagar a Blockstream una cuota mensual. Esto no sería ningún problema si no fuera porque muchos desarrolladores de Bitcoin Core están en nómina de Blockstream. Una parte de la comunidad les acusa de querer centralizar Bitcoin y para ello llevarán Bitcoin al colapso, con el tamaño de bloque sin modificar para que la gente use soluciones mejores, en este caso las de Blockstream. Si la gente se queda sin espacio en la cadena pública deberán pasarse a la cadena de Blockstream, que si tendrá espacio para tus transacciones.

Sidechain

La desconfianza en Blockstream creció cuando firmó contratos con mineros chinos para poder sustentar Core y las cadenas laterales derivadas de él, productos de Blockstream. Estos contratos han hecho sospechar a mucha gente, que empezó a demonizar contra ellos. Estos mensajes han sido borrados sistemáticamente de ciertos foros y subreddits que se creen en control de Blockstream. Así pues Blockstream parece querer controlar Bitcoin junto con la opinión pública.

Algunos afirman que Blockstream no durará mucho, que caerá, que Classic triunfará. Otros afirman que el resto de desarrolladores de Core no dejarán que Blockstream se oponga a la modifiación del tamaño de bloque y que Core evolucionará.

Bitcache

Y ahora... Bitcache. El nuevo proyecto de Kim Dotcom (fundador de Megaupload). Todavía no ha visto la luz pero este proyecto cuenta con la aprobación de BankToTheFuture. Se trataría de un sistema que relacionaría archivos con transacciones Bitcoin y parece ser que resolvería los problemas de escalabilidad. Su salida se ha planeado para enero de 2017. Estaré atento.

Y en los siguientes capítulos

  • Ethereum y la DAO

  • El robo de Bitfinex


Suscríbete por correo electrónico para no perderte los dos episodios que vienen.]]>
https://blog.adrianistan.eu/la-criptonovela-del-verano-una-historia-tres-capitulos-capitulo-1 Sat, 27 Aug 2016 00:00:00 +0000
Programando para Haiku - Barras de menús - Parte III https://blog.adrianistan.eu/programando-haiku-barras-menus-parte-iii https://blog.adrianistan.eu/programando-haiku-barras-menus-parte-iii tutoriales de Haiku, hoy veremos como crear barras de menú. Partimos del código de la ventana vacía, lo tienes en la foto junto con el comando de compilación.

MiAplicacionHaikuCodigo Haz click en la imagen para verlo en grande

Debemos saber que en la API de BeOS hay tres clases que nos van a servir.

  • BMenuBar, se trata de la barra en sí, que esta pegada a la ventana. Un BMenuBar contiene BMenus.

  • BMenu, son los menús en sí. Un menú es un elemento que contiene acciones, organizadas como si fuera una lista. Los menús no definen acciones, pero sí su organización. Así, a un BMenu le tendremos que añadir BMenuItems u otro BMenus (menús anidados).

  • BMenuItem, son las acciones de los menús. Cada BMenuItem tiene la capacidad de generar un BMessage nuevo, listo para ser procesado por nuestra aplicación.


En la imagen se puede ver perfectamente

BMenu

Vamos a crear una barra de menú con dos menús, uno de ellos simple y otro con un menú anidado y un separador.


#include <AppKit.h>
#include <InterfaceKit.h>
#include <InterfaceDefs.h>

#define NEW_FILE 3
#define EXPORT_AS 5

class VentanaPrueba : public BWindow{
public:
VentanaPrueba() : BWindow(BRect(100,100,900,700),"Mi ventana",B_TITLED_WINDOW,0){
AddChild(CreateMenuBar());
}
bool QuitRequested(){
be_app_messenger.SendMessage(B_QUIT_REQUESTED);
return BWindow::QuitRequested();
}
void MessageReceived(BMessage* msg){
switch(msg->what){
default:
BWindow::MessageReceived(msg);
}
}
BMenuBar* CreateMenuBar(){
BMenuBar* bar = new BMenuBar(BRect(0,0,100,20),"MenuBar");

BMenu* file = new BMenu("File");
BMenu* help = new BMenu("Help");
BMenu* exportMenu = new BMenu("Export");

bar->AddItem(file);
bar->AddItem(help);

/* FILE */
BMenuItem* newFile = new BMenuItem("New file",new BMessage(NEW_FILE));
newFile->SetShortcut('N',B_COMMAND_KEY);
file->AddItem(newFile);

file->AddItem(exportMenu);
file->AddSeparatorItem();

BMenuItem* quit = new BMenuItem("Quit",new BMessage(B_QUIT_REQUESTED));
quit->SetShortcut('Q',B_COMMAND_KEY);
file->AddItem(quit);

/* EXPORT */
BMenuItem* exportAs = new BMenuItem("Export as...",new BMessage(EXPORT_AS));
exportMenu->AddItem(exportAs);

/* HELP */
BMenuItem* helpVersion = new BMenuItem("Help",NULL);
help->AddItem(helpVersion);

return bar;
}
};

class AplicacionPrueba : public BApplication{
public:
VentanaPrueba* ventana;
AplicacionPrueba() : BApplication("application/x-aplicacion-prueba"){
ventana = new VentanaPrueba();
ventana->Show();
}
};

int main(int argc, char** argv){
AplicacionPrueba app;
return app.Run();
}


Y el resultado es el siguiente:

BMenuBar

Como vemos, SetShortcut hace que los menús sean seleccionables con combinaciones de teclado. Hay que tener en cuenta que en BeOS, la tecla que en Windows normalmente Ctrl, es Alt. Así operaciones como copiar y pegar con Alt+C y Alt+V. Para responder al evento solo hace falta escuchar en la función MessageReceived. En el caso de B_QUIT_REQUESTED, el mensaje ya está implementado en la función QuitRequested.]]>
https://blog.adrianistan.eu/programando-haiku-barras-menus-parte-iii Wed, 17 Aug 2016 00:00:00 +0000
Beta privada de TucTum, la red social que te paga https://blog.adrianistan.eu/beta-privada-tuctum-ga-la-red-social-te-paga https://blog.adrianistan.eu/beta-privada-tuctum-ga-la-red-social-te-paga Este post se conserva por motivos históricos, sin embargo TucTum fue cerrado dado su poco éxito
Hace poco me enteré de que Tsu, la red social que pagaba a sus usuarios una parte de lo que generasen por publicidad, había cerrado. Me lo comentó un amigo, había intentado acceder desde la app de Android y ya no podía. Efectivamente, Tsu había cerrado. Llevaba acumulados unos cuantos euros (6 o 7 euros) y me sentó mal, porque además no han explicado los motivos del cierre. Había que hacer algo.

TucTum

Me gustaría inventar algo como Tsu, que permitiese a la gente compartir y hacer amigos mientran ganan dinero. Pero solo soy una persona, hacer una red social desde cero sería demasiado complicado. Entonces recordé GNU Social, una red social que suelo usar y cuyo código fuente está disponible. GNU Social está hecho en PHP, pero además tiene una completa API de plugins, por lo que no he tocado el código central de la red social, solo he diseñado un plugin que permite a los usuarios ganar dinero.

TucTumHome

Se paga por cada favorito que recibas (obviamente la multicuenta estará baneada), cuando tengas 1.000.000 de satoshis puedes recibir el pago en Bitcoin. La cantidad de satoshis que da TucTum por cada favorito es algo que quiero estudiar. Por una parte quiero dar lo máximo posible pero que me permita mantener el servidor. En TucTum tampoco se pueden subir imágenes. Esto es algo temporal, si veo que TucTum despega habilitaré la subida de archivos multimedia. De momento recomiendo usar Imgur, del mismo modo que se hace en Reddit.

Otra característica es el acortamiento de enlaces usando ShortKin.gq, también de mi propiedad.

Beta privada


TucTum entra en Beta privada, limitada por invitaciones. Voy a repartir 100 invitaciones entre la gente de este blog para registrarse en TucTum e intentar ganar dinero. Estas 100 personas podrán invitar a quien quieran a la red social, aunque aviso que todavía no hay sistema de referidos, por lo que no habrá reparto de beneficios por invitar a alguien.

Para pedir tu invitación puedes hacer lo siguiente:

No hace falta que hagas las tres cosas, con que hagas una ya vale. Mientras uséis TucTum es interesante ir comunicándome vuestras sensaciones, que podría mejorar, que esta bien, que esta rematadamente mal,etc]]>
https://blog.adrianistan.eu/beta-privada-tuctum-ga-la-red-social-te-paga Mon, 15 Aug 2016 00:00:00 +0000
ShortKin.gq, un acortador de enlaces anónimo https://blog.adrianistan.eu/shortkin-gq https://blog.adrianistan.eu/shortkin-gq ShortKin.gq es un proyecto en el que he trabajado solamente dos días, sin embargo, creo que ya puede ser de utilidad. Se trata de un acortador de enlaces, gratuito y anónimo. Los enlaces durán para siempre pero nadie sabe quien los creó. El servicio no dispone de anuncios, por lo que puedes estar tranquilo y usarlo donde te apetezca, no será baneado de foros y redes sociales. Actualmente usa Heroku

ShortKing

Bonus: API pública


Puedes usar ShortKin.gq en tu propio proyecto a través de una API muy sencilla. Simplemente has de llamar a:
http://shortkin.gq/s?url=URL

Y se te devolverá el enlace acortado. Muy fácil de usar en tus aplicaciones. Si tus aplicaciones usan Node.js recuerda que puedes usar unos paquetes de acortadores que programé para que todo fuese más sencillo.]]>
https://blog.adrianistan.eu/shortkin-gq Thu, 4 Aug 2016 00:00:00 +0000
Subir APK a Google Play automáticamente https://blog.adrianistan.eu/subir-apk-google-play-automaticamente https://blog.adrianistan.eu/subir-apk-google-play-automaticamente
desarrollador-android-680x394

 

Google Play, como muchos otros servicios de Google, dispone de una API. Gracias a ella podemos interactuar con los servicios de Google directamente desde nuestro código. Para ello necesitaremos un Client ID, un Client Secret y un refresh_token. En mi entrada sobre como crear credenciales OAuth2 para Google sabras como hacerlo. Busca la API de Google Play Android Developer API y actívala. Anota el client ID, el client secret y el refresh_token. Además es necesario que entres a la consola de desarrollo de Google Play y autorices tus credenciales en Configuración -> Acceso a API

screenshot-play google com 2016-07-26 17-23-56

Ahora podemos pasar a escribir nuestro script de subida de APK. Para ello usaremos Node.js y el fantástico paquete googleapis, que se encuentra en npm.

Iniciar sesión en googleapis


Primero instala el paquete con npm.
npm install googleapis

Teniendo a mano el client ID, el client secret y el refresh_token podemos iniciar sesión.


var CLIENT_ID="";
var CLIENT_SECRET="";
var REFRESH_TOKEN="";

var fs = require("fs");
var google = require("googleapis");
var androidpublisher = google.androidpublisher("v2"); // Esta es la API que vamos a usar, AndroidPublisher v2
var OAuth2 = google.auth.OAuth2;

var auth = new OAuth2(CLIENT_ID,CLIENT_SECRET,"");
auth.setCredentials({
refresh_token: REFRESH_TOKEN
});

auth.refreshAccessToken(function(err,tokens){
google.options({auth: auth});
// Ya estamos dentro
});


Crear y publicar una edición


¿Cuál es el procedimiento para subir un APK a Google Play usando la API?

  1. Crear una nueva edición

  2. Subir un APK a esa edición

  3. Asignar el APK a algún canal de distribución

  4. Guardar y publicar los cambios de la edición


Cuando creemos una edición se nos dará un ID de edición. Lo usaremos en el resto de llamadas. También cuando subamos un APK nos darán un versionCode que usaremos para asignar a uno de los canales de distribución. Google Play dispone de 4 canales de distribución:

  • alpha: Solo para los usuarios que hayas autorizado explícitamente

  • beta: Solo para los usuarios que hayas autorizado explícitamente

  • production: La versión que tienen todos los usuarios que no están registrados en alpha y beta.

  • rollout: Similar a production. Se indica un procentaje y Google se encargará de que solo el porcentaje de usuarios indicado tengan la actualización. Es ideal para probar nuevas características sin involucrar a todos tus usuarios.


Finalmente el código final del script queda así.


var CLIENT_ID="";
var CLIENT_SECRET="";
var REFRESH_TOKEN="";

var fs = require("fs");
var google = require("googleapis");
var androidpublisher = google.androidpublisher("v2");
var OAuth2 = google.auth.OAuth2;

var auth = new OAuth2(CLIENT_ID,CLIENT_SECRET,"");
auth.setCredentials({
refresh_token: REFRESH_TOKEN
});

auth.refreshAccessToken(function(err,tokens){
google.options({auth: auth});
androidpublisher.edits.insert({
packageName: "ga.yayeyo.cronoquiz.historia"
},function(err,edit){
console.dir(err);
androidpublisher.edits.apks.upload({
packageName: "ga.yayeyo.cronoquiz.historia",
editId: edit.id,
uploadType: "media",
media: {
mediaType: "application/vnd.android.package-archive",
body: fs.createReadStream("platforms/android/build/outputs/apk/android-release.apk")
}
},function(err,req){
console.dir(err);
var versionCode = req.versionCode;
androidpublisher.edits.tracks.update({
packageName: "ga.yayeyo.cronoquiz.historia",
editId: edit.id,
track: "production",
resource: {
versionCodes: [versionCode]
}
},function(err,req){
console.dir(err);
androidpublisher.edits.commit({
packageName: "ga.yayeyo.cronoquiz.historia",
editId: edit.id
},function(err,req){
console.dir(err);
});
});
});
});
});

]]>
https://blog.adrianistan.eu/subir-apk-google-play-automaticamente Tue, 26 Jul 2016 00:00:00 +0000
Crear credenciales OAuth2 para Google https://blog.adrianistan.eu/crear-credenciales-oauth2-para-google https://blog.adrianistan.eu/crear-credenciales-oauth2-para-google

Crea un proyecto de API


Crea un proyecto de Google y crea unos credenciales nuevos en la consola de APIs de Google.

screenshot-console developers google com 2016-07-26 14-52-43

Los credenciales pueden ser de varios tipos: clave de API, ID de OAuth y cuenta de servicio. La clave de API solo funciona en algunas API, el ID de OAuth es el sistema universal y la cuenta de servicio es para APIs de desarrollo. Creamos un ID de OAuth, pues es el método más universal.

screenshot-console developers google com 2016-07-26 16-29-16

Dependiendo de dónde opere nuestra aplicación tendremos un método u otro. El método Web requiere disponer de un servidor web y es el sistema óptimo si tenemos una aplicación Web (por ejemplo, tenemos una red social y queremos una opción de leer contactos de Gmail, usaríamos Web, pues es el procedimiento más cómodo). Si tu programa no tiene un servidor web detrás (es un programa normal de Windows, una herramienta de línea de comandos, un plugin de Firefox,...) usa Otro. Voy a explicar como funciona Otro.

screenshot-console developers google com 2016-07-26 16-30-31

Obtendremos un ID de cliente y un secreto de cliente. Guardamos esas dos claves, son muy importantes. Ahora vamos a activar las APIs. A la izquierda accedemos a Biblioteca. Buscamos la APIs que queramos usar y las activamos.

screenshot-console developers google com 2016-07-26 16-36-27

Obteniendo un access_token y un refresh_token


Para poder llamar a la API de Google es necesario tener un access_token. Los access_token caducan a las pocas horas. Para poder seguir realizando operaciones en la API pasado ese tiempo necesitamos un refresh_token. El refresh_token sirve exclusivamente para pedir un access_token nuevo. Este no caduca, pero no garantiza que siempre nos devuelva un access_token pues puede que el usuario haya decidido eliminar el permiso a nuestro programa.

El procedimiento de OAuth2 es el siguiente:

  1. Nuestra aplicación quiere autenticar al usuario en Google

  2. Nuestra aplicación genera una URL especial que debe abrir el usuario en el navegador

  3. En esa URL inicia sesión en Google el usuario, igual que si fuera a iniciar sesión en Gmail

  4. Se le informa al usuario de los permisos/APIs que nuestro programa quiere usar

  5. El usuario acepta o rechaza

  6. Si acepta ocurren varias cosas dependiendo del método. Si elegiste Otro se mostrará un código que deberás copiar en la aplicación.

  7. Ese código la aplicación lo envía a Google junto a nuestros datos de client ID y client secret.

  8. Google nos envía el access_token y el refresh_token.


Elaborar la URL de inicio de sesión


En la URL de inicio de sesión figuran varios datos.

  • El tipo de petición

  • Los permisos o APIs a las que queremos acceder (scopes)

  • Nuestro ID de cliente

  • Y la URL de redirección, que en nuestro caso, es especial.


En este ejemplo vemos una petición, con dos APIs o permisos, Chrome WebStore y Google Play.
https://accounts.google.com/o/oauth2/auth?response_type=code&scope=https://www.googleapis.com/auth/chromewebstore+https://www.googleapis.com/auth/androidpublisher&client_id=AQUI EL ID DE CLIENTE&redirect_uri=urn:ietf:wg:oauth:2.0:oob

Validar el código


El usuario nos dará el código que le haya dado Google. Nosotros ahora para obtener el access_token y el refresh_token tenemos que enviarlo de vuelta. En Curl el comando sería el siguiente:
curl "https://accounts.google.com/o/oauth2/tken" -d "client_id=AQUI EL ID DE CLIENTE&client_secret=AQUI EL CLIENT SECRET&code=ESTE ES EL CÓDIGO QUE NOS DA EL USUARIO&grant_type=authorization_code&redirect_uri=urn:ietf:wg:oauth:2.0:oob"

La respuesta, en un fichero JSON, contendrá el access_token y el refresh_token. En las librerías de APIs de Google muchas veces basta con especificar el client ID, el client secret y el refresh_token, pues se encargan de pedir automáticamente el access_token.]]>
https://blog.adrianistan.eu/crear-credenciales-oauth2-para-google Tue, 26 Jul 2016 00:00:00 +0000
Programando para Haiku - File panel - Parte II https://blog.adrianistan.eu/programando-haiku-file-panel-parte-ii https://blog.adrianistan.eu/programando-haiku-file-panel-parte-ii
File Panel de Windows

BFilePanel


La clase básica es BFilePanel. Esta clase se encarga de mostrar esa ventanita que nos deja elegir el archivo o carpeta para abrir o para guardar. Lo primero que tenemos que saber si queremos hacer un File Panel es si va a ser para abrir un archivo existente o para guardar un nuevo archivo. Así, distinguimos entre B_OPEN_PANEL y B_SAVE_PANEL. Si estamos dentro de un B_OPEN_PANEL además indicaremos si aceptamos archivos, carpetas, enlaces simbólicos o alguna combinación de estas cosas. Por último, ¿cómo recibimos la información del panel? Pues usando BMessage, como es habitual en Haiku/BeOS. Pero hay que indicar quién va procesar el mensaje, el conocido como BMessenger. Veamos código:


const uint32 OPEN_FILE = 42;
BFilePanel* filepanel = new BFilePanel(B_OPEN_PANEL,new BMessenger(this),NULL,B_FILE_NODE,new BMessage(OPEN_FILE));
filepanel->Show();


En este código creamos un file panel para abrir un archivo. El BMessenger encargado de procesarlo será el del mismo contexto en el que se ejecute este código. Hay que tener en cuenta que tanto BApplication como BWindow heredan de BMessenger y por tanto cualquier objeto de estas clases es apto. El siguiente parámetro es la carpeta por defecto, que con NULL la dejamos a elección de Haiku. Luego indicamos que queremos abrir archivos, no carpetas ni enlaces simbólicos. Por último especificamos el ID del BMessage que enviará el panel. Esto nos servirá para después saber que ID tenemos que leer dentro de la función MessageReceived del BMessenger. Por último mostramos el panel para que el usuario decida el archivo a abrir. Si la acción es cancelada también será disparado el mensaje, tendremos que comprobar si el usuario eligió el archivo o cerró el diálogo.

Haiku File Panel

Leer la respuesta


Dentro de la función MessageReceived del BMessenger tenemos que accionar un caso especial si el ID del BMessage es el que hemos especificado en el panel.


void MiVentana::MessageReceived(BMessage* msg)
{
switch(msg->what){
case READ_FILE: {
if (msg->HasRef("refs")) {
entry_ref ref;
if (msg->FindRef("refs", 0, &ref) == B_OK) {
BEntry entry(&ref, true);
BPath path;
entry.GetPath(&path);
std::cout << "El archivo es " << path.Path() << std::endl;
}
}
break;
}
}

}


Tenemos que comprobar si el mensaje tiene la propiedad "refs". La propiedad "refs" la ajusta el File Panel cuando se ha seleccionado un archivo. Si la propiedad existe entonces lo leemos. Leeremos una entryref. Un entryref es una entrada dentro del sistema de archivos. Sin embargo esta estructura es de bajo nivel y no sabe exactamente donde se ubica. BEntry representa localizaciones dentro del sistema de archivos. Se construye con un entry_ref y esta clase ya sabe donde se ubica de forma legible por un humano (o un programador perezoso). Si queremos saber la ruta del archivo antes tendremos que crear un objeto vacío BPath que llenaremos con contenido. Finalmente la ruta, como string, la podremos leer llamando a la función Path dentro del objeto BPath.

Ya hemos visto como se usan los file panel en Haiku. Los file panel de guardar archivo se programan exactamente igual cambiando esa pequeña flag al principio.]]>
https://blog.adrianistan.eu/programando-haiku-file-panel-parte-ii Sat, 4 Jun 2016 00:00:00 +0000
Tutorial de Hugo en español, generador de sitios estáticos https://blog.adrianistan.eu/tutorial-hugo-espanol-generador-sitios-estaticos https://blog.adrianistan.eu/tutorial-hugo-espanol-generador-sitios-estaticos
hugo

Los generadores más populares son:






































NombreLenguajePlantillasLicenciaSitio web
JekyllRubyLiquidMIThttp://jekyllrb.com
HexoJavaScriptEJS y SwigMIThttp://hexo.io
HugoGoGo Template, Acer y AmberApachehttp://gohugo.io
PelicanPythonJinja2GPLhttp://blog.getpelican.com

Además es también bastante conocido Octopress pero Octopress no es más que Jekyll con una colección de utilidades extra, el núcleo del programa sigue siendo Jekyll.

¿Por qué voy a elegir Hugo? Yo empecé con Jekyll y me gustó. Sin embargo Liquid no me acabó de convencer nunca y miré otras alternativas. Hexo me pareció excelente si lo que quieres hacer es un blog, funciona muy bien y es más rápido que Jekyll pero Jekyll tenía la ventaja de que se podía usar no solo en blogs, sino en cualquier web en general. Entonces oí hablar de Hugo. Hugo es el más rápido y el más flexible. No está enfocado solo en blogs, soporta todo lo que le eches. Sin embargo me parece que Hugo no es el más sencillo de configurar, así que aquí va el tutorial.

Instalando Hugo


Hugo está hecho en Go, quiere decir que está compilado y por tanto hay una versión diferente para cada sistema operativo. Descarga la versión de tu sistema operativo desde aquí. Si usas GNU/Linux es posible que tu distro haya empaquetado ya Hugo. Búscalo.

Una vez lo tengamos instalado comprobamos que todo esté en orden:
hugo version 

Por defecto Hugo no trae ningún tema. Si quieres instalarte uno y no crear uno de cero puedes clonarlos desde Git. Si quieres probar los temas antes de instalarlos no dejes de mirar Hugo Themes
git clone --recursive https://github.com/spf13/hugoThemes ~/themes 

Si queremos tener coloreado de sintaxis podemos usar Pygments. Si tienes PIP instalado es fácil.
sudo pip install Pygments 

Además si quieres activar el autocompletado de Bash solo tienes que hacer
sudo hugo gen autocomplete . /etc/bash_completion 

Y con esto ya tenemos Hugo instalado correctamente. Ejecuta:
hugo new site MiSitioSupercalifragilisticoespialidoso 

hugo-themes

Organización en Hugo


En Hugo tenemos que tener muy en cuenta la organización de los ficheros. En primer lugar van los themes. Como puedes comprobar la carpeta themes generada esta vacía. Para ir probando los distintos temas puedes hacer un sencillo enlace simbólico entre la carpeta con los temas descargada y esta.

rmdir themes
ln -s ../themes .

Veamos el resto de carpetas:

  • archetypes. Arquetipos. Son plantillas para cuando añadimos un nuevo elemento. Por ejemplo, podemos tener un arquetipo de post sobre un vídeo de YouTube. Es posible crear un arquetipo que contenga configuración ya específica (categorías, reproductor insertado, etc) y que cuando escribamos ya lo tengamos medio hecho. A la hora de generar el sitio los arquetipos de origen no son tenidos en cuenta.

  • config.toml (o config.json o config.yaml). Este archivo contiene la configuración del sitio.

  • content. Aquí va el contenido central de la web. Dentro de content debes crear tantas carpetas como secciones tengas (aunque se puede sobreescribir vía configuración, es práctica recomendada). Cada sección tiene asignado un layout distinto. Dentro de la carpeta de cada sección la organización es libre, los archivos suelen ser de Markdown, pero HTML puro también vale.

  • layouts. ¿Cómo se organiza el contenido? Los layouts son la respuesta. Por cada sección hay que crear mínimo dos layouts, uno para mostrar un contenido solamente y otro para múltiples contenidos del mismo tipo (listas).

  • data. Aquí puedes almacenar archivos en JSON, YAML o TOML a los que puedes acceder desde Hugo. Estos archivos pueden contener cualquier tipo de información, piensa en ellos como en una especie de base de datos.

  • static. El contenido estático, imágenes, JavaScript, CSS, que no deba ser procesado por Hugo debes ponerlo aquí.


Configuración


Dentro del fichero config.toml hay que editar unos cuantos valores.
baseurl = "mi-sitio.com" # La dirección base del sitio
languageCode = "es-es" # El idioma de la web
title = "" # El título de la web
theme = "bleak" # El tema que se va a aplicar al contenido
googleAnalytics = "" # Código de seguimiento de Google Analytics
disqusShortname = ""

[Params] # A estos parámetros se puede acceder de forma directa con .Site.Params.NOMBRE
Author = "Adrián Arroyo"

 

También es configurable Blackfriday el motor de Markdown de Hugo, aunque las opciones por defecto son más que suficientes.

Creando contenido


Crea un archivo dentro de content. Puede estar dentro de una sección si así lo prefieres. En Hugo al igual que en Jekyll cada contenido tiene un front matter, es decir, los metadatos se añaden al principio en un formato que no se va a renderizar. Hugo soporta TOML, YAML y JSON. Si usamos TOML, los delimitadores del front matter serán +++, si usamos YAML --- y si usamos JSON tenemos que poner un objeto con las llaves, {}
+++
title = "El título de la entrada"
description = "Una pequeña descripción"
tags = ["hola","otra","etiqueta"]
date = "2016-05-23"
categories = ["Sobre el blog"]
draft = true
+++

Aquí va el contenido en Markdown o HTML que va a ser renderizado.

Podemos crear variables nuevas a las que podremos acceder desde .Params. Otras opciones predefinidas son type (que sobreescriben el valor de la sección), aliases (que permite hacer redirecciones), weight (la prioridad cuando el contenido sea ordenado) y slug (permite ajustar la URL del contenido).

Modificando el tema


Puedes modificar el tema usando la carpeta layouts. En el fondo un tema es una colección de layouts y recursos estáticos que son combinados con el tuyo. Si ya usas un tema y solo quieres realizar pequeñas modificaciones puedes editar el tema directamente. Si quieres añadir nuevas secciones o crear un tema de 0 entra a la carpeta layouts.

Hay varias subcarpetas dentro de layouts importantes:

  • _default. Es la que se usa cuando no hay otro disponible. Normalmente los temas sobreescriben esta carpeta. Si sobreescribes esta carpeta perderás el tema.

  • index.html. La página de entrada a la web

  • partials. En este carpeta se pueden guardar trozos HTML reutilizables para ser usados por los layouts.

  • shortcodes. Son pequeños trozos de HTML reutilizables con parámetros de entrada para ser usados por el contenido.


Dentro de cada layout (como en _default) tiene que haber mínimo dos archivos. Un archivo single.html que se usará cuando solo se tenga que representar una unidad de ese contenido y un archivo list.html que se usará cuando sea necesario mostrar un conjunto de esos contenidos.

Estos archivos han de programarse usando el motor de plantillas de Go y la API de Hugo. Un archivo single.html básico que muestra el título y el contenido tal cual sería así.
{{ partial "header.html" . }}
{{ partial "subheader.html" . }}
<section id="main">
<h1 id="title">{{ .Title }}</h1>
<div>
<article id="content">
{{ .Content }}
</article>
</div>
</section>
{{ partial "footer.html" . }}

Dentro de las páginas list.html es práctica habitual definir una vista li.html como un elemento individual. Esos elementos individuales se unen para formar la lista en list.html.

Algunos extras


Los shortcodes son pequeños trozos de HTML que aceptan parámetros. Podemos usarlos en el contenido. Piensa en ellos como Mixins de CSS o funciones de JavaScript. Por ejemplo, para marcar un resaltado de sintaxis:
{{< highlight html >}}
<section id="mira-este-super-codigo">
<p class="html-is-broken">Rompiendo el HTML</p>
</section>
{{< /highlight >}}

O un enlace dentro de nuestra web:
{{< ref "blog/este-es-otro-post-fantastico.md" >}}
]]>
https://blog.adrianistan.eu/tutorial-hugo-espanol-generador-sitios-estaticos Thu, 26 May 2016 00:00:00 +0000
Programando para Haiku - BApplication, BWindow y BButton - Parte I https://blog.adrianistan.eu/programando-haiku-bapplication-bwindow-bbutton-parte-i https://blog.adrianistan.eu/programando-haiku-bapplication-bwindow-bbutton-parte-i BeBook y la nueva de Haiku en Haiku API. Aun así hay APIs nuevas que todavía no aparecen documentadas. En ese caso hay que recurrir al código fuente.

Librerías en Haiku


Haiku (y BeOS) comparte con UNIX muchas características de bajo nivel. El tema de las librerías es uno de ellos. También han de empezar por lib y terminar por .so si son compartidas y .a si son estáticas. Para compilar también se usa GCC. Sin embargo hay una pequeña diferencia con el resto de sistemas UNIX. En UNIX normalmente disponemos de una librería del C, libc y una librería de funciones matemáticas, libm. En Haiku no existe libm, en cambio existen muchas más, libroot y libbe, para interactuar con el sistema de manera básica, libtracker, con funciones relacionadas con el explorador de archivos, libnetwork y libnetapi, con funciones de red, y muchas otras.

Además la API se divide en Kits, cada Kit se encarga de una tareas diferentes dentro del sistema operativo. AppKit, Game Kit, Interface Kit, Media Kit, Storage Kit, etc... Si queremos usar la funcionalidad de un kit tendremos que revisar que hemos añadido la librería correcta al compilador y que hemos añadido #include dentro del código que lo usemos.

Un hola mundo


Vamos a empezar por lo simple, una aplicación que muestre una ventana y ya.

Creamos un archivo de C++, será el punto de inicio de nuestra aplicación. Como sabéis, el punto de inicio de un programa de C o C++ es la función main.


int main(int argc, char** argv)
{
AplicacionPrueba app;
return app.Run();
}


Hemos creado un objeto llamado app del tipo AplicacionPrueba y después hemos ejecutado la aplicación. AplicacionPrueba tiene que ser del tipo BApplication. Es la clase básica de todas las aplicaciones Haiku/BeOS. BApplication provee de mensajería entre los distintos procesos del programa (hay que tener en cuenta que BeOS se diseñó pensando en el multiproceso). Vamos a ver como definimos AplicacionPrueba


#include <AppKit.h>

class AplicacionPrueba : public BApplication {
public:
VentanaPrueba* ventana;
AplicacionPrueba() : BApplication("application/x-applicion-prueba"){
ventana = new VentanaPrueba();
ventana->Show();
}
};


Las Application necesitan un MIME type, al igual que se usa para indicar los tipos de archivo. No es necesario que sea real. Además hemos creado un objeto VentanaPrueba y la mostramos. VentanaPrueba es del tipo BWindow y es la ventana básica de Haiku, lo que vemos. Veamos la definición:


class VentanaPrueba : public BWindow{
public:
VentanaPrueba() : BWindow(BRect(100,100,900,700),"Mi ventana", B_TITLED_WINDOW,0){
// iniciar ventana
}
bool QuitRequested(){
be_app_messenger.SendMessage(B_QUIT_REQUESTED);
return BWindow::QuitRequested();
}
void MessageReceived(BMessage* msg){
switch(msg->what){
default:
BWindow::MessageReceived(msg);
}
}
};


BWindow necesita un tamaño, que es indicado con BRect, un título, un estilo (por defecto es BTITLEDWINDOW, pero podemos tener ventanas sin bordes o modal) y opciones varias. En las opciones varias podemos especificar que al cerrar la ventana se cierre la aplicación (BQUITONWINDOWCLOSE), que el usuario no pueda cambiar su tamaño (BNOTRESIZABLE), que no se pueda minimizar (BNOTMINIMIZABLE) y otras opciones por el estilo.

Además dentro de la clase hemos definido dos funciones virtuales, es decir, que tienen implementación por defecto de la clase padre, BWindow, pero nosotros podemos modificar su comportamiento.

QuitRequested es llamada cuando algo pide el cierre de la ventana. El objeto global beappmessenger es del tipo BApplication, pero está definido en todos los puntos de nuestra aplicación sin que nosotros hagamos nada. Gracias a este objeto podemos enviar mensajes entre procesos. En este caso enviamos el mensaje a la aplicación de BQUITREQUESTED. Y luego llamamos a la función sin modificar.

MessageReceived es muy importante. Se encarga de procesar todos los mensajes que recibe la ventana. Para distinguir los mensajes (que son del tipo BMessage) tenemos que inspeccionar la propiedad what. Se trata de un valor de tipo uint32. Hay algunos ya definidos por el sistema como BQUITREQUESTED pero nosotros podemos definir más. Veremos más tarde como. De momento simplemente devolvemos el procesado de mensajes a BWindow padre.

Con esto ya podemos compilar.
gcc -o AplicacionPrueba app.cpp -lbe -lroot 

Añadiendo BView, BButton y BGroupLayout


Ahora vamos a añadir cosas a nuestra ventana sosa. Ponemos una vista dentro de la ventana. Las vistas en Haiku son muy potentes pero eso lo trataré en otro momento. A esa vista le añadiremos un botón.


VentanaPrueba() : BWindow(BRect(100,100,900,700),"Mi ventana", B_TITLED_WINDOW,0){
// iniciar ventana
BGroupLayout* sizer = new BGroupLayout(B_HORIZONTAL);
BView* panel = new BView(Bounds(), NULL, B_FOLLOW_ALL_SIDES,
B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE |
B_FRAME_EVENTS | B_DRAW_ON_CHILDREN);
panel->SetViewColor(220, 220, 220);
panel->SetLayout(sizer);

BButton* boton = new BButton("Hola Mundo",NULL);

sizer->AddView(boton);
AddChild(panel);
}


Aquí hemos hecho varias cosas. Por una parte he creado un layout horizontal. Es decir, dispongo el espacio de la ventana de manera horizontal, según se vayan añadiendo elementos lo harán a la derecha. Esto no estaba en BeOS y es particular de Haiku, pero recomiendo usar este sistema pues permite realizar un responsive design. Creamos una vista o panel. Bounds() indica que cubra todo el espacio disponible. El resto son propiedades de la vista más o menos estándar. Con SetViewColor le podemos poner un color de fondo, y con SetLayout le aplicamos el layout previamente creado.

Creamos un botón, que es del tipo BButton. BButton tiene muchos constructores si revisais la documentación pero este es muy cómodo si usamos el sistema de layouts. Simplemente indicamos el texto que va a mostrar y el mensaje que envía. En este caso NULL pues no vamos a poner ninguno.

sizer->AddView() lo usamos para añadir el botón al layaout y AddChild para añadir la vista a la ventana. Puedes compilar.

Añadiendo eventos. Mensajería con BMessage. Diálogo con BAlert.


Vamos ahora a crear un evento para el botón. Cuando pulsemos el botón mostrará un mensaje al usuario.

Los eventos se realizan por el sistema de mensajería basado en BMessage y BHandler. Para crear un BMessage necesitamos un ID, que es del tipo uint32. Eso es lo mínimo y con eso ya serviría para este caso.


const uint32 MOSTRAR_DIALOGO = 1;

...

BMessage* msg = new BMessage(MOSTRAR_DIALOGO);
BButton* boton = new BButton("Hola mundo",msg);

...


Pero los mensajes pueden llevar información adicional de cualquier tipo. Por ejemplo si queremos añadir además una cadena de texto al mensaje usaremos AddString.


msg->AddString("NombrePropiedad","ValorPropiedad");


Podremos recuperar el valor en cualquier momento con FindString.

Ahora si vamos a MessageReceived podemos añadir código que gestione este tipo de mensaje.


void MessageReceived(BMessage* msg){
switch(msg->what){
case MOSTRAR_DIALOGO:
BAlert* alert = new BAlert("Hola", "Sabes pulsar el boton, eh?", "Sip");
alert->Go();
break;
default:
BWindow::MessageReceived(msg);
}
}


Con un simple case gestionamos el mensaje. Para mostrar un diálogo simple se puede usar BAlert. Es muy simple, indicamos el título, el contenido del mensaje y el texto del botón que aparecerá. Y con Go lo mostramos.

Esta ha sido la primera parte del tutorial. Os ha gustado. Hay algo que no haya quedado claro. Comentádmelo.


#include <AppKit.h>
#include <InterfaceKit.h>
#include <Layout.h>
#include <GroupLayout.h>

const uint32 MOSTRAR_DIALOGO = 1;

class AplicacionPrueba : public BApplication {
public:
VentanaPrueba* ventana;
AplicacionPrueba() : BApplication("application/x-applicion-prueba"){
ventana = new VentanaPrueba();
ventana->Show();
}
};

class VentanaPrueba : public BWindow{
public:
VentanaPrueba() : BWindow(BRect(100,100,900,700),"Mi ventana", B_TITLED_WINDOW,0){
// iniciar ventana
BGroupLayout* sizer = new BGroupLayout(B_HORIZONTAL);
BView* panel = new BView(Bounds(), NULL, B_FOLLOW_ALL_SIDES,
B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE |
B_FRAME_EVENTS | B_DRAW_ON_CHILDREN);
panel->SetViewColor(220, 220, 220);
panel->SetLayout(sizer);

BMessage* msg = new BMessage(MOSTRAR_DIALOGO);
BButton* boton = new BButton("Hola Mundo",msg);

sizer->AddView(boton);
AddChild(panel);
}
bool QuitRequested(){
be_app_messenger.SendMessage(B_QUIT_REQUESTED);
return BWindow::QuitRequested();
}
void MessageReceived(BMessage* msg){
switch(msg->what){
case MOSTRAR_DIALOGO:
BAlert* alert = new BAlert("Hola", "Sabes pulsar el boton, eh?", "Sip");
alert->Go();
break;
default:
BWindow::MessageReceived(msg);
}
}
};

int main(int argc, char** argv)
{
AplicacionPrueba app;
return app.Run();
}
]]>
https://blog.adrianistan.eu/programando-haiku-bapplication-bwindow-bbutton-parte-i Wed, 18 May 2016 00:00:00 +0000
Programando el Chromecast desde JavaScript https://blog.adrianistan.eu/programando-chromecast-desde-javascript https://blog.adrianistan.eu/programando-chromecast-desde-javascript
chromecast-box

¿Cómo funciona Chromecast por dentro?


Primero voy a explicar como funciona el Chromecast. Chromecast sigue la filosofía de los Chromebooks, en el que todo el sistema es el navegador web, en este caso Chrome. Una app se conecta a Chromecast a través del protocolo de descubrimiento, deben estar en la misma red Wi-Fi. La aplicación solicita iniciar una aplicación, para lo cual manda el ID de la aplicación que desea abrir. Chromecast busca en la base de datos de Google (https://cast.google.com, tasa de registro de 5$) y allí le indicará una URL.

Chromecast abre la web y ejecuta la aplicación, que usando una librería de JavaScript le permite comunicarse con la app original. Podemos distinguir sin embargo varias aplicaciones "preinstaladas". Son webs como el resto pero están alojadas por Google sin marca. Son DefaultMediaReceiver y StyledMediaReceiver. Se trata de dos reproductores multimedia básicos, que soportan vídeo en MP4 y WebM desde una URL, subtítulos, carátula y en el caso de StyledMediaReceiver es posible modificar un poco el aspecto del reproductor.

chromecast

Reproduciendo un vídeo con Chromecast, Node.js y DefaultMediaReceiver


Como hemos visto, reproducir un vídeo en Chromecast no es muy difícil. Para ello me voy a ayudar en la librería chromecast-js que en última instancia remite a node-castv2-client.

En un ordenador, dentro de la misma red Wi-Fi que el Chromecast y con Node.js instalado ejecutamos:

mkdir chromecast-video
cd chromecast-video
npm install chromecast-js

Creamos un archivo de JavaScript con el siguiente contenido


var chromecastjs = require('chromecast-js'); // Obtenemos la librería

var browser = new chromecastjs.Browser(); // Iniciamos la búsqueda

browser.on('deviceOn', function(device){ // Cuando encuentre un dispositivo...
device.connect(); // Nos conectamos a él
device.on('connected', function(){ // Y cuando nos conectemos

device.play('http://commondatastorage.googleapis.com/gtv-videos-bucket/big_buck_bunny_1080p.mp4', 60, function(){ // Mandamos reproducir el vídeo Big Buck Bunny, en MP4, no desde el principio, sino desde el primer minuto
console.log('Reproduciendo en el Chromecast!');
});

setTimeout(function(){ // Pasados 30 segundos paramos el vídeo
device.pause(function(){
console.log('Paused!')
});
}, 30000);

setTimeout(function(){ // Pasados otros 10 segundos más, se corta la retransmisión
device.stop(function(){
console.log('Stoped!')
});
}, 40000);

})
})


Es un ejemplo muy sencillo. Como veis, Chromecast se conecta directamente al vídeo que reproduce, el dispositivo de control solo es eso, solo controla, nunca envía la película.

Personalizando con StyledMediaReceiver


El módulo chromecast-js también permite usar el StyledMediaReceiver, cuyo funcionamiento es idéntico al de DefaultMediaReceiver pero se le puede personalizar el aspecto. Además añadimos subtítulos (formato WebVTT) y una carátula.


var chromecastjs = require('chromecast-js')

var browser = new chromecastjs.Browser()

var media = {
url : 'http://commondatastorage.googleapis.com/gtv-videos-bucket/big_buck_bunny_1080p.mp4', // El vídeo
subtitles: [{
language: 'en-US',
url: 'http://carlosguerrero.com/captions_styled.vtt',
name: 'English',
},
{
language: 'es-ES',
url: 'http://carlosguerrero.com/captions_styled_es.vtt',
name: 'Spanish',
}
], // Los subítulos, un fichero para inglés y otro para español, en formato WebVTT
cover: {
title: 'Big Bug Bunny',
url: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg' // Carátula, se muestra cuando el vídeo se está cargando
},
subtitles_style: {
backgroundColor: '#FFFFFFFF', // see http://dev.w3.org/csswg/css-color/#hex-notation
foregroundColor: '#000FFFF', // see http://dev.w3.org/csswg/css-color/#hex-notation
edgeType: 'DROP_SHADOW', // can be: "NONE", "OUTLINE", "DROP_SHADOW", "RAISED", "DEPRESSED"
edgeColor: '#AA00FFFF', // see http://dev.w3.org/csswg/css-color/#hex-notation
fontScale: 1.5, // transforms into "font-size: " + (fontScale*100) +"%"
fontStyle: 'BOLD_ITALIC', // can be: "NORMAL", "BOLD", "BOLD_ITALIC", "ITALIC",
fontFamily: 'Droid Sans',
fontGenericFamily: 'CURSIVE', // can be: "SANS_SERIF", "MONOSPACED_SANS_SERIF", "SERIF", "MONOSPACED_SERIF", "CASUAL", "CURSIVE", "SMALL_CAPITALS",
windowColor: '#AA00FFFF', // see http://dev.w3.org/csswg/css-color/#hex-notation
windowRoundedCornerRadius: 10, // radius in px
windowType: 'ROUNDED_CORNERS' // can be: "NONE", "NORMAL", "ROUNDED_CORNERS"
} // Aquí creamos un estilo para los subtítulos, la notación es fácil si ya conoces CSS
}


browser.on('deviceOn', function(device){
device.connect()
device.on('connected', function(){

// Iniciamos la retransmisión, pero esta vez enviamos más información al Chromecast, usaremos StyledMediaReceiver
device.play(media, 0, function(){
console.log('Playing in your chromecast!')

setTimeout(function(){
console.log('subtitles off!')
device.subtitlesOff(function(err,status){ // Desactivamos los subtítulos
if(err) console.log("error setting subtitles off...")
console.log("subtitles removed.")
});
}, 20000);

setTimeout(function(){
console.log('subtitles on!')
device.changeSubtitles(1, function(err, status){ // Restablecemos los subtítulos, pero al Español
if(err) console.log("error restoring subtitles...")
console.log("subtitles restored.")
});
}, 25000);

setTimeout(function(){
device.pause(function(){
console.log('Paused!')
});
}, 30000);

setTimeout(function(){
device.unpause(function(){
console.log('unpaused!')
});
}, 40000);

setTimeout(function(){
console.log('I ment English subtitles!')
device.changeSubtitles(0, function(err, status){
if(err) console.log("error restoring subtitles...")
console.log("English subtitles restored.")
});
}, 45000);

setTimeout(function(){
console.log('Increasing subtitles size...')
device.changeSubtitlesSize(10, function(err, status){ // Cambiamos el tamaño de los subtítulos
if(err) console.log("error increasing subtitles size...")
console.log("subtitles size increased.")
});
}, 46000);

setTimeout(function(){
device.seek(30,function(){ // Nos movemos dentro del vídeo
console.log('seeking forward!')
});
}, 50000);

setTimeout(function(){
console.log('decreasing subtitles size...')
device.changeSubtitlesSize(1, function(err, status){
if(err) console.log("error...")
console.log("subtitles size decreased.")
});
}, 60000);

setTimeout(function(){
device.pause(function(){
console.log('Paused!')
});
}, 70000);

setTimeout(function(){
device.seek(30,function(){
console.log('seeking forward!')
});
}, 80000);

setTimeout(function(){
device.seek(30,function(){
console.log('seeking forward!')
});
}, 85000);

setTimeout(function(){
device.unpause(function(){
console.log('unpaused!')
});
}, 90000);


setTimeout(function(){
device.seek(-30,function(){
console.log('seeking backwards!')
});
}, 100000);


setTimeout(function(){
device.stop(function(){
console.log('Stoped!')
});
}, 200000);

})
})
}


En definitiva, para muchas aplicaciones este módulo es más que suficiente. En caso de que queramos llevar un juego a Chromecast la cosa se complica. Tenemos que programar una app del tipo CustomMediaReceiver en HTML5 y luego su cliente (en Node.js o usando las librerías oficiales de Google para Android, iOS y Chrome). Si os ha gustado esta entrada y queréis saber como realizar esto último, compartid y comentad, me gustaría saber que opináis al respecto y podéis darme ideas.]]>
https://blog.adrianistan.eu/programando-chromecast-desde-javascript Sat, 2 Apr 2016 00:00:00 +0000
Enebro, un robot automático para FOREX en JavaScript https://blog.adrianistan.eu/enebro-robot-automatico-forex-javascript https://blog.adrianistan.eu/enebro-robot-automatico-forex-javascript
Probablemente tú también querrás usar robots. Hay soluciones para pequeños inversores como MetaTrader, pero voy a tratar de construir un bot de bolsa desde 0 en JavaScript. ¿Suena divertido?

forex

PyAlgoTrading


El diseño de mi robot, Enebro, va a estar basado en PyAlgoTrade. Los componentes principales de PyAlgoTrade son 6:

  • Estrategias - Definen cuando comprar y cuando vender

  • Entradas (o feeds) - Proveen datos. Pueden ser tantos datos financieros como datos provienientes de portales de noticias o Twitter.

  • Brokers - Se encargan de ejecutar las órdenes

  • DataSeries - Una abstracción usada para manejar los datos en su conjunto

  • Indicadores técnicos - Realiza los cálculos de los indicadores técnicos usando datos de DataSeries.

  • Optimizador - Cuando se realiza backtesting (probar la estrategia con datos pasados) este módulo permite realizarse en menos tiempo, distribuyendo el trabajo entre distintos ordenadores. Este módulo no lo voy a implementar en Enebro.


Vamos a trabajar ya en el programa, que operará el par EURUSD. El programa es meramente educativo y no está tan bien programado como debería pero servirá para entender algún concepto.

Programando: Entradas o feeds


Lo primero que vamos a implementar son la entrada de los datos. Voy a implementar dos formas de obtener la información, una es leer los datos desd e un fichero CSV. Esto servirá para realizar backtesting. Además voy a añadir la entrada de datos de Uphold, para obtener los datos en tiempo real


"use strict";
var csv = require("csv");
var fs = require("fs");
var Bar = require("./strategy").Bar;

class Feed{
constructor(instruments){
this.instruments = instruments;
}
}

class UpholdFeed extends Feed{
constructor(instruments){
super(instruments);
this.Uphold = require("uphold-sdk-node")({
"key" : CLIENT_ID,
"secret" : CLIENT_SECRET,
"scope" : SCOPE,
"pat" : TOKEN
});
}
run(cb){
var self = this;
setInterval(function(){
self.Uphold.tickers(function(err,tickers){
var date = new Date();
var isodate = date.toISOString();
var ask = tickers[12].ask;
var bid = tickers[12].bid;
var media = (ask+bid)/2;
console.log("EURUSD: "+ask+"-"+bid);
var bar = new Bar(media,media,media,media);
var bars = {};
bars[self.instruments] = bar;
cb(bars);
});
},1000*60);
}
}

class CSVFeed extends Feed{
constructor(instruments,file){
super(instruments);
this.file = file;
}
run(cb){
var reader = fs.createReadStream(this.file);
var parser = csv.parse({
delimiter: ";"
});
var data = "";
var self = this;
parser.on("readable",function(){
while(data = parser.read()){
// Run onBars;
var bar = new Bar(data[1],data[4],data[2],data[3]);
var bars = {};
bars[self.instruments] = bar;
cb(bars);
}
});
reader.on("data",function(chunk){
parser.write(chunk);
})
}
}

module.exports = {CSVFeed: CSVFeed, UpholdFeed: UpholdFeed};


Veamos como funciona. Definimos una estrctura básica de Feed. Cada Feed hace referencia a un instrumento, en nuestro caso el instrumento es "EURUSD". Un feed necesita una función run() que será llamada una vez cuando tenga que funcionar el robot. A partir de ese momento el Feed asume la responsabilidad de proveer de datos al robot llamando siempre que haya nuevas barras (velas japonesas) a la función de callback cb(). En el caso de CSVFeed, la llamada a cb() se produce cada vez que se ha procesado una línea del fichero. En el caso de UpholdFeed, se envía una nueva barra cada minuto. En el caso de CSVFeed se pide un fichero en el constructor que se procesa con la librería csv y en el caso de Uphold, se usa la API para obtener los precios.

En esta primera parte hemos visto como usamos un objeto Bar que todavía no he enseñado, no es muy difícil.


class Bar{
constructor(open,close,high,low){
this.open = open;
this.close = close;
this.high = high;
this.low = low;
}
getPrice(){
return this.close;
}
}


El bróker


Necesitamos un bróker, que se encargará de realizar las operaciones. Para realizar backtesting es necesario disponer de un bróker simulado, lo he llamando EnebroBroker. Soporta solo la operación de entrar en largo y salir de mercado.


class EnebroBroker{
constructor(capital){
this.capital = capital;
this.register = {};
this.benefit = 0;
}
enterLong(instrument,price){
if(!this.register[instrument])
this.register[instrument] = [];
this.register[instrument].push({
shares: Math.floor(this.capital/price),
price: price
});
this.strategy.open = true;
this.strategy.onEnterOK();
}
exitMarket(instrument,price){
var last = this.register[instrument].pop();
var diff = price - last.price;
var total = diff * last.shares;
this.benefit += total;
this.strategy.open = false;
this.strategy.onExitOK();
console.log("Operation Closed: "+total);
console.log("Benefit: "+this.benefit);
}
}


Básicamente, se le añade un capital al iniciarse y usa TODO el capital en realizar la operación. La estrategia si queremos diversificar con Enebro es tener muchos brokers con una estrategia asignada a cada uno. Para realizar la compra especificamos el instrumento y el precio actual, para salir igual. Se nor informará en la pantalla del beneficio por la operación y del beneficio acumulado.

La estrategia


Veamos una estrategia sencilla usando la media móvil simple de 15.


class Strategy{
constructor(feed,instrument,broker){
this.setup();
this.sma = new SMA(15);
this.feed = feed;
this.instrument = instrument;
this.broker = broker;
this.open = false;
this.broker.strategy = this;
}
onBars(bars){

}
run(){
var self = this;
this.feed.run(function(bars){
self.onBars(bars);
});
}
isOpen(){
return this.open;
}
}
class MMS extends Strategy{
constructor(feed,instrument,broker){
super(feed,instrument,broker);
}
onBars(bars){
var bar = bars[this.instrument];
this.sma.add(this.instrument,bar);
if(!this.isOpen()){
// Buy
if(bar.getPrice() < this.sma.get(this.instrument)){
this.broker.enterLong(this.instrument,bar.getPr$
}
}else{
// Sell
if(bar.getPrice() > this.sma.get(this.instrument)){
this.broker.exitMarket(this.instrument,bar.getP$
}
}
}
}


Esta estrategia está basada en la media móvil simple, únicamente en eso. El feed provee de datos a la estrategia en la función onBars. Se actualiza el único indicador, la media móvil simple, SMA. Cuando se cumple la condición de compra y no hay operaciones abiertas, se entra en largos. Cuando hay operaciones abiertas y hay condición de venta, se vende. Cuando creamos la estrategia y la ejecutamos con run() tenemos que especificar el feed, el instrumento y el bróker.

Los indicadores: SMA


Hemos usado el indicador de la media móvil simple. ¿Cómo lo hemos definido?


class SMA{
constructor(period){
this.period = period;
this.sma = {};
}
add(instrument,bar){
if(!this.sma[instrument])
this.sma[instrument] = [];
this.sma[instrument].push(bar);
}
get(instrument){
if(this.sma[instrument].length < this.period +1)
return 0;
var suma = 0;
for(var i=this.sma[instrument].length - this.period;i<this.sma[$
suma += parseFloat(this.sma[instrument][i].getPrice());
}
return suma/this.period;
}
}


Simplemente almacena los datos y los usa para realizar la media cuando sea necesario.

Juntándolo todo


Ahora juntamos todo en el archivo index.js

var CSVFeed = require("./feed").CSVFeed;
var UpholdFeed = require("./feed").UpholdFeed;
var MMS = require("./strategy").MMS;
var EnebroBroker = require("./strategy").EnebroBroker;
var UpholdBroker = require("./strategy").UpholdBroker;

var feed = new CSVFeed("EURUSD","DAT_ASCII_EURUSD_M1_201602.csv");
//var feed = new UpholdFeed("EURUSD");

//var broker = new UpholdBroker(8.69);
var broker = new EnebroBroker(100);

var strategy = new MMS(feed,"EURUSD",broker);
strategy.run();

Conclusión


Espero que esta entrada al menos os sirva de inspiración para diseñar vuestro propio sistema de trading automático. Enebro es un simple experimento, pero si quereis aportar información, sugerir algo o preguntar, simplemente escribid en los comentarios.

Yo tengo mi propia versión de Enebro operativa, si alguien la quisiera, puede contactar conmigo. Además el plugin de bróker de Uphold, necesario para realizar las operaciones reales de FOREX también está disponible para quien lo quiera.]]>
https://blog.adrianistan.eu/enebro-robot-automatico-forex-javascript Sat, 12 Mar 2016 00:00:00 +0000
Mapas interactivos en HTML5 con SnapSVG https://blog.adrianistan.eu/mapas-interactivos-html5-snapsvg https://blog.adrianistan.eu/mapas-interactivos-html5-snapsvg encuesta del blog HTML5 era uno de los temas en los que estabais más interesados. Hoy vamos a ver como se puede hacer un mapa interactivo fácilmente, compatible con PCs, tabletas y móviles y veremos como los podemos animar.


Haz click en la provincia de Valladolid múltiples veces

SnapSVG


Aquí entra en juego SnapSVG. Se trata de una librería para JavaScript financiada y acogida por Adobe. Es una mejora de la ya popular librería para tratar SVG conocida como RaphaëlJS (ambas librerías son del mismo autor). Sin embargo, SnapSVG aporta muchas mejoras respecto a RaphaëlJS. La fundamental es que SnapSVG permite cargar archivos SVG ya existentes.

snapsvg

Mapas en SVG


Actualmente es fácil encontrar mapas en SVG de cualquier territorio. Sin embargo para que sea fácil trabajar con ellos hay que procurar que estén preparados para interactuar con ellos. Es necesario que las etiquetas <path> posean un atributo id y sea fácilmente reconocible. En el caso del mapa de España que hay al principio, el mapa está muy bien organizado. Las provincias empiezan por pr, los enclaves por en y las islas por is. Así que Valladolid es pr_valladolid y Menorca es is_menorca. Encontrar mapas así ya puede ser más difícil pero no imposible.

Primeros pasos


En nuestro HTML creamos una etiqueta <svg> con un id, por ejemplo id=papel. Ya está. Ahora pasamos al JavaScript.

Primero necesitamos obtener un papel (Paper en la documentación), con la función Snap y un selector CSS obtenemos el papel que ya hemos creado.


var s = Snap("#papel");


Ahora ya podemos usar todo el poder de SnapSVG, pero si queremos trabajar con un SVG ya existente el procedimiento es un poco distinto.


var s = Snap("#papel"); // Obtenemos el papel
Snap.load("/mapa.svg",function(f){
// Al cargar el mapa se nos devuelve un fragmento
// los fragmentos contienen elementos de SVG
// Como queremos añadir todos los elementos, los seleccionamos todos, como un único grupo
// otra vez vemos los selectores CSS en acción
var g = f.selectAll("*");
// y ahora añadimos el grupo al papel
s.append(g);

// cuando querramos acceder a un elemento podemos usar un selector CSS
var valladolid = s.select("#pr_valladolid");
});

Atributos


Podemos alterar los atributos de estilo de SVG. Para quién no los conozca, funcionan igual que las propiedades CSS pero se aplican de manera distinta. Con SnapSVG podemos cambiar esos atributos en tiempo de ejecución. Por ejemplo, el relleno (propiedad fill).


s.attr({
fill: "red"
});
// Cambia el relleno a rojo, afecta a los elementos inferiores, en este caso como es el papel, afecta a todo el SVG.

Figuras simples


Podemos añadir figuras simples de manera muy sencilla


var rect = s.rect(0,0,100,20).attr({fill: "cyan"});
// Creamos un rectángulo de 100x20 en la posición (0,0) con relleno cyan.
// Luego lo podemos borrar
rect.remove();

Eventos y animaciones


Ahora viene la parte interesante, eventos y animaciones. SnapSVG soporta varios tipos de evento en cada elemento. Veamos el click simple aunque existe doble click, ratón por encima, táctil (aunque click funciona en pantallas táctiles).


var murcia = s.select("#pr_murcia");
murcia.click(function(){
murcia.attr({
fill: "yellow"
});
});



Podemos animar los elementos especificando las propiedades que cambian y su tiempo


murcia.animate({fill: "purple"},1000);


SnapSVG es muy potente y permite realizar muchas más operaciones, como elementos arrastrables, grupos, patrones, filtros y más. El objetivo, según Adobe, es ser el jQuery de los documentos SVG.

snapsvg-game

Escalar imagen automáticamente


viewBox

SVG es un formato vectorial así que podemos aumentar el tamaño sin tener que preocuparnos por los píxeles. Sin embargo si simplemente cambias el tamaño del elemento <svg> vía CSS verás que no funciona. Es necesario especificar un atributo viewBox y mantenerlo constante. Básicamente viewBox da las dimensiones reales del lienzo donde se dibuja el SVG. Si cambian width y height y viewBox también entonces la imagen no se escala, simplemente se amplía el área del lienzo. Algunos mapas en SVG no ofrecen viewBox. En ese caso espeficicamos como viewBox el tamaño original del fichero SVG. En el caso de querer ocupar toda la pantalla.


s.attr({ viewBox: "0 0 800 600",width: window.innerWidth, height: window.innerHeight});
window.addEventListener("resize",function(){
s.attr({ viewBox: "0 0 800 600",width: window.innerWidth, height: window.innerHeight});
});

Cordova y Android


SnapSVG se puede usar en Apache Cordova. Sin embargo yo he tenido problemas de rendimiento con la configuración por defecto en Android. Para solventar este problema he tenido que:

Solo así conseguí un rendimiento decente dentro de Cordova.]]>
https://blog.adrianistan.eu/mapas-interactivos-html5-snapsvg Sun, 6 Mar 2016 00:00:00 +0000
Ethereum y SmartContracts https://blog.adrianistan.eu/ethereum-y-smartcontracts https://blog.adrianistan.eu/ethereum-y-smartcontracts
Si revisamos la lista de criptodivisas por capitalización de mercado en CoinMarketCap.com veremos que en el puesto número aparece una que no he mencionado, se trata de Ethereum. Pero no adelantemos acontecimientos.

CoinMarketCap

Hemos dicho que la tecnología de la cadena de bloques se ha usado para diseñar criptodivisas. Sin embergo, recientemente ha aparecido una nueva aplicación de esta tecnología. Los contratos inteligentes (o Smart Contracts).

¿Qué son los contratos inteligentes?


Podemos definir los contratos inteligentes como un tipo de aplicación informática que se encargan de ejecutar una cierta acción si se cumple la condición especificada. Podemos pensar en ellos como un contrato con cláusulas específicas según la casuística. Los contratos inteligentes además serían fácilmente verificables y a su vez seguros. La idea es que dadas unas condiciones, se ejecuten las acciones especificadas, sin ninguna excepción. El concepto de los contratos inteligentes surgió de manos de Nick Szabo en la década de los noventa. Y ahora ya tenemos la primera implementación de aquellas ideas en Ethereum. Un ejemplo muy sencillo de contrato inteligente es una apuesta con un amigo en un partido de fútbol, cada uno apuesta por un equipo y deposita el dinero en el contrato. Cuando el partido haya finalizado el contrato ejecutará la cláusula correspondiente y enviará al dinero al afortunado.

¿Qué es Ethereum?


Si piensas en el Bitcoin como una hoja de cálculo global, piensa en Ethereum como un sistema operativo global

EthereumFrontier

Ethereum es una implementación de los smart contracts basada en la cadena de bloques. Es descentralizado, como Bitcoin. Los aplicaciones (los contratos inteligentes) en Ethereum se ejecutan sin nisiquiera la posibilidad de caída de la red, censura, fraude o intervención de terceras partes. Los contratos inteligentes simplemente se ejecutan, es imposible que no se ejecuten. Esa es la gran ventaja de Ethereum respecto al Internet como lo conocíamos antes.

Las aplicaciones en Ethereum se suben a la cadena de bloques y se ejecutan bajo demanda, con una potencia no muy elevada (piensa en un smartphone de 1999) pero con una cantidad de memoria y una capacidad de almacenamiento permanente ilimitados. Eso no significa que cualquiera pueda hacer lo que quiera con un programa, pues los contratos pueden estar diseñados para ignorar las peticiones hechas desde usuarios desconocidos. En último término, el objetivo de Ethereum es proveer una computación 100% determinista.

EthereumLogo

¿Cómo funciona?


Usar Ethereum no es gratis, el sistema operativo global necesita combustible. Ese combustible es Ether, aunque en muchos sitios se le llama directamente Ethereum por estar ligado a la plataforma. Ether es una criptodivisa al estilo Bitcoin, pero se puede gastar directamente en ejecutar contratos inteligentes en Ethereum. Al igual que en Bitcoin, en Ethereum hay mineros, que ejecutan los contratos para comprobar que todos obtienen el mismo resultado. Esos mineros reciben su recompensa en Ether que pueden usar o vender en sitios como ShapeShift.

Además necesitaremos un cliente para subir y pedir la ejecución de los contratos inteligentes. Hay muchos, voy a hablar de los cuatro más importantes.:

  • Eth: el cliente en C++

  • Geth: el cliente en Go (recomendado)

  • Web3.js: una unión entre el navegador y Ethereum usando la interfaz RPC de otro cliente Ethereum

  • Mist: se trata de un navegador basado en Electrum (o sea, Chromium) que integra las funciones de Ethereum. Podemos interactuar con las DApps directamente si usamos este navegador. Veremos que son las DApps más adelante.


Desde Geth podemos sincronizarnos con la red Ethereum, minar para ganar Ether, ejecutar contratos en la red y subirlos.

Podemos ver como se ejecuta un contrato inteligente en acción en EtherDice, un simple juego de apuestas con dado.
eth.sendTransaction({from: eth.accounts[0], value: web3.toWei(1, 'ether'), to: '0x2faa316fc4624ec39adc2ef7b5301124cfb68777', gas: 500000}) 

Esta orden se introduce dentro de Geth. Básicamente está realizando un traspaso de fondos desde nuestra cuenta principal (eth.accounts[0], aunque se puede especificar otra si nos sabemos la dirección), el valor de la transacción que es la cantidad de Ether a traspasar. Ether tiene muchos submúltiplos, en este caso usa el Wei. 1 ether = 1000000000000000000 wei. Se especifica la dirección de destino y además el máximo de gas que estaríamos dispuesto a perder en la ejecución (no es posible cuanto va a costar una ejecución). Este valor máximo es el producto del gas por el precio del gas y representa el tope de weis que puede consumir el contrato antes de que se cancele. Con contratos muy probados valdría cualquier valor, pero si estás desarrollando un contrato de vendrá muy bien para que una programación errónea no liquide todos tus fondos antes de tiempo.

Bien, este ejemplo es muy sencillo. De manera más genérica usaríamos la función eth.contract
var greeter = eth.contract(ABI).at(Direccion); 
greeter.greet(VALORES DE INPUT,{from: TuDireccion, gas: 50000});

Siendo la ABI la definición de la interfaz para poder interactuar con el contrato y la dirección es donde reside el contrato en sí. Luego llamamos a la función greet dentro del contrato, puede aceptar parámetros de entrada. Todo esto esta muy bien pero no hemos visto como son realmente los contratos todavía. Un ejemplo muy bueno es Etheria

Etheria

Solidity y la máquina virtual


Los contratos se ejecutan en una máquina virtual llamada EVM (Ethereum Virtual Machine). Esta máquina virtual es Turing completa pero para evitar un colapso (bucles infinitos) tiene en cuenta el gas. Las operaciones en la EVM son lentas, porque cada contrato es ejecutado simultaneamente en el resto de nodos de la red, siendo el resultado final un resultado de consenso de la red. Se han diseñado varios lenguajes que compilan a EVM, pero sin duda el más popular es Solidity.


contract mortal {
/* Define variable owner of the type address*/
address owner;

/* this function is executed at initialization and sets the owner of the contract */
function mortal() { owner = msg.sender; }

/* Function to recover the funds on the contract */
function kill() { if (msg.sender == owner) suicide(owner); }
}

contract greeter is mortal {
/* define variable greeting of the type string */
string greeting;

/* this runs when the contract is executed */
function greeter(string _greeting) public {
greeting = _greeting;
}

/* main function */
function greet() constant returns (string) {
return greeting;
}
}


Este sería un ejemplo de Greeter en Solidity. No voy a explicar la programación en Solidity, ni como se inician los contratos. Si hay demanda popular explicaré como se suben los programas y se inicializan.

DApps


Decentralized Apps, con la tecnología de Ethereum ha surgido un nuevo concepto. Aplicaciones web que se separan del concepto tradicional de cliente-servidor y se ejecutan de manera descentralizada. Estas aplicaciones, aunque siguen necesitando Internet pueden funcionar sin un servidor central si nuestro ordenador dispone de un nodo de Ethereum. Esto es precisamente lo que hace el navegador Mist. Otro aprovechamiento más tradicional de las DApps es dejar un servidor central que corra como nodo de Ethereum y tenga una IP asignada. Sin embargo este servidor central puede ser muy ligero, pues solo sirve de puerta de entrada a la red Ethereum. Este aprovechamiento funcionaría en navegadores tradicionales siempre que los gastos de la red corran a cuenta del administrador de la app. Un ejemplo de DApp que requiere usar el navegador Mist es EthereumWall, la aplicación usa nuestros fondos para su funcionamiento y aunque tiene un servidor central estático para entregar los archivos HTML y el JavaScript, esto no sería necesario pues la lógica la hace la red Ethereum con nuestro nodo local en Mist.

Mist

Conclusión


¿Qué os parecen los contratos inteligentes? ¿Qué os parece la plataforma Ethereum? ¿Tendrá futuro o es una moda pasajera? ¿Crees que puede revolucionar la manera de pensar la web? Comenta, quiero saber tu opinión.

Para más información no dudes en consultar el sitio oficial de Ethereum y el libro oficial

Si crees que lo merece puedes enviarme: BTC(1A2j8CwiFEhQ4Uycsjhr3gQPbJxFk1LRmM), LTC(LXkefu8xYwyD7CcxWRfwHhSRTdk6Sp38Kt), DOGE(D7fvbHocEGS7PeexBV23ktWjgVL1y9RnoK), ReddCoin(RsHAsr6PVs8y4f5pGLS2cApcGpgw15TwUJ)]]>
https://blog.adrianistan.eu/ethereum-y-smartcontracts Wed, 2 Mar 2016 00:00:00 +0000
¿Cómo programar en C (en 2016)? https://blog.adrianistan.eu/programar-c-2016 https://blog.adrianistan.eu/programar-c-2016 Este artículo es una traducción del artículo How to C in 2016. Todo el contenido aparece originalmente en aquel artículo, yo solo me he limitado a traducirlo.
c

La primera regla de C es no escribir en C si puedes evitarlo.

Si te ves obligado a escribir en C, deberías seguir las reglas modernas.

C ha estado con nosotros desde principios de los 70. La gente a "aprendido C" en numerosos puntos de su evolución, pero el conocimiento normalmente se para después de aprender. Así pues todo el mundo piensa diferente sobre C según el año en que empezaron a aprenderlo.

Es importante no quedarse paralizado en las "cosas que aprendí en los 80/90" cuando programas en C.

Esta página asume que estás en una plataforma moderna, con estándares modernos y no tienes que mantener una compatibilidad con sistemas antiguos muy elevada. No debemos estar atados a estándares anticuados solo porque algunas compañías rechacen actualizar sistemas con más de 20 años de antigüedad.

Preliminar


Standard C99 (C99 significa "Estándar C de 1999"; C11 significa "Estándar C de 2011", así que C11 > C99)

  • clang, por defecto

    • C99 es la implementación de C por defecto en clang, no necesita opciones extra

    • Sin embargo esta implementación no es realmente estándar. Si quieres forzar el estándar, usa -std=c99

    • Si quieres usar C11, debes especificar -std=c11

    • clang compila el código fuente más rápidamente que gcc



  • gcc necesita que especifiques -std=c99 o -std=c11

    • gcc compila más lentamente pero a veces genera ejecutables más rápidos

    • gcc-5 establece por defecto -std=gnu11, así que debes seguir especificando una versión estándar c99 o c11.




Optimizaciones

  • -O2, -O3

    • generalmente querrás -O2, pero algunas veces querrás -O3. Prueba tu código con ambos niveles (y entre distintos compiladores) y mantente con los ejecutables más eficientes y rápidos.



  • -Os

    • -Os ayuda si te preocupa la eficiencia de la caché (que debería)




Advertencias

  • -Wall -Wextra -pedantic

    • las nuevas versiones de los compiladores tienen -Wpedantic, pero todavía aceptan el antiguo -pedantic por cuestiones de compatibilidad.

    • durante las pruebas deberías añadir -Werror y -Wshadow en todas tus plataformas

    • puede ser peliagudo enviar a producción con -Werror porque cada plataforma y cada compilador y cada librería pueden emitir distintas advertencias. Probablemente no querrás terminar la compilación entera de un usuario porque su versión de GCC en una plataforma que nunca habías visto se queja de manera nueva y sorprendente.

    • algunas opciones más sofisticadas son -Wstrict-overflow -fno-strict-aliasing

    • especifica -fno-strict-aliasing o estate seguro de que solo accedes a los objetos con el tipo que tuvieron en su definición. Como mucho código en C ya existente se salta lo último es mucho más seguro usar -fno-strict-aliasing particularmente si no controlas todo el código que debes compilar.

    • ahora mismo, clang reporta alguna sintaxis válida como advertencia, así que debes añadir -Wno-missing-field-initializers

    • GCC resolvió este problema después de GCC 4.7




Compilando

  • Unidades de compilación

    • La manera más común de compilar proyectos en C es generar un fichero objeto de cada fichero fuente y unirlo todos al final. Este procedimiento es muy bueno para el desarrollo incremental, pero no lo es para el rendimiento y la optimización. El compilador no puede detectar optimizaciones entre archivos con este método.



  • LTO - Link Time Optimization

    • LTO arregla el problema de las unidades de compilación generando además una representación intermedia que puede ser sujeta de optimizaciones entre archivos. Este sistema ralentiza el tiempo de enlazado significativamente pero make -j puede ayudar.

    • clang LTO (guía)

    • gcc LTO

    • Ahora mismo, 2016, clang y gcc soportan LTO simplemente añadiendo -flto en las opciones tanto de compilación como de enlazado.

    • LTO todavía necesita asentarse. A veces, si tu programa tiene código que no usas directamente pero alguna librería sí, LTO puede borrarlo, porque detecta que en tu código no se hace nunca una llamada a esa función.




Arquitectura

  • -march=native

    • Le da al compilador permiso para usar todas las características de tu CPU

    • otra vez, compara el funcionamiento con los distintos tipos de optimización y que no tengan efectos secundarios.



  • msse2 y -msse4.2 pueden ser útiles si necesitas características que no están disponibles en el sistema desde el que compilas.


Escribiendo código


Tipos


Si te encuentras escribiendo char o int o short o long o unsigned, lo estás haciendo mal.

Para los programas modernos deberías incluir #include <stdint.h> y usar los tipos estándar.

Los tipos estándar comunes son:

  • int8_t, int16_t, int32_t, int64_t - enteros con signo

  • uint8_t, uint16_t, uint32_t, uint64_t - enteros sin signo

  • float - coma flotante de 32 bits

  • double - coma flotante de 64 bits


Te darás cuenta que ya no tenemos char. char está malinterpretado en C.

Los desarrolladores han abusado de char para representar un byte incluso cuando hacen operaciones sin signo. Es mucho más limpio usar uint8_t para representar un único byte sin signo y uint8_t * para representar una secuencia de bytes sin signo.

Una excepción a nunca-char


El único uso aceptable de char en 2016 es si una API ya existente necesita char (por ejemplo, strncat, printf,...) o si estás inicializando una cadena de texto de solo lectura (const char *hello = "hello";) porque el tipo de C para cadenas de texto sigue siendo char *

Además, en C11 tenemos soporte Unicode nativo y el tipo para cadenas UTF-8 sigue siendo char * incluso para secuencias multibyte como const char *abcgrr = u8"abc?";.

El signo


A estas alturas de la película no deberías escribir unsigned nunca en tu código. Podemos escribir sin usar la fea convención de C para tipos multi-palabra que restan legibilidad. ¿Quién quiere escribir unsigned long long int cuando puede escribir uint64_t? Los tipos de <stdint.h> son más explícitos, más exactos en su significado y son más compactos en su escritura y su legibilidad.

Pero podrías decir, "¡Necesito hacer cast a punteros a long para realizar aritmética de punteros sucia!"

Podrías decirlo. Pero estás equivocado.

El tipo correcto para aritmética de punteros es uintptr_t definido en <stddef.h>.

En vez de:

long diff = (long)ptrOld - (long)ptrNew;


Usa:


ptrdiff<em>t diff = (uintptr</em>t)ptrOld - (uintptr_t)ptrNew;


Además:



printf("%p is unaligned by %" PRIuPTR " bytes\",(void *)p, ((uintptr_t)somePtr &amp; (sizeof(void *) - 1)));

Tipos dependientes del sistema


Sigues argumentando, "¡en una plataforma de 32 bits quiero longs de 32 bits y en una de 64 bits quiero longs de 64 bits!"

Si nos saltamos la idea de que estás introduciendo deliberadamente código que dificulta la comprensión del código al tener tamaños distintos dependiendo de la plataforma, aún no tendrías necesidad de usar long.

En estas situaciones debes usar intptr_t que se define según la plataforma en que te encuentres.

En plataformas de 32 bits, intptr_t es int32_t.

En plataformas de 64 bits, intptr_t es int64_t.

intprt_t también tiene una versión sin signo uintptr_t.

Para almacenar diferencias entre punteros, tenemos el ptrdiff_t.

Máxima capacidad


¿Quieres tener el entero con mayor capacidad de tu sistema?

La gente tiene a usar el más grande que conozca, en este caso uint64_t nos podrá almacenar el número más grande. Pero hay una manera más correcta de garantizar que podrá contener cualquier otro valor que se esté utilizando en el programa.

El contenedor más seguro para cualquier entero es intmax_t (también uintmax_t). Puedes asignar cualquier entero con signo a intmax_t sin pérdida de precisión. Puedes asignar cualquier entero sin signo a uintmax_t sin pérdida de precisión.

Ese otro tipo


Otro tipo que depende del sistema y es usado comúnmente es size_t.

size_t se define como "un entero capaz de contener el mayor tamaño de memoria disponible".

En el lado práctico, size_t es el tipo que devuelve el operador sizeof.

En cualquier caso, la definición de size_t es prácticamente la misma que la de uintptr_t en todas las plataformas modernas.

También existe ssize_t que es size_t con signo y que devuelve -1 en caso de error. (Nota: ssize_t es POSIX así que no se aplica esto en Windows).

Así que, ¿debería usar sisze_t para aceptar tamaños dependientes del sistema en mis funciones? Sí, cualquier función que acepte un número de bytes puede usar size_t.

También lo puedes usar en malloc, y ssize_t es usado en read() y write() (solo en sistemas POSIX).

Mostrando tipos


Nunca debes hacer cast para mostrar el valor de los tipos. Usa siempre los especificadores adecuados.

Estos incluyen, pero no están limitados a:

  • size_t - %zu

  • ssize_t - %zd

  • ptrdiff_t - %td

  • valor del puntero - %p (muestra el valor en hexadecimanl, haz cast a (void *) primero)

  • los tipos de 64 bits deben usar las macros PRIu64 (sin signo) y PRId64 (con signo)

    • es imposible especificar un valor correcto multiplataforma sin la ayuda de estas macros



  • intptr_t - "%" PRIdPTR

  • uintptr_t - "%" PRIuPTR

  • intmax_t - "%" PRIdMAX

  • uintmax_t - "%" PRIuMAX


Recordar que PRI* son macros, y las macros se tienen que expandir, no pueden estar dentro de una cadena de texto. No puedes hacer:


printf("Local number: %PRIdPTR\n\n", someIntPtr);


deberías usar:


printf("Local number: %" PRIdPTR "\n\n", someIntPtr);


Tienes que poner el símbolo '%' dentro de la cadena de texto, pero el especificador fuera.

C99 permite declaraciones de variables en cualquier sitio


Así que no hagas esto:


void test(uint8_t input) {
uint32_t b;

if (input > 3) {
return;
}

b = input;
}
[/cpp+

haz esto


void test(uint8_t input) {
if (input > 3) {
return;
}

uint32_t b = input;
}


Aunque si tienes un bucle muy exigente (un tight loop) las declaraciones a mitad de camino pueden ralentizar el bucle.

C99 permite a los bucles for declarar los contadores en la misma línea


Así que no hagas esto


uint32_t i;

for (i = 0; i < 10; i++)


Haz esto:


for (uint32_t i = 0; i < 10; i++)

La mayoría de compiladores soportan #pragma once


Así que no hagas esto:



#ifndef PROJECT_HEADERNAME
#define PROJECT_HEADERNAME
.
.
.
#endif /* PROJECT_HEADERNAME */


haz esto:


#pragma once


#pragma once le dice al compilador que solo incluya el archivo de cabecera una vez y no necesitas escribir esas tres líneas para evitarlo manualmente. Este pragma esta soportado por todos los compiladores modernos en todas las plataformas y está recomendado por encima de nombrar manualmente las cláusulas.

Para más detalles, observa la lista de compiladores que lo soportan en pragma once

C permite la inicialización estática de arrays ya asignados memoria


Así que no hagas:


uint32_t numbers[64];
memset(numbers, 0, sizeof(numbers));


Haz esto:


uint32_t numbers[64] = {0};


C permite la inicialización estática de structs ya asignados en memoria


Así que no hagas esto:


struct thing {
uint64_t index;
uint32_t counter;
};

struct thing localThing;

void initThing(void) {
memset(&localThing, 0, sizeof(localThing));
}


Haz esto:


struct thing {
uint64_t index;
uint32_t counter;
};

struct thing localThing = {0};


NOTA IMPORTANTE: Si tu estructura tiene padding (relleno extra para coincidir con el alineamiento del procesador, todas por defecto en GCC, __attribute__((__packed__)) para desactivar este comportamiento), el método de {0} no llenará de ceros los bits de padding. Si necesitases rellenar todo de ceros, incluido los bits de padding, deberás seguir usando memset(&localThing, 0, sizeof(localThing)).

Si necesitas reinicializar un struct puedes declarar un struct global a cero para asignar posteriormente.


struct thing {
uint64_t index;
uint32_t counter;
};

static const struct thing localThingNull = {0};
.
.
.
struct thing localThing = {.counter = 3};
.
.
.
localThing = localThingNull;

C99 permite arrays de longitud variable (VLA)


Así que no hagas esto:


uintmax_t arrayLength = strtoumax(argv[1], NULL, 10);
void *array[];

array = malloc(sizeof(*array) * arrayLength);

/* remember to free(array) when you're done using it */


Haz esto:


uintmax_t arrayLength = strtoumax(argv[1], NULL, 10);
void *array[arrayLength];

/* no need to free array */


NOTA IMPORTANTE: Los VLA suelen situarse en el stack, junto a los arrays normales. Así que si no haces arrays de 3 millones de elementos normalmente, tampoco los hagas con esta sintaxis. Estos no son listas escalables tipo Python o Ruby. Si especificas una longitud muy grande en tiempo de ejecución tu programa podría empezar a hacer cosas raras. Los VLA están bien para situaciones pequeñas, de un solo uso y no se puede confiar en que escalen correctamente.

Hay gente que considera la sintaxis de VLA un antipatrón puesto que puede cerrar tu programa fácilmente.

NOTA: Debes estar seguro que arrayLength tiene un tamaño adecuado (menos de un par de KB se te darán para VLA). No puede asignar arrays enormes pero en casos concretos, es mucho más sencillo usar las capacidades de C99 VLA en vez de pringarse con malloc/free.

NOTA DOBLE: como puedes ver no hay ninguna verificación de entrada al usar VLA, así que cuida mucho el uso de las VLA.

C99 permite indicar parámetros de punteros que no se solapan


Mira la palabra reservada restrict (a veces, __restrict)

Tipos de los parámetros


Si una función acepta arbitrariamente datos y longitud, no restrinjas el tipo del parámetro.

Así que no hagas:

void processAddBytesOverflow(uint8_t *bytes, uint32_t len) {
for (uint32_t i = 0; i < len; i++) {
bytes[0] += bytes[i];
}
}


Haz esto:


void processAddBytesOverflow(void *input, uint32_t len) {
uint8_t *bytes = input;

for (uint32_t i = 0; i < len; i++) {
bytes[0] += bytes[i];
}
}


Los tipos de entrada definen la interfaz de tu código, no lo que tu código hace con esos parámetros. La interfaz del código dice "aceptar un array de bytes y una longitud", así que no quieres restringirles usar solo uint8_t. Quizá tus usuarios quieran pasar char * o algo más inesperado.

Al declarar el tipo de entrada como void * y haciendo cast dentro de tu función, los usuarios ya no tienen que pensar en abstracciones dentro de tu librería.

Algunos lectores afirman que podría haber problemas de alineamiento con este ejemplo, pero como estamos accediendo a los bytes uno por uno no hay problema en realidad. Si por el contrario tuviéramos tipos más grandes tendríamos que vigilar posibles problemas de alineamiento, mirar Unaligned Memory Access

Parámetros de devolución


C99 nos da el poder de usar <stdbool.h> que define true como 1 y false como 0.

Para valores de éxito/error, las funciones deben devolver true o false, nunca un entero especificando manualmente 1 y 0 (o peor, 1 y -1 (¿o era 0 éxito y 1 error? ¿o era 0 éxito y -1 error?))

Si una función modifica el valor de un parámetro de entrada, no lo devuelvas, usa dobles punteros.

Así que no hagas:


void *growthOptional(void *grow, size_t currentLen, size_t newLen) {
if (newLen > currentLen) {
void *newGrow = realloc(grow, newLen);
if (newGrow) {
/* resize success */
grow = newGrow;
} else {
/* resize failed, free existing and signal failure through NULL */
free(grow);
grow = NULL;
}
}

return grow;
}


Haz esto:

/* Return value:
* - 'true' if newLen > currentLen and attempted to grow
* - 'true' does not signify success here, the success is still in '*_grow'
* - 'false' if newLen <= currentLen */
bool growthOptional(void **_grow, size_t currentLen, size_t newLen) {
void *grow = *_grow;
if (newLen > currentLen) {
void *newGrow = realloc(grow, newLen);
if (newGrow) {
/* resize success */
*_grow = newGrow;
return true;
}

/* resize failure */
free(grow);
*_grow = NULL;

/* for this function,
* 'true' doesn't mean success, it means 'attempted grow' */
return true;
}

return false;
}


O incluso mejor:


typedef enum growthResult {
GROWTH_RESULT_SUCCESS = 1,
GROWTH_RESULT_FAILURE_GROW_NOT_NECESSARY,
GROWTH_RESULT_FAILURE_ALLOCATION_FAILED
} growthResult;

growthResult growthOptional(void **_grow, size_t currentLen, size_t newLen) {
void *grow = *_grow;
if (newLen > currentLen) {
void *newGrow = realloc(grow, newLen);
if (newGrow) {
/* resize success */
*_grow = newGrow;
return GROWTH_RESULT_SUCCESS;
}

/* resize failure, don't remove data because we can signal error */
return GROWTH_RESULT_FAILURE_ALLOCATION_FAILED;
}

return GROWTH_RESULT_FAILURE_GROW_NOT_NECESSARY;
}

Formato


El estilo del código es muy importante.

Si tu proyecto tiene una guía de formato de 50 páginas, nadie te ayudará, pero si tu código tampoco se puede leer, nadie querrá ayudarte.

La solución es usar siempre un programa para formatear el código.

El único formateador de código usable en el 2016 es clang-format. clang-format tiene los mejores ajustes por defecto y sigue en desarrollo activo.

Aquí está el script que uso para formatear mi código:


#!/usr/bin/env bash

clang-format -style="{BasedOnStyle: llvm, IndentWidth: 4, AllowShortFunctionsOnASingleLine: None, KeepEmptyLinesAtTheStartOfBlocks: false}" "$@"


Luego lo llamo
./script.sh -i *.{c,h,cc,cpp,hpp,cxx} 

La opción -i indica que sobrescriba los archivos con los cambios que realice, en vez de generar nuevos archivos o crear copias.

Si tienes muchos archivos, puedes hacer la operación en paralelo


#!/usr/bin/env bash

# note: clang-tidy only accepts one file at a time, but we can run it
# parallel against disjoint collections at once.
find . \( -name \*.c -or -name \*.cpp -or -name \*.cc \) |xargs -n1 -P4 cleanup-tidy

# clang-format accepts multiple files during one run, but let's limit it to 12
# here so we (hopefully) avoid excessive memory usage.
find . \( -name \*.c -or -name \*.cpp -or -name \*.cc -or -name \*.h \) |xargs -n12 -P4 cleanup-format -i


Y ahora el contenido del script cleanup-tidy aquí.


#!/usr/bin/env bash

clang-tidy \
-fix \
-fix-errors \
-header-filter=.* \
--checks=readability-braces-around-statements,misc-macro-parentheses \
$1 \
-- -I.


clang-tidy es una herramienta de refactorización basada en reglas. Las opciones de arriba activan dos arreglos:

  • readability-braces-around-statements - fuerza a que todos los if/while/for tengan el cuerpo rodeado por llaves.

    • ha sido un error que C permitiese las llaves opcionales. Son causa de muchos errores, sobre todo al mantener el código con el tiempo, así que aunque el compilador te lo acepte, no dejes que ocurra.



  • misc-macro-parentheses - añade automáticamente paréntesis alrededor de los parámetros usados en una macro.


clang-tidy es genial cuando funciona, pero para código complejo puede trabarse. Además, clang-tidy no formatea, así que necesitarás llamar a clang-format para formatear y alinear las nuevas llaves y demás cosas.

Legibilidad


Comentarios


Comentarios con sentido, dentro del código, no muy extensos.

Estructura de archivos


Intenta no tener archivos de más de 1000 líneas (1500 como mucho)

Otros detalles


Nunca uses malloc


Usa siempre calloc. No hay penalización de rendimiento por tener la memoria limpia, llena de ceros.

Los lectores han informado de un par de cosas:

  • calloc sí tiene un impacto en el rendimiento en asignaciones enormes

  • calloc sí tiene un impacto en el rendimiento en plataformas extrañas (sistemas empotrados, videoconsolas, hardware de 30 años de antigüedad, ...)

  • una buena razón para no usar malloc() es que no puede comprobar si hay un desbordamiento y es un potencial fallo de seguridad


Todos son buenos puntos, razón por la que siempre debes probar el funcionamiento en todos los sistemas que puedas.

Una ventaja de usar calloc() directamente es que, al contrario que malloc(), calloc() puede comprobar un desbordamiento porque suma todo el tamaño necesario antes de que lo pida.

Algunas referencias al uso de calloc() se pueden encontrar aquí:

Sigo recomendando usar siempre calloc() para la mayoría de escenarios en 2016.

Nunca uses memset (si puedes evitarlo)


Nunca hagas memset(ptr, 0, len) cuando puedes inicializar una estructura (o un array) con {0}.

Generics en C11


C11 ha añadido los Generics. Funcionan como un switch, que distingue entre los tipos y dependiendo del valor que se le de devuelve una u otra cosa. Por ejemplo:


#define probarGenerics(X) _Generic((X), char: 1, int32_t: 2, float: 3, default: 0)

probarGenerics('a') // devolverá 1
probarGenerics(2) // devolverá 2
]]> https://blog.adrianistan.eu/programar-c-2016 Sun, 10 Jan 2016 00:00:00 +0000 Kovel 1.0, diseña modelos en 3D usando vóxeles https://blog.adrianistan.eu/kovel-1-0-disena-modelos-3d-usando-voxeles https://blog.adrianistan.eu/kovel-1-0-disena-modelos-3d-usando-voxeles Kovel. Estuve trabajando en esta aplicación a finales de 2015 y no he hecho muchos cambios últimamente por lo que voy a publicarlo antes de que ¡se me olvide!

¿Qué es Kovel?


Kovel es una aplicación para Linux, Haiku y Windows para diseñar modelos en 3D usando el concepto de los vóxeles. ¿Qué es un vóxel? Se ha definido un vóxel como un píxel con volumen, es decir, el equivalente a un píxel en entornos tridimensionales. Los vóxeles nunca existieron (los gráficos 3D no funcionan a través de vóxeles, son siempre vectoriales) y son simplemente un concepto artístico, muy sencillo de usar.

Kovel-1

KovelRotate

¿Cómo funciona?


Es muy sencillo. Al crear un nuevo archivo seleccionaremos el tamaño de la rejilla. Por defecto la rejilla está puesta en 5. Esto quiere decir que el modelo tendrá una dimensión máxima de 5x5x5. Ahora seleccionamos el material. En esta versión solo hay colores puros como materiales, en futuras versiones habrá texturas también. Ahora simplemente hacemos click en los elementos de la rejilla. Vemos como se pone un vóxel en la posición que hemos indicado en la rejilla. Para subir y bajar de piso usamos los botones Up y Down. Podemos rotar y hacer zoom al modelos para centrarnos en determinadas áreas. En cualquier momento podemos deshacer. Los modelos se guardan como ficheros KVL. Es un formato que he tenido que inventar, es compacto y a la vez muy fácil de manipular. Está basado en BSON, la implementación binaria de JSON hecha por la gente de MongoDB. Pero además podemos exportar nuestras creaciones al formato Collada DAE (se puede abrir con Blender, Maya, etc).

BlenderKovel

¿Dónde puedo obtenerlo?


Todo el código fuente está en GitHub y se compila usando CMake. Pero además hay disponible un PPA para usuarios de Ubuntu. Lamentablemente por temas de dependencias con CMake, solo está disponible en Wily (15.10) y Xenial (16.04), aunque si os descargais el DEB manualmente quizá os funcione también en Trusty (14.04) y Jessie (Debian 8). Los usuarios de Windows tienen un ejecutable también para 64 bits (no he compilado para 32 todavía) pero requiere las DLL de wxWidgets 3.0. Los usuarios de Haiku tendrán que conformarse de momento con el código fuente. Todas las descargas están en la página oficial de Kovel (incluidas las DLL).

KovelHaiku

¿Algo más?


Sí, aparte de Kovel, la aplicación gráfica, también existe kovelcli, la interfaz de línea de comandos. Permite realizar la conversión de KVL a Collada DAE de manera automática.

Finalmente, doy las gracias a todos los que vayan a probar Kovel, un simple comentario con sugerencias o de agradecimiento si os ha servido vale mucho para mí. ¡Felices vóxeles!]]>
https://blog.adrianistan.eu/kovel-1-0-disena-modelos-3d-usando-voxeles Sat, 9 Jan 2016 00:00:00 +0000
Mi primer debug. Primeros pasos con gdb, Valgrind y strace. https://blog.adrianistan.eu/primer-debug-primeros-pasos-gdb-valgrind-strace https://blog.adrianistan.eu/primer-debug-primeros-pasos-gdb-valgrind-strace crash) y no sabes el motivo. En algunos lenguajes como Rust, el propio compilador y el lenguaje evitan estas situaciones, pero en C++ la situación es mucho más estimulante.

Recientemente, trabajando en Kovel tuve uno de estos incidentes inesperados. Pero más inesperada fue su aparición, pues en Debian, donde programo actualmente, el programa se ejecutaba normalmente. Sin embargo en Windows el programa no llegaba a arrancar. Pensé que sería una diferencia Linux-Windows pero al probar en Fedora ocurrió lo mismo que en Windows, no llegaba a arrancar. Si encontraba el fallo en Fedora, que no se daba en Debian, resolvería también el fallo en Windows.

Preparando la aplicación y el entorno


Símbolos de depuración


Aunque no es obligatorio, es recomedable compilar los ejecutables que vayamos a someter a depuración con símbolos de depuración. En Windows se usan archivos independientes (ficheros PDB) mientras que en Linux se usan los mismos ejecutables con más metadatos en su interior. En GCC simplemente hay que añadir la opción -g para retener los datos de depuración.

Ficheros core


Ahora sería conveniente activar la generación de los ficheros core en el sistema. En algunas distro ya está activado:
ulimit -c unlimited 

Los ficheros core los usaremos si nuestra aplicación se paró en un punto de difícil acceso o que no podemos recrear nosotros mismos.

Instalar gdb, Valgrind y los símbolos de las librerías


Ahora vamos a instalar el componente más importante, el debugger, la aplicación que usaremos para analizar la ejecución del programa.

gdb
# En Fedora sudo dnf install gdb 

Además querremos tener los símbolos de depuración de las bibliotecas que use nuestro ejecutable. Con DNF, en Fedora, el proceso usa un comando específico:
sudo dnf debuginfo-install wxGTK SDL libstdc++ # Y las librerías que usemos 

Y si queremos mantener los símbolos de depuración actualizados:
sudo dnf --enablerepo=updates-debuginfo update 

Vamos a usar Valgrind también, aunque menos
sudo dnf install valgrind 

Cazando al vuelo


Supongamos que sabemos como generar el error. Llamamos a nuestro programa desde gdb:
gdb ./MiPrograma 

Entraremos en gdb, con su propios comandos de herramientas. Lo primero que haremos será iniciar el programa, con el comando run o r
(gdb) r 

El programa se iniciará. Nosotros provocaremos el error. Una vez lo hayamos provocado podremos introducir más comandos. Vamos a ver que pasos se han seguido para producir el error.
(gdb) bt full 

Y desde aquí podemos inspeccionar que funciones fueron llamadas justo antes de que el programa petase. En este punto también podemos buscar el valor de ciertas variables que nos interesen con p nombrevariable.

Volviendo al pasado


No sabemos como se produjo el error, pero tenemos un fichero core que nos va a permitir restablecer la situación del pasado para poder analizarla. Llamamos a gdb con el fichero core y nuestra aplicación.
gdb ./MiPrograma ./core 

Una vez dentro podemos dirigirnos al punto crítico.
(gdb) where 

Y analizamos como antes.

Valgrind y fugas de memoria


Valgrind es muy usado para comprobar en que partes nuestro programa tiene fugas de memoria. En determinados casos puede ser más útil que gdb.
valgrind --leak-check=yes ./MiPrograma 

Nuestro programa se ejecutará aproximadamente 20 o 30 veces más lento, pero se nos informará en todo momento de la gestión errónea de memoria que está produciéndose. En alguna situación será interesante saber de donde provienen estos fallos con mayor precisión, la opción --track-origins=yes es nuestra amiga.
valgrind --leak-check=yes --track-origins=yes ./MiPrograma 

Valgrind es muy estricto y puede generar falsos positivos. Hay varias GUI disponibles para Valgrind, una de ellas es KCacheGrind.

KCacheGrind

Otra de ellas es Valkyrie

Valkyrie

¿Y si algún fichero no existe?


Para terminar vamos a suponer que nuestro programa falla porque hay un archivo que no logra encontrar y no puede abrirlo. Gracias a strace es posible saber que archivos está abriendo el programa.
strace -eopen ./MiPrograma 

Y nos saldrá en tiempo real los archivos que ha abierto nuestro programa.

Strace

Y espero que con este pequeño resumen ya sepais que hacer cuando vuestro programa se cierra inesperadamente.]]>
https://blog.adrianistan.eu/primer-debug-primeros-pasos-gdb-valgrind-strace Fri, 4 Dec 2015 00:00:00 +0000
Emulando a Linus Torvalds: Crea tu propio sistema operativo desde 0 (VIII) https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-viii https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-viii Este artículo lo escribí para el blog en español DesdeLinux el 23 de diciembre de 2014 y ahora lo dejo aquí, en mi blog personal. El artículo está tal cual, sin ninguna modificación desde aquella fecha.
Volvemos a la serie de tutoriales sobre como crear nuestro propio sistema operativo. Supongo que este capítulo os gustará mucho porque por fin podremos interactuar con nuestro sistema operativo. Hoy leeremos la entrada del teclado. Para ello el esquema es similar al del timer. Tenemos que usar los IRQ igualmente así que empezaremos igual que con el timer.


ND_IRQ_InstallHandler(1,&ND_Keyboard_Handler);


Nuestro handler de teclado sin embargo es algo más complejo ya que vamos leyendo las teclas y las vamos depositando en un buffer.


extern "C"
void ND_Keyboard_Handler(struct regs* r)
{
unsigned char scancode = ND::Keyboard::GetChar();
if(scancode!=255)
{
ND::Screen::PutChar(scancode);
stringBuffer[stringPos]=scancode;
stringPos++;
}
}


Podemos comprobar como llamamos a una función llamada ND::Keyboard::GetChar. Allí obtenemos el caracter y después si no es un caracter vacío (aquí he usado 255, habría que usar un sistema mejor) ponemos el caracter en pantalla y lo almacenamos en un buffer simple de chars (esto también es susceptible de mejora, el sistema actual puede desbordarse).


unsigned char ND::Keyboard::GetChar()
{
unsigned char scancode;
scancode=(unsigned char)ND::Ports::InputB(0x60);
if(scancode & ND_KEYBOARD_KEY_RELEASE)
{
return 255;
}else{
return en_US[scancode];
}
}

char* ND::Keyboard::GetString()
{
while(stringBuffer[stringPos-1]!='\n')
{
}
stringPos=0;
return stringBuffer;
}


Aquí podemos ver como se obtiene la tecla que ha sido pulsada. En 0x60 siempre va a estar la última tecla pulsada. De hecho se puede leer directamente sin tener que usar el IRQ, pero entonces no sabremos indentificar cuando se ha producido un cambio. Allí comprobamos con la operación AND que el código de obtuvimos corresponde a una tecla que se ha dejado de pulsar.

En ese caso devolvemos 255 (porque luego lo ignoraremos) y en caso contrario la tecla ha sido pulsada. En ese caso devolvemos la posición de un array llamado enUS. ¿Qué información contiene este array? Este array es lo que llamaríamos un keymap o un mapa de caracteres. Como sabrán diferentes idiomas tienen diferentes teclados y no son compatibles ya que sobreescriben las teclas. Así enUS nos dará la tecla correspondiente a cada código y funcionará en un teclado americano.


unsigned char en_US[128]=
{
0,27,'1','2','3','4','5','6','7','8','9','0','-','=', '\b',
'\t','q','w','e','r','t','y','u','i','o','p','[',']','\n',
0, /* Ctrl */
'a','s','d','f','g','h','j','k','l',';',
'\'','`',0, /* Left Shift */
'\\','z','x','c','v','b','n','m',',','.','/', 0,/* Right shift */
'*', 0, /* Alt */
' ',
0, /* Caps lock*/
0,0,0,0,0,0,0,0,0,0, /* F1-F10 keys */
0, /* Num lock */
0, /* Scroll lock */
0, /* Home key */
0, /* Up arrow */
0, /* Page up */
'-',
0, /* Left arrow */
0,
0, /* Right arrow */
'+',
0, /* End key */
0, /* Down arrow */
0, /* Page down */
0, /* Insert key */
0, /* Delete key */
0,0,0,
0, 0, /* F11-F12 Keys */
0
};


También había una función definida que obtenía una frase. El propósito es simplemente obtener acceso más fácilmente a los strings desde las aplicaciones que lo necesiten, de momento solo una. Hablo de NextShellLite, una versión reducida del posible futuro shell que tendría NextDivel. El propósito de NextShellLite es únicamente el de proveer de un shell reducido para ir probando poco a poco nuevas funcionalidades. No voy a poner el código del shell aquí pero lo he incluido dentro del código de NextDivel.

De momento no funciona como un programa aparte sino como una función que llama el kernel, principalmente porque todavía no añadimos la opción de ejecutar ejecutables. Y claro, unas imágenes de como funciona el shell con las nuevas funciones de entrada de teclado.

NextShellLite]]>
https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-viii Thu, 3 Dec 2015 00:00:00 +0000
Emulando a Linus Torvalds: Crea tu propio sistema operativo desde 0 (VII) https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-vii https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-vii Este artículo lo escribí para el blog en español DesdeLinux el 25 de agosto de 2014 y ahora lo dejo aquí, en mi blog personal. El artículo está tal cual, sin ninguna modificación desde aquella fecha.
Bienvenidos a otro post sobre cómo crear nuestro propio sistema operativo. Ha pasado mucho tiempo desde el último post, debido principalmente a un bug que encontré en lo que nos toca hoy. Veremos cómo manejar el reloj en arquitecturas x86.

Anteriormente habíamos activado los IRQ de manera genérica, pero hubo un pequeño problema ya que no los activábamos correctamente y pasábamos datos extra. Finalmente lo solucionamos carlosorta y yo y os puedo seguir comentando cómo seguir.

Bien, el reloj es un IRQ, concretamente el primero. Para configurarlo usaremos la función que definimos anteriormente para instalar de manera genérica los IRQ, la NDIRQInstallHandler.


int ND_TIMER_TICKS=0;

void ND::Timer::Phase(int hz)
{
int divisor=1193180/hz;
ND::Ports::OutputB(0x43,0x36);
ND::Ports::OutputB(0x40, divisor & 0xFF);
ND::Ports::OutputB(0x40, divisor >> 8);
}
void ND::Timer::Wait(int ticks)
{
unsigned long eticks;
eticks=ND_TIMER_TICKS+ticks;
while(ND_TIMER_TICKS < eticks)
{

}
}
void ND::Timer::Setup()
{
ND::Screen::SetColor(ND_SIDE_FOREGROUND, ND_COLOR_BLACK);
ND::Screen::PutString("\nSetup timer...");

ND_IRQ_InstallHandler(0,&ND_Timer_Handler);

ND::Screen::SetColor(ND_SIDE_FOREGROUND,ND_COLOR_GREEN);
ND::Screen::PutString("done");
}
extern "C"
void ND_Timer_Handler(struct regs* r)
{
ND_TIMER_TICKS++;
if(ND_TIMER_TICKS % 18 ==0)
{
ND::Screen::SetColor(ND_SIDE_FOREGROUND,ND_COLOR_BROWN);
ND::Screen::PutString("\nOne more second"); WE SHOULD DO A REFRESH SCREEN
}
}


El código se ejecuta de la siguiente manera: el sistema de inicialización llama a ND::Timer::Setup, que llama a NDIRQInstallHandler para insertar en la primera posición, el IRQ0, una función de callback cuando el evento se produzca, esa es NDTimerHandler que aumenta los ticks. Como hemos puesto la velocidad del reloj a 18 Hz, como veremos más adelante, si lo dividiésemos entre 18 y nos diese entero habría pasado un segundo.

La función ND::Timer::Phase nos sirve para ajustar la velocidad del timer, ese número tan extravagante es 1.19 MHz que es un valor común. Bien, esta función la deberemos llamar si quisiésemos cambiar la velocidad del timer, por defecto va a 18,22 Hz, un valor peculiar que debió de decidir alguien dentro de IBM y se ha quedado hasta nuestros días.

La función ND::Timer::Wait es bastante simple, solamente espera con un bucle while hasta que se hayan alcanzado los ticks necesarios para continuar.

En la imagen podemos comprobar que si descomentamos el código dentro del NDTimerHandler obtenemos esto:

NextDivelSegundos]]>
https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-vii Wed, 2 Dec 2015 00:00:00 +0000
Emulando a Linus Torvalds: Crea tu propio sistema operativo desde 0 (VI) https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-vi https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-vi Este artículo lo escribí para el blog en español DesdeLinux el 11 de enero de 2014 y ahora lo dejo aquí, en mi blog personal. El artículo está tal cual, sin ninguna modificación desde aquella fecha.
Bien, después de un pequeño paréntesis seguimos con nuestra serie de tutoriales. Si retomamos el código anterior debemos de tener el ISR de la división por cero. Ahora debemos de rellenar el resto de las ISR para las que habíamos puesto mensaje (las 32 primeras). Bien ahora vamos a seguir programando interrupciones, vamos a hacer las IRQ también conocidas como Interrupts Requests. Estas IRQ se generan por los dispositivos de hardware tales como teclados, ratones, impresoras, etc. Inicialmente las primeras 8 IRQ se mapean automáticamente en las posiciones de la IDT del 8 al 15. Como hemos usado las 32 primeras para las excepciones ahora tenemos que remapearlas. Nosotros pondremos la IRQ desde la 32 hasta la 45. Para ello primero debemos remapear los los IRQ:


void ND::IRQ::Remap(int pic1, int pic2)
{
#define PIC1 0x20
#define PIC2 0xA0
#define ICW1 0x11
#define ICW4 0x01
/* send ICW1 */

ND::Ports::OutputB(PIC1, ICW1);
ND::Ports::OutputB(PIC2, ICW1);

/* send ICW2 */

ND::Ports::OutputB(PIC1 + 1, pic1); /* remap */
ND::Ports::OutputB(PIC2 + 1, pic2); /* pics */

/* send ICW3 */

ND::Ports::OutputB(PIC1 + 1, 4); /* IRQ2 -> connection to slave */
ND::Ports::OutputB(PIC2 + 1, 2);

/* send ICW4 */

ND::Ports::OutputB(PIC1 + 1, ICW4);
ND::Ports::OutputB(PIC2 + 1, ICW4);

/* disable all IRQs */

ND::Ports::OutputB(PIC1 + 1, 0xFF);
}

Ahora creamos una función para instalar los IRQ:

void ND::IRQ::Install()
{
ND::Screen::SetColor(ND_SIDE_FOREGROUND,ND_COLOR_BLACK);
ND::Screen::PutString("\nInstalling IRQ...");
ND::IRQ::Remap(0x20,0x28);
ND::IDT::SetGate(32,(unsigned)ND::IRQ::IRQ1,0x08,0x8E);
ND::IDT::SetGate(33,(unsigned)ND::IRQ::IRQ2,0x08,0x8E);
ND::IDT::SetGate(34,(unsigned)ND::IRQ::IRQ3,0x08,0x8E);
ND::IDT::SetGate(35,(unsigned)ND::IRQ::IRQ4,0x08,0x8E);
ND::IDT::SetGate(36,(unsigned)ND::IRQ::IRQ5,0x08,0x8E);
ND::IDT::SetGate(37,(unsigned)ND::IRQ::IRQ6,0x08,0x8E);
ND::IDT::SetGate(38,(unsigned)ND::IRQ::IRQ7,0x08,0x8E);
ND::IDT::SetGate(39,(unsigned)ND::IRQ::IRQ8,0x08,0x8E);
ND::IDT::SetGate(40,(unsigned)ND::IRQ::IRQ9,0x08,0x8E);
ND::IDT::SetGate(41,(unsigned)ND::IRQ::IRQ10,0x08,0x8E);
ND::IDT::SetGate(42,(unsigned)ND::IRQ::IRQ11,0x08,0x8E);
ND::IDT::SetGate(43,(unsigned)ND::IRQ::IRQ12,0x08,0x8E);
ND::IDT::SetGate(44,(unsigned)ND::IRQ::IRQ13,0x08,0x8E);
ND::IDT::SetGate(45,(unsigned)ND::IRQ::IRQ14,0x08,0x8E);
ND::IDT::SetGate(46,(unsigned)ND::IRQ::IRQ15,0x08,0x8E);
ND::IDT::SetGate(47,(unsigned)ND::IRQ::IRQ16,0x08,0x8E);
ND::Screen::SetColor(ND_SIDE_FOREGROUND,ND_COLOR_GREEN);
ND::Screen::PutString("done");
asm volatile("sti");
}


La sentencia de asm sti nos activa los IRQ. Bien ahora vamos con algo similar a los ISR. Las funciones de un IRQ básico:


void ND::IRQ::IRQ1()
{
asm volatile(
"cli \n"
"pushl 0\n"
"pushl 32\n"
"jmp ND_IRQ_Common"
);
}


Una parte común (igual que la de los ISR):


extern "C"
void ND_IRQ_Common()
{
asm volatile(
"pusha \n"
"push %ds\n"
"push %es\n"
"push %fs\n"
"push %gs\n"
"movw $0x10, %ax \n"
"movw %ax, %ds \n"
"movw %ax, %es \n"
"movw %ax, %fs \n"
"movw %ax, %gs \n"
"movl %esp, %eax \n"
"push %eax \n"
"movl $ND_IRQ_Handler, %eax \n"
"call *%eax \n"
"popl %eax \n"
"popl %ds \n"
"popl %es \n"
"popl %fs \n"
"popl %gs \n"
"popa \n"
"addl 8, %esp \n"
"iret \n"
);
}


Y un handler básico:


extern "C"
void ND_IRQ_Handler(struct regs* r)
{
void (*handler)(struct regs *r);
if(r->int_no >= 40)
{
ND::Ports::OutputB(0xA0,0x20);
}
ND::Ports::OutputB(0x20,0x20);
}


Con esto ya deberíamos tener activados los IRQ aunque todavía no hagan nada. En el siguiente capítulo veremos como obtener datos a partir de estos IRQ como el reloj o el teclado.

NextDivel-IRQ

Y con esto termina el post de hoy. Como habeis podido comprobar ahora escribo menos regularmente debido a otros asuntos. Aun así seguiré hasta tener un sistema operativo más completo]]>
https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-vi Tue, 1 Dec 2015 00:00:00 +0000
Emulando a Linus Torvalds: Crea tu propio sistema operativo desde 0 (V) https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-v https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-v Este artículo lo escribí para el blog en español DesdeLinux el 11 de enero de 2014 y ahora lo dejo aquí, en mi blog personal. El artículo está tal cual, sin ninguna modificación desde aquella fecha.
En esta quinta entrega veremos una tabla bastante parecida a la GDT tanto en teoría como en uso, nos referimos a la IDT. Las siglas de IDT hacen referencia a Interrupts Description Table y es una tabla que se usa para manejar las interrupciones que se produzcan. Por ejemplo, alguien hace una división entre 0, se llama a la función encargada de procesar. Estas funciones son los ISR (Interrupt Service Routines). Así pues vamos a crear la IDT y añadir algunos ISR.

Lo primero vamos a declarar las estructuras correspondientes a la IDT:


struct Entry{
uint16_t base_low;
uint16_t sel;
uint8_t always0;
uint8_t flags;
uint16_t base_high;
} __attribute__((packed));

struct Ptr{
uint16_t limit;
uint32_t base;
} __attribute__((packed));


Como se observa si comparáis con la GDT la estructura Ptr es idéntica y la Entry es bastante parecida. Por consiguiente las funciones de poner una entrada (SetGate) e instalar (Install) son muy parecidas.

void ND::IDT::SetGate(uint8_t num,uint32_t base,uint16_t sel, uint8_t flags)
{
idt[num].base_low=(base & 0xFFFF);
idt[num].base_high=(base >> 16) & 0xFFFF;
idt[num].sel=sel;
idt[num].always0=0;
idt[num].flags=flags;
}


Instalar:


idtptr.limit=(sizeof(struct ND::IDT::Entry)*256)-1;
idtptr.base=(uint32_t)&idt;
ND::Memory::Set(&idt,0,sizeof(struct ND::IDT::Entry)*256);
ND::IDT::Flush();


Si nos fijamos veremos que la función de instalar usa la función ND::Memory::Set que habíamos declarado en el otro post. También podemos apreciar como no hacemos ninguna llamada a SetGate todavía y llamamos a ND::IDT::Flush, para esta función usamos otra vez la sentencia asm volatile:


asm volatile("lidtl (idtptr)");


Si todo va bien y hacemos un arreglo estético debería quedar así:

NextDivel-IDT

Bien, ahora vamos a empezar a rellenar la IDT con interrupciones. Aquí voy a crear solo una pero para el resto se haría igual. Voy a hacer la interrupción de división por cero. Como bien sabrán en matemáticas no se puede dividir un número entre 0. Si esto ocurre en el procesador se genera una excepción ya que no puede continuar. En la IDT la primera interrupción en la lista (0) corresponde a este suceso.

Añadimos esto entre el seteo de memoria y el flush dentro de la función Install de la IDT:


ND::IDT::SetGate(0,(unsigned)ND::ISR::ISR1,0x08,0x8E);


La función de callback va a ser ND::ISR::ISR1 que es bastante simple aunque debemos usar ASM:


void ND::ISR::ISR1()
{
asm volatile(
"cli \n"
"pushl 0 \n"
"pushl 0 \n"
"jmp ND_ISR_Common \n");
}


NDISRCommon lo definiremos como una función en lenguaje C. Para ahorrar ficheros y mejorar legibilidad podemos usar extern “C”{}:


extern "C"

void ND_ISR_Common()
{
asm volatile(
"pusha \n"
"push %ds \n"
"push %es \n"
"push %fs \n"
"push %gs \n"
"movw $0x10, %ax \n"
"movw %ax, %ds \n"
"movw %ax, %es \n"
"movw %ax, %fs \n"
"movw %ax, %gs \n"
"movl %esp, %eax \n"
"push %eax \n"
"movl $ND_ISR_Handler, %eax \n"
"call *%eax \n"
"popl %eax \n"
"popl %ds \n"
"popl %es \n"
"popl %fs \n"
"popl %gs \n"
"popa \n"
"addl 8, %esp \n"
"iret \n"
);
}

Este código en ASM puede ser un poco difícil de entender pero esto es así porque vamos a declarar una estructura en C para acceder a los datos que genere la interrupción. Obviamente si no quisieras eso podrías llamar simplemente en ND::ISR::ISR1 al Kernel Panic o algo por el estilo. La estructura tiene una forma tal que así:

struct regs{
uint32_t ds;
uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax;
uint32_t int_no, err_code;
uint32_t eip, cs, eflags, useresp, ss;
};


Y por último hacemos la función NDISRHandler (también con link del C) en que mostramos un kernel panic y una pequeña descripción del error según el que tenemos en una lista de errores.


extern "C"

void ND_ISR_Handler(struct regs *r)
{
if(r->int_no < 32) {
ND::Panic::Show(exception_messages[r->int_no]);
for(;;);
}

}


Bien y con esto ya somos capaces de manejar esta interrupción. Con el resto de interrupciones pasaría parecido salvo que hay algunas que devuelven parámetros y usaríamos la estructura reg para obtenerlo. Sin embargo te preguntarás que como sabemos si funciona de verdad. Para probar si funciona vamos a introducir una sencilla línea después del ND::IDT::Install():


int sum=10/0;


Si compilamos nos dará un warning y si tratamos de ejecutarlo nos saldrá una bonita pantalla:

NextDivel-ISR

Y con esto termina este post, creo que es uno de los más extensos pero bastante funcional.]]>
https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-v Mon, 30 Nov 2015 00:00:00 +0000
Emulando a Linus Torvalds: Crea tu propio sistema operativo desde 0 (IV) https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-iv https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-iv Este artículo lo escribí para el blog en español DesdeLinux el 5 de enero de 2014 y ahora lo dejo aquí, en mi blog personal. El artículo está tal cual, sin ninguna modificación desde aquella fecha.
Bienvenidos de nuevo a esta serie de posts titulada “Emulando a Linus Torvalds”. Hoy veremos la GDT. Primero tenemos que ver que es la GDT. Según Wikipedia:
The Global Descriptor Table or GDT is a data structure used by Intel x86-family processors starting with the 80286 in order to define the characteristics of the various memory areas used during program execution, including the base address, the size and access privileges like executability and writability

Que traducido sería una Tabla de Descriptores Global, una estructura de datos usada en los procesadores Intel x86 desde el 80286 para definir las características de varias áreas de memoria usadas durante la ejecución del programa.

Resumiendo, si estamos en un procesador Intel x86 deberemos definir una GDT para un correcto uso de la memoria. Nosotros no vamos a hacer mucha complicación y vamos a definir 3 entradas en la tabla:

  • Una entrada NULL, obligatoria para todas las tablas.

  • Una entrada para la sección data, usaremos el máximo, que en 32 bits son 4 GB.

  • Una entrada para la sección code, usaremos el máximo, que en 32 bits son 4 GB.


Como veis data y code usarán el mismo espacio. Bien, ahora vamos a implementarlo. Para ello usaremos dos estructuras, la primera se encargará de contener un puntero hacia los datos reales de nuestra GDT. Y la segunda será un array con las entradas de la GDT. Primero vamos a definirlas.

struct Entry{
uint16_t limit_low;
uint16_t base_low;
uint8_t base_middle;
uint8_t access;
uint8_t granularity;
uint8_t base_high;
} __attribute__((packed));

struct Ptr{
uint16_t limit;
uint32_t base;
} __attribute__((packed));


Habrán observado un curioso __attribute__((packed)) al final de las estructuras. Esto le dice al GCC que no optimice las estructuras porque lo que queremos es pasar los datos tal cual al procesador. Ahora vamos a hacer una función para instalar la GDT. Antes deberemos haber declarado las estructuras, ahora vamos a inicializarlas.


struct ND::GDT::Entry gdt[3];
struct ND::GDT::Ptr gp;
void ND::GDT::Install()
{
gp.limit=(sizeof(struct ND::GDT::Entry)*3)-1;
gp.base=(uint32_t)&gdt;
}


Así conseguimos el construir el puntero que va hacia nuestra tabla de 3 entradas.

Ahora definimos una función común para poner los datos en las entradas


void ND::GDT::SetGate(int num, uint32_t base, uint32_t limit, uint8_t access,uint8_t gran)
{
gdt[num].base_low=(base & 0xFFFF);
gdt[num].base_middle=(base >> 16) & 0xFF;
gdt[num].base_high=(base >> 24) & 0xFF;
gdt[num].limit_low=(limit & 0xFFFF);
gdt[num].granularity=(limit >> 16) & 0x0F;
gdt[num].granularity |= (gran & 0xF0);
gdt[num].access=access;
}


Y la llamamos 3 veces desde la función de instalar


ND::GDT::SetGate(0,0,0,0,0); /* NULL segmente entry */
ND::GDT::SetGate(1,0,0xFFFFFFFF,0x9A,0xCF); /* 4 GiB for Code Segment */
ND::GDT::SetGate(2,0,0xFFFFFFFF,0x92,0xCF); /* 4 GiB for Data segment */


Por último debemos decirle al procesador que tenemos una GDT, para que la cargue, y en nuestro caso al cargar el kernel con GRUB, sobreescribir la GDT de GRUB. Para cargar la GDT existe una instrucción en asm llamada lgdt (o lgdtl dependiendo de la sintaxis), vamos a usarla.


asm volatile("lgdtl (gp)");
asm volatile(
"movw $0x10, %ax \n"
"movw %ax, %ds \n"
"movw %ax, %es \n"
"movw %ax, %fs \n"
"movw %ax, %gs \n"
"movw %ax, %ss \n"
"ljmp $0x08, $next \n"
"next: \n"
);


Bien una vez hayamos terminado esto nuestro sistema ya contará con GDT. En el siguiente capítulo veremos la IDT, una tabla muy parecida a la GDT pero con interrupciones. Yo he puesto unos mensajes de estado y confirmación con la GDT así que NextDivel ahora luce así:

NextDivel-GDT]]>
https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-iv Sun, 29 Nov 2015 00:00:00 +0000
Emulando a Linus Torvalds: Crea tu propio sistema operativo desde 0 (III) https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-iii https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-iii Este artículo lo escribí para el blog en español DesdeLinux el 1 de enero de 2014 y ahora lo dejo aquí, en mi blog personal. El artículo está tal cual, sin ninguna modificación desde aquella fecha.
NextDivel-3

Continuamos esta serie de posts sobre cómo crear nuestro sistema operativo. Hoy no nos vamos a centrar en un tema sino que vamos a definir algunas funciones útiles de ahora en adelante. En primer lugar vamos a definir 3 funciones que cumplan la función de memcpy, memset y memcmp:


void* ND::Memory::Set(void* buf, int c, size_t len)
{
unsigned char* tmp=(unsigned char*)buf;
while(len--)
{
*tmp++=c;
}
return buf;
}
void* ND::Memory::Copy(void* dest,const void* src, size_t len)
{
const unsigned char* sp=(const unsigned char*)src;
unsigned char* dp=(unsigned char*)dest;
for(;len!=0;len--) *dp++=*sp++;
return dest;
}
int ND::Memory::Compare(const void* p1, const void* p2, size_t len)
{
const char* a=(const char*)p1;
const char* b=(const char*)p2;
size_t i=0;
for(;i<len;i++)
{
if(a[i] < b[i])
return -1;
else if(a[i] > b[i])
return 1;
}
return 0;
}


Todas ellas se auto-implementan. Estas funciones yo las he sacado de una pequeña librería del C, la implementación suele ser parecida en todos los sistemas operativos. Ahora vamos a hacer 3 funciones simulares pero para manipular strings. Cumplirían la función de strcpy, strcat y strcmp.


size_t ND::String::Length(const char* src)
{
size_t i=0;
while(*src--)
i++;
return i;
}
int ND::String::Copy(char* dest, const char* src)
{
int n = 0;
while (*src)
{
*dest++ = *src++;
n++;
}
*dest = '\0';
return n;
}
int ND::String::Compare(const char *p1, const char *p2)
{
int i = 0;
int failed = 0;
while(p1[i] != '\0' && p2[i] != '\0')
{
if(p1[i] != p2[i])
{
failed = 1;
break;
}
i++;
}
if( (p1[i] == '\0' && p2[i] != '\0') || (p1[i] != '\0' && p2[i] == '\0') )
failed = 1;

return failed;
}
char *ND::String::Concatenate(char *dest, const char *src)
{
int di = ND::String::Length(dest);
int si = 0;
while (src[si])
dest[di++] = src[si++];

dest[di] = '\0';

return dest;
}


Vamos ahora con unas funciones bastante interesantes. Con estas funciones podremos leer y escribir en los puertos del hardware. Esto normalmente se hace con ASM y corresponde (en x86) a las instrucciones in y out. Para llamar de una manera fácil a ASM desde C se usa la instrucción asm, con el peligro que conlleva de que no es portable. A esta sentencia le añadimos el volatile para que GCC no intente optimizar ese texto. Por otra parte la instrucción asm tiene una forma curiosa de aceptar parámetros, pero eso creo que se entiende mejor viendo los ejemplos.


uint8_t ND::Ports::InputB(uint16_t _port)
{
unsigned char rv;
asm volatile("inb %1, %0" : "=a"(rv) : "dN"(_port));
return rv;
}
uint16_t ND::Ports::InputW(uint16_t port)
{
uint16_t rv;
asm volatile("inw %1, %0" : "=a"(rv) : "dN"(port));
}
void ND::Ports::OutputB(uint16_t port, uint8_t value)
{
asm volatile("outb %1, %0" : : "dN"(port), "a"(value));
}


Y hasta aquí el post 3, hoy no hemos hecho nada vistoso pero sí hemos definido una funciones que nos vendrán bien de cara a un futuro. Aviso a los usuarios de 64 bits que estoy trabajando en solucionar un bug que impide compilar correctamente en 64 bits. En el siguiente post veremos un componente importante de la arquitectura x86, la GDT.]]>
https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-iii Sat, 28 Nov 2015 00:00:00 +0000
Emulando a Linus Torvalds: Crea tu propio sistema operativo desde 0 (II) https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-ii https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-ii Este artículo lo escribí para el blog en español DesdeLinux el 29 de diciembre de 2013 y ahora lo dejo aquí, en mi blog personal. El artículo está tal cual, sin ninguna modificación desde aquella fecha.
Bienvenidos a otro post sobre como crear nuestro propio sistema operativo, en este caso NextDivel.

Si retomamos el código del primer post al final de todo nos debería haber salido algo como esto:

NextDivel-1

Si esto es correcto podemos continuar. Voy a usar el sistema y la estructura que tengo en GitHub (http://github.com/AdrianArroyoCalle/next-divel) ya que es más cómodo para mí y para vosotros. Como se puede apreciar el texto es un texto básico, no resulta atractiv0. Puede parecer algo más del montón. Pero como dice el dicho, para gustos colores, y en nuestro sistema operativo habrá colores. Los primeros colores que vamos a poder poner van a ser los que definen las tarjetas VGA y son 16:

  1. Negro

  2. Azul

  3. Verde

  4. Cyan

  5. Rojo

  6. Magenta

  7. Marrón

  8. Gris claro

  9. Gris oscuro

  10. Azul claro

  11. Verde claro

  12. Cyan claro

  13. Rojo claro

  14. Magenta claro

  15. Marrón claro

  16. Blanco


Estos colores los vamos a definir en un header para tenerlo más a mano y quizá en un futuro formar parte de la API del sistema. Así creamos el archivo ND_Colors.hpp en el include de NextDivel.


#ifndef ND_COLOR_HPP
#define ND_COLOR_HPP

typedef enum ND_Color{
ND_COLOR_BLACK = 0,
ND_COLOR_BLUE = 1,
ND_COLOR_GREEN = 2,
ND_COLOR_CYAN = 3,
ND_COLOR_RED = 4,
ND_COLOR_MAGENTA = 5,
ND_COLOR_BROWN = 6,
ND_COLOR_LIGHT_GREY = 7,
ND_COLOR_DARK_GREY = 8,
ND_COLOR_LIGHT_BLUE = 9,
ND_COLOR_LIGHT_GREEN = 10,
ND_COLOR_LIGHT_CYAN = 11,
ND_COLOR_LIGHT_RED = 12,
ND_COLOR_LIGHT_MAGENTA = 13,
ND_COLOR_LIGHT_BROWN = 14,
ND_COLOR_WHITE = 15

} ND_Color;
#endif


A su vez vamos a definir nuevas funciones para escribir en pantalla de una manera más cómoda (no, todavía no vamos a implementar printf, sé que lo estais deseando). Crearemos un archivo y su header para un set de funciones relacionadas con la pantalla (NDScreen.cpp y NDScreen.hpp). En ellas vamos a crear funciones para: cambiar el color de las letras y el fondo, escribir frases y letras, limpiar la pantalla y desplazarnos por la pantalla. Seguimos usando las pantallas VGA pero ahora usaremos unos bytes que darán el color. ND_Screen.cpp quedaría como:


#include <ND_Types.hpp>
#include <ND_Color.hpp>
#include <ND_Screen.hpp>

uint16_t *vidmem= (uint16_t *)0xB8000;
ND_Color backColour = ND_COLOR_BLACK;
ND_Color foreColour = ND_COLOR_WHITE;
uint8_t cursor_x = 0;
uint8_t cursor_y = 0;

/**
* @brief Gets the current color
* @param side The side to get the color
* */
ND_Color ND::Screen::GetColor(ND_SIDE side)
{
if(side==ND_SIDE_BACKGROUND){
return backColour;
}else{
return foreColour;
}
}
/**
* @brief Sets the color to a screen side
* @param side The side to set colour
* @param colour The new colour
* @see GetColor
* */
void ND::Screen::SetColor(ND_SIDE side, ND_Color colour)
{
if(side==ND_SIDE_BACKGROUND)
{
backColour=colour;
}else{
foreColour=colour;
}
}
/**
* @brief Puts the char on screen
* @param c The character to write
* */
void ND::Screen::PutChar(char c)
{
uint8_t attributeByte = (backColour << 4) | (foreColour & 0x0F);
uint16_t attribute = attributeByte << 8;
uint16_t *location;
if (c == 0x08 && cursor_x)
{
cursor_x--;
}else if(c == '\r')
{
cursor_x=0;
}else if(c == '\n')
{
cursor_x=0;
cursor_y=1;
}
if(c >= ' ') /* Printable character */
{
location = vidmem + (cursor_y*80 + cursor_x);
*location = c | attribute;
cursor_x++;
}
if(cursor_x >= 80) /* New line, please*/
{
cursor_x = 0;
cursor_y++;
}
/* Scroll if needed*/
uint8_t attributeByte2 = (0 /*black*/ << 4) | (15 /*white*/ & 0x0F);
uint16_t blank = 0x20 /* space */ | (attributeByte2 << 8);
if(cursor_y >= 25)
{
int i;
for (i = 0*80; i < 24*80; i++)
{
vidmem[i] = vidmem[i+80];
}

// The last line should now be blank. Do this by writing
// 80 spaces to it.
for (i = 24*80; i < 25*80; i++)
{
vidmem[i] = blank;
}
// The cursor should now be on the last line.
cursor_y = 24;
}
}
/**
* @brief Puts a complete string to screen
* @param str The string to write
* */
void ND::Screen::PutString(const char* str)
{
int i=0;
while(str[i])
{
ND::Screen::PutChar(str[i++]);
}
}
/**
* @brief Cleans the screen with a color
* @param colour The colour to fill the screen
* */
void ND::Screen::Clear(ND_Color colour)
{
// Make an attribute byte for the default colours
uint8_t attributeByte = (colour /*background*/ << 4) | (15 /*white - foreground*/ & 0x0F);
uint16_t blank = 0x20 /* space */ | (attributeByte << 8);

int i;
for (i = 0; i < 80*25; i++)
{
vidmem[i] = blank;
}

// Move the hardware cursor back to the start.
cursor_x = 0;
cursor_y = 0;
}
/**
* @brief Sets the cursor via software
* @param x The position of X
* @param y The position of y
* */
void ND::Screen::SetCursor(uint8_t x, uint8_t y)
{
cursor_x=x;
cursor_y=y;
}


El header será muy básico así que no lo incluyo aquí, pero destacar la definición del tipo ND_SIDE


typedef enum ND_SIDE{
ND_SIDE_BACKGROUND,
ND_SIDE_FOREGROUND
}ND_SIDE;


También mencionar que hacemos uso del header NDTypes.hpp, este header nos define unos tipos básicos para uint8t, uint16t, etc basado en los char y los int. Realmente este header es el en el estándar C99 y de hecho mi NDTypes.hpp es un copia/pega del archivo desde Linux, así que podeis intercambiarlos y no pasaría nada (solo hay definiciones, ninguna función).

Para probar si este código funciona vamos a modificar el punto de entrada en C del kernel:


ND::Screen::Clear(ND_COLOR_WHITE);
ND::Screen::SetColor(ND_SIDE_BACKGROUND,ND_COLOR_WHITE);
ND::Screen::SetColor(ND_SIDE_FOREGROUND,ND_COLOR_GREEN);
ND::Screen::PutString("NextDivel\n");
ND::Screen::SetColor(ND_SIDE_FOREGROUND,ND_COLOR_BLACK);
ND::Screen::PutString("Licensed under GNU GPL v2");


Y si seguimos estos pasos obtendríamos este resultado

NextDivel-3

Gracias a estas funciones que hemos creado podemos empezar a hacer pequeñas GUI, como por ejemplo un kernel panic que mostraremos cada vez que haya un error irrecuperable. Algo tal que así:

NextDivel-4

Y esta pequeña GUI la hicimos solamente con estas funciones:

void ND::Panic::Show(const char* error)
{
ND::Screen::Clear(ND_COLOR_RED);
ND::Screen::SetColor(ND_SIDE_BACKGROUND, ND_COLOR_WHITE);
ND::Screen::SetColor(ND_SIDE_FOREGROUND, ND_COLOR_RED);
ND::Screen::SetCursor(29,10); //(80-22)/2
ND::Screen::PutString("NextDivel Kernel Error\n");
ND::Screen::SetCursor(15,12);
ND::Screen::PutString(error);
}


Y aprovecho para daros las gracias por la excelente acogida que tuvo el primer post.]]>
https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-ii Fri, 27 Nov 2015 00:00:00 +0000
Emulando a Linus Torvalds: Crea tu propio sistema operativo desde 0 (I) https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-i https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-i Este artículo lo escribí para el blog en español DesdeLinux el 27 de diciembre de 2013 y ahora lo dejo aquí, en mi blog personal. El artículo está tal cual, sin ninguna modificación desde aquella fecha.
En esta serie vamos a emular a Linus Torvalds, vamos a crear nuestro sistema operativo desde 0. En este primer episodio vamos a ver el arranque y pondremos un texto en pantalla desde nuestro kernel.

LinusTorvalds

En mi caso el sistema operativo se llama NextDivel. La primera decisión que debemos hacer nada más plantearnos el sistema operativo es ¿cuál va a ser el bootloader?

Aquí existen múltiples variantes, e incluso podríamos crear uno nosotros; sin embargo, en este tutorial voy a usar GRUB, porque la mayoría conoce más o menos algo de él. Creamos una carpeta que será el root de nuestro sistema operativo y allí creamos la carpeta /boot/grub
mkdir nextroot && cd nextroot
mkdir -p boot/grub

Allí creamos el fichero grub.cfg de la siguiente manera:


menuentry "NextDivel" {
echo "Booting NextDivel"
multiboot /next/START.ELF
boot
}


En este fichero hemos visto como GRUB cargará nuestro kernel, en este caso, en /next/START.ELF. Ahora debemos crear nuestro kernel.

Para ello necesitaremos el GCC y GAS (el ensamblador del proyecto GNU, suele venir con el gcc). Así pues vamos a crear el kernel.

Primero hacemos un archivo llamado kernel.asm. Este archivo contendrá el punto de inicio de nuestro kernel y además definirá el multiboot (una característica de algunos bootloaders como GRUB). El contenido de kernel.asm será:


.text
.globl start
start:
jmp multiboot_entry
.align 4
multiboot_header:
.long 0x1BADB002
.long 0x00000003
.long -(0x1BADB002+0x00000003)
multiboot_entry:
movl $(stack + 0x4000), %esp
call NextKernel_Main
loop: hlt
jmp loop
.section ".bss"
.comm stack,0x4000


Todo lo relacionando con multiboot es simplemente seguir la especificación nada más. Todo empezará en start, llamará a multiboot_entry, habremos definido el multiboot header en los primeros 4k y lo pondremos (con movl).

Más tarde llamamos a NextKernel_Main que es nuestra función en C del kernel. En el loop hacemos un halt para parar el ordenador. Esto se compila con:
as -o kernel.o -c kernel.asm 

Ahora vamos a entrar a programar en C. Pensarás que ahora todo es pan comido, ponemos un printf en main y ya está, lo hemos hecho.

Pues no, ya que printf y main son funciones que define el sistema operativo, ¡pero nosotros lo estamos creando! Solo podremos usar las funciones que nosotros mismos definamos.

En capítulos posteriores hablaré de como poner nuestra propia libraría del C (glibc, bionic, newlibc) pero tiempo al tiempo. Hemos hablado que queremos poner texto en pantalla, bueno veremos como lo hacemos.

Hay dos opciones, una es llamar a la BIOS y otra es manejar la memoria de la pantalla directamente. Vamos a hacer esto último pues es más claro desde C y además nos permitirá hacerlo cuando entremos en modo protegido.

Creamos un fichero llamado NextKernel_Main.c con el siguiente contenido:


int NextKernel_Main(/*struct multiboot *mboot_ptr*/)
{
const char* str="NextDivel says Hello World", *ch;
unsigned short* vidmem=(unsigned short*)0xb8000;
unsigned i;
for(ch=str, i=0;*ch;ch++, i++)
vidmem[i]=(unsigned char) *ch | 0x0700;

return 0;
}


Con esto manipulamos directamente la memoria VGA y caracter a caracter lo vamos escribiendo. Compilamos desactivando la stdlib:
gcc -o NextKernel_Main.o -c NextKernel_Main.c -nostdlib -fPIC -ffreestanding 

Si has llegado hasta aquí querrás probar ya tu nuevo y flamante sistema operativo, pero todavía no hemos terminado. Necesitamos un pequeño fichero que diga al compilador en que posición del archivo dejar cada sección. Esto se hace con un linker script. Creamos link.ld:


ENTRY(start)
SECTIONS
{
. = 0x00100000;

.multiboot_header :
{
*(.multiboot_header)
}
.text :
{
code = .; _code = .; __code = .;
*(.text)
. = ALIGN(4096);
}

.data :
{
data = .; _data = .; __data = .;
*(.data)
*(.rodata)
. = ALIGN(4096);
}

.bss :
{
bss = .; _bss = .; __bss = .;
*(.bss)
. = ALIGN(4096);
}

end = .; _end = .; __end = .;
}


Con esto definimos la posición de cada sección y el punto de entrada, start, que hemos definido en kernel.asm. Ahora ya podemos unir todo este mejunje:
gcc -o START.ELF kernel.o NextKernel_Main.o -Tlink.ld -nostdlib -fPIC -ffreestanding -lgcc 

Ahora copiamos START.ELF al /next dentro de nuestra carpeta que simula el root de nuestro sistema operativo. Nos dirigimos a la carpeta root de nuestro sistema operativo nuevo con la consola y verificamos que hay dos archivos: uno /boot/grub/grub.cfg y otro /next/START.ELF.

Vamos al directorio superior y llamamos a una utilidad de creación ISOs con GRUB llamada grub-mkrescue
grub-mkrescue -o nextdivel.iso nextroot 

Una vez hayamos hecho esto tendremos una ISO. Esta ISO puede abrirse en ordenadores x86 (64 bits también) y máquinas virtuales. Para probarlo, voy a usar QEMU. Llamamos a QEMU desde la línea de comandos:
qemu-system-i386 nextdivel.iso 

Arrancará SeaBIOS y más tarde tendremos GRUB. Después si todo va correcto veremos nuestra frase. Pensarás que esto es difícil, te respondo, sí lo es.

Realmente crear un sistema operativo es difícil y eso que este de aquí no hace nada útil. En próximos capítulos veremos como manejar colores en la pantalla, reservar memoria y si puedo, como obtener datos del teclado.

Si alguien no quiere copiar todo lo que hay aquí, tengo un repositorio en GitHub (más elaborado) con el sistema operativo NextDivel. Si quieres compilar NextDivel solo tienes que tener git y cmake:


git clone https://github.com/AdrianArroyoCalle/next-divel
cd next-divel
mkdir build && cd build
cmake ..
make
make DESTDIR=next install
chmod +x iso.sh
./iso.sh
qemu-system-i386 nextdivel.iso


Os animo a colaborar en NextDivel si tienes tiempo y ganas de crear un sistema operativo. Quizá incluso superior a Linux… el tiempo lo dirá.]]>
https://blog.adrianistan.eu/emulando-linus-torvalds-crea-propio-sistema-operativo-desde-0-i Thu, 26 Nov 2015 00:00:00 +0000
Redox, el sistema operativo escrito en Rust https://blog.adrianistan.eu/redox-sistema-operativo-escrito-rust https://blog.adrianistan.eu/redox-sistema-operativo-escrito-rust Redox, un sistema operativo relativamente nuevo, escrito totalmente en Rust. Redox sigue la filosofía UNIX y la mayor parte del sistema es accesible a través del sistema de archivos. En Redox esta función la cumplen las URL. Además, es un sistema operativo seguro, una de las principales características de Rust. En Redox además todas las aplicaciones corren en modo sandbox.

Compilar Redox


Redox se distribuye únicamente a través del código fuente. En el futuro habrá imágenes ISO. Funciona en x86 de 32 bits y se está trabajando en el soporte x86_64 de 64 bits. En Debian/Ubuntu hay que seguir estas instrucciones.
git clone http://github.com/redox-os/redox
cd redox
cd setup
./ubuntu.sh
./binary.sh
cd ..
make all

Una vez haya terminado podemos ejecutar Redox en una máquina virtual. Yo voy a usar QEMU.
sudo apt install qemu-system-x86 qemu-kvm 
make qemu
# make qemu_no_kvm

Un vistazo rápido


Redox1 Redox2 Redox3

Entramos directamente al escritorio gráfico, no ha hecho falta seleccionar nada. Redox es un sistema operativo diseñado con la interfaz ya en mente, no como los sistemas UNIX donde el sistema gráfico viene de terceras partes (X11, Quartz, DirectFB, Wayland, Mir, ...).

Aplicaciones


Redox dispone de dos editores, el editor básico (que como vemos, el archivo que abre por defecto es none:/, el concepto de todo es una URL es básico en Redox) y Sodium, un editor más avanzado (tipo Vi o Emacs). Tenemos un explorador de archivos, un terminal de Lua, un terminal de comandos, un visor de imágenes y una aplicación de prueba de SDL. Han sido portados DOSBox, zlib, libpng, libiconv y FreeCiv a Redox. GCC, newlib y binutils están muy cerca de funcionar nativamente pero todavía hay algunos problemas.

Componentes


Concepto de URL. En Redox todo debe ser una URL. Los registros se almacenan en log://, los daemons usan la interfaz bus://, /dev/null aquí es none://.

fired es el sistema de arranque (SysV, systemd o Upstart en Linux), escrito totalmente en Rust. Usa ficheros Toml para la configuración, recurre a la paralelización pero solo tiene como dependencia el kernel Redox y no hace más cosa que el sistema de arranque (esto es un mensaje indirecto contra systemd).

ZFS. El sistema de archivos principal será el magnífico ZFS de Sun/Oracle. Todavía está en desarrollo pero el trabajo se está concentrando exclusivamente en ZFS.

Oxide. Oxide es el gestor de paquetes de Redox. Todavía muy verde.

Lua. Lua es el lenguaje usado para realizar scripts en Redox.

Ion. Un shell más compatible con UNIX que Lua. Inspirado en fish y zsh.

Bohr. El sistema gráfico de Redox.

Orbital. El sistema de ventanas de Redox.]]>
https://blog.adrianistan.eu/redox-sistema-operativo-escrito-rust Sun, 1 Nov 2015 00:00:00 +0000
Acortar enlaces en Node.js https://blog.adrianistan.eu/acortar-enlaces-node-js https://blog.adrianistan.eu/acortar-enlaces-node-js Node.js. Además, muchos acortadores añaden anuncios intersticiales de los que podemos sacar un dinero. Algunos ejemplos de acortadores que comparten ganancias son:

Para facilitar el manejo de estos servicios y generar ingresos de manera sencilla he diseñado paquetes para todos esos servicios. Están disponibles en el registro de npm y todos usan una API similar.

Ejemplo práctico


Para el ejemplo voy a usar el paquete de Adf.ly, por ser quizá el proveedor de este tipo de enlaces más conocido.

Lo primero es instalar el paquete que provee acceso a Adf.ly:
npm install adf.ly --save 

Ahora tenemos que cargar el módulo donde lo vayamos a usar. Aquí tenemos que escribir nuestra clave de API. Si lo dejais vacío seguirá funcionano, pero no ganareis nada, ¡los ingresos irán para mí!


var adfly=require("adf.ly")("TU_CLAVE_DE_API");
o
var adfly=require("adf.ly")();


Para transformar un enlace en normal en uno acortado simplemente se usa el método short.


adfly.short("http://nexcono.appspot.com",function(url){
console.log("Enlace acortado: "+url);
// Podemos usar la URL en algún motor de plantillas como Jade o EJS o donde queramos
});


adfly-npm

En el paquete de Shink.in hay una particularidad. Shink.in permite acortar enlace en modo adulto. Esta opción está desactivada por defecto pero si quereis usarla solo hay que indicar true como tercer parámetro


shinkin.short("http://nexcono.appspot.com",procesarURL,true);
// El enlace se acorta en Modo Adulto


Listado de paquetes


El listado completo de paquetes que he creado es este. Todos tienen una API similar.
]]>
https://blog.adrianistan.eu/acortar-enlaces-node-js Sat, 31 Oct 2015 00:00:00 +0000
Usar GNU Parallel para aumentar el rendimiento de tus scripts https://blog.adrianistan.eu/usar-gnu-parallel-aumentar-rendimiento-tus-scripts https://blog.adrianistan.eu/usar-gnu-parallel-aumentar-rendimiento-tus-scripts paralelizar. Todo lo que sea susceptible de ser paralelizado deberá ser paralelizado. Desafortunadamente la programación en paralelo es compleja y requiere una planificación mucho más larga. ¡Pero no desistamos! ¡Podemos usar GNU Parallel para paralelizar algunas tareas que llevan tiempo pero son independientes las unas de las otras! ¡Usemos GNU Parallel en nuestros scripts!

GNUParallel

Ejemplos prácticos:
- Convertir una biblioteca de MP3 en OGG (con ffmpeg)
- Normalizar el audio (con sox)
- Optimizar las imágenes de un sitio web (con OptiPNG, jpegoptim, etc)

Instalar GNU Parallel


En Debian/Ubuntu:
sudo apt install parallel

En Fedora:
dnf install parallel

En openSUSE:
zypper install gnu_parallel

En Arch Linux:
pacman -S parallel

En NetBSD/SmartOS:
pkgin install parallel

Convertir una biblioteca de MP3 en OGG y normalizar el audio


Esta era la tarea que tenía que realizar. Primero realicé una versión sencilla, que funcionaba utilizando un solo core del procesador.


#!/bin/bash
shopt -s globstar
for i in **/*.mp3; do
BASENAME="${i%.mp3}"
ffmpeg -i "${BASENAME}.mp3" "${BASENAME}.tmp.ogg"
sox --show-progress --norm "${BASENAME}.tmp.ogg" "${BASENAME}.ogg"
rm "${BASENAME}.tmp.ogg"
done


Con este script conseguimos el objetivo que nos habíamos propuesto, pero podemos optimizar el rendimiento. Usando GNU Parallel:


#!/bin/bash

# Guardar como "normalize.sh"

if [ "$2" = "1" ]; then
BASENAME="${1%.mp3}"
ffmpeg -i "${BASENAME}.mp3" "${BASENAME}.tmp.ogg"
sox --show-progress --norm "${BASENAME}.tmp.ogg" "${BASENAME}.ogg"
rm "${BASENAME}.tmp.ogg"
else
find . -name "*.mp3" | parallel ./normalize.sh "{}" 1
fi


Con esta modificación GNU Parallel se encarga de poner en cola los trabajos de conversión y normalización y los reparte entre los cores disponibles del procesador. La gráfica explica claramente la diferencia de uso entre los dos scripts.
Versión básica

TradicionalCore
Versión GNU Parallel

GNUParallel

Optimizar las imágenes de un sitio web


Aquí viene otro ejemplo que usa GNU Parallel para realizar la tarea más rápidamente.


#!/bin/bash

# Guardar como "optimize-img.sh"

if [ "$2" = "1" ]; then
BASENAME="${1%.png}"
optipng -o7 "${BASENAME}.png"
elif [ "$2" = "2" ]; then
BASENAME="${1%.jpg}"
jpegoptim "${BASENAME}.jpg"
else
find . -name "*.png" | parallel ./optimize-img.sh "{}" 1
find . -name "*.jpg" | parallel ./optimize-img.sh "{}" 2
fi

Hay muchos más usos para GNU Parallel, solo tienes que usar tu imaginación. ¿Y tú? ¿Conocías GNU Parallel? ¿Qué opinas al respecto?]]>
https://blog.adrianistan.eu/usar-gnu-parallel-aumentar-rendimiento-tus-scripts Tue, 20 Oct 2015 00:00:00 +0000
js13k y Miss City (postmortem) https://blog.adrianistan.eu/js13k-miss-city-postmortem https://blog.adrianistan.eu/js13k-miss-city-postmortem js13kGames 2015. El objetivo es construir un juego en HTML5, en el plazo de un mes o menos que no ocupe más de 13kb. EL fichero que no debe superar los 13kb debe ser un fichero ZIP con compresión estándar que tenga un fichero index.html desde el cual arrancará el juego y todo lo necesario para su funcionamiento estará también en el fichero ZIP. JavaScript, CSS, imágenes, sonido, fuentes, etc deberán estar en el fichero ZIP que no supere los 13kb. Está explicitamente prohibido cargar algún recurso del exterior como Google Web Fonts, CDNs de JavaScript, imágenes en otro servidor, etc. Además hay un tema para los juegos, que fue anunciado el 13 de agosto. El tema ha sido Reversed.

js13k

MissCity


MissCityGameplay

El juego que he presentado se llama MissCity. El nombre viene de darle la vuelta a Sim de SimCity. Mis no existe, pero Miss sí, y es perdido. Así MissCity es ciudad perdida.
Euralia es la ciudad perfecta. Nuestra compañía desea construir un centro comercial en un solar abandonado pero el actual alcalde desea constuir una biblioteca. Dentro de poco son elecciones. ¡Debemos ganar las elecciones! Para ello puedes usar nuestro dron y repartir diversos tipos de ataques publicitarios a la población

Los controles son:

  • WASD para desplazarse (si estamos en móvil o tablet, se usa la inclinación del dispositivo)

  • VBNM para los 4 diferentes tipos de ataque (si estamos en un dispositivo táctil, aparecen cuatro botones en pantalla que realizan el mismo efecto)


Ganamos:

  • Si conseguimos suficientes votos entre el electorado


Perdemos:

  • Si pasan dos minutos

  • Si nos quedamos sin dinero (cada ataque publicitario cuesta una cantidad de dinero sin especificar)


MissCityOpera

Vamos a ver el postmortem en profundidad

Cosas que fueron bien


PathFinding


Los habitantes de Euralia son inteligentes, no van de una casilla a otra porque sí sino que tienen una ruta que realizar. El origen y el destino sí se calculan aleatoriamente, pero la ruta no, se usa un algoritmo de pathfinding. Debía encontrar una librería sencilla, pequeña, pero que implementase el algoritmo de manera limpia. Finalmente elegí EasyStar.js que es asíncrona, sin dependencias y entre sus características asegura que es pequeña (~5kb) lo cual comprimido en ZIP resulta menos de lo que esperaba. Usa licencia MIT así que perfecto. El único inconveniente que presenta es que la rejilla que usa es bidimensional y yo definí la ciudad y el sistema de renderizado con un array unidimensional que voy cortando al procesarlo. Así que el juego tiene que transformar el array unidimensional en otro bidimensional para que EasyStar lo procese correctamente. Al obtener los resultados, es necesario volver a transformarlos.

Recursos gráficos


Creía que mi juego iba a tener peores gráficos, sinceramente. Las imágenes en formato GIF ocupaban menos de lo que esperaba y pude incluir bastantes detalles. Al principio no usé imágenes, tiré de colores en CSS. Renderizar toda la ciudad fue muy sencillo. Esta no es la versión final por supuesto, pero no es muy diferente.


// city es el array unidimensional donde defino el mapa de la ciudad

var draw=city.map(function(val){
switch(val){
case 0: return "rgba(91,196,124,1)";
case 1: return "rgb(76, 77, 76)";
case 2: return "rgb(84, 230, 54)";
case 3: return "rgb(37, 88, 219)";
case 4: return "rgb(223, 155, 23)";
}
});
var x=0,y=0;
draw.forEach(function(cell){
ctx.fillStyle=cell;
ctx.fillRect(x,y,box,box);
x+=box;
if((x+box)>id("a").width){
x=0;
y+=box;
}
});

Debug en Firefox para Android


Me lo esperaba peor y realmente con WebIDE, el cable USB y ADB es muy sencillo ver la consola de JavaScript de Firefox para Android en tu ordenador.

Problemas


MissCityFirefox

Dichosas APIs de pantalla y orientación


En HTML5 siempre me torturo con los aspect ratio y demás temas relacionados con la pantalla. En HTML5 hay tantas pantallas diferentes que simplemente no sé por donde empezar. El método que he usado en este juego es diferente al usado en otras ocasiones y daría para una entrada de blog suelta. Pero también me gustaría decir que las APIs de gestión de pantalla (saber si estás en modo landscape o portrait) no funcionan entre navegadores todavía. Incluso tienen nombres incompatibles que surgen de distintas versiones del estándar. Es una cosa que las aplicaciones nativas de móviles saben hacer desde el día 1.

EasyZIP


En MissCity he usado Gulp como herramienta que se encarga de la automatización de todo (ya sabes ¡haz scripts!). Usé EasyZIP para generar el fichero ZIP y posteriormente comprobar que su tamaño seguía siendo inferior a los 13kb. Mi sorpresa vino cuando al subir el fichero ZIP provoqué un error en el servidor de js13kgames. Tuve que contactar con el administrador, hubo que borrar archivos que se habían extraído correctamente en el servidor aunque hubiese devuelto un error. La solución fue comprimirlo manualmente con File Roller y el tamaño del fichero aumentó (sin pasar los 13kb).

API de gestión de teclado


Firefox recomienda usar KeyboardEvent.key para leer el teclado y marca como obsoleta la manera antigua, que era KeyboardEvent.keyCode. Leyendo MDN uno piensa que usando KeyboardEvent.key es la solución sin más. Y efectivamente en Firefox funciona bien, pero en Chrome y Opera no. Y pudiendo usar keyCode, quién va a usar key. keyCode será obsoleto pero funciona en todos los navegadores. Finalmente implementé el teclado usando key y keyCode si no soportan key.

Juega


Si has leído hasta aquí, es un buen momento para jugar a MissCity. Hay un premio por compartir en redes sociales, si quieres ayudarme ya sabes.

Código fuente: http://github.com/AdrianArroyoCalle/miss-city]]>
https://blog.adrianistan.eu/js13k-miss-city-postmortem Sun, 20 Sep 2015 00:00:00 +0000
eurocookie-js https://blog.adrianistan.eu/eurocookie-js https://blog.adrianistan.eu/eurocookie-js España. La ley define que no se pueden almacenar datos que identifiquen al usuario con fines estadísticos (o publicitarios) a menos que se pida un consentimiento al usuario y este lo acepte. Solo lo deben cumplir aquellas personas que tengan un beneficio económico con la web. En empresas hay que aplicarlo siempre. El almacenamiento más usado para este tipo de identifición han sido las cookies, de ahí el nombre popular de "ley de cookies".

UnionEuropea

Odisea entre las cookies


Yo uso cookies. Las uso en este blog y en otros sitios. Google Analytics requiere consentimiento, Disqus requiere consentimiento, Google AdSense requiere consentimiento. Los widgets sociales de Twitter, Facebook, Google+, etc requieren consentimiento.
¿Pero entonces todas las cookies necesitan consentimiento?

No. Sólo las que identifican al usuario con fines estadísticos, que en el caso de los gigantes de Internet es siempre. Si usamos nuestras propias cookies y no las conservamos para posterior análisis no haría falta y no hay que pedir consentimiento.

Cookies
Cookies son las más usadas, pero si usas WebStorage (localStorage) o almacenamiento de Flash también tendrás que cumplir

eurocookie-js al rescate


Basado en un pequeño plugin hecho por Google bajo licencia Apache 2.0. Se trata de un pequeño fichero JavaScript que al cargarse pedirá el consentimiento (si no ha sido preguntado antes). Este consentimiento es molesto, forzando al usuario a aceptar si quiere leer cómodamente la web. Una vez acepta el consentimiento se carga todo el JavaScript que necesitaba consentimiento. Esto último es algo que muchos plugins de consentimiento de cookies no hacen. Realmente no sé si simplemente avisando se cumple la ley, bajo mi interpretación no. Y por eso este plugin. Además eurocookie-js está traducido a una gran cantidad de idiomas de la Unión Europea (tomadas directamente de las traducciones oficiales de Google para webmasters sobre esta ley). Vamos a ver como se usa.

Instalando eurocookie-js


Instalar eurocookie-js es más simple que el mecanismo de un botijo. Hay 3 maneras:

Biscuit

Usando eurocookie-js


Identificar JavaScript que necesita consentimiento


Primero, necesitamos identificar que JavaScript necesita consentimiento. Si usa una etiqueta script es fácil. Es importante declarar el tipo MIME del script como texto plano y le añadimos la clase cookie.



<script class="cookie" type="text/plain">
// USEMOS COOKIES FELIZMENTE, ENVIEMOS DATOS A LA NSA
</script>


En muchos casos bastará, por ejemplo con Disqus o con Google Analytics, puesto que cargan asíncronamente los archivos. En otros casos donde usemos el atributo src de script tendremos que modificar el código que se nos provee por el siguiente.



<script src="http://servidor.com/archivo.js"></script><!-- Será sustituido por --><script class="cookie" type="text/plain">
var file = document.createElement('script'); file.type = 'text/javascript'; file.async = true; file.src = "http://servidor.com/archivo.js"; document.getElementsByTagName('head')[0].appendChild(file);
</script>


Aunque si el proveedor te requería usar script src posiblemente no soporte la carga asíncrona.

Activando eurocookie-js


Ahora solo nos falta activar eurocookie-js. Es fácil. Al final, antes de cerrar body tenemos que añadir lo siguiente:
Si usamos Bower o el método manual




<script>
euroCookie("http://enlace-a-politica-de-privacidad.com");
</script>


Si usamos npm + browserify


 
var ec=require("eurocookie-js");
ec.euroCookie("http://link-to-privacy-policy.com");


El fichero generado por browserify podrá ser añadido directamente al HTML.

Más información


Como siempre, toda la información está en GitHub: http://github.com/AdrianArroyoCalle/eurocookie-js]]>
https://blog.adrianistan.eu/eurocookie-js Wed, 16 Sep 2015 00:00:00 +0000
Tutorial de WiX (Windows Installer MSI) https://blog.adrianistan.eu/tutorial-wix-windows-installer-msi https://blog.adrianistan.eu/tutorial-wix-windows-installer-msi tutorial de CMake hoy traigo el tutorial de WiX.

¿Qué es WiX?


WiX es un proyecto opensource destinado a producir instaladores de la plataforma Windows Installer. Windows Installer es la plataforma de instalaciones preferida por Microsoft desde que comenzó su desarrollo en 1999. Los archivos MSI son los instaladores genéricos que usa Windows Installer. Provee de un entorno más seguro para modificar el sistema al de un EXE tradicional por el hecho de que Windows Installer es declarativo, no imperativo. Windows Installer es transaccional, facilita el despliegue en entornos empresariales, tiene APIs (Windows Installer API y Windows Installer SDK), permite la localización de manera sencilla, la validación de instalaciones y la gestión de reinicios. Tiene los siguientes inconvenientes: es complejo, los accesos directos, su línea de comandos, su uso del registro y sus herramientas. Así pues con WiX podemos crear paquetes MSI de Windows Installer con unos ficheros XML donde definimos la instalación.

WindowsInstaller

También conviene diferenciar los diferentes tipos de archivos que soporta Windows Installer.

  • MSI | Instalador convencional

  • MSM | Módulo de fusión

  • MSP | Módulo de parche

  • MST | Módulo de transformación


No hay que confundir estos archivos con los MSU, también usados por Microsoft pero para Windows Update y cuya estructura es diferente.

Instalando WiX


Para instalar WiX podemos usar Chocolatey. Abrimos PowerShell como administrador y ejecutamos.


choco install -y wixtoolset


Si queremos añadir las herramientas al PATH, en PowerShell.


$PATH = [Environment]::GetEnvironmentVariable("PATH")
$WIX_BIN = "C:\Program Files (x86)\WiX Toolset v3.9\bin"
[Environment]::SetEnvironmentVariable("PATH","$PATH;$WIX_BIN")


Herramientas


WiX viene con un conjunto de herramientas diseñadas para trabajar con Windows Installer.

  • candle.exe - Compila los archivos .wxs y .wxi para generar archivos objeto .wixobj

  • light.exe - Enlaza los objetos .wixobj y .wixlib produciendo el .msi

  • lit.exe - Permite unir archivos .wixobj en una librería .wixlib

  • dark.exe - Convierte un archivo ya compilado en código fuente WiX

  • heat.exe - Podemos añadir archivos al fichero WiX en masa, sin especificar manualmente

  • insignia.exe - Permite firmar archivos MSI con las mismas firmas que los CABs

  • melt.exe - Convierte un archivo MSM a un fichero código fuente de WiX

  • torch.exe - Extrae las diferencias entre objetos WiX para generar una transformación. Usado para generar parches.

  • smoke.exe - Valida ficheros MSI o MSM

  • pyro.exe - Genera un fichero de parches MSP

  • WixCop.exe - Recomienda estándares en los ficheros WiX

  • WixUnit.exe - Realiza una validación a los ficheros WiX

  • lux.exe - Usado para tests unitarios

  • nit.exe - Usado para tests unitarios


Nota: Vamos a necesitar generar GUIDs. En PowerShell podeis usar `[GUID]::NewGuid()` o `[GUID]::NewGuid().ToString()`. Si teneis instalado Visual Studio, también está disponible `uuidgen`

El fichero .wxs


El fichero WXS es la base de WiX y es un fichero XML. Para el ejemplo voy a empaquetar Lumtumo. Lumtumo contiene un ejecutable, varias DLL y varios archivos como imágenes y fuentes que están distribuidos por carpetas. He depositado la carpeta con los binarios y todos lo necesario en SourceDir.


<?xml version="1.0" encoding="utf-8"?>
<!-- Variables del preprocesador -->
<?define Platform = x86 ?>
<?if $(var.Platform) = x64 ?>
<?define ProductName = "Lumtumo (64 bit)" ?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
<?define ProductName = "Lumtumo" ?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<!-- Códigos de idioma https://msdn.microsoft.com/en-us/library/Aa369771.aspx -->
<Product Name="Lumtumo" Manufacturer="Adrian Arroyo Calle" Id="BAE7D874-3177-46A6-BB2D-043EFF8C59F2" UpgradeCode="457861BD-A051-4150-8752-82907E7BAF19" Language="1033" Codepage="1252" Version="1.0">
<!-- Nunca, NUNCA, se debe cambiar el valor de UpgradeCode. Es el identificador que distingue a los productos entre versiones -->
<Package Description="$(var.ProductName)" Platform="$(var.Platform)" Keywords="Game" Id="*" Compressed="yes" InstallScope="perMachine" InstallerVersion="300" Languages="1033" SummaryCodepage="1252" Comments="Space invaders redefined" Manufacturer="Adrian Arroyo Calle"/>
<!-- Generamos el listado de ficheros a instalar con heat.exe dir ..\..\build\Release\ -dr LumtumoDir -cg LumtumoComponent -gg -g1 -sf -srd -out files.wxs -->
<!-- Es necesario actualizar Shortcut/@Target cada vez que usemos Heat para conservar la referencia al ejecutable principal válida -->
<!-- Instalar con msiexec /i lumtumo.msi , desinstalar con msiexec /x lumtumo.msi-->

<MajorUpgrade DowngradeErrorMessage="A later version of Lumtumo is already installed. Setup will now exit."/>
<!-- Gestiona las actualizaciones posteriores. Estos MSI de actualización deben tener igual UpgradeCode, distinto Id y una versión superior-->

<Icon Id="icon.ico" SourceFile="SourceDir/Lumtumo.exe"/>
<!-- El icono puede estar en un ejecutable -->
<Property Id="ARPPRODUCTICON" Value="icon.ico" />
<!-- Este es el icono del panel de control -->
<Property Id="ARPHELPLINK" Value="http://adrianarroyocalle.github.io" />
<!-- Enlace de ayuda para el panel de control. Hay más propiedades con enlaces para el panel de control-->

<MediaTemplate CabinetTemplate="LUM{0}.cab" CompressionLevel="high" EmbedCab="yes"/>
<!-- WiX se encargará de generar los archivos Cabinet necesarios. Esto será cuando llamemos a light.exe -->

<Feature Id="ProductFeature" Title="Game" Level="1">
<ComponentGroupRef Id="LumtumoComponent"/>
<ComponentRef Id="ApplicationShortcut" />
</Feature>

<!-- Solo hay una funcionalidad, el juego completo. Esta incluye todos los archivos de Lumtumo y además el acceso directo en el menú de Inicio. LumtumoComponent está definido en files.wxs -->

<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='$(var.PlatformProgramFilesFolder)'>
<Directory Id="LumtumoDir" Name='Lumtumo'>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ApplicationProgramsFolder" Name="Lumtumo"/>
</Directory>
</Directory>
<!-- La estructura de carpetas básica. TARGETDIR debe ser el ROOT siempre. Además, ProgramFilesFolder (en este caso usamos el preprocesador antes) y ProgramMenuFolder ya están definidos por Windows. Así que realmente creamos LumtumoDir (que usa files.wxs) y ApplicationProgramsFolder (justo abajo trabajamos con esa carpeta). Ambas se crean con el nombre de Lumtumo en el sistema de archivos -->

<DirectoryRef Id="ApplicationProgramsFolder">
<Component Id="ApplicationShortcut" Guid="03466DA8-7C3F-485E-85E6-D892E9F7FFE4">
<Shortcut Id="ApplicationStartMenuShortcut" Name="Lumtumo" Description="Space Invaders redefined" Target="[#fil7BBA1E2E293173E87EC3F765BF048B16]" Icon="icon.ico" WorkingDirectory="LumtumoDir"/>
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
<!-- Nos aseguramos de borrar la carpeta con el acceso directo al desinstalar-->
<RegistryValue Root="HKCU" Key="Software\Microsoft\Lumtumo" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
<!-- Con esta llave del registro apareceremos en el panel de control para ser desinstalados-->
</Component>
</DirectoryRef>

<Condition Message='This application only runs on Windows NT'>
VersionNT
</Condition>
<!-- Un ejemplo de condiciones. En este caso pedimos que Windows tenga el núcleo NT. Se pueden especificar rangos de versiones de Windows e incluso las ediciones. Las condiciones se pueden aplicar a componentes específicos también-->

</Product>
</Wix>


Ahora se genera el archivo MSI


heat.exe dir SourceDir -dr LumtumoDir -cg LumtumoComponent -gg -g1 -sf -srd -out files.wxs
# Editamos el archivo Lumtumo.wxs para actualizar la referencia al ejecutable principal en el acceso directo
candle.exe files.wxs
candle.exe Lumtumo.wxs
light.exe files.wixobj Lumtumo.wixobj -out lumtumo.msi


Este ejemplo básico está muy bien, instala todo, añade un icono en el menú de inicio (también se puede poner en el escritorio pero no me gusta), se integra con el panel de control para hacer una desinstalación limpia e incluso con modificar un valor podemos generar el paquete para 64 bits. Sin embargo faltan cosas. No se puede especificar la carpeta de instalación, de hecho, el MSI no muestra ninguna opción al usuario al instalarse. Para introducir GUI en WiX tenemos que usar la librería WiXUI.

WixUI


Para usar WixUI tenemos que cambiar el comando que usamos para llamar a light


light.exe -ext WixUIExtension files.wixobj Lumtumo.wixobj -out lumtumo.msi

Con esto ya podemos usar la librería WixUI. La manera más básica de añadir una GUI es introducir la GUI Minimal. Al final de la etiqueta Product podemos insertar

<WixVariable Id="WixUILicenseRtf" Value="LICENSE.rtf" />
<UIRef Id="WixUI_Minimal" />
<UIRef Id="WixUI_ErrorProgressText" />


Y LICENSE.rtf en un archivo que tenemos junto a los ficheros WXS.

Si queremos un buen resultado visualmente hablando toca diseñar dos ficheros BMP.

<WixVariable Id="WixUIBannerBmp" Value="path\banner.bmp" /> <!-- 493x58 -->
<WixVariable Id="WixUIDialogBmp" Value="path\dialog.bmp" /> <!-- 493x312 -->


Aquí uso la interfaz Minimal que lo único que hace es pedir aceptar la licencia e instalarse tal como habíamos visto antes. Hay más interfaces: WixUI_Mondo, WixUI_FeatureTree, WixUI_InstallDir y WixUI_Advanced. Todas ellas permiten más ajustes que WixUI_Minimal. También podemos crear ventanas personalizadas pero en mi opinión es algo a evitar. Las instalaciones deben ser lo más simples posibles y dejar a la aplicación en sí toda la configuración que necesite. Respecto a cambiar el directorio de instalación, no soy partidario pero si quereis la cuestión es sustituir LumtumoDir por INSTALLDIR (en hate.exe y en el fichero Lumtumo.wxs) y en la etiqueta Feature añadir `Display="expand" ConfigurableDirectory="INSTALLDIR"`

Bundle y Bootstrap


Un archivo MSI está bien pero quizá nos gusté más un EXE que además instale las dependencias.


<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Bundle Name="Lumtumo" Version="1.0" Manufacturer="Adrián Arroyo Calle" UpgradeCode="AC44218D-B783-4DF9-B441-5AE54394DAA9" AboutUrl="http://adrianarroyocalle.github.io">
<!-- <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense" /> -->
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.HyperlinkLicense">
<bal:WixStandardBootstrapperApplication LicenseUrl="" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"/>
</BootstrapperApplicationRef>

<Chain>
<ExePackage Id="Dependency1" SourceFile="Dependency_package_1.exe" />
<ExePackage Id="Dependency2" SourceFile="Dependency_package_2.exe" />

<RollbackBoundary Id="RollBack" />

<MsiPackage Id="MainPackage" SourceFile="lumtumo.msi" Vital="yes" />
</Chain>
</Bundle>
</Wix>


Y compilamos


candle.exe setup.wxs
light.exe -ext WixBalExtension setup.wxs


Y se genera el archivo `setup.exe`. Por defecto el archivo MSI se instala en modo silencioso por lo que la GUI pasa a ser la de Bootstrap.

Más sintaxis WiX


Registrar un tipo de archivo



<ProgId Id='ACME.xyzfile' Description='Acme Foobar data file' Icon="icon.ico">
<Extension Id='xyz' ContentType='application/xyz'>
<Verb Id='open' Command='Open' TargetFile='[#ID_DE_EJECUTABLE]' Argument='"%1"' />
</Extension>
</ProgId>

Escribir configuración en archivo INI



<IniFile Id="WriteIntoIniFile" Action="addLine" Key="InstallDir" Name="Foobar.ini" Section="Paths" Value="[INSTALLDIR]" />

Escribir en el registro



<RegistryKey Id='FoobarRegInstallDir' Root='HKLM' Key='Software\Acme\Foobar 1.0' Action='createAndRemoveOnUninstall'>
<RegistryValue Type='string' Name='InstallDir' Value='[INSTALLDIR]'/>
<RegistryValue Type='integer' Name='Flag' Value='0'/>
</RegistryKey>

Borrar archivos extra en la desinstalación



<RemoveFile Id='LogFile' On='uninstall' Name='Foobar10User.log' />
<RemoveFolder Id='LogFolder' On='uninstall' />
]]>
https://blog.adrianistan.eu/tutorial-wix-windows-installer-msi Thu, 3 Sep 2015 00:00:00 +0000
Lista de sponsors para juegos HTML5 https://blog.adrianistan.eu/lista-sponsors-juegos-html5 https://blog.adrianistan.eu/lista-sponsors-juegos-html5

Mercados de juegos



Portales de juegos



Kongregate

Tiendas tradicionales


Algunas permiten cobrar, otras son 100% gratis y deben ser monetizadas por anuncios o micropagos.

Tiendas especializadas



firefox-os

CMS de juegos



Streaming



Si conoces alguno más, dímelo. Te lo agradeceré.]]>
https://blog.adrianistan.eu/lista-sponsors-juegos-html5 Sat, 15 Aug 2015 00:00:00 +0000
Probando OpenIndiana https://blog.adrianistan.eu/probando-openindiana https://blog.adrianistan.eu/probando-openindiana OpenIndiana. Para esto he usado la última nightly disponible fechada a 30 de marzo de 2015.

OpenIndianaInstall

OpenIndiana entra por defecto en GNOME 2 en modo Live. Desde allí podemos lanzar el instalador para instalarlo en el disco.

Instalando paquetes


OpenIndiana usa IPS como gestor de paquetes. Para poder instalar paquetes debemos usar los siguientes comandos
sudo su 
pkg install git
pkg install gcc-48

Para buscar paquetes
pkg search -pr NOMBRE_PAQUETE 

IPS puede estar desactualizado. Afortunadamente, ya que SmartOS y OpenIndiana comparten el kernel illumos es posible usar los paquetes de SmartOS. Para ello hay que instalar pkgin. Añadimos al PATH las rutas /opt/local/sbin y /opt/local/bin. Usamos el archivo .profile.
PATH=/opt/local/sbin:/opt/local/bin:$PATH 
export PATH

Después usamos el instalador
curl http://pkgsrc.joyent.com/packages/SmartOS/bootstrap/bootstrap-2015Q2-x86_64.tar.gz | gtar -zxpf - -C / 

Una vez hecho esto podemos actualizar la base de datos de pkgin
pkgin -y update 

Y podemos instalar normalmente
pkgin in mercurial 
pkgin in wxGTK30

De todos modos yo no instalaría ninguna aplicación GUI vía pkgin.

OpenIndiana]]>
https://blog.adrianistan.eu/probando-openindiana Fri, 14 Aug 2015 00:00:00 +0000
¡Haz scripts! https://blog.adrianistan.eu/haz-scripts https://blog.adrianistan.eu/haz-scripts
Automation

Se es más productivo


Escribir scripts es la mejor manera de ser productivo. Perdemos parte de nuestro tiempo inicialmente para posteriormente recuperarlo en el futuro. Escribir un script es una inversión.

Estan siempre actualizados


Puede que hoy sepas el procedimiento exacto a realizar para una determinada tarea pero en el futuro puede que no lo sepas. El script siempre está actualizado, pues en cuanto deja de funcionar se debe reparar para seguir usándose. La documentación por ejemplo

bash

Menos errores


Tener un script reduce la posibilidad de error humano. Piensa que cada vez que hacemos algo de memoria podemos fallar. ¡Y puede que no sepamos cuando ha pasado eso!

Fáciles de compartir


Hoy día es muy fácil compartir scripts con el mundo. Cualquier programa de 'paste' funciona y sirve. Pastebin, BitBin.it, DesdeLinux Paste, GitHub Gist, BitBucket Snippets, Mozilla Paste, Ubuntu Paste, CryptoBin, Pasted, ...

Algunos incluso te pagan por las visitas

Son TUS herramientas


Eres programador. Usas herramientas diseñadas por otros programadores. ¿Por qué no hacer tú alguna de tus herramientas? Agilizan tu entorno de trabajo como solo tu sabes. El verdadero maestro conoce sus herramientas al dedillo, si las diseñas todo ya tendrás mucho trecho hecho.

powershell

¿A qué esperas?


Creo que se ha quedado demostrado que quiero que hagas scripts; que automatices. Da igual el lenguaje que uses, pero usa tus propios scripts. Puedes usar Perl, Python, JavaScript, Ruby, Tcl, Bash, Fish, PowerShell, Lua y muchos más.]]>
https://blog.adrianistan.eu/haz-scripts Tue, 4 Aug 2015 00:00:00 +0000
¿Qué hacer ante la falta de espacio en Android? https://blog.adrianistan.eu/ante-la-falta-espacio-android https://blog.adrianistan.eu/ante-la-falta-espacio-android root.

Android

0. Pasar imágenes, vídeo y WhatsApps a la SD


Pongo 0 porque esto lo deberías haber hecho nada más recibir el teléfono. Si el teléfono no soporta SD, borrar cosas y si se consideran importantes pasar al ordenador.

1. Borrar la caché de las aplicaciones


Se puede hacer desde la interfaz de ajustes de Android o desde Link2SD.

2. Pasar aplicaciones a la tarjeta SD


Usa Link2SD. Tiene un modo básico con el script app2sd y otro más avanzado que funciona con particiones.

3. Borrar aplicaciones innecesarias de usuario


Usa Link2SD o en los ajustes de Android.

4. Borrar aplicaciones innecesarias del sistema


Usa Link2SD. Usa esto con cuidado. Me encuentro que mucha gente puede borrar sin problemas las Play cosas (menos Play Store) y las aplicaciones de la operadora. También puedes:
mount -o remount,rw /system rm /system/app/APLICACION.apk mount -o remount,ro /system 

5. Limpia la Dalivk Cache


Usa Link2SD. Si no lo puedes usar
rm /data/dalvik-cache/* 

6. Borrar datos de algunas aplicaciones


Ciertas aplicaciones usan datos a lo tonto. Mejor borrar y empezar de nuevo. Sé cuidadoso.

7. Pasar SD Maid


Si lo tienes instalado perfecto. Si no, sigue más adelante.

8. Usar FilesGo


FilesGo es una aplicación de Google que encuentra archivos innecesarios en el teléfono y propone su borrado. Muy útil.

9. Borra la caché de Telegram


En Telegram -> Ajustes ->Datos y almacenamiento -> Uso del almacenamiento -> Borrar caché. Es totalmente seguro y todo se puede volver a descargar.

10. Usa Google Photos


Google Photos hace un backup de las fotos y permite borrar las fotos antiguas del teléfono que ya tengan copia de seguridad en la nube.

11. Borra copias de seguridad de WhatsApp


WhatsApp almacena copias de seguridad por la noche. Los ficheros en Whatsapp/Databases que contienen fechas se pueden borrar. Cuidado de no borrar el que no tiene fecha.

12. Borra contenido de grupos de WhatsApp


En Ajustes-> Datos y almacenamiento -> Uso de almacenamiento -> Selecciona el grupo que quieras borrar. Aquí a diferencia de Telegram, los mensajes NO se pueden recuperar.

13. Cambiar el porcentaje de alerta


En la terminal o usando adb shell.
sqlite3 /data/data/com.android.providers.settings/databases/settings.db 
insert into secure (name, value) VALUES('sys_storage_threshold_percentage','5');
insert into gservices (name, value) VALUES('sys_storage_threshold_percentage','5');
.quit

¿Y si no tengo SQlite 3 instalado en mi teléfono? Algo totalmente comprensible, en cuyo caso deberás descargar una versión ya compilada (también puedes compilarla tú con el Android NDK). En este hilo de XDA Developers lo puedes encontrar actualizado.

14. Optimizar bases de datos


for i in $(find /data -iname "*.db"); do sqlite3 $i 'VACUUM;'; done 

quizá esto también valga
find /data -iname "*.db" -exec sqlite3 {} "VACUUM;" \; 

15. Borrar archivos generados por Android


rm /data/tombstones/* 
rm /data/system/dropbox/*
rm /data/system/usagestats/*

16. Reseteo de fábrica


Solo en caso excepcional deberías plantearte empezar de nuevo. En muchos dispositivos se accede al recovery por Volumen Bajo + Boton Power. Desde allí buscamos la opción de Factory Reset o directamente instalamos otra ROM más ligera.

Más


Si alguien conoce más métodos para reducir el espacio usado en Android me lo puede comentar.]]>
https://blog.adrianistan.eu/ante-la-falta-espacio-android Sun, 2 Aug 2015 00:00:00 +0000
Rust Essentials, reseña del libro https://blog.adrianistan.eu/rust-essentials https://blog.adrianistan.eu/rust-essentials Dicen que a las personas importantes les pide la gente su opinión. Así que no entiendo porque tengo ahora que dar opiniones...
Hoy voy a hablar del libro Rust Essentials de Ivo Balbaert. En primer lugar quiero agradecer a la editorial Packt Publishing por haber contado conmigo a la hora de valorar este libro para que todos vosotros conozcais algo más acerca de él.

RustEssentials

Rust Essentials es un libro de introducción al lenguaje de programación Rust, lenguaje del que ya he hablado anteriormente. El libro está en inglés y asume que no conoces nada o muy poquito de Rust pero sí que has programado con anterioridad. Así pues, el libro no explica conceptos de programación tales como funciones o variables sino que directamente expone sus peculiaridades. Es recomendable haber programado C para entender algunas partes del libro al 100%.

El libro se estructura en 9 capítulos, a saber:

  • Starting with Rust

  • Using variables and types

  • Using functions and control structures

  • Structuring data and matching patterns

  • Generalizing code with high-order functions and parametrization

  • Pointers and memory safety

  • Organizing code and macros

  • Concurrency and parallelism

  • Programming at the boundaries


En estos temas se tratan desde cosas tan triviales como los comentarios (que no lo son, pues según explica el libro, puedes hacer comentarios de RustDoc, que serán compilados como documentación en HTML y tienen marcado Markdown) hasta la gestión multihilo de Rust, para aprovechar uno de los 3 apartados en los que se enfoca Rust: la concurrencia.

Veremos la magia de Rust, respetando la convención de estilo (esto es importante, no vaya a pasar como con JavaScript) y las características que hacen de Rust un gran lenguaje de programación. El libro contiene ejercicios e incluso analizarás porque en determinados lugares obtenemo un error de compilación.
Hacen falta un tiempo para que dejes de ver al compilador de Rust como un protestón sin sentido y lo empieces a ver como tu mejor amigo en la programación

El libro también se adentra a explicar las partes de programación funcional de Rust, no sin antes explicar las closures y las funciones de primer orden. Más tarde nos adentramos en las traits, que posibilitan la programación orientada a objetos pero no como se plantea desde C++, C# o Java. En Rust, es mucho más flexible y unido a las funciones genéricas podemos evitar la repetición del código en un 100%. DRY (don't repeat yourself). El capítulo 6 es interesante y quizá algo denso para alguien que venga de lenguajes donde la gestión de memoria es administrada por una máquina virtual o intérprete. No es difícil, pero hay que saber las diferencias. Rust tiene muchos tipos de punteros y he de decir que este libro los explica mejor que mi antiguo libro de C de Anaya.

Rust

Más tarde se ve el sistema de módulos y la creación de macros en Rust. Las macros en Rust son muy potentes, más que en C donde también son bastante usadas. El capítulo 8 se dedica por completo a los hilos y la gestión de datos entre distintos hilos. El capítulo 9 nos explica cosas interesantes pero que no tienen mucha densidad y no se merecen un capítulo propio como la intercomunicación entre C y Rust o instrucciones en ensamblador directamente en el código.

Me gusta que tenga ejercicios para practicar (las soluciones están en GitHub), que use las herramientas disponibles de Cargo, que explique porque un determinado código no compilará, que hable de como desarrollar tests unitarios y de que explore todas las características del lenguaje de manera incremental, muchas veces modificando ejemplos anteriores.

No me gusta que quizá sea un libro muy rápido que presupone algunos conceptos y que casi no explora la librería estándar mas que para hablar de ciertas traits muy útiles y la gestión de hilos. No habla en ningún momento de como leer archivos por ejemplo aunque en el anexo menciona librerías para leer distintos tipos de archivos.

En definitiva es un libro que puede estar bien para todos aquellos con experiencia programando y quieran aprender un nuevo lenguaje, lleno de peculiaridades diseñadas para trabajar en: velocidad, seguridad y concurrencia. No se lo recomendaría a alguien que no hubiese programado nunca.

Actualmente existen libros mejores en el mercado y además, en Adrianistán puedes aprender con Rust 101, mi tutorial de Rust.]]>
https://blog.adrianistan.eu/rust-essentials Fri, 17 Jul 2015 00:00:00 +0000
Easter egg en Linux: la syscall reboot https://blog.adrianistan.eu/easter-egg-linux-la-syscall-reboot https://blog.adrianistan.eu/easter-egg-linux-la-syscall-reboot reboot usada para reiniciar el ordenador. Primero veamos lo que nos dice el manual sobre reboot
man 2 reboot 
#o también en GNOME
yelp 'man:reboot(2)'

man-reboot

Tiene algo interesante. Los dos primeros parámetros para llamar a reboot se llaman magic y magic2 y no son muy importantes realmente porque reboot también admite una versión sin esos parámetros.

Si leemos algo más podemos ver que magic debe ser igual al valor de LINUX_REBOOT_MAGIC1, que es 0xfee1dead (feel dead ?) y que para magic2 se admiten varios posibles valores

  • LINUX_REBOOT_MAGIC2 que es 672274793

  • LINUX_REBOOT_MAGIC2A que es 85072278

  • LINUX_REBOOT_MAGIC2B que es 369367448

  • LINUX_REBOOT_MAGIC2C que es 537993216


¿Por qué la constante primera está en hexadecimal y el resto no? ¿Qué pasa si pasamos los posibles valores de magic2 a hexadecimal?
672274793 = 0x28121969

85072278 = 0x05121996

369367448 = 0x16041998

537993216 = 0x20112000

¿A qué se parece? ¿Serán fechas? Veamos la primera. 28 de diciembre de 1969. ¿Qué ocurrió? ¡Pues que nació Linus Torvalds ese día! Y el resto de fechas son las fechas de nacimiento de sus hijas. Así que ya sabes, cada vez que reinicias en Linux estás usando fechas de nacimiento de la familia Torvalds.

Torvalds

 ]]>
https://blog.adrianistan.eu/easter-egg-linux-la-syscall-reboot Sat, 11 Jul 2015 00:00:00 +0000
Un repaso por las aplicaciones de Haiku https://blog.adrianistan.eu/repaso-las-aplicaciones-haiku https://blog.adrianistan.eu/repaso-las-aplicaciones-haiku Haiku dispone de un catálogo pequeño pero interesante de aplicaciones que merecen la pena ser vistas. Voy a probar estas aplicaciones en una versión nightly recién descargada del sitio de Haiku (hrev49344)
Solo voy a usar los paquetes binarios. En Haikuports hay todavía más software, pero requiere compilación y puede estar desactualizado.

HaikuDepot


Se trata de la tienda de aplicaciones de Haiku. Similar a Ubuntu Software Center. Desde aquí podemos instalar cientos de aplicaciones ya compiladas para Haiku. Además permite comentar los progranas con una valoración.
HaikuDepot dispone de una versión web

HaikuDepot

HaikuDepot2
Instalar

HaikuDepot ya viene instalado en Haiku

WonderBrush


WonderBrush se trata del editor gráfico de Haiku para imágenes de mapa de bits como PNG, JPEG o BMP.

La ventana principal puede contener cualquier número de documentos llamados Canvas. Un canvas tiene asociado un nombre y una resolución por píxel además de muchas otras propiedades. También referencia a dos archivos, uno como formato de exportación y otro como formato nativo.
Cada canvas puede tener cualquier número de capas, actualmente representadas como una lista. Cada capa representa una imagen del tamaño del canvas. Dependiendo del método de fusión de capas, estas son unidas para formar la imagen final.

WonderBrush WonderBrush2
Instalar

Lo puedes encontrar en HaikuDepot o


pkgman install wonderbrush


Icon-O-Matic


Icon-O-Matic es la aplicación de gráficos vectoriales nativa de Haiku. Trabaja con el formato HVIF (Haiku Vector Icon Format) y está pensada para diseñar iconos principalmente. También exporta a PNG y SVG.

IconOMatic
Instalar

Ya viene instalado por defecto en Haiku

WebPositive


WebPositive es el navegador web por defecto en Haiku. Usa una versión especial de WebKit adaptada a Haiku y algo retrasada en funcionalidad.

haiku-webpositive

MultiversosHaiku
Instalar

WebPositive ya viene instalado

PoorMan


PoorMan es un ligero servidor HTTP totalmente gráfico.

PoorMan
Instalar

PoorMan ya viene instalado por defecto en Haiku

Vision


Vision es un cliente IRC

Vision
Instalar

Ya viene instalado por defecto en Haiku

People


People es el gestor de contactos de Haiku

People
Instalar

Ya viene instalado por defecto en Haiku

Album


Una aplicación para tratar los metadatos de las imágenes

Album
Instalar

En HaikuDepot o pkgman install album

ArmyKnife


Una aplicación para tratar los metadatos de las canciones

ArmyKnife
Instalar

En HaikuDepot o pkgman install armyknife

BeAE


BeAE es una gran aplicación junto a Wonderbrush e Icon-O-Matic para la edición multimedia. En este caso podremos editar audio de manera muy intuitiva. En su momento fue una aplicación de pago para BeOS.

BeAE
Instalar

En HaikuDepot o pkgman install beae

BePDF


El visor de documentos PDF de Haiku

BePDF
Instalar

En HaikuDepot o pkgman install bepdf

BeShare


Haiku también dispone de un sistema de transferencia de archivos P2P propio. Se trata de BeShare y esta es la implementación referencia. BeShare como no podía ser menos, tiene un magnífico soporte para búsquedas.

BeShare
Instalar

En HaikuDepot o pkgman install beshare_x86. Para permitir la conexión entre distintos sistemas operativos a las redes BeShare se creó JavaShare

BeZilla


El port de Mozilla Firefox para Haiku. Está muy desactualizado y no lo recomiendo.

BeZilla
Instalar

En HaikuDepot o pkgman install bezilla

MailNews


El port de Mozilla Thunderbird para Haiku. Está igual de desactualizado que BeZilla pero al tratarse de correo electrónico puedes tener menos problemas al usarlo.

MailNews MailNews-2
Instalar

En HaikuDepot o pkgman install mailnews

Beam


Se trata de un cliente de correo electrónico plenamente integrado en Haiku

Beam
Instalar

En HaikuDepot o pkgman install beam

BlogPositive


Una aplicación para escribir en blogs de WordPress y otros proveedores sin tener que usar el navegador

BlogPositive
Instalar

En HaikuDepot o pkgman install blogpositive

CapitalBe


Una aplicación de contabilidad sencilla y fácil de empezar a utilizar. GnuCash, que es el que uso en Linux, es mucho más complejo de empezar a usar.

CapitalBe CapitalBe2
Instalar

En HaikuDepot o pkgman install capitalbe

Sum-It


Una sencilla hoja de cálcula para realizar operaciones no muy complejas. La mejor hoja de cálcula es la de GoBe Productive, pero sigue siendo un producto privado que no se puede adquirir.

SumIt
Instalar

En HaikuDepot o pkgman install sum_it

Caya


Caya es una aplicación que unifica la mensajería instantánea el estilo de Pidgin. Soporta AIM, Google Talk, Jabber, Facebook, MSN y Yahoo.

Caya
Instalar

En HaikuDepot o pkgman install caya

LibreCAD


LibreCAD es una aplicación de CAD 2D escrita en Qt.

LibreCAD
Instalar

En HaikuDepot o pkgman install librecad_x86

Pe


Pe es el editor de texto más usado en Haiku. Tiene resaltado de sintaxis y soporte para extensiones.

Pe Pe-2
Instalar

Pe viene instalado por defecto en Haiku

NetSurf


NetSurf es un navegador ligero que no implementa JavaScript. Tiene una arquitectura interna muy limpia. Todo está contenido y modularizado. Está diseñado pensando en Haiku, RISC OS y otros sistemas desconocidos, aunque en Linux también funciona.

NetSurf
Instalar

En HaikuDepot o pkgman install netsurf

BePodder


BePodder es una aplicación para escuchar tus podcasts favoritos por RSS

BePodder
Instalar

En HaikuDepot o pkgman install bepodder

A-Book


Una aplicación de calendario con recordatorios
Instalar

En HaikuDepot o pkgman install a_book

BurnItNow


Una aplicación gráfica y fácil de usar para grabar CDs de todo tipo (datos, audio, rescate, ...)

BurnItNow
Instalar

En HaikuDepot o pkgman install burnitnow_x86

Clockwerk


Ya hemos hablado de Wonderbrush, Icon-O-Matic y BeAE. Le toca el turno a Clockwerk, el editor de vídeo no linear open source que trabaja en Haiku.

Clockwerk
Instalar

En HaikuDepot o pkgman install clockwerk

LMMS


LMMS es una completa suite de edición de audio pensada para Linux pero que funciona también en Haiku.

LMMS
Instalar

En HaikuDepot o pkgman install lmms_x86

MilkyTracker


MilkyTracker es un programa para componer música de estilo 8 bits o tune.

MilkyTracker
Instalar

En HaikuDepot o pkgman install milkytracker

Paladin


Paladin nace con la idea de ser el IDE de referencia en Haiku. Trae plantillas y ejemplos y se integra con el resto de Haiku para no reduplicar esfuerzos. Por ejemplo, el editor de texto es Pe.

Paladin
Instalar

En HaikuDepot o pkgman install paladin

QEMU


QEMU es un contenedor de máquinas virtuales open source que permite emular arquitecturas diferentes a la de nuestro ordenador. Con QEMU podemos ejecutar Linux dentro de Haiku.
Instalar

En HaikuDepot o pkgman install qemu_x86

Yab IDE


Yab es un entorno que permite programar aplicaciones para Haiku en BASIC. Yab IDE es el IDE para este entorno espefífico.

Yab Yab2
Instalar

En HaikuDepot o pkgman install yab_ide

Juegos


En este apartado voy a mencionar algunos juegos disponibles para Haiku. Todos se pueden encontrar en HaikuDepot

BeMines

  • BeLife - pkgman install belife

  • BeMines - pkgman install bemines

  • Critical Mass - pkgman install criticalmass

  • DOSBox - pkgman install dosbox_x86

  • Flare - pkgman install flare_x86

  • FreedroidRPG - pkgman install freedroidrpg_x86

  • LBreakout2 - pkgman install lbreakout2

  • LMarbles - pkgman install lmarbles

  • LTris - pkgman install ltris

  • OpenTTD - pkgman install openttd_x86

  • Pipepanic - pkgman install pipepanic

  • Road Fighter - pkgman install roadfighter

  • Rocks'n'diamonds - pkgman install rocksndiamonds_x86

  • SDL Lopan - pkgman install sdllopan

  • Slime Volley - pkgman install slime_volley

  • Super Transball 2 - pkgman install super_transball

  • XRick - pkgman install xrick

]]>
https://blog.adrianistan.eu/repaso-las-aplicaciones-haiku Wed, 1 Jul 2015 00:00:00 +0000
Tutorial de CMake https://blog.adrianistan.eu/tutorial-de-cmake https://blog.adrianistan.eu/tutorial-de-cmake
1º Regla de CMake. La documentación puede ser confusa

¿Qué es CMake?


CMake se trata de una herramienta multiplataforma para generar instrucciones de compilación del código. No sustituye a las herramientas de compilación como Make o MSBuild, sino que nos proporciona un único lenguaje que será transformado a las instrucciones del sistema operativo donde nos encontremos. Sería un sustituto de Autotools.

cmake-dia

Las ventajas son que no tenemos que tener varios archivos para gestionar la compilación. Usando CMake podemos generar el resto. Actualmente CMake (3.2.3) soporta:

  • Unix Make

  • Ninja

  • CodeBlocks

  • Eclipse CDT

  • KDevelop

  • Sublime Text 2

  • Borland Make

  • MSYS Make

  • MinGW Make

  • NMake

  • NMake JOM

  • Watcom WMake

  • Kate

  • CodeLite

  • Xcode

  • Visual Studio (desde el 6 hasta 2013)


Usando CMake


En CMake las configuraciones estan centralizadas por defecto en un archivo llamado CMakeLists.txt. Este se encuentra en la carpeta central del proyecto. Normalmente con CMake los proyectos se construyen en una carpeta diferente de la que tenemos el código fuente. Es corriente crear una carpeta build en lo alto del proyecto. Así si tenemos un proyecto con CMake ya descomprimido haríamos lo siguiente.
mkdir build
cd build
cmake ..
# make o ninja o nmake o lo que toque

También puedes usar la aplicación gráfica. Muy cómoda cuando debamos modificar las configuraciones.

cmake-gui

Podemos ajustar las variables de CMake desde la interfaz de usuario, usando el modo interactivo de la línea de comandos (`cmake .. -i`) o usando flags cuando llamamos a CMake (`cmake .. -DCMAKE_CXX_FLAGS=-std=c++11`)

El archivo CMakeLists.txt


Ya estamos listos para crear nuestro primer archivo de configuración de CMake.

proyecto

Vamos a ir viendo distintas versiones del archivo donde voy a ir añadiendo diferentes tareas. Estate atento a los comentarios de los archivos

Compilar como programa main.cpp


PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# Indicamos la versión mínima que necesitamos de CMake

SET(MiProyecto_SRC "src/main.cpp")
# Creamos la variable MiProyecto_SRC y le asignamos el valor "src/main.cpp" que es la ubicación de nuestro archivo.
# Por defecto las variables son listas o arrays
# Si tenemos dos archivos sería SET(MiProyecto_SRC "src/main.cpp"
"src/segundo.cpp")
# Se permite multilínea

ADD_EXECUTABLE(MiProyecto ${MiProyecto_SRC})

# Se creará un ejecutable llamado MiProyecto en Linux o MiProyecto.exe en Windows.
# Se hace referencia a las variables con ${NOMBRE_VARIABLE}.

Y ya está.

Trabajar con opciones y condicionales


CMake permite ajustar muchas opciones como hemos visto con el asistente gráfico de CMake. Sin embargo no todas las variables se muestran ahí. Solo son modificables las que nosotros marquemos explícitamente. Se usa OPTION()
PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

SET(MiProyecto_SRC "src/main.cpp")

OPTION(EXPERIMENTAL_FEATURE "Activar característica experimental" OFF)
# OPTION(NOMBRE_VARIABLE DESCRIPCION_LEGIBLE VALOR_POR_DEFECTO)
# ON/OFF es la pareja de valores booleanos en CMake. TRUE/FALSE también es correcto

IF(EXPERIMENTAL_FEATURE) # El condicional más básico
LIST(APPEND MiProyecto_SRC "experimental_feature.cpp")
# Añadimos un elemento a la lista
# También se puede hacer con
# SET(MiProyecto_SRC ${MiProyecto_SRC} "experimental_feature.cpp")
ENDIF()

ADD_EXECUTABLE(MiProyecto ${MiProyecto_SRC})

Usar librería estática


PROJECT(MiProyecto C CXX)
# Podemos marcar opcionalmente los lenguajes para que CMake busque los compiladores
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

SET(MiProyecto_SRC "src/main.cpp")
SET(Lib_SRC "lib/lib.cpp")

ADD_LIBRARY(Lib STATIC ${Lib_SRC})
# El comando es exactamente igual que ADD_EXECUTABLE, pero marcamos si STATIC o SHARED
ADD_EXECUTABLE(MiProyecto ${MiProyecto_SRC})
TARGET_LINK_LIBRARIES(MiProyecto ${Lib})
# Necesitamos "unir" la librería con nuestro ejecutable
# Si necesitamos una librería tal cual usamos su nombre
# TARGET_LINK_LIBRARIES(MiProyecto pthread)
# Se pueden hacer las llamadas que se quiera a TARGET_LINK_LIBRARIES

Usar librería dinámica


PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

SET(MiProyecto_SRC "src/main.cpp")
SET(Lib_SRC "lib/lib.cpp")

ADD_LIBRARY(Lib SHARED ${Lib_SRC})
ADD_EXECUTABLE(MiProyecto ${MiProyecto_SRC})
TARGET_LINK_LIBRARIES(MiProyecto ${MiProyecto_SRC})

Seleccionar archivos de forma masiva


Usar SET para los archivos es muy fácil de entender, pero es posible que no queramos mantener una lista explícita del código fuente.
PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

FILE(GLOB MiProyecto_SRC "src/*.cpp")
# FILE GLOB selecciona todos los archivos que cumplan la característica y los almacena en MiProyecto_SRC
# GLOB no es recursivo. Si lo necesitas, usa GLOB_RECURSE

ADD_EXECUTABLE(MiProyecto ${MiProyecto_SRC})

Esto tiene un inconveniente y es que CMake no detecta automáticamente si hay nuevos archivos que cumplen la característica, por lo que hay que forzar la recarga.

Copiar, crear, eliminar y descargar archivos


PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

FILE(GLOB MiProyecto_SRC "src/*.cpp")

# Copiar archivos
FILE(COPY "MiArchivo.cpp" DESTINATION mi-carpeta)
# COPY usa como destino siempre una carpeta
# Se puede crear con FILE(MAKE_DIRECTORY mi-carpeta)

# Crear archivos

FILE(WRITE "Generado.txt" "Este archivo ha sido generado por CMake\nLos archivos son: ${MiProyecto_SRC}")

# Borrar archivos

FILE(REMOVE "Generado.txt")
# No es recursivo, REMOVE_RECURSE sí lo es

# Descargar archivos

FILE(DOWNLOAD http://mi-servidor.com/archivo.tar.gz archivo.tar.gz)
# Podemos mostrar el progreso
# FILE(DOWNLOAD http://mi-servidor.com/archivo.tar.gz archivo.tar.gz SHOW_PROGRESS)
# Comprobar la suma MD5
# FILE(DOWNLOAD http://mi-servidor.com/archivo.tar.gz archivo.tar.gz EXPECTED_MD5 LaSumaMD5)
# Usar SSL
# FILE(DOWNLOAD http://mi-servidor.com/archivo.tar.gz archivo.tar.gz TLS_VERIFY ON)
# Guardar la información en un archivo de log
# FILE(DOWNLOAD http://mi-servidor.com/archivo.tar.gz archivo.tar.gz LOG descarga.log)

# Calcular suma de control

FILE(SHA256 archivo.tar.gz VARIABLE_CON_EL_HASH)

ADD_EXECUTABLE(MiProyecto ${MiProyecto_SRC})

Incluir archivos de cabecera


A veces es necesario incluir archivos de cabecera en localizaciones no estándar
PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

SET(MiProyecto_SRC
"src/main.cpp"
"src/algo_mas.cpp")

INCLUDE_DIRECTORIES("src/includes")
# Se añade el directorio a la ruta de búsqueda del compilador de turno

ADD_EXECUTABLE(MiProyecto ${MiProyecto_SRC})

Plugins de CMake


CMake es extensible a través de módulos. La instalación por defecto de CMake trae unos cuantos módulos, no obstante, podemos añadir módulos solo para nuestro proyecto. Los módulos tienen extensión .cmake. Normalmente se dejan en una carpeta llamada `cmake`.
PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

LIST(APPEND CMAKE_PLUGIN_PATH "cmake")
# Simplemente añadimos un nuevo lugar a buscar. Veremos como se usan los módulos más adelante

ADD_EXECUTABLE(MiProyecto_SRC "src/main.cpp")

Mostrar información y generar errores


En ciertas situaciones querremos que no se pueda compilar el proyecto. MESSAGE es la solución.
PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

MESSAGE("Información relevante")
MESSAGE(STATUS "Información sin relevancia")
MESSAGE(WARNING "Alerta, continúa la configuración y generación")
MESSAGE(SEND_ERROR "Error, continúa la configuración pero no generará")
MESSAGE(FATAL_ERROR "Error grave, detiene la configuración")

ADD_EXECUTABLE(MiProyecto "src/main.cpp")

Condicionales avanzados


PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

## Con variables booleanas, es decir, ON/OFF, TRUE/FALSE

IF(NOMBRE_VARIABLE)
MESSAGE("Algo es cierto")
ENDIF()

IF(NOT NOMBRE_VARIABLE)
MESSAGE("Algo no es cierto")
ENDIF()

# La estructura completa es algo así

IF(CONDICION)

ELSEIF(CONDICION_2)

ELSE()

ENDIF()

# Se pueden aplicar operadores lógicos

IF(CONDICION AND CONDICION_2)

IF(CONDICION OR CONDICION_2)

# Con números y texto

IF(VAR_1 LESS VAR_2) # VAR_1 &lt; VAR_2 IF(VAR_1 GREATER VAR_2) # VAR_1 &gt; VAR_2

IF(VAR_1 EQUAL VAR_2) # VAR_1 === VAR_2

IF(VAR_1 MATCHES REGEX) # Se comprueba la expresión regular

# Además, CMake provee operadores para trabajar directamente con archivos, comandos y ejecutables

IF(DEFINED VAR_1) # ¿Está definida VAR_1?

IF(COMMAND CMD_1) # ¿CMD_1 es un comando de CMake?

IF(POLICY POL_1) # ¿La directiva POL_1 está activada?

IF(TARGET MiProyecto) # ¿Está definido el ejecutable MiProyecto?

IF(EXISTS src/main.cpp) # ¿Existe el archivo src/main.cpp?

IF(src/main.cpp IS_NEWER_THAN src/old/main.cpp) # ¿Es src/main.cpp más nuevo que src/old/main.cpp?

IF(IS_DIRECTORY src/includes) # ¿src/includes es un archivo o una carpeta?


Bucles


PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

SET(MiProyecto_SRC
"src/main.cpp"
"src/list.cpp"
"src/algomas.cpp")

FOREACH(Archivo_SRC IN MiProyecto_SRC)
MESSAGE(STATUS "Procesando archivo ${Archivo_SRC}")
ENDFOREACH()

Submódulos


CMake usa un único archivo, pero quizá nos conviene repartir la configuración de CMake por varias carpetas entre zonas diferenciadas.
PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

ADD_SUBDIRECTORY(lib)
ADD_SUBDIRECTORY(src)

# src y lib tienen un CMakeLists.txt cada uno

Librerías externas


Una de las características más interesantes de CMake es que es capaz de encontrar librerías externas que necesite nuestro programa. Esta característica se implementa con plugins de CMake. Aquí voy a necesitar wxWidgets.
PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

FIND_PACKAGE(wxWidgets)
# El plugin debe llamarse FindPackagewxWidgets.cmake, este esta incluido en la distribución estándar de CMake
# En grandes librerías como wxWidgets, podemos pedir solo ciertos componentes
# FIND_PACKAGE(wxWidgets COMPONENTS core gl html base net)
# Podemos hacer que CMake no continúe si no encuentra la librería
# FIND_PACKAGE(wxWidgets REQUIRED)
# Si todo va bien, tenemos las variables wxWidgets_FOUND, wxWidgets_LIBRARIES y wxWidgets_INCLUDE_DIR

INCLUDE_DIRECTORIES(${wxWidgets_INCLUDE_DIR})
TARGET_LINK_LIBRARIES(MiProyecto ${wxWidgets_LIBRARIES})

Definiciones


Podemos añadir directivas del preprocesador de C++ con CMake
PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

ADD_DEFINITIONS(-DPREMIUM_SUPPORT)
# Ahora #ifdef PREMIUM_SUPPORT en el código evaluará como cierto

ADD_EXECUTABLE(MiProyecto "src/main.cpp")

Dependencias


Se pueden crear árboles de dependencias en CMake
PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

ADD_EXECUTABLE(MiProyecto "src/main.cpp")
ADD_EXECUTABLE(NecesitaMiProyecto "src/otro.cpp")

ADD_DEPENDENCY(NecesitaMiProyecto MiProyecto)
# NecesitaMiProyecto ahora depende de MiProyecto

Usando Qt


Ejemplo práctico usando CMake y Qt5 que es capaz de usar QML. Soporta archivos QRC de recursos. Requiere los plugins de Qt5.
PROJECT(ProyectoQt)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

SET(CMAKE_AUTOMOC ON)
SET(CMAKE_INCLUDE_CURRENT_DIR ON)

FILE(GLOB ProyectoQt_SRC "src/*.cpp")

FIND_PACKAGE(Qt5Core REQUIRED)
FIND_PACKAGE(Qt5Widgets REQUIRED)
FIND_PACKAGE(Qt5Qml REQUIRED)
FIND_PACKAGE(Qt5Quick REQUIRED)

qt5_add_resources(Res_SRC "src/res.qrc")

ADD_EXECUTABLE(ProyectoQt ${ProyectoQt_SRC} ${Res_SRC})

qt5_use_modules(ProyectoQt Widgets Qml Quick)

Usando Java


CMake soporta Java, aunque no maneja dependencias como Maven o Gradle.
PROJECT(ProyectoJava)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

FIND_PACKAGE(Java REQUIRED)
INCLUDE(UseJava)

SET(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.6" "-target" "1.6")

FILE(GLOB JAVA_SRC "src/*.java")
SET(DEPS_JAR "deps/appengine.jar")

add_jar(ProyectoJava ${JAVA_SRC} INCLUDE_JARS ${DEPS_JAR} ENTRY_POINT "PuntoDeEntrada")


Usar C++11


A partir de CMake 3.1, podemos definir el estándar de C y C++ que vamos a usar
SET_PROPERTY(TARGET Ejecutable PROPERTY CXX_STANDARD 11) # Para C++11. Solo afecta al target Ejecutable

SET(CMAKE_CXX_STANDARD 11) # Para C++11. Afecta globalmente al proyecto.


Comandos personalizados, Doxygen


En CMake podemos crear comandos personalizados. Por ejemplo, generar documentación con Doxygen
PROJECT(Doxy)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

ADD_CUSTOM_TARGET(doxygen doxygen ${PROJECT_SOURCE_DIR}/Doxyfile DEPENDS MiProyectoEjecutable WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMENT "Generando documentación" VERBATIM )

# Ahora puedes usar "make doxygen"
# Como es un TARGET cualquiera de CMake, puedes usar ADD_DEPENDENCY
# También puedes usar el plugin FindDoxygen para más portabilidad

Archivos de configuración


En Autotools es común usar un archivo con configuraciones en tiempo de compilación. Normalmente se trata de una cabecera con soporte para plantillas. En CMake se puede hacer.

config.hpp.in
#ifndef CONFIG_HPP
#define CONFIG_HPP

#cmakedefine PREMIUM_SUPPORT

/* Si PREMIUM_SUPPORT está definido en CMakeLists.txt, se definirá aquí */

#define AUTHOR @AUTHOR@

/* Se definirá AUTHOR con el valor que tenga CMakeLists.txt de la variable AUTHOR */

#endif

PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

SET(AUTHOR "\"Adrian Arroyo Calle\"")

CONFIGURE_FILE(src/config.hpp.in src/config.hpp)

Instalar


CMake permite instalar también los programas
PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

ADD_EXECUTABLE(MiProyecto "src/main.cpp")

INSTALL(TARGETS MiProyecto DESTINATION bin/)
# Instala un ejecutable o librería en la carpeta seleccionada. Tenemos que tener en cuenta los prefijos, que son configurables en CMake.
# Si un programa en Linux suele ir en /usr/local/bin, debemos usar bin, pues /usr/local será añadido por CMake automáticamente
INSTALL(FILES ${ListaDeArchivos} DESTINATION .)
# Archivos normales
INSTALL(DIRECTORY mi-carpeta DESTINATION .)
# Copia la carpeta entera, conservando el nombre
# Se permiten expresiones regulares y wildcards
# INSTALL(DIRECTORY mi-carpeta DESTINATION . FILES_MATCHING PATTERN "*.png")

INSTALL(SCRIPT install-script.cmake)
# Un archivo de CMake que se ejecutará en la instalación

INCLUDE(InstallRequiredSystemLibraries)
# Importante si usas Windows y Visual Studio

# Y con esto se puede usar 'make install'

CPack


Pero `make install` es un poco incómodo. No se puede distribuir fácilmente. Aquí CMake presenta CPack, que genara instaladores. Yo soy reacio a usarlos pues son de mala calidad pero soporta:

  • ZIP

  • TAR.GZ

  • TAR.BZ2

  • TZ

  • STGZ - Genera un script de Bash que ejecutará la descompresión y hará la instalación

  • NSIS

  • DragNDrop

  • PackageMaker

  • OSXX11

  • Bundle

  • Cygwin BZ2

  • DEB

  • RPM


CPack necesita que usemos el comando `cpack` en vez de `cmake`
PROJECT(MiProyecto)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

ADD_EXECUTABLE(MiProyecto "src/main.cpp")
INSTALL(TARGETS MiProyecto DESTINATION bin)

INCLUDE(CPack)
# Esto servirá para ZIP, TAR.GZ, TAR.BZ2, STGZ y TZ
# Para el resto deberás configurar manualmente unas cuantas variables necesarias
# http://www.cmake.org/Wiki/CMake:CPackPackageGenerators

Usando ensamblador


CMake soporta correctamente GNU ASM. Nasm requiere más trabajo.
PROJECT(gnu-asm ASM C)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

ENABLE_LANGUAGE(ASM-ATT)

FILE(GLOB ASM_SOURCES "*.asm")
FILE(GLOB C_SOURCES "*.c")

ADD_LIBRARY(asm STATIC ${ASM_SOURCES})
ADD_EXECUTABLE(gnu-asm ${C_SOURCES})
TARGET_LINK_LIBRARIES(gnu-asm asm)

Algunas variables interesantes


|CMAKE_CURRENT_SOURCE_DIR|La ruta completa a la carpeta donde se encuentra CMakeLists.txt|
|CMAKE_MODULE_PATH|Las rutas para buscar plugins de CMake|
|PROJECT_BINARY_DIR|La carpeta que se está usando para guardar los resultados de la compilación|
|CMAKE_INCLUDE_PATH|Las carpetas de búsqueda de cabeceras|
|CMAKE_VERSION|Versión de CMake|
|CMAKE_SYSTEM|El nombre del sistema|
|CMAKE_SYSTEM_NAME|El sistema operativo|
|CMAKE_SYSTEM_PROCESSOR|El procesador|
|CMAKE_GENERATOR|El generador usado en ese momento|
|UNIX|Si estamos en Linux, OS X, BSD o Solaris será cierto|
|WIN32|Si estamos en Windows|
|APPLE|En OS X|
|MINGW| Usando MinGW|
|MSYS| Usando MSYS|
|BORLAND| Usando Borland|
|CYGWIN| Usando Cygwin|
|WATCOM| Usando OpenWatcom|
|MSVC| Usando Visual Studio|
|MSVC10| Usando Visual Studio 10|
|CMAKE_C_COMPILER_ID| El identificador de compilador de C|
|CMAKE_CXX_COMPILER_ID| El identificador de compilador de C++|
|CMAKE_COMPILER_IS_GNUCC| El compilador de C es una variante de GNU GCC|
|CMAKE_COMPILER_IS_GNUCXX| El compilador de C++ es una variante de GNU G++|
|CMAKE_BUILD_TYPE| La configuración Debug/Release que estamos usando|
|CMAKE_C_COMPILER| La ruta al compilador de C|
|CMAKE_C_FLAGS| La configuración del compilador de C|
|CMAKE_C_FLAGS_DEBUG| La configuración del compilador de C solo si estamos en la configuración Debug|
|CMAKE_C_FLAGS_RELEASE| La configuración del compilador de C solo si estamos en la configuración Release|
|CMAKE_SHARED_LINKER_FLAGS| La configuración del compilador para librerías compartidas|
|BUILD_SHARED_LIBS| Por defecto en ADD_LIBRARY, las librerías son compartidas. Podemos cambiar esto|

Muchas más en la wiki de CMake

RPath


El RPath es importante en los sistemas UNIX. Se trata de cargar librerías dinámicas que no están en directorios estándar.
SET(CMAKE_SKIP_BUILD_RPATH FALSE)
SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
SET(CMAKE_INSTALL_RPATH "$ORIGIN")
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)

Esto hará que los ejecutables construidos en UNIX puedan cargar librerías desde la carpeta donde se encuentran. Al estilo Windows.]]>
https://blog.adrianistan.eu/tutorial-de-cmake Mon, 22 Jun 2015 00:00:00 +0000
Proyecto Ceres https://blog.adrianistan.eu/proyecto-ceres https://blog.adrianistan.eu/proyecto-ceres
DMAIC

Parte de mi software más exitoso de cara al público han sido las extensiones que he ido desarrollando para Firefox. Ayudan a la gente y además gano un dinerillo. Recibes feedback fácilmente e incluso me han propuesto ideas de desarrollo de nuevos complementos. Es algo agradable si sale bien, pero no he tenido tiempo últimamente para diseñar nuevas extensiones o mejorar las existentes. Así que algunas extensiones dejaron de ser tan funcionales y decidí que requerían pasar por un proceso de mejora continua de calidad. El Proyecto Ceres.

DMAIC-2

Este proyecto busca actualizar las extensiones para una mejor experiencia de uso. El registro de cambios es voluminoso, aunque en muchos casos la solución adoptada para un complemento es válida para otro.

Lista de cambios



  • DivTranslate

    • La API de ScaleMT había dejado de funcionar. Ahora se usa APY.



  • mozCleaner

    • Errores de escritura



  • The Super Clock

    • Pequeño fallo que hacía que horas con un cero delante no ocupasen lo mismo que las demás. Por ejemplo, 21:04 era representado como 21:4.



  • Google+ Share

    • Arreglar un fallo molesto que hacía que desapareciese el icono en algunas ventanas. Gracias a ismirth por decírmelo. Se actualizó a Australis.

    • Traducción de Google+ Share a diversos idiomas

    • Gestión analítica



  • No más 900

    • Nuevo complemento



  • Google+ Share for Android

    • Actualizar rutas a los módulos de Addon SDK

    • No interrumpir en las selecciones de texto. Se ha cambiado el lugar donde aparece el botón para no quitar la opción de seleccionar texto de Firefox.



  • Google AdSense Earnings

    • Nuevo complemento

    • Módulo en npm de identificación con Google via OAuth2



  • Bc.vc shortener

    • Nuevo complemento



  • DivHTTP

    • Traducciones

    • Ahora viene con su propia copia de httpd.js, puesto que ha sido retirado de Firefox



  • Divel Notepad

    • Traducciones




El objetivo con esto es elevar la calificación que tengo en AMO por encima de las 3 estrellas sobre 5 actuales que tengo. He recibido malas críticas en algunos de estos complementos que me gustaría intentar remediar.]]>
https://blog.adrianistan.eu/proyecto-ceres Thu, 4 Jun 2015 00:00:00 +0000
La gestión de la memoria en Rust https://blog.adrianistan.eu/la-gestion-de-la-memoria-en-rust https://blog.adrianistan.eu/la-gestion-de-la-memoria-en-rust ha sido publicada la versión 1.0 de Rust. El lenguaje diseñado por Mozilla basado en 3 principios:

  • Seguridad

  • Concurrencia

  • Rendimiento


Hoy voy a hablar del primer principio, la razón principal para querer ser un sustituto de C++. Porque C++ está bien, pero puedes liarla mucho si no sabes lo que haces.

Rust

El concepto de dueño


En Rust todo tiene un dueño. No puede haber más de uno ni ninguno, debe ser uno exactamente.
fn main(){
let A = 5;
// El dueño de A es main()

}

Hasta aquí todo es sencillo. Ahora pasaremos la variable A a otra función.
fn sumar(a: i32, b: i32) -> i32{
a+b
}
fn main(){
let A = 5;
let suma = sumar(A,4);
println!("{}",suma);
}

El programa compila y nos da el resultado, que es 9. En los lenguajes de bajo nivel las variables pueden usar memoria del stack o del heap. Un pequeño repaso sobre sus diferencias.
Stack


  • Se reserva su espacio en RAM cuando el programa arranca

  • Son más rápidas de acceder

  • No se les puede cambiar el tamaño

  • Son más seguras


Heap


  • Se debe reservar manualmente la RAM cuando queramos

  • Son más lentas de acceder

  • Pueden cambiar su tamaño en tiempo real

  • Son menos seguras. Pueden dar lugar a fugas de memoria.


En este último caso, la variable A cuyo dueño es main() le pasa la propiedad temporalmente a sumar(). La propiedad se devuelve a main() rápidamente y esta garantizado que así suceda. El compilador lo permite. Veamos ahora un ejemplo más complejo.

Veamos ahora un código más complejo
struct Config{
debug_mode: bool
}

struct App{
config: Config
}

fn main(){
let config=Config{debug_mode: true};

let app=App{config: config};

println!("OK");
}

Por supuesto el código compila pero este de aquí abajo no y solo he cambiado una línea.
struct Config{
debug_mode: bool
}

struct App{
config: Config
}

fn main(){
let config=Config{debug_mode: true};

let app=App{config: config};
let backup=App{config: config}; // He añadido esta línea

println!("OK");
}

La razón es que cuando creamos la estructura de App por primera vez le prestamos config a la estructura app. Así la función main no le puede pasar la propiedad a backup porque ya se la prestó a app.

Préstamos


Para solucionar este problema Rust usa los préstamos. Así la propiedad de config seguirá siendo main() pero lo podrán usar las estructuras app y backup. Para usar referencias usamos el símbolo &.
struct Config{
debug_mode: bool
}

struct App{
config: &Config
}

fn main(){
let config=Config{debug_mode: true};

let app=App{config: &config};
let backup=App{config: &config};

println!("OK");
}

La estrucura ahora acepta &Config en vez de Config. Es de decir usa una referencia en vez de un valor. Sin embargo esto no compilará. El compilador normalmente deduce si es posible hacer una referencia a algo no existente, un fallo común en C++. En caso de tener dudas no compilará. Rust es bastante inteligente pero no es mágico. En el caso de la estructura App, es necesario indicar que la propiedad config vivirá el mismo tiempo que la estructura.
struct Config{
debug_mode: bool
}

struct App<'a>{
config: &'a Config
}

fn main(){
let config=Config{debug_mode: true};

let app=App{config: &config};
let backup=App{config: &config};
println!("OK");
}

He usado la anotación de tiempo llamada a. Puedes poner cualquier nombre pero a es muy corto.

Implementaciones y préstamos


Voy a introducir un concepto de Rust que son las implementaciones. Para haceros una idea rápida, serían como clases de C++, pero solo alojan funciones.
impl<'a> App<'a>{
fn isDebugMode(&self) -> (){
println!("DEBUG MODE: {}",self.config.debug_mode);
}

fn delete(self) -> (){

}
}

He creado dos funciones para implementar App. Son idénticas salvo por un pequeño detalle, una toma el valor self (como this en C++) por referencia y la otra toma el valor directamente.
	let app=App{config: &config};
app.isDebugMode();
app.delete();

Compila y funciona. Cambiemos el orden.
    let app=App{config: &config};
app.delete();
app.isDebugMode();

Ya no compila. La razón es que cuando llamamos a delete() estamos prestando app entera. Ahora delete() es la dueña de app y cuando salimos de la función eliminamos app porque si su dueña ha muerto, app también debe morir (no es tan sangriento como pensais). Rust lo detecta y delete() será la última función que podemos llamar de app. Por cierto si os preguntais como funcionan las implementaciones en Rust (que no son clases), este código haría lo mismo llamando a funciones estáticas. Quizá así veais mejor como se pasa el concepto de dueños y préstamos.
	let app=App{config: &Config};
App::isDebugMode(&app);
App::delete(app);

Diversión con punteros en el heap


Todo estas variables eran del stack que siempre es la manera más sencilla de operar. Vamos ahora a ver como funcionaría esto con punteros. Los punteros operan como variables en el stack que hacen referencia a partes de la memoria que están en el heap. En Rust podemos operar con punteros con máxima seguridad pues todo lo aplicable a variables en el stack sigue siendo válido. Solo hay un dueño y podemos hacer referencias, aunque quizá necesitemos marcar el tiempo de vida manualmente.
	let puntero: Box<i32>=Box::new(42);

Ahora el valor 42 estará en el heap y con puntero podremos acceder a él. Sin embargo como es lógico, no podemos operar directamente con él.
	puntero+1 //No funciona

Para operar el valor directamente tenemos que derreferenciarlo. Se usa *
	*puntero+1 // Sí funciona, y será 43

Así que esta operación sería correcto. Nótese el uso de mut para permitir editar el valor. En Rust por defecto las variables no son mutables. Ese privilegio tiene que ser declarado por adelantado.
	let mut puntero: Box<i32>=Box::new(41);
*puntero+=1;
println!("La respuesta es: {}",*puntero);

Como curiosidad mencionar que la macro println! (en Rust si algo termina con ! es una macro) acepta puntero o *puntero indistintamente ya que se da cuenta si es necesario derreferenciar o no.

El problema final


¿Qué pasaría si copiamos un puntero en otro? Pues como un valor en el heap solo puede tener un dueño, la propiedad será del último puntero.
	let mut puntero: Box<i32>=Box::new(41);
*puntero+=1;
let puntero_inmutable=puntero;
println!("La respuesta es: {}",puntero); // Esta línea no compilará pues el acceso a la respuesta última del universo ahora es propiedad de puntero_inmutable
println!("La respuesta, ahora sí, es: {}",puntero_inmutable);

Como curiosidad, este es un curioso método para bloquear en un determinado momento el acceso de escritura a nuestro puntero aunque es fácil volver a obtener el acceso a escritura con un nuevo cambio de dueño.

Conclusiones


Podemos ver que es un lenguaje que presta mucha atención a la seguridad. C++ es mucho más liberal en ese sentido y Mozilla cree que es un problema a la hora de desarrollar grandes aplicaciones. ¿Qué te ha parecido? Si tienes alguna duda no titubees y pregunta.]]>
https://blog.adrianistan.eu/la-gestion-de-la-memoria-en-rust Fri, 22 May 2015 00:00:00 +0000
Crea tu propio lenguaje de programación https://blog.adrianistan.eu/crea-tu-propio-lenguaje-de-programacion https://blog.adrianistan.eu/crea-tu-propio-lenguaje-de-programacion DesdeLinux hace ya un tiempo y la quiero conservar aquí. La entrada no es la original sino que la he modificado para que siga vigente - MAYO DE 2015

LenguajesProgramacion

Después de escribir el primer artículo sobre cómo crear tu propio sistema operativo, alguien me dijo que si podía hacer un artículo sobre cómo crear un lenguaje de programación. Al principio no hice mucho caso, pero ahora y por otros caminos he aprendido bastante más sobre la creación de los lenguajes de programación. Así pues, vamos a hacer un lenguaje de programación básico, fácilmente empotrable en otros programas y que funcione con una máquina virtual que también diseñaremos. Hoy nos toca hacer la máquina virtual más básica.

Probablemente te preguntes: “¿una máquina virtual? ¿Pero eso no es muy difícil y además ralentiza los programas?” Al contrario, una máquina virtual simple es muy sencilla y relativamente rápida. He elegido Rust como lenguaje para la máquina virtual. Pero, ¿qué es Rust?
Rust es un lenguaje de programación que está enfocado en la seguridad en las ejecuciones, así que utilizándolo será prácticamente imposible que alguien consiga cerrar la máquina virtual. Es un lenguaje compilado en desarrollo creado por Mozilla. Servo, el sustituto de Gecko, se está desarrollando en él. Todavía puede cambiar su sintaxis pero el código que voy a usar va a mantenerse hasta la primera versión estable.

Rust se instala en Linux de manera sencilla. Antes se podía usar un PPA pero ahora el script de RustUp es muy bueno y se encarga de todo.
curl -s https://static.rust-lang.org/rustup.sh | sudo sh

¿Cómo funciona una máquina virtual?


Si sabes como funciona el mundo en ensamblador es exactamente igual, con el stack o la pila. Si no, te lo explico. Imaginémonos el siguiente código:
print 2+3

El ordenador no entiende lo que significa 2+3, ni tampoco sabe qué orden hay que seguir. Los ordenadores funcionan con pilas o stacks en los que se van acumulando datos y se van sacando continuamente. Ese código en nuestra máquina virtual debería ser algo parecido a esto:
PUSH 2
PUSH 3
ADD
PRINT

Básicamente, pondríamos el 2 en la pila en lo alto, el 3 también. ADD sacaría (es decir, lo elimina de la pila y obtiene su valor) los 2 últimos elementos de la pila y añadiría el resultado en lo alto de la pila. PRINT cogería el último elemento de la pila y lo usaría para mostrárnoslo. Ahora hagamos eso en Rust.
Ahora es cuando deberías descargarte el código fuente que está en GitHub. Voy a empezar por el archivo vm.rs

Primeramente deberemos definir un lenguaje para el Bytecode, podríamos usar uno ya existente como el de Java o el CLR de .NET/Mono, pero vamos a crear nosotros uno más básico.
#[deriving(FromPrimitive)]
enum Instruction {
INTEGER = 0x00,
STRING = 0x01,
ADD = 0x02,
SHOWINTEGER = 0x0A,
SHOWVERSION = 0x0E,
EXITVM = 0x0F
}

 

Usamos notación hexadecimal para cada instrucción. Para hacer la traducción entre el código hexadecimal y la instrucción voy a usar la librería (crate en el argot de Rust) de enum_primitive. Antes se podía usar #[derive(FromPrimitive)] pero en Rust 1.0 no está disponible.

Ahora debemos hacer una función que ejecute cada una de esas instrucciones. Para ello debemos leer un byte y compararlo con las instrucciones que tenemos en la enumeración. Si se encuentra alguna que exista se debe ejecutar su acción.
pub fn interpreter(&mut self,bytecode: &'static str) -> (){
for execbyte in bytecode.chars() {
self.execute(execbyte as u8);
}
}

 

Eso hacemos para leer cada byte individualmente y para ejecutarlas:
fn execute(&mut self, execbyte: u8) -> () {
if self.push {
self.push(execbyte);
self.push=false;
}else{
let op: Option<Instruction> = FromPrimitive::from_u8(execbyte);
match op{
None => {
println!("Unknown instruction, skipping...");
},
Some(bc) => {
match bc{
INTEGER => {
self.push=true;
},
ADD => {
let a=self.pop() as int;
let b=self.pop() as int;
let c=a+b;
self.push(c as u8);
},
SHOWINTEGER => {
println!("Integer value {}",self.pop() as int);
},
SHOWVERSION => {
println!("PerinVM v0.1.0");
},
EXITVM => {
println!("Exit VM");
},
STRING => {
println!("Unsupported instruction 'STRING' ");
}
}
}
}
}
}

 

Como ven, diferenciamos si antes se nos dio la orden de PUSH (nuestra orden INTEGER), el siguiente byte será llevado completamente a la pila. Ahí estamos usando dos funciones que no les he enseñado, self.pop() y self.push(), que se encargan obviamente de manejar el stack.
fn push(&mut self, value: u8) -> (){
self.stack.push(value);
}
fn pop(&mut self) -> u8{
let a: Option<u8>=self.stack.pop();
match a{
None => {
println!("Failed to pop");
0
},
Some(result) => {
result
}
}
}

 

No son muy complejas, pero la función de pop tiene mecanismos de detección de errores. De hecho, en Rust, si quitásemos esos mecanismos nos daría un error de compilación. Ahora simplemente debemos llamar en un programa a Perin (nuestra máquina virtual) y que ejecute un bytecode.
mod vm;

fn main(){
let bytecode = "\x0E\x00\x02\x00\x03\x02\x0A";
let mut perin = vm::PerinVM::new();
perin.interpreter(bytecode);
}

 

Ese bytecode puede ser leído de un fichero, pero aquí para simplificar lo he almacenado en una variable. Si lo ejecutamos nos dará el resultado esperado:
Perin v0.1
Perin VM executes FlopFlip bytecode
Starting PerinVM instance
PerinVM v0.1.0
Integer value 5

Todo el código está disponible en GitHub bajo la Apache License 2.0: https://github.com/AdrianArroyoCalle/perin. Para compilar deben tener Cargo instalado y poner:
cargo run
]]>
https://blog.adrianistan.eu/crea-tu-propio-lenguaje-de-programacion Wed, 13 May 2015 00:00:00 +0000
Fliuva, o como crear un servicio de analíticas https://blog.adrianistan.eu/fliuva-o-como-crear-un-servicio-de-analiticas https://blog.adrianistan.eu/fliuva-o-como-crear-un-servicio-de-analiticas motivos que tengo para diseñar un servicio de analíticas desde 0. Ahora veremos como lo he hecho.
Fliuva

No he tocado ningún CSS.

La base de datos


En un servicio de analíticas lo más importante son los datos, así que debemos de definir como almacenaremos la información. En principio Fliuva iba a estar centrado en analizar una única aplicación. Más tarde he pensado en un soporte multiaplicaciones pero no lo voy a desarrollar de momento. Usaría un espacio de nombres delante de todas las tablas y las peticiones GET.
Usuarios = Sesiones

En ciertos servicios de analíticas los usuarios y las sesiones son dos conceptos diferentes. En Fliuva sin embargo, y por razones de simplificar la estructura, ambos conceptos son idénticos y hablaremos de ellos como sesiones.

Tablas


Será necesario tener una tabla de eventos.

Events



  • CATEGORY: Categoría del evento

  • SUBCATEGORY: Subcategoría del evento

  • NAME: Nombre del evento

  • DESCRIPTION: Descripción del evento

  • DATA: Datos del evento

  • ID: Identificador

  • TIME: Hora en que se produjo

  • SESSION: Sesión a la que pertenece el evento


CREATE TABLE IF NOT EXISTS EVENTS(ID INT NOT NULL AUTO_INCREMENT, CATEGORY TEXT, SUBCATEGORY TEXT, NAME TEXT, DESCRIPTION TEXT, DATA TEXT, TIME DATETIME, SESSION TEXT, PRIMARY KEY (`ID`) )

 

Y ya con esta tabla tendremos para almacenar muchos datos. Los campos CATEGORY, SUBCATEGORY, NAME, DESCRIPTION y DATA sirven unicamente para organizar eventos y subeventos en categorías. Los nombres de los campos son triviales. DESCRIPTION no guarda realmente la descripción sino que podemos definir otro subevento. Con 5 campos para categorizar eventos superamos a Google Analytics que tiene CATEGORY, ACTION, LABEL y VALUE. Además VALUE debe ser numérico mientras que en Fliuva todos son de tipo texto (en la práctica, cualquier cosa).

Código de seguimiento


¿Cómo introducir datos en la base de datos desde nuestra aplicación? Con una pequeña llamada GET a /collect. Yo la he definido en un fichero llamado collect.js
var mysql=require("mysql");

// GET /collect

module.exports=function(req,res){
var connection=mysql.createConnection({
host: process.env.OPENSHIFT_MYSQL_DB_HOST,
port: process.env.OPENSHIFT_MYSQL_DB_PORT,
user: process.env.OPENSHIFT_MYSQL_DB_USER,
password: process.env.OPENSHIFT_MYSQL_DB_PASSWORD,
database: "fliuva"
});
connection.connect(function(err){
if(err){
res.send(501,"MySQL connection error\n"+err);
}
connection.query("CREATE TABLE IF NOT EXISTS EVENTS(ID INT NOT NULL AUTO_INCREMENT,"+
"CATEGORY TEXT, SUBCATEGORY TEXT, NAME TEXT, DESCRIPTION TEXT, DATA TEXT, "+
"TIME DATETIME, SESSION TEXT,"+
"PRIMARY KEY (`ID`) )",function(err,results,fields){
if(err){
res.send(501,"MySQL table creation error\n"+err);
}
connection.query("INSERT INTO EVENTS SET ?",{
SESSION: req.query.SESSION,
TIME: new Date(),
CATEGORY: req.query.CATEGORY || "",
SUBCATEGORY: req.query.SUBCATEGORY || "",
NAME: req.query.NAME || "",
DESCRIPTION: req.query.DESCRIPTION || "",
DATA: req.query.DATA || ""
},function(err,results,fields){
if(err){
res.send(501,"Query error\n"+err);
}
res.send("OK");
});
});
});
}


Y se llama muy fácilmente
GET /collect?CATEGORY=<category>&SUBCATEGORY=<subcategory>&NAME=<name>&DESCRIPTION=<description>&DATA=<data>&SESSION=<session>


Si estamos en HTML5, necesitaremos una librería de cliente para poder realizar las llamadas
function login(){
var xhr=new XMLHttpRequest();
xhr.open("GET","/uuid");
xhr.addEventListener("load",function(){
sessionStorage.__fliuvaSession=xhr.responseText;
});
xhr.send();
}

function sendEvent(category,subcategory,name,description,data){
var xhr=new XMLHttpRequest();
var url="/collect"+
"?CATEGORY="+category+
"&SUBCATEGORY="+subcategory+
"&NAME"+name+
"&DESCRIPTION"+description+
"&DATA"+data+
"&SESSION"+sessionStorage.__fliuvaSession;
xhr.open("GET",url);
xhr.send();
}


Así, simplemente hay que llamar a sendEvent. Llamar a login no es necesario siempre que rellenes el valor de sessionStorage.__fliuvaSession correctamente.

Análisis de datos


Ahora debemos analizar los datos. Primero debemos obtener los valores a analizar. La llamada a /get devuelve un fichero JSON con la información completa. En un futuro lo ideal sería espeficicar intervalos de fechas.
//GET /get
var mysql=require("mysql");

module.exports=function(req,res){
var connection=mysql.createConnection({
host: process.env.OPENSHIFT_MYSQL_DB_HOST,
port: process.env.OPENSHIFT_MYSQL_DB_PORT,
user: process.env.OPENSHIFT_MYSQL_DB_USER,
password: process.env.OPENSHIFT_MYSQL_DB_PASSWORD,
database: "fliuva"
});
connection.query("SELECT * FROM EVENTS",function(err,results,fields){
res.send(JSON.stringify(results));
});
}


Y en el cliente tratamos los datos. Aquí es donde debemos nosotros mismos crear las estadísticas según las métricas que hayamos definido. Yo solo voy a poner una métrica universal, usuarios por día.
/* Visualization App */
window.addEventListener("load",function(){
usersPerDay();
sessionsTable();
});

function id(idx){
return document.getElementById(idx);
}

function uniqBy(a, key) {
var seen = {};
return a.filter(function(item) {
var k = key(item);
return seen.hasOwnProperty(k) ? false : (seen[k] = true);
})
}
function ISODateString(d){
function pad(n){return n<10 ? '0'+n : n}
return d.getUTCFullYear()+'-'
+ pad(d.getUTCMonth()+1)+'-'
+ pad(d.getUTCDate());
}

/* Users-per-day */
function usersPerDay(){
var xhr=new XMLHttpRequest();
xhr.overrideMimeType("application/json");
xhr.open("GET","/get");
xhr.addEventListener("load",function(){
var json=JSON.parse(xhr.responseText);
var dataset=new vis.DataSet();
/*var data=json.filter(function(){

});*/
var array=uniqBy(json,function(item){
return item.SESSION;
}); // Eventos de sesiones repetidas eliminados (mismos usuarios). Ahora tenemos sesiones únicas y tiempos distintos
for(var i=0;i<array.length;i++)
{
var time=new Date(array[i].TIME);
var date=ISODateString(time); //time.toISOString().substring(0,time.toISOString().indexOf("T"));

var y;
if(dataset.get(date)==null)
y=1;
else
y=dataset.get(date).y+1;

console.log(date);
dataset.update({x: date, id: date, y: y});
}
var options = {
catmullRom: false
};
var graph2d = new vis.Graph2d(id("users-per-day"), dataset, options);
});
xhr.send();
}

/* Table for sessions */

function sessionsTable(){
var table=document.getElementById("sessions");
var xhr=new XMLHttpRequest();
xhr.open("GET","/get");
xhr.addEventListener("load",function(){
var json=JSON.parse(xhr.responseText);
var data=uniqBy(json,function(item){
return item.SESSION;
});
for(var i=0;i<data.length;i++)
{
var item=data[i];
var tr=document.createElement("tr");
var time=document.createElement("td");
time.textContent=item.TIME;
var session=document.createElement("td");
var link=document.createElement("a");
link.href="/session/"+item.SESSION;
link.textContent=item.SESSION;
session.appendChild(link);
tr.appendChild(time);
tr.appendChild(session);
table.appendChild(tr);
}
});
xhr.send();
}


Que se corresponde a este pequeño HTML
<!DOCTYPE html>
<html>
<head>
<title>Fliuva - Página principal</title>
<meta charset="utf-8"/>
<script src="bower_components/vis/dist/vis.min.js" type="text/javascript"></script>
<link href="bower_components/vis/dist/vis.min.css" rel="stylesheet" media="all" type="text/css">
<script src="/app.js"></script>
</head>
<body>
<h1>Fliuva</h1>
<section>
<h3>Usuarios por día</h3>
<div id="users-per-day"></div>
</section>
<section>
<table id="sessions">
<tr>
<th>Tiempo</th>
<th>Sesión</th>
</tr>
</table>
</section>
</body>
</html>

Visualizar cada sesión por separado es posible con la llamada a /session/NOMBRE_DE_SESION
var mysql=require("mysql");

module.exports=function(req,res){
var session=req.params.session;
var connection=mysql.createConnection({
host: process.env.OPENSHIFT_MYSQL_DB_HOST,
port: process.env.OPENSHIFT_MYSQL_DB_PORT,
user: process.env.OPENSHIFT_MYSQL_DB_USER,
password: process.env.OPENSHIFT_MYSQL_DB_PASSWORD,
database: "fliuva"
});
connection.query("SELECT * FROM EVENTS WHERE SESSION = ?",[session],function(err,results){
if(err)
res.send(502,"Error: "+err);
res.render("session.jade",{events: results});
});
}


Y para un rápido procesamiento he decidido usar Jade con JavaScript en el servidor. Y entonces session.jade queda
doctype html
html
head
title Vista de sesión
meta(charset="utf-8")
body
h1 Vista de sesión
table
tr
th Categoría
th Subcategoría
th Nombre
th Descripción
th Datos
th Tiempo
each event in events
tr
td= event.CATEGORY
td= event.SUBCATEGORY
td= event.NAME
td= event.DESCRIPTION
td= event.DATA
td= event.TIME


Juntando piezas


Por último, la aplicación se tiene que iniciar en algún lado. Server.js contiene el arranque
var express=require("express");
var mysql=require("mysql");
var collect=require("./collect");
var getdata=require("./getdata");
var session=require("./session");
var uuid=require("node-uuid");

var app=express();

app.set("views",__dirname + "/jade");
app.set("view engine","jade");

app.get("/collect",collect);

app.get("/get",getdata);

app.get("/uuid",function(req,res){
res.send(uuid.v4());
});

app.get("/session/:session",session);

app.use(express.static("www"));

var ip=process.env.OPENSHIFT_NODEJS_IP || process.env.OPENSHIFT_INTERNAL_IP || "127.0.0.1";
var port=process.env.OPENSHIFT_NODEJS_PORT || process.env.OPENSHIFT_INTERNAL_PORT || 8080;

var server=app.listen(port,ip);


Y así en un pis pas hemos hecho una aplicación de seguimiento y analíticas en JavaScript. Ahora toca empezar a diseñar estadísticas con los datos que tenemos a nuestra disposición y por supuesto cuando tengamos los datos a obrar en consecuencia.]]>
https://blog.adrianistan.eu/fliuva-o-como-crear-un-servicio-de-analiticas Mon, 6 Apr 2015 00:00:00 +0000
La información es poder https://blog.adrianistan.eu/la-informacion-es-poder https://blog.adrianistan.eu/la-informacion-es-poder

Obteniendo la información


Esto venía para explicar que finalmente y después de pensarlo un rato he decidido crear mi propio sistema de analíticas para Secta Sectarium. Las analíticas pueden ofrecerme valiosa información pero no encontré ningún servicio que me gustase. Muchos sistemas de analíticas están centrados en blogs (como Google Analytics o New Relic Browser) o en aplicaciones móviles (Google Analytics, Flurry). ¿Había algún servicio dedicado solo a juegos? Sí, GameAnalytics es específico pero no tiene API para HTML5 (y las APIs REST no se pueden llamar entre dominios en HTML5, CORS se llama la idea). Google Analytics se puede modificar lo suficiente para funcionar pero ya que tenía que trabajarmelo he preferido crear mi propia solución.

LaGenteSeInventaEstadisticas

Fliuva


Así que he decidido gastar una gear de OpenShift para una aplicación Node.js 0.12 y MySQL 5.5. Entre SQL y NoSQL he elegido SQL porque para introducir datos de eventos que luego, posteriormente, van a ser tratados, SQL da un mejor rendimiento y el esquema de tabla es más común. Las analíticas las podré ver desde la propia aplicación, que usa Vis.js para la visualización.

LaPersonaMasPoderosa

En próximas entradas veremos como se puede crear Fliuva y que métricas son más importantes.]]>
https://blog.adrianistan.eu/la-informacion-es-poder Sun, 5 Apr 2015 00:00:00 +0000
Crea tu primer paquete para Haiku https://blog.adrianistan.eu/crea-tu-primer-paquete-para-haiku https://blog.adrianistan.eu/crea-tu-primer-paquete-para-haiku
SuperFreeCell-1

Nuestra propia rama de desarrollo


Si queremos publicar nuestros cambios a Haikuports, debemos hacernos una cuenta en BitBucket y hacer un fork del repositorio. Esto se hace desde http://github.com/haikuports/haikuports y dándole al botón de fork o dividir. En mi caso creé el repositorio https://bitbucket.org/AdrianArroyoCalle/haikuports-superfreecell. Añadimos el repositorio a nuestra lista de orígenes en Git.




git remote add superfreecell https://bitbucket.org/AdrianArroyoCalle/haikuports-superfreecell



Ubicando el juego en Haikuports


Nada más empezar tenemos que encontrar la categoría a la que pertenecerá el paquete. Cada carpeta dentro de la carpeta haikuports representa una categoría, que siguen el esquema de Gentoo. En mi caso creo que lo conveniente es "haiku-games". Dentro de esta carpeta creamos una con el nombre de nuestro paquete y allí almacenaremos la información sobre nuestro paquete. Estas carpetas deben tener al menos un archivo .recipe y pueden incluir las carpetas licenses y patches con licencias y parches adicionales respectivamente. En mi caso no usaremos ninguna de estas dos carpetas, así que creamos el archivo superfreecell-0.1.0.recipe . Es importante esta estructura para encontrar la versión fácilmente.

El archivo .recipe


El archivo .recipe contiene la información necesaria de metadatos e instrucciones para compilar e instalar el programa en cuestión.




SUMMARY=&quot;Descripción de menos de 70 caracteres&quot;
DESCRIPTION=&quot;
Descripción extensa del programa usando \
para separar entre renglones que no deben superar \
los 80 caracteres
&quot;
HOMEPAGE=&quot;http://pagina-de-inicio.org.es&quot;
SOURCE_URI=&quot;http://una-pagina.com/con-el-archivo-del-codigo-fuente.tar.gz&quot; # Se admiten muchas variaciones aquí. Podemos usar git://, hg://, svn://, bzr://, ftp://, http:// y los formatos de compresión tar.gz, tar.bz2, zip. Se admiten combinaciones de protocolos.
LICENSE=&quot;MIT&quot;
COPYRIGHT=&quot;Año y autor&quot;
REVISION=&quot;1&quot; # Siendo la misma versión del programa, revisiones del propio empaquetado
ARCHITECTURES=&quot;?x86 x86_gcc2 !x86_64&quot; # Arquitecturas compatibles, siendo x86_gcc2 estable, x86 sin probar (untested) pero que debería ir y x86_64 incompatible
if [ $effectiveTargetArchitecture != x86_gcc2 ]; then
ARCHITECTURES=&quot;$ARCHITECTURES x86_gcc2&quot;
fi
SECONDARY_ARCHITECTURES=&quot;x86&quot; # Arquitecturas secundarias
PROVIDES=&quot;
miaplicacion$secondaryArchSuffix = $portVersion # Todos los paquetes se proveen a sí mismos
app:miaplicacion$secondaryArchSuffix = $portVersion # además es una aplicación de Haiku accesible desde los menús
&quot;

REQUIRES=&quot;
haiku$secondaryArchSuffix # Bastante claro. Aquí vendrían librerías en tiempo de ejecución
&quot;

BUILD_REQUIRES=&quot;
haiku_devel$secondaryArchSuffix # Actualmente todos los Haiku tienen haiku_devel pero por si las moscas. Aquí vendrían librerías de desarrollo
&quot;

BUILD_PREREQUIRES=&quot;
cmd:gcc$secondaryArchSuffix # Aquí vendrían las herramientas de línea de comandos. El prefijo cmd: indica que se llama desde la línea de comandos
cmd:ld$secondaryArchSuffix
cmd:make # Hay herramientas que da igual en que arquitectura estén para funcionar correctamente
&quot;

SOURCE_DIR=&quot;LA_CARPETA_CON_EL_CODIGO_FUENTE&quot;

PATCH()
{
# Aquí se ponen los parches que se puedan aplicar son sed
}
BUILD()
{
# Las instrucciones de configuración y compilación. Si usamos autotools
runConfigure ./configure
make
# Si usamos CMake
cmake .
make

}
INSTALL()
{
# Los comandos para instalar la aplicación. Hay que tener cuidado con los directorios especiales de Haiku, que no son POSIX
# Si usamos autotools y CMake
make install

# También podemos copiar manualmente
mkdir -p $includeDir
cp include/libreria.h $includeDir/
# Hay unas cuantas variables de carpetas que podemos usar

addAppDeskbarSymlink $appsDir/MiApplicacion
# Es muy posible que el make install no instale los enlaces para mostrarse en el lanzador de aplicaciones de Haiku
}




Y este sería el caso más básico de receta que podemos hacer. Si empaquetamos librerías la cosa se complica un poco más ya que tenemos que distinguir la parte de ejecución de la parte de desarrollo y entonces tendremos secciones como PROVIDES_devel que es especifico al paquete de desarrollo. Hay otra manera de aplicar parches que es con patchsets. Nosotros editamos la aplicación hasta que funcione y Haikuporter nos generará un archivo con los cambios que hay que aplicar a las fuentes. Es el mejor método para software un poco más complejo.

SuperFreeCell-Recipe

Publicar cambios


Una vez hayamos comprobado que funciona, lo subimos a nuestra copia de Git.




git add haiku-games/superfreecell
git commit -m &quot;SuperFreeCell 0.1.0&quot;
git push superfreecell master




Y desde BitBucket hacemos una pull request o solicitud de integración]]>
https://blog.adrianistan.eu/crea-tu-primer-paquete-para-haiku Fri, 3 Apr 2015 00:00:00 +0000
Instala programas en Haiku con HaikuPorts y HaikuPorter https://blog.adrianistan.eu/instala-programas-en-haiku-con-haikuports-y-haikuporter https://blog.adrianistan.eu/instala-programas-en-haiku-con-haikuports-y-haikuporter
haiku-depot

Haikuports y Haikuporter


Haikuports es una colección se software en forma de recetas que dicen como se deben de compilar los programas pero no los almacena. Un sistema similar al de Gentoo y FreeBSD. Haikuports usa Haikuporter para construir los paquetes así que debemos instalar antes de nada Haikuports y Haikuporter.

Instalando Haikuporter


Instalar Haikuporter requiere que abras la terminal y obtengamos su código fuente




git clone https://bitbucket.org/haikuports/haikuporter




Ahora debemos configurarlo con nuestros datos




cd haikuporter
cp haikuports-sample.conf /boot/home/config/settings/haikuports.conf
ln -s /boot/home/haikuporter/haikuporter /boot/home/config/non-packaged/bin/
lpe /boot/home/config/settings/haikuports.conf




Tendremos que editar un archivo. Os pongo como tengo el mío




TREE_PATH="/boot/home/haikuports"
PACKAGER="Adrián Arroyo Calle <micorreo@gmail.com>"
ALLOW_UNTESTED="yes"
ALLOW_UNSAFE_SOURCES="yes"
TARGET_ARCHITECTURE="x86_gcc2"
SECONDARY_TARGET_ARCHITECTURES="x86"




Aunque en vuestro caso podeis poner "no" en ALLOW_UNTESTED y ALLOW_UNSAFE_SOURCES.

Instalando Haikuports


Volvemos a nuestra carpeta y obtenemos el código




cd ~
git clone https://bitbucket.org/haikuports/haikuports.git --depth=10



Usando Haikuporter y Haikuports


Ya estamos listo para construir cualquier paquete con Haikuporter, no solo los nuestros. Con esto podemos acceder a gran cantidad de software. El uso básico de haikuporter es




haikuporter NOMBRE_DEL_PAQUETE




Aunque si las dependencias nos abruman podemos saltarnoslo




haikuporter NOMBRE_DEL_PAQUETE --no-dependencies




Los paquetes no se instalan automáticamente, se guardan en /boot/home/haikuports/packages y para instalarlos los debemos copiar a /boot/home/config/packages. También podemos compilar con GCC4 en vez de GCC2 si el programa lo soporta. Hay que añadir _x86 al final. Comprobemos que todo funciona con un paquete al azar




haikuporter cmake_haiku_x86




Tardará en actualizar la base de datos y pueden que nos salten errores de arquitecturas pero no hay que preocuparse. En mi caso, Haikuporter quería instalar paquetes del sistema ya que había versiones nuevas en haikuports. Sin embargo, como se que iba a tardar mucho cancelé y ejecuté




haikuporter cmake_haiku_x86 --no-dependencies




Convendría ahora instalar todo lo compilado




cp /boot/home/haikuports/packages/*.hpkg /boot/home/config/packages/


]]>
https://blog.adrianistan.eu/instala-programas-en-haiku-con-haikuports-y-haikuporter Thu, 2 Apr 2015 00:00:00 +0000
Secta Sectarium, JavaScript vs Rust https://blog.adrianistan.eu/secta-sectarium-javascript-vs-rust https://blog.adrianistan.eu/secta-sectarium-javascript-vs-rust

Lenguaje de programación


Todavía no os voy a contar las mecánicas principales del juego así que vamos a ir directamente al tema técnico. Tenía serias dudas de cual usar. Me rondaban la cabeza C++, Rust y JavaScript.

C++



  • Pros

    • Alto rendimiento

    • Soporte multiplataforma (incluso para Haiku)



  • Contras

    • Tareas sencillas tienen que ser implementadas

    • Largos tiempos de compilación si usas librerías

    • Tampoco podía decidir si SDL o SFML




Rust



  • Pros

    • Alto rendimiento

    • Librerías fáciles de usar

    • Un lenguaje nuevo, con el que conviene acostumbrarse



  • Contras

    • Mal soporte en Windows

    • Poco soporte

    • Todavía no ha salido la versión 1.0




JavaScript



  • Pros

    • Soporte multiplataforma

    • Gran cantidad de librerías para todo

    • Lo conozco



  • Contras

    • Bajo rendimiento

    • Es fácil cometer errores simples




Al final me decanté por JavaScript (espero usar TypeScript de verdad) porque aunque el rendimiento puede ser peor, es un amigo (y enemigo a la vez, la historia es larga) conocido. Tiene un soporte para publicar juegos real (en contraposición a Rust) y es bastante rápido desarrollar.

Así que la elección de JavaScript es definitiva y de repente me surgen dos dudas:

  • ¿Es mejor dibujar un mundo 2.5D en WebGL o en Canvas?

  • ¿Grunt o Gulp?


WebGL o Canvas


Inicialmente pensaba hacerlo en Canvas, una API similar a la que esperaba usar en SFML o SDL con C++ o Rust. Fue cuando estaba viendo el issue tracker de un motor de 2.5D en JavaScript cuando comentaban que sería mejor usar WebGL por rendimiento. Y me surgió la duda. Sé que Canvas 2D es acelerado por hardware en Firefox pero ¿es algo en lo que se puede confiable? y aún siendo confiable ¿es peor el rendimiento de Canvas que WebGL?

Estuve investigando y resulta que WebGL es más rápido que Canvas en operaciones 2D si lo usas bien. Interesante concepto. En principio Secta Sectarium no va a ser muy exigente gráficamente así que acepto el desafío ya que soy algo familiar con OpenGL ES 2.0 y WebGL.

Grunt o Gulp


Luego me surgió una duda relacionada con la construcción del proyecto. Había usado Grunt anteriormente pero el esqueleto de juegos npm me quitaba las ganas de seguir usando Grunt. Era poco claro con tantas configuraciones. Sabía que existía Gulp, así que heché un vistazo y me han quedado buenas impresiones respecto a claridad. Así que uso Gulp.

Seguiré informando


Seguiré informando sobre Secta Sectarium y su desarrollo. Próximamente os enseñaré el proceso de documentación.]]>
https://blog.adrianistan.eu/secta-sectarium-javascript-vs-rust Fri, 6 Mar 2015 00:00:00 +0000
Introducción a D-Bus https://blog.adrianistan.eu/introduccion-a-d-bus https://blog.adrianistan.eu/introduccion-a-d-bus Esta entrada la he realizado originalmente para el blog DesdeLinux
dbus

Si llevas algún tiempo en Linux quizás te hayas llegado a preguntar que es D-Bus. D-Bus es un componente incorporado no hace mucho a las distribuciones de escritorio en Linux que previsiblemente jugará un papel muy importante para la programación en Linux.

¿Qué es D-Bus?


D-Bus es un sistema de comunicación entre aplicaciones de muy diverso origen. Con este sistema podremos llamar incluso a aplicaciones privativas (si estas implementan D-Bus). No juega el mismo papel que una librería pues una librería no es un programa independiente y la librería forma parte de tu ejecutable. La idea de D-Bus está inspirada en los objectos OLE, COM y ActiveX de Windows. Los objetos COM de Windows ofrecen una manera sencilla de llamar a cualquier programa desde otro programa llegando incluso a poder incrustarse visualmente uno dentro de otro sin necesidad de usar el mismo lenguaje de programación. D-Bus no llega tan lejos pero ofrece esa comunicación de la que UNIX carecía.

¿Por qué es importante D-Bus?


D-Bus es importante dada la gran diversidad de lenguajes que pueden funcionar en Linux y la gran diversidad también de librerías. Pongamos un ejemplo práctico. Yo quiero mandar una notificación al sistema notify-osd de Ubuntu desde mi aplicación en Node.js. Primero tendría que ver que librería ofrece esa funcionalidad en el sistema, libnotify en este caso, y después debería hacer unos bindings para poder llamar la librería programada en C desde JavaScript. Además imaginemos que queremos hacer funcionar nuestra aplicación con un escritorio que no usa libnotify para las notificaciones.

Usando D-Bus


Entonces hemos decidido que vamos a usar D-Bus para crear la notificación de nuestra aplicación en JavaScript.
var dbus=require("dbus-native");

var bus=dbus.sessionBus();
bus.getService("org.freedesktop.Notifications").getInterface("/org/freedesktop/Notifications","org.freedesktop.Notifications",function(err,notify){

notify.Notify("Probando D-Bus",0,"/usr/share/pixmaps/firefox.png","Título","Cuerpo de la notificación",[],[],5,function(err,id){
console.log("Notificación enviada");
});
});

 

Hay 2 tipos de buses en D-Bus, un D-Bus único al sistema y un D-Bus para cada sesión de usuario. Luego en D-Bus tenemos servicios que son "los nombres de los proveedores D-Bus", algo así como las aplicaciones D-Bus. Después en una estructura como de carpeta están los objetos que puede tener ese servicio o instancias y finalmente la interfaz es la manera de interactuar con los objetos de ese servicio. En este caso es muy redundante pues el servidor de notificaciones es muy simple.

¿Quién usa D-Bus?



  • com.Skype.API

  • com.canonical.Unity

  • org.freedesktop.PolicyKit1

  • org.gnome.Nautilus

  • org.debian.apt

  • com.ubuntu.Upstart


Para averiguar todos los servicios de D-Bus que tienes instalados puedes usar D-Feet]]>
https://blog.adrianistan.eu/introduccion-a-d-bus Sat, 28 Feb 2015 00:00:00 +0000
Usando Node con XULRunner y Gecko https://blog.adrianistan.eu/usando-node-con-xulrunner-y-gecko https://blog.adrianistan.eu/usando-node-con-xulrunner-y-gecko HTML5 para el escritorio han ido en aumento. Soluciones como node-webkit, recientemente renombrado a NW.js, han crecido en popularidad. Juegos como Game Dev Tycoon o aplicaciones como Atraci o Popcorn Time son solo unos ejemplos de apliaciones que usan node-webkit. Sin embargo las personas que preferimos tecnología de Mozilla nos encontrabas con que no había nada que sirviese.

node-xulrunner


Así que tenía que hacer algo. De momento es más que nada una prueba de concepto sin nada reseñable y con miles de bugs potenciales. Lo he diseñado para ser llamado desde una API de CommonJS. Esto permitirá tener un cliente de línea de comandos de manera sencilla, pero de momento no lo voy a hacer. Os enseñaré a usarlo con otro método

Nuestra aplicación HTML5, Node.js, XULRunner


El primer paso será clonar el proyecto node-xulrunner de GitHub.




git clone http://github.com/AdrianArroyoCalle/node-xulrunner html5-app
cd html5-app
npm install




La estructura del proyecto ya es una aplicación empaquetable, de manera que solo tendremos que modificar ciertos ficheros. Necesitaremos editar el archivo test.js para indicar ciertas preferencias sobre la aplicación. Algunos parámetros interesantes son os que puede ser: win32, mac, linux-i686 o linux-x86_64 y version que debe coincidir con una versión de XUL Runner disponible y publicada. El resto de opciones son autoexplicativas cuando las veais. En mi caso test.js queda así:




var xul=require("./index.js");

var options={
os: "linux-i686",
version: "35.0.1",
name: "HTML5 App",
vendor: "Adrián Arroyo",
appVersion: "0.1.0",
buildId: "00000001",
id: "test@node-xulrunner",
copyright: "2015 Adrián Arroyo Calle",
width: 800,
height: 800
};

xul.packageApp(options);




Ahora debemos crear nuestra aplicación propiamente dicha. Se encuentra bajo el directorio app/ y concretamente el fichero main.js será el ejecutado nada más arrancar la aplicación. main.js puede usar todas las APIs de Node. De hecho si usamos npm en esa carpeta funcionaría correctamente. En mi caso:




var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(4200, '127.0.0.1');
console.log('Server running at http://127.0.0.1:4200/');




Y ahora los pasos mágicos. Vamos a la terminal y ejecutamos test.js con Node.




node test.js




Nos saldrá una advertencia y empezará a descargar XULRunner y Node.js. Este ejecución no creará ninguna aplicación. Para, ahora sí, crear la aplicación debemos llamar otra vez a test.js




node test.js




Y en la carpeta build/ tendremos la aplicación lista para ser probada. Simplemente tendremos que ejecutar app

HTML5App-XULRunner]]>
https://blog.adrianistan.eu/usando-node-con-xulrunner-y-gecko Tue, 24 Feb 2015 00:00:00 +0000
Convertir a Haiku en rolling release https://blog.adrianistan.eu/convertir-a-haiku-en-rolling-release https://blog.adrianistan.eu/convertir-a-haiku-en-rolling-release Arch Linux han hecho famoso el concepto de rolling release. ¿No sabes lo qué es? Consiste en el software que está en constante actualización y elimina el concepto de versión, al menos como se pensaba de él. Por ejemplo en Ubuntu que no es rolling release el sistema se actualiza pero llegará un momento que para dar un cambio mayor deberemos cambiar a otra versión (de 12.04 a 14.04 por ejemplo). Arch Linux es rolling release y no tiene versiones, siempre que actualizas estás en la última versión.

Rolling release en Haiku ¿era algo esperado?


Seguramente la gente que conoce Haiku no piense que sea un sistema que aspirase a ser rolling release. Yo tampoco y de hecho no es rolling release oficialmente, sigue teniendo versiones y nightlies pero ahora y gracias al nuevo sistema de paquetería ha sido posible convertir Haiku en rolling release.

Los pasos


Los pasos a seguir son sencillos pero deben hacerse por línea de comandos. Así que abre la aplicación Terminal y escribe:




pkgman list-repos




Esto mostrará los repositorios que haya en el sistema. Tendrás que tener 1 por lo menos llamado HaikuPorts. Lo vamos a eliminar, pues está fijado a una versión y entraría en conflicto con los repositorios de rolling release.




pkgman drop-repo HaikuPorts




Y ahora añadimos el repositorio HaikuPorts de rolling release




pkgman add-repo http://packages.haiku-os.org/haikuports/master/repo/x86_gcc2/current




Ahora ya tendremos las últimas aplicaciones, sin embargo el sistema más básico (el kernel y el navegador) no se actualizarán. Debemos de añadir otro repositorio




pkgman add-repo http://packages.haiku-os.org/haiku/master/x86_gcc2/current




Y ya está. Ahora podemos actualizar el sistema entero (requiere reinicio para usar el nuevo kernel).

Updating




pkgman update



Otros repositorios


Todavía no ha salido la Beta 1 y ya hay repositorios externos para pkgman. Se añaden de igual manera que los anteriores.

  • Guest One's bin ports: http://haiku.uwolke.ru/repo/binaries-x86_gcc2

  • Guest One's Java ports: http://haiku.uwolke.ru/repo/java-ports

  • Clasqm: http://clasquin-johnson.co.za/michel/repo

  • BeSly: http://software.besly.de/repo

  • FatElk: http://coquillemartialarts.com/fatelk/repo


ListRepos]]>
https://blog.adrianistan.eu/convertir-a-haiku-en-rolling-release Sat, 31 Jan 2015 00:00:00 +0000
La experiencia GCI 2014 con Haiku https://blog.adrianistan.eu/la-experiencia-gci-2014-con-haiku https://blog.adrianistan.eu/la-experiencia-gci-2014-con-haiku Google Code-In 2014 y hasta el 19 de enero pude trabajar en la organización que había elegido, en este caso Haiku, como el año anterior.

HaikuToDo


HaikuToDo-1

La tarea consistía en reusar cierto código de una aplicación de tareas para crear un frontend en Haiku usando la BeAPI. Sin embargo ese código no funcionaba. No tenía sistema de compilación y más tarde descubrí que carecía de punto de entrada y ciertas headers no coincidían con la implementación. Así que lo hice de 0. En primera instancia usé SQlite para almacenar las tareas. Sin embargo me pidieron que mejorara mucho más la interfaz. Eso hice y en el camino tuvo que inventarme el sistema de categorías. La siguiente tarea consistía en reemplazar SQlite por algo específico de Haiku y BeOS. Hablo de BeFS y su sistema de Queries. Gracias a C++ solo tuve que crear un objeto que implementase las operaciones de escritura con BeFS y pude mantener el de SQlite (hay que activarlo en tiempo de compilación). La siguiente tarea consistía en añadirle el soporte online. Valía cualquiera pero elegí Google Tasks porque ya tenía cuenta. Toda la gestión de HTTP se debía realizar por las APIs nativas de Haiku. Estas son APIs que BeOS no llegó a incorporar nunca. Además el procesado de JSON corría a cargo de las APIs de Haiku que al igual que las de HTTP están indocumentadas. Obtener la información de Google puede parecer lioso pero una vez lo entiendes es bastante sencillo. Para la autenticación, HaikuToDo abre WebPositive para que te registres con Google y recibes un código. Ese código lo pones en la aplicación y se descargarán tus tareas de Google. El soporte actual es de solo lectura. La aplicación tiene licencia MIT y se puede descargar.

HaikuToDo-2

SuperFreeCell


SuperFreeCell-1

¿Quién no ha jugado al solitario de Windows? ¿Y al Carta Blanca también conocido como FreeCell? Seguro que alguno más pero también muchos. La tarea consistía en hacer el clon de FreeCell para Haiku. Por supuesto usando la BeAPI, en concreto la gran flexibilidad de BView para estas tareas. Para ello tomé como referencia BeSpider, el clon del solitario spider en Haiku. Luego comprendí que no entendía mucho el código y lo empecé a hacer a mi manera. Se me ocurrió hacer las cartas un objeto que se tenía que dibujar. Error. La interación con el resto del mundo se hecha en falta. Luego pensé en que heredasen BView. Error. El número de glitches gráficos era digno de mencionar. Finalmente las cartas pasaron a ser estructuras simplemente e hice toda la lógica en la BView del tablero. El resultado era mucho mejor. Cambié el algoritmo de ordenación de cartas (barajar, si alguien no se enterá) para ganar en velocidad. Antes usé uno con bucles muy simple pero lentísimo. Otros detalles fue que use DragMessage para mover las cartas y que las cartas son PNGs de BeSpider integrados dentro del ejecutable (una cosa que siempre he hechado en falta a Linux). Obviamente al hacer un clon tuve que jugar mucho a otras implementaciones de FreeCell. Jugué a la de Windows 7, Ubuntu 14.04 y Android. Todo sea por SuperFreeCell. La aplicación tiene licencia MIT y se puede descargar

SuperFreeCell-2

Haiku EGL


EGL es una API del Khronos Group para definir superficies sobre las que operar con otras APIs de Khronos Group, entre ellas OpenGL y OpenVG. EGL se ha usado sobre todo en móviles pero el salto a escritorio es inminente (Wayland y Mir dependen de EGL para funcionar). La API es teóricamente multiplataforma aunque en la práctica hay que modificar el código si usamos las X11 o Wayland o en este caso Haiku. La tarea consiste en portar EGL de manera que funcione en Haiku como wrapper de BGLView. La tarea requería modificar código de Mesa 10. Tuve muchos problemas en esta tarea: documentación nula, características exclusivas de C99 (y que no están en C++), desconocimiento de parte del sistema gráfico tanto en Linux como en Haiku, desconocimiento de SCons, etc. Esto me llevó a implementar EGL en un principio como un driver DRM2. Pero luego descubrí que DRM2 solo funciona en Linux. Así que tuve que escribir un driver desde 0. Este crea una configuración placebo y realiza operaciones básicas como crear una ventana y hacer el intercambio de búferes.

Haikuports


El resto de tareas consistían en portar software normal y corriente a Haiku. He portado con más o menos dificultad:

Quería haber portado Node.js pero dependía de libuv que no estaba portado. Después comprobé que nuestra versión del motor V8 estaba desactualizada para Node.js así que no tuve tiempo para terminarla.]]>
https://blog.adrianistan.eu/la-experiencia-gci-2014-con-haiku Fri, 23 Jan 2015 00:00:00 +0000
Post Mortem de Torre Utopía https://blog.adrianistan.eu/post-mortem-de-torre-utopia https://blog.adrianistan.eu/post-mortem-de-torre-utopia esqueleto. Este esqueleto es open-source y lo podeis encontrar en GitHub.

Desarrollo


elevator-action

Torre Utopía esta fuertemente inspirado en Elevator Action. Inicialmente iba a ser un clon del juego pero según avanzó el desarrollo del juego vi partes que no eran fáciles de implementar y que requerirían un rediseño. Estoy hablando sobre todo del scrolling. Torre Utopía inicialmente fue pensado para ser programado en TypeScript que es como el esqueleto lo admite por defecto. Sin embargo terminé usando muy poco las características de TypeScript. Creo que porque no tengo soltura suficiente todavía en TypeScript como para usarlo correctamente. Aun así la inclusión fue positiva en cierto punto porque el compilador era capaz de avisarme de variables no definidas, un error que en JavaScript corriente es tedioso de solucionar pues debes mirar la consola del navegador.

Un acierto, creo yo, fue usar gráficos pre-generados para ir probando las mecánicas. Esto facilita mucho desde el principio saber como va a quedar cada cosa. Sin embargo, este arte pregenerado no fue sustituido al final enteramente dando una mala imagen al espectador. Realmente se cambiarón muchos gráficos pero la falta de tiempo hizo que tuviesen poco nivel de detalle. Una cosa que no me gustó fue que el personaje se moviera mirándote a ti. Y me cabrea más cuando pienso que hacerlo bien es muy sencillo de implementar.

La música era repetitiva y sin copyright pero quizá debería haber un botón para acabar con el dolor de oídos que genera escuchar una y otra vez lo mismo durante varias horas. Aunque el tema del audio lo implementé casi al final. Sin embargo no fue difícil alterar el resto del código para que al subir el ascensor sonase (aunque es posible que con algo de retardo).

La fuente usada quería que fuese estilo pixel para que encajase mejor; fue más difícil de lo que esperaba. Al final encontré una que encajaba con el estilo que quería dar y era gratuita.

TorreUtopia

Distribución


Una vez di por terminado el juego empezó la distribución. Estaba un poco impaciente porque era la primera vez que iba a publicar en tantos sitios a la vez. Sin embargo el tema de crearse cuentas en tantos sitios hizo que me decantase solo por 5 distribuidoras. Estas han sido:

Kongregate


Kongregate

Simple y directo. Me publicaron el juego en poco tiempo y recibí comentarios de usuarios en el juego. Cuando miré por primera vez había generado 4 céntimos de euro. Lo más molesto de Kongregate es el tema fiscal. Ellos insisten en que lo cumplimentes todo aunque no es necesario. Esto es porque se pueden ahorrar impuestos (y tú ganar más) si lo cumplimentas. Para los que no lo conozcan en Kongregate ganas dinero por la publicidad que se ve en Kongregate cuando gente juega a tu juego. Es un portal de juegos pero con mucho tráfico.

FGL


Originalmente diseñado para juegos Flash se ha adaptado y ofrece ahora también para HTML5 y Unity. FGL tiene muchas opciones de distribución y para un principiante puede ser confuso. Por suerte, FGL tiene ayudas en los márgenes muy útiles. En FGL ganamos dinero si nos compran el juego para publicarlo en portales de juegos. Todavía no he recibido ninguna oferta (tampoco espero, es mi primera vez) y se encargan de hacerte una valoración con nota en diferentes aspectos. En mi corta experiencia con FGL puedo decir que los administradores ayudan mucho (me enviaron correos varias veces para comunicarme fallos y como podría mejorar ciertas cosas)

MarketjS


MarketJS es como FGL pero centrado exclusivamente en HTML5. Mal. Creí que por estar centrados en HTML5 (y obviamente más nuevos en el mundillo) tendrían algo más de simpatía. No sé si será la plataforma (mucho más verde en todos los aspectos) pero no recibí respuesta al publicar el juego. Semanas más tarde les envié un correo personalmente. Su respuesta fue que cambiase la imagen de la descripción. La cambié. Volví a publicar. Y ya está. Ninguna respuesta.

Clay.io


Ya les conocía y el proceso es el habitual, algo mejor que de costumbre y todo. En Clay.io ganamos dinero por anuncios como Kongregate, pero también nos pueden comprar el juego como FGL y tiene un tercer modo que es dejar a los portales de juegos el juego tal cual y por los anuncios de Clay.io recibiremos dinero como si fuera en el propio sitio. Puse un anuncio de vídeo al principio y nada más. Y todavía no he ganado nada pero es más sospechoso si pensamos que Clay.io ha añadido mi juego a más portales con el tercer sistema como html5games.club

Firefox Marketplace


firefox-os

Una tienda al estilo Google Play o App Store pero para Firefox OS. Fue aprobado y está disponible pero en esta versión no he añadido anuncios. Comentar que en Firefox Marketplace obtienes los certificados PEGI, ESRB y parecidos de manera gratuita, aunque solo valen para Firefox OS. Las certificaciones en principio te las dan según rellenas unas preguntas sobre el juego. Además hacen pruebas aleatorias cada institución y por ello pueden cambiarte la calificación. En mi caso ESRB me daba T y PEGI me daba +7. Pero en diferentes análisis (llegaron en diferentes meses) me dijeron que al estar los gráficos muy pixelados la violencia que contiene el juego es mínima, por ello actualmente tengo en ESRB la E y en PEGI +3.

Recepción


Vamos a ver las críticas del juego a día de hoy:

  • Firefox Marketplace: Es como wrecking cree pero más chafa 4/5

  • FGL: Definitely not my type of game, but could be fun. You need to think about touch controls (how will one play it on a mobile browser) and also improve graphics, like game over screen for example.

  • FGL: I like the concept for the game! Although the graphics needs some serious improvment but I think that the game could be fun with new graphics and some polish!

  • FGL, la review oficial:

    • Intuitiveness:    5    Needs Improvement

    • Fun:    6    Average

    • Graphics:    5    Needs Improvement

    • Sound:    6    Average

    • Quality:    6    Average

    • Overall:    6    Average

    • Comments: Instructions should be included in game. No-one will understand what's going on without them.Graphics are poor. They're programmer's art, not professional finished product. They're basic and very static(lack of animations). Need to be changed. Music is little low-quality. Also, there should be mute button on every screen. It's very frustrating for players if they can't mute music, especially if it's annoying. There should be always a way back to main menu(Home button or something like that). Policemans are staying in the same place as player and they can't shoot him if you don't move. There should be some text on main menu saying "Press anything to play".



  • Firefox Marketplace, un comentario oficial: The game doesn't cover large screens.

  • Kongregate: Nice game, i recomend adding a menu, and make AI on the enemies.


Conclusión


Hay que mejorar el esqueleto npm, el audio, los sprites (aunque seguiré usando gráficos pre-diseñados al principio). También creo que debe haber un menú, un sistema de anuncios integrados fácilmente desactivables via parámetros GET, integración con Google Analytics, ajustes para quitar el sonido, etc. Espero mejorar todos estos (y más aspectos que seguro que me olvido) para futuros juegos.]]>
https://blog.adrianistan.eu/post-mortem-de-torre-utopia Thu, 22 Jan 2015 00:00:00 +0000
¿Qué nos espera en Haiku Beta? https://blog.adrianistan.eu/que-nos-espera-en-haiku-beta https://blog.adrianistan.eu/que-nos-espera-en-haiku-beta Haiku. Se trata de un sistema operativo libre (bajo licencia MIT) que intenta ser un clon libre de BeOS siguiendo su filosofía. Podríamos compararlo a Linux respecto a UNIX o ReactOS respecto a Windows NT. El caso es que no comparte nada con otros sistemas operativos disponibles en el mercado y eso lo hace muy interesante. La historia de Haiku y BeOS es muy interesante y podeis encontrar mucha información al respecto que no voy a replicar. Sin embargo el desarrollo de Haiku siempre ha sido muy lento y ahora mismo estamos en los albores de la Beta 1 de Haiku. Después de 4 alphas bastante estables se van a atrever a lanzar la primera beta. ¿Por qué ahora? Vamos a ver las novedades que traerá Haiku para saber porque se ha decidido este importante paso.

haiku-basic

Compilador actualizado


Haiku ya dispone de una vía rápida para obtener un compilador con las últimas características que se esperan de él. La versión que está instalada en mi máquina virtual tiene GCC 4.8.3 (publicado el 22 de mayo de 2014). Sin embargo no será el compilador por defecto. Durante mucho tiempo en Haiku solo se podía usar GCC 2.95 y va a seguir siendo el compilador por defecto. Las razones es que el código generado por este compilador son compatibles con el último BeOS. Actualmente en Haiku se ofrecen 4 descargas para x86: una usando solo GCC 2, otra usando solo GCC 2, una híbrida usando por defecto GCC 2 y otra híbrida usando por defecto GCC 4. Recomiendo la híbrida usando por defecto GCC 2.

haiku-gcc

Nuevas arquitecturas


Relacionado con lo anterior Haiku ha podido ser portado a arquitecturas diferentes como PPC, ARM, m68k y x86_64. Decir que la versión ARM es todavía muy prematura aunque se han hecho grandes avances en ello.

Navegador web actualizado


Haiku antes disponía de Firefox. Un día Firefox decidió usar Cairo como librería de renderizado. Esto supone un aumento de velocidad para Firefox en aquel momento pero rompe el port existente con BeOS/Haiku/ZETA. Cairo al igual que GTK nunca han estado soportados de manera oficial en BeOS y con GTK2 el port que había dejó de funcionar. Desde entonces Haiku ha sufrido la carencia de navegadores decentes. Algo más grave si tenemos en cuenta la importancia de HTML5. Así pues ahora mismo en 2014 tenemos los siguientes navegadores gráficos actualizados funcionando sobre Haiku:

Pero también tendríamos versiones antiguas de:

  • Firefox (BeZilla)

  • Opera

  • NetPositive


Qupzilla usa WebKit y Qt, además es bastante inestable. Y su interfaz no es la típica de Haiku. Por eso no es muy usado por la comunidad. NetSurf es bastante estable ya que es una de las plataformas a las que está enfocado, pero NetSurf es un navegador muy pequeño comparado con otros. Usa su propio motor y es muy ligero pero muy poco compatible. De hecho no soporta JavaScript. Nos queda WebPositive, el navegador oficial de Haiku. Usa WebKit pero usando una interfaz nativa de Haiku y con llamadas propias al sistema. Está implementado con la API BWebView para que otras aplicaciones lo puedan usar. Es el navegador más avanzado con un soporte decente para HTML5 y JavaScript. Le sigue faltando WebGL y alguna que otra API pero el soporte de SVG, Canvas 2D, Audio y muchas otras características es bastante decente. Mencionar también que Haiku soporta OpenJDK 7 como un port que funciona decentemente.

haiku-webpositive

Sistema de paquetería


Quizá la característica más importante para Haiku ha sido el sistema de paquetería. Un gran sistema que se podría calificar como de lo mejores en mucho tiempo. Esto no quiere decir que Haiku no tuviese sistema de paquetería antes, realmente tenía uno muy simple pero que servía, eran los OptionalPackages. Sin embargo el concepto de sistema de paquetería ha sido rediseñado para Haiku y el resultado es bastante bueno. Lo primero que vemos son dos programas dedicados a gestionar la paquetería para los usuarios, el primero es pkgman, el de línea de comandos y el segundo es HaikuDepot, para realizar todas las operaciones desde la línea de comandos. Esta es una cosa que me gusta de Haiku, cuidan tanto la línea de comandos como la interfaz gráfica y quieren que tengas las mismas características en cualquier entorno. Respecto a los paquetes, usan una extensión .HPKG y para instalarlos los tendremos que copiar en nuestra carpeta de usuario (actualmente es /boot/home/config/packages) y ya está.
¿Sólo hay que copiar los paquetes? ¿ya está?

Sí, solo hay que copiar. La magia reside en el PackageFS, un sistema de archivos que surge de la lectura del HPKG y montando los ficheros en sus respectivos lugares en modo lectura. Para ello hay un demonio vigilando la carpeta y si recibe cambios los descomprime y los monta en el sistema de ficheros. Esto influye a que ahora solo la carpeta /boot/home pueda ser escrita simple y llanamente porque el resto de carpetas (/boot/system es la única que veo) están montadas en modo solo lectura de los paquetes del sistema. Estos estan localizados en /boot/system/packages. En esta carpeta también podremos escrbir y borrar pero teniendo en cuenta que el HPKG con el kernel también está ahí conviene tener cuidado con la carpeta. Y la cosa se hace más graciosa cuando comprobamos que cada paquete tiene cargada su instancia del sistema operativo y sus librerías (/boot/system/package-links). La verdad es que las instalaciones son muy rápidas y funciona por lo general. Si quieres saber como crear paquetes para Haiku, revisa este post. También crearemos un repositorio y veremos como hacer Haiku un sistema operativo 100% rolling release usando los HPKG.

haiku-depot

Conclusión


Me habré dejado cosas seguro y además la compatibilidad con aplicaciones de terceros ha aumentado. Haiku se acerca a su primera beta y las cosas se ponen muy pero que muy interesantes.]]>
https://blog.adrianistan.eu/que-nos-espera-en-haiku-beta Mon, 17 Nov 2014 00:00:00 +0000
Esqueleto de juegos NPM https://blog.adrianistan.eu/esqueleto-de-juegos-npm https://blog.adrianistan.eu/esqueleto-de-juegos-npm One Game a Month pero mis herramientas no estaban listas tal y como recomendaba el libro The Game Jam Survival Guide de Christer Kaitila. Así que este mes lo dediqué a hacer herramientas para las juegos. Concretamente he desarrollado dos sets de herramientas (esqueletos a partir de ahora). Uno es de JavaScript/TypeScript y el otro de C++. Quería haber hecho uno de Rust, pero hasta que no salga la 1.0 y lo pueda probar en Windows en condiciones no habrá esqueleto de Rust. Así pues hecha la introducción, en este post voy a hablar del esqueleto de JavaScript/TypeScript que es el que hice primero.

Descripción del esqueleto


El esqueleto ha sido diseñado alrededor de Grunt para su fácil manejo. Me encanta ver como Grunt puede con todo lo que le echen y más (lo único que le falta es un plugin nativo de C++ al estilo de Gradle). Actualmente el esqueleto usa TypeScript para el código. Este maneja las definiciones con TSD, se compila y finalmente se le  pasa por Browserify para convertirlo en un único fichero. A parte no hay ficheros HTML, sino que son plantillas Jade; la razón no es muy clara pero prefería tener Jade antes que HTML básico. Quizá me sentía inspirado y pensé que le daba un toque más node.js. Y el CSS tampoco se usa directamente, sino que debemos pasar por el compilador de LESS (aquí la ventaja es más clara que con Jade/HTML). Adicionalmente podemos generar documentación si estuviese documentado el código con TypeDoc y generamos una página del manual de UNIX con markedman. Posteriormente en etapas finales del juego podemos generar iconos de todos los tamaños imaginables (Apple iOS iPhone sin retina, con retina, iPad, iPad mini,... incluso para el navegador Coast). Además se minifican las imágenes con OptiPNG y sus equivalentes para JPG y GIF. Para acabar se añade una tag al repositorio Git y se publica en el repositorio, pero en el branch gh-pages. También he diseñado otra ruta para el empaquetamiento en local de la aplicación. Usando NodeWebkit y un plugin para hacer un paquete Debian (tengo pendiente el RPM) y comprimiendo el resultado en un ZIP listo para el Firefox Marketplace. Además, sin estar automatizados hay código para Ubuntu Touch, Android y Chrome OS.

¿Cómo se usa?


Buena pregunta. Primero debemos clonar el repositorio Git y tenemos que tener en cuenta que a partir de ahora podemos modificar todo con fin de que se adapte a nuestras necesidades. Entonces clonamos con:



git clone https://github.com/AdrianArroyoCalle/skeleton-npm-game TU_JUEGO



Ahora debemos instalar las dependencias. Te recomiendo que vayas a por un café o algo ya que el sistema de dependencias empotradas de NPM hace que el tamaño de la descarga se nos vaya de las manos con ciertas dependencias duplicadas y reduplicadas. Así que entra en la carpeta creada llamada TU_JUEGO y pon:



npm install



Adicionalmente si no tienes Grunt instalado, instálalo con:



npm -g grunt-cli



Es posible que según tu configuración lo debas ejecutar como root o no.
Una vez descargado solo nos quedaría empezar el desarrollo. Podemos hacer una pequeña prueba de que todo se haya instalado con:



grunt test


Librerías incluidas


He incluido las siguientes librarías y me gustaría añadir unas cuantas más pronto:

  • Babylon.js

  • three.js

  • GameJS

  • Box2d.js

  • Cannon

  • meSpeak

  • Bongo.js

  • Hammer.js

  • Canvace

  • i18next


Y sin duda me gustaría añadir alguna más, pero con estas creo que se pueden dar muchas combinaciones.He intentado recopilar la mayor cantidad de sitios donde publicar los juego una vez terminados.

Y una cosa más...


La lista ya es bastante grande pero no están probados todas las empresas allí expuestas. Además hay que tener ciudado ya que hay tiendas que son incompatibles entre ellas.]]>
https://blog.adrianistan.eu/esqueleto-de-juegos-npm Sun, 26 Oct 2014 00:00:00 +0000
SmartOS, una introducción https://blog.adrianistan.eu/smartos-una-introduccion https://blog.adrianistan.eu/smartos-una-introduccion SmartOS. SmartOS es el sistema operativo de Joyent para aplicaciones cloud. Usa un kernel illumos (fork de OpenSolaris cuando Oracle cerró el proyecto, a nivel técnico comparte mucho con Solaris 10) y usa extensivamente características únicas de Solaris. Además, es el único illumos que tiene KVM, una característica de Linux para permitir virtualización de alto rendimiento.

Descargando SmartOS


Lo primero que tenemos que hacer para probar SmartOS es descargarlo. Lo podemos descargar desde SmartOS.org. Allí encontraremos un enlace a la wiki donde está alojada la descarga. En SmartOS no se habla de versiones, hay versiones nuevas cada 2 semanas y se identifican las descargas con las fechas. La descarga está disponible en varios formatos. El formato recomendado es USB, pero para este post vamos a descargar la imagen de VMware. La descarga no es muy grande (~250 MB) y cuando lo tengamos lo descomprimimos en alguna carpeta. Abrimos VMware Player (o VMware Fusion o VMware Workstation, el que te guste más) y desde allí abrimos la máquina virtual. Nos preguntará que si la hemos movido o copiado. Decimos que la hemos copiado. Ahora tenemos que arrancarla. Antes de nada comprueba que tu ordenador puede ejecutar máquinas virtuales de 64 bits, ya que SmartOS solo es de 64 bits. Una vez lo hemos arrancado nos aparecerá una imagen de GRUB. Seleccionamos la primero opción Live 64-bit (text). Si tienes experiencia instalando sistemas operativos verás que aquí no hay ninguna entrada para instalar. Esto es porque SmartOS no se instala, se configura.

Configurando SmartOS


Ahora nos saldrá un asistente con una serie de preguntas. Respondemos a ellas. En el tema de la conexión yo elegí DHCP y tenía configurada la máquina virtual para conectarse usando NAT. En el apartado de discos destacar que solo podemos seleccionar el último. Si elegimos el primer disco que se nos ofrece estaremos sobreescribiendo la imagen Live de SmartOS y la suiente vez no arrancará. Cuando hayan terminado las preguntas se reiniciará y en GRUB seleccionamos la misma opción. Ahora veremos un curioso login con el símbolo de Joyent. En este momento el sistema ya está configurado para empezar a trabajar dentro de él. Podemos usar SSH o la terminal del ordenador.

Creando la SmartMachine


Ahora vamos a crear una SmartMachine. ¿Qué es una SmartMachine? Es una zona dentro de SmartOS autocontenida que no puede interactuar con el exterior. Es una manera de virtualización que ofrece SmartOS (la otra es KVM). En las SmartMachines será el único lugar donde podremos instalar software (realmente sí se puede instalar fuera de una zone con un instalador manual, pero no es recomendable). Bien, ahora debemos actualizar la lista de imágenes base para las SmartMachines.
imgadm update

Ahora buscamos la que más se adecue a lo que buscamos.
imgadm avail

En mi caso buscaba algo que tuviese ya Node.js e hice esto.
imgadm avail | grep node

Y la descargamos con
imgadm import UUID_DE_LA_IMAGEN

Es importante ver que en SmartOS el uso de los UUID está muy presente como veremos

Ahora que ya tenemos la base vamos a definir nuestra SmartMachine mejor. Así pues creamos un fichero JSON tal que así. Yo lo he decidido guardar en /tmp/node-zone.json
{
"brand" : "joyent",
"dataset_uuid": "NUESTRO_UUID",
"resolvers" : ["8.8.8.8"],
"nics" : [
"nic_tag" : "dhcp",
"ip" : "dhcp"
],
"autoboot" : true,
"ram" : 512
}

Y en hay muchísimas más opciones. Puedes verlas todas en el manual de vmadm
man vmadm

Ahora creamos una nueva zone basada en nuestra imagen base con:
vmadm create -f /tmp/node-zone.json

Ahora podemos encontrar las zonas que estan activas:
zoneadm list -civ

Como vemos hay dos zonas activas, una es la global, en la que estamos trabajando. La otra es la que acabamos de crear y que tiene un nuevo UUID asignado. Mencionar que las zonas las podemos encender y apagar con vmadm también:
vmadm stop UUID && vmadm start UUID

Bien, para entrar a las zonas podemos hacerlo con zlogin
zlogin UUID

y estaremos dentro de una zona. En mi caso se puede comprobar que ya tengo Node.js instalado con
node -v && npm -v

Dentro de la SmartMachine


Ahora podemos instalar paquetes para la zona con pkgin. Por ejemplo digamos que quiero instalar CMake.
pkgin update && pkgin install cmake

Ahora debemos ajustar todo para la aplicación que deseemos probar. Una buena manera para empezar sería clonar un repositorio Git en la carpeta dentro del usuario de la carpeta /home. Luego instalaríamos todo con
npm install

Ahora crearemos el servicio. Creamos un fichero XML parecido a este:
<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
<service_bundle type="manifest" name="sonzono">
<service name="site/sonzono" type="service" version="1">

<create_default_instance enabled="true"/>

<single_instance/>

<dependency name="network" grouping="require_all" restart_on="refresh" type="service">
<service_fmri value="svc:/milestone/network:default"/>
</dependency>

<dependency name="filesystem" grouping="require_all" restart_on="refresh" type="service">
<service_fmri value="svc:/system/filesystem/local"/>
</dependency>

<method_context working_directory="/home/admin/sonzono">
<method_credential user="admin" group="staff" privileges='basic,net_privaddr'  />
<method_environment>
<envvar name="PATH" value="/home/admin/local/bin:/usr/local/bin:/usr/bin:/usr/sbin:/bin"/>
<envvar name="HOME" value="/home/admin"/>
</method_environment>
</method_context>

<exec_method
type="method"
name="start"
exec="/opt/local/bin/node /home/admin/sonzono/app.js"
timeout_seconds="60"/>

<exec_method
type="method"
name="stop"
exec=":kill"
timeout_seconds="60"/>

<property_group name="startd" type="framework">
<propval name="duration" type="astring" value="child"/>
<propval name="ignore_error" type="astring" value="core,signal"/>
</property_group>

<property_group name="application" type="application">

</property_group>


<stability value="Evolving"/>

<template>
<common_name>
<loctext xml:lang="C">node.js sonzono</loctext>
</common_name>
</template>
</service>
</service_bundle>

Lo guardamos dentro de la configuración con.
svccfg import sonzono.xml

Y ya podemos activar el servicio
svcadm enable sonzono

Podemos ver los logs de todos los servicios en /var/svc/log/

Y con esto creo que ya teneis suficiente información acerca de SmartOS como para ir jugueteando con él. Quiero mencionar que SmartOS es un sistema operativo con muy poco soporte pero que poco a poco se va haciendo más importante.

Referencias


https://github.com/isaacs/joyent-node-on-smart-example

http://docs.instantservers.telefonica.com/display/isc2/Developing+a+Node.js+Application

http://wiki.smartos.org/display/DOC/How+to+create+a+zone+%28+OS+virtualized+machine+%29+in+SmartOS

http://www.machine-unix.com/beginning-with-smartos/]]>
https://blog.adrianistan.eu/smartos-una-introduccion Mon, 13 Oct 2014 00:00:00 +0000
Lumtumo ya es open source https://blog.adrianistan.eu/lumtumo-ya-es-open-source https://blog.adrianistan.eu/lumtumo-ya-es-open-source Azpazeta, este año sin embargo voy a publicar Lumtumo. Básicamente comentar que Azpazeta no está en desarrollo y sí otro tipo de juegos como Lumtumo que según puedo observar en BitBucket el último commit es de abril de este año. Realmente Lumtumo ya había sido publicado y se podía adquirir previo pago. Sin embargo, a partir de ahora Lumtumo tendrá una licencia open-source auténtica. Concretamente va a tener la GNU GPLv2 y seguirá alojado en BitBucket, aunque ahora el repositorio será abierto.

Lumtumo
En Lumtumo tenemos que ir destruyendo las naves enemigos que se opongan a nosotros. Para ellos podremos lanzar misiles y tendremos un tiempo para acabar con todos antes de que lleguen refuerzos. ¿Serás capaz de mantener a las naves alienígenas fuera de nuestro planeta?

Así pues ya puedes descargar el código fuente original: http://bitbucket.org/AdrianArroyoCalle/lumtumo]]>
https://blog.adrianistan.eu/lumtumo-ya-es-open-source Thu, 25 Sep 2014 00:00:00 +0000
La Odisea de una aplicación comercial hecha en Ogre y CEGUI https://blog.adrianistan.eu/la-odisea-de-una-aplicacion-comercial-hecha-en-ogre-y-cegui https://blog.adrianistan.eu/la-odisea-de-una-aplicacion-comercial-hecha-en-ogre-y-cegui OGRE y CEGUI. Y la verdad es que la odisea es larga. Así que he decidido comparar todas las opciones, sus pros y sus contras.

OGRE

Opciones


Las opciones que he valorado para comparar son:

  • Paquetería nativa DEB, RPM y TAR.XZ

  • Distribuir un TAR.GZ binario

  • Estatificar (palabro inventado) el binario

  • Paquetería multi-distro, Autopackage, Listaller


Paquetería nativa DEB, RPM y TAR.XZ


Esta opción, es a priori la más conveniente. El usuario dispondrá de un bonito paquete nativo (que se podrá actualizar) y se le gestionarán las dependencias, por ello ahorrará espacio en disco. Además tendrá una integración real con el sistema, lo podrá desinstalar desde donde acostumbra a desinstalar y el juego le aparecerá en los menús. Sin embargo los problemas salen enseguida, por una parte tienes que centrarte en ofrecer varios sistemas de distribución. Por otra parte, los paquetes nativos suelen no llevarse muy bien con otros paquetes de otras arquitecturas, ya que en algunos casos se nos pedirá instalar prácticamente el sistema de 32 bits encima del de 64, cuando realmente no es necesario. Además el tema de las dependencias es un arma de doble filo, pues en el caso de OGRE y CEGUI, o no hay, o están desfasadas. Así pues nuestro paquete puede quedarse inservible pronto. Y una peculiaridad de OGRE, resulta que tenemos que definir en plugins.cfg la localización de los plugins que usemos (normalmente con definir el de OpenGL ya sirve), pero la localización de esos plugins puede estar dispersa entre sistemas y arquitecturas. Con lo que deberíamos modificar el fichero plugins.cfg en cada sistema y arquitectura.

Distribuir un TAR.GZ binario


Visto lo anterior, parece ser más cómodo distribuir un TAR.GZ binario. Muchas aplicaciones como Oracle Java o Mozilla Firefox son distribuidas desde el sitio oficial usando este sistema. Sin embargo, y aunque pueda parecer una buena idea, tiene algún que otro inconveniente. En primer lugar debemos intentar usar librerías estáticas. Si las librerías que usamos tienen esa opción, correcto. Puede que esas librerías dependan en otras que no puedan ser estáticas, y aunque lo fueran, añadirían más carga al ejecutable y complejidad en la compilación. Si dependemos de librerías compartidas las podemos poner en una carpeta que tengamos dentro del TAR.GZ y usar RPATH con $ORIGIN o LD\_LIBRARY\_PATH. El caso es que hay ciertas herramientas que nos pueden servir para ello. Por ejemplo cpld nos puede servir para copiar todas las dependencias. El problema es que el proceso no puede ser automatizado completamente ya que el script no puede diferenciar una dependencia base de Linux de una que realmente debamos distribuir. Así que despúes de usar la herramienta nos tocará volver a revisar los ficheros que ha copiado ya que habrá copiado libc, libm, libpthread, libX11 y otras que posiblemente no necesitemos. Pero si no las copiamos podríamos tener problemas en sistemas de 64 bits. Una solución efectiva consiste en hacer un pequeño script que cargue las librerías según el sistema en el que estemos, habiendo usado el script en un sistema de 32 bits y en otro de 64 bits. Esto puede parecer razonable, pero tanto OGRE como CEGUI usan una arquitectura basada en plugins. Estos plugins no pueden ser analizados por ninguna herramienta y deberemos saber manualmente que plugins tendremos que pasar a  el comprimido. Por supuesto debemos separar las dos arquitecturas. En OGRE es fácil pasar los plugins ya que por defecto se usa el fichero plugins.cfg para leer los plugins, pero CEGUI no tiene esa característica (o yo no la he encontrado). Sin embargo es una alternativa muy sólida.

Estatificar


Una opción que encontré mientras buscaba una solución se puede llamarla como estatificar. Es un método poco común que coge las dependencias compartidas y las empaqueta en un único binario. No se debe confundir con compilación estática ya que estatificar requiere un binario ya compilado con dependencias. Únicamente hay dos aplicaciones que hacen esta función, y las dos son del mismo creador, aunque difieren en el funcionamiento de base. Por un lado Statifier, open source y gratuita, trabaja a un modo más rudimentario; por otro lado, Magic Ermine, trabaja con un sistema más perfeccionado, es de pago pero tiene una demo gratuita para probar. Los resultados no eran muy buenos, Statifier modificó el binario de manera que ya no arrancaba, por otro lado Magic Ermine funcionaba correctamente, pero el plugin de OpenGL de OGRE usa libGL.so, y como es cargada dinámicamente, Magic Ermine no sabe que tenía que empaquetarla y como consecuencia falla con un bonito error.

Paquetería multidistro


Autopackage, Listaller, 0install, ... Prometen mucho pero no funcionan bien. Autopackage ha sido descontinuado en favor de Listaller, Listaller se tiene que integrar con AppStream y PackageKit, pero esos mismos componentes no están finalizados, Listaller falla más que una escopeta de feria en la práctica y la documentación es pésima. Realmente espero que mejoren ya que prometen mucho, pero no se cuando será realmente usable. 0install trabaja de otra manera y al menos en mi caso, fallaba y no podía siquiera empaquetar una simple aplicación. Esta opción quizá en otro momento, pero ahora no.

Conclusión


Hemos visto 4 opciones. Dos de ellas no funcionaban bien en la práctica (estatificar y la paquetería multidistro), las otras dos eran tediosas, pero si se estudia al detalle puede funcionar bien. Así pues podemos elegir entre la paquetería nativa y el TAR.GZ binario con scripts de dependencias. Así pues cada cuál elija la que más le guste.]]>
https://blog.adrianistan.eu/la-odisea-de-una-aplicacion-comercial-hecha-en-ogre-y-cegui Sat, 30 Aug 2014 00:00:00 +0000
Mis predicciones para el futuro (en lenguajes de programación) I https://blog.adrianistan.eu/mis-predicciones-para-el-futuro-en-lenguajes-de-programacion-i https://blog.adrianistan.eu/mis-predicciones-para-el-futuro-en-lenguajes-de-programacion-i
Python
Python va a subir su popularidad con a corto plazo debido a su implantación en la educación. Si se consigue la transición a Python 3 que la gente lleva esperando tanto tiempo podría estabilizarse pero si no tendríamos en el problema de compatiblidad con grandes aplicaciones en Python 2. Quizá interese a largo plazo un nuevo JIT, de manera que este aumente su velocidad. Por estos motivos y teniendo en cuenta su sintaxis no de C creo que a largo plazo podría bajar considerablemente su popularidad.

Perl
Sencillamente cada vez va a bajar más. Es un lenguaje que para la mayoría resulta demasiado complejo teniendo otras alternativas para el scripting como Python, Bash o Node. Perl se seguirá usando en las tareas de compilación debido a la gran dependencia de las autotools pero no creo que evolucione mucho más de lo que es. Habrá que ver como reacciona la gente con Perl 6. Mencionar también que los sitios usando CGI van poco a poco a ir desapareciendo. De hecho el módulo de CGI está desrecomendado dentro de Perl 5.22 .

Node y JavaScript
Le veo un gran futuro a esta plataforma, al igual que a las bases de datos al estilo de MongoDB y al gran gestor de paquetes NPM. Su rapidez y el concepto de asíncrono van a ser sus grandes bazas. Me parece que va a ser un gran contrincante en el mundo web y también para el scripting, pudiendo sustituir eventualmente a Python y Perl en esas tareas. En el lado del cliente su uso es obligatorio pero va a crecer mucho con las tecnologías HTML5.

PHP
Se estabilizará bastante. Dentro de lo que cabe, es rápido para la poca memoria que gasta en comparación con otros lenguajes (Python, Java,...). Se va a seguir usando bastante sobre todo debido al gran público que conoce PHP. Además muchas empresas seguirán usando PHP antes que arriesgar con otros lenguajes más novedosos.

Java
Perderá adeptos. El modelo actual de Java es a mi parecer demasiado estricto y complejo y además cuenta con limitaciones de soporte en escritorio y perdida de velocidad en la web. Opino que si bien Java es de los lenguajes más populares, va a perder comunidad, que se va a desplazar a otros lenguajes más cómodos. Pero será muy gradual debido a la misma razón que PHP, muchas empresas seguirán usando Java un tiempo

Ruby
Ruby va a ser más importante sobre todo en scripting local, y quizá algo menos en web. La razón es que Ruby no aporta atractivas ventajas para las empresas al cambiar de PHP o Java a Ruby. Por eso creo que va a crecer más en scripting local, aunque posiblemente las startups usen Ruby para sus proyectos.

C y C++
Grandes proyectos seguirán usando C y C++ y muy dificilmente cambien. Los nuevos proyectos quizá se sientan menos atraídos por C++ teniendo Rust y Go, pero aguantará mucho.

C#
Crecerá. En cuanto la plataforma .NET se abra más a diferentes sistemas, el crecimiento será exponencial. Y ese es el rumbo que se está tomando con iniciativas como Xamarin o ASP.NET vNext, que han comunicado que será open-source y funcionará sobre Mono en Mac OS X y Linux.

Rust
Creo que este lenguaje va a dar mucho que hablar, no hoy, pero sí en un futuro a medio plazo. Rust aporta ventajas al venerado C++ y conserva su velocidad. Creo que Rust va a ocupar un lugar en los videojuegos, creo que en algún tiempo veremos algún Triple A hecho en Rust como lenguaje principal.

Seguiré con mis predicciones en otro post]]>
https://blog.adrianistan.eu/mis-predicciones-para-el-futuro-en-lenguajes-de-programacion-i Wed, 2 Jul 2014 00:00:00 +0000
PortAudio + libsndfile. Reproducir Ogg Vorbis, FLAC y WAV en C++ https://blog.adrianistan.eu/portaudio-libsndfile-reproducir-ogg-vorbis-flac-y-wav-en-c https://blog.adrianistan.eu/portaudio-libsndfile-reproducir-ogg-vorbis-flac-y-wav-en-c Lo primero es descargar e instalar las librerías, ambas son multiplataforma. En Windows irás a la página oficial y seguirás las instrucciones:

A su vez si tenemos que compilar libsndfile también necesitaremos libogg y libvorbis para activar la reproducción de Ogg Vorbis o las librerías de FLAC para activar FLAC en la librería. El soporte para WAV es nativo y no hace falta nada.
En Linux podemos ahorrarnos tiempo instalando los paquetes binarios. En Debian/Ubuntu:
sudo apt-get install portaudio19-dev libsndfile1-dev

Ahora creamos un fichero de C. Atención, aunque C++ sea compatible con el código de C, este código de C tiene problemas en compiladores de C++. Así pues el código de aquí abajo da error con g++ pero funciona correctamente y sin advertencias en gcc. Si quieres insertarlo en un programa de C++, sigue creando este fichero como fichero de C y en el header encapsula la función definida en un "extern C":
extern "C" {   
miFuncionAudio();
}

Ahora en el ejemplo voy a usar una función main para no liarnos y usar solo C, pero eso serán los pasos a seguir si tienes C++.  Creamos el fichero con extensión .c y escribimos la función main(). Ahora abriremos el archivo de audio con la librería encargada de decodificar el audio, libsndfile. Después inicializamos PortAudio y obtenemos el dispositivo de audio por defecto (en el caso de mi Ubuntu, es ALSA). Configuramos el dispositivo con los datos que tenemos del fichero. Abrimos un stream de PortAudio que leerá el archivo. Esta función necesita dos funciones de callbak que se crean más arriba. La principal tiene como objetivo leer el fichero más y la otra hacer algo cuando termine el fichero (se puede dejar vacía). En la de leer el fichero tenemos que tener en cuenta si el equipo es Mono o Estéreo. Yo no he llegado a saber como detectarlo, así que hago la multiplicación por 2 para el estéreo. Luego en el flujo principal iniciamos el stream, sonará, esperamos 10 segundos, paramos y cerramos el stream. Finalmente cerramos el fichero de audio y deshabilitamos PortAudio. Fácil y sencillo. Ahora el código:
#include "portaudio.h"
#include "sndfile.h"

SF_INFO sfinfo;
PaStreamParameters out_param;
PaStream * stream;
PaError err;
SNDFILE * file;
static int
output_cb(const void * input, void * output, unsigned long frames_per_buffer,
const PaStreamCallbackTimeInfo *time_info, PaStreamCallbackFlags flags, void * data)
{
SNDFILE * filex = data;
/* Here you need to multiply per 2 for stereo and per 1 for mono*/
sf_read_short(filex, output, frames_per_buffer*2);
return paContinue;
}
static void
end_cb(void * data)
{
Pa_StopStream(stream);
Pa_CloseStream(stream);
sf_close(file);
Pa_Terminate();
}
#define error_check(err) \ do {\ if (err) { \ fprintf(stderr, "line %d ", __LINE__); \ fprintf(stderr, "error number: %d\n", err); \ fprintf(stderr, "\n\t%s\n\n", Pa_GetErrorText(err)); \ return err; \ } \ } while (0)
int main(int argc, char ** argv)
{
file = sf_open(argv[1], SFM_READ, &amp;sfinfo);
printf("%d frames %d samplerate %d channels\n", (int)sfinfo.frames,
sfinfo.samplerate, sfinfo.channels);
/* init portaudio */
err = Pa_Initialize();
error_check(err);
/* we are using the default device */
out_param.device = Pa_GetDefaultOutputDevice();
if (out_param.device == paNoDevice)
{
fprintf(stderr, "Haven't found an audio device!\n");
return -1;
}
/* stero or mono */
out_param.channelCount = sfinfo.channels;
out_param.sampleFormat = paInt16;
out_param.suggestedLatency = Pa_GetDeviceInfo(out_param.device)-&gt;defaultLowOutputLatency;
out_param.hostApiSpecificStreamInfo = NULL;
err = Pa_OpenStream(&amp;stream, NULL, &amp;out_param, sfinfo.samplerate,
paFramesPerBufferUnspecified, paClipOff,output_cb, file);
error_check(err);
err = Pa_SetStreamFinishedCallback(stream, &amp;end_cb);
error_check(err);
err = Pa_StartStream(stream);
error_check(err);
printf("Play for 10 seconds.\n");
Pa_Sleep(10000);
err = Pa_StopStream(stream);
error_check(err);
err = Pa_CloseStream(stream);
error_check(err);
sf_close(file);
Pa_Terminate();
return 0;
}

Y para terminar un diagrama de salidas de audio nativas soportadas por PortAudio:
]]>
https://blog.adrianistan.eu/portaudio-libsndfile-reproducir-ogg-vorbis-flac-y-wav-en-c Wed, 4 Sep 2013 00:00:00 +0000