Adrianistán https://blog.adrianistan.eu El blog de Adrián Arroyo es Diario, a fast and safe blog engine Los blogs cada vez son más importantes, no menos https://blog.adrianistan.eu/blogs-cada-vez-mas-importantes-no-menos https://blog.adrianistan.eu/blogs-cada-vez-mas-importantes-no-menos Desde hace un tiempo se viene oyendo lo que cada vez los blogs son menos interesantes, lo que antes se hacía en blogs ahora se hace en redes sociales, ... Y se notó que cada vez había menos blogs "auténticos", aquellos en los que un friki se ponía a escribir sobre el tema que le apasionaba (un poco como este blog). Sin embargo, creo que hay algunos factores a tener en cuenta para replantear este asunto.

Las redes sociales

Se ha dicho que los blogs cada vez eran menos relevantes porque la gente cada vez consume más contenido por redes sociales. Mucha gente que antes escribía un blog, ahora hace hilos en Twitter. Y no estoy diciendo que haya que pasar olímpicamente de las redes sociales, pero igual no es oro todo lo que reluce.

La primera red social que tuve fue Tuenti, quizá fuera de España es más desconocida, pero aquí fue una red social relativamente popular sobre todo entre gente joven. Hace años y ante la imposibilidad de rentabilizar la red social, que iba perdiendo usuarios respecto a Facebook, se cerró. Y con el cierre se borraron todos los datos.

Este es uno de los grandes puntos negativos de las redes sociales. Nada de lo que publiquemos allí nos pertenece, no somos dueños. Si la plataforma quiere, todo el contenido que tengamos allí lo perderemos (siempre podremos hacer copias, pero muchas veces no es fácil). Y esto no es solamente por el cierre de una red social. También puede llegar alguien como Elon Musk, comprar Twitter e imponer cambios de políticas y en su algoritmo de recomendación (¿quieres poner un enlace para ir a tu web a comprar algo? pues te quito relevancia, que no quiero que los usuarios salgan de mi jardín).

En este último caso, mucha gente se fue de la plataforma (a Mastodon y BlueSky principalmente) y Elon Musk además hizo que los tuits tuviesen más dificultades para visualizarse para gente sin cuenta en X. Toda una gran cantidad de conocimiento en esos hilos, que se ha vuelto mucho menos accesible.

En un blog, no dependemos tanto de decisiones empresariales ajenas. Si nos alojamos nuestro propio blog, por ejemplo con WordPress, en un pequeño servidor, somos 100% dueños del contenido y de cómo acceder a él. También existen opciones hosteadas, como Wordpress.com, Medium o incluso Substack, que sirve tanto de newsletter como de blog. En estos casos no tenemos control total de nuestro contenido, pero sigue siendo mucho más sencillo de exportar a otras plataformas que las redes sociales.

Otra ventaja, es tu blog puede ser algo más que eso. Si contratas un programador para WordPress no te resultará caro ni complicado añadir una tienda, un foro, una zona privada para usuarios, o lo que se te ocurra. Las posibilidades son infinitas. Y sin depender de terceros, más allá del alojamiento y el dominio.

La IAs conversacionales

Por otro lado, otro gran tema. De hecho, el tema de 2023, fueron las IAs conversacionales. Fue el año de ChatGPT, la gente se empezó a poner nerviosa con Google que parecía que la habían pillado desprevenida, gente que de repente empezó a usar Bing, ...

En este sentido, los blogs tienen mucho que aportar. Al igual que los buscadores, las IAs conversacionales se nutren, en principio de contenido de webs y blogs, fuentes generalmente más serias y completas de información. Y aunque ChatGPT inicialmente no soportaba citas, las demandas de los usuarios para saber si el contenido tiene sentido, hacen que cada vez más los asistentes conversacionales añadan citas para respaldar sus afirmaciones (por ejemplo, ya lo hacen Bing y Perplexity).

Con esto, quiero recomendaros a todos los que estáis generando contenido en redes sociales, o pensáis hacerlo, que no os olvidéis de los blogs. Son la esencia de la web, y lo seguirán siendo, porque gracias a su descentralización, resisten mejor las modas que cualquier otro medio en Internet.

]]>
https://blog.adrianistan.eu/blogs-cada-vez-mas-importantes-no-menos Thu, 8 Feb 2024 20:52:14 +0000
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
Videojuegos en 2023 https://blog.adrianistan.eu/videojuegos-2023 https://blog.adrianistan.eu/videojuegos-2023 Aprovechando que 2023 termina, voy a repasar los juegos que he jugado este año y si los recomiendo. Todos son juegos de PC. Los juegos no están ordenador por orden cronológico ni por nota.

The Last of Us Part 1 (Steam)

Este juego había sido muy popular en el mundo de PlayStation, donde lo calificaban de obra maestra, pero yo apenas conocía detalles. En 2023 salió un port de PC y una serie (que no he visto). El juego cuenta una historia lineal de una invasión zombie producida por un hongo que toma el control de los humanos y los vuelve agresivos. El juego es bonito, la historia es interesante, los detalles están cuidados y tiene un punto de dificultad muy interesante. Esta dificultad viene tanto por enemigos poderosos como por falta de recursos para craftear consumibles, aunque esto último en mi gameplay no fue tan importante al final. En este juego el sigilo será parte muy importante de las misiones aunque habrá otras en las que sea imposible hacerlo así. Es bastante inmersivo y consigues entrar en el mundo, aunque los personajes a veces hacían cosas "raras". Me pasé el DLC Left Behind que venía también.

Como punto negativo, la versión de PC salió muy mal optimizada, requiriendo más de una media hora la compilación de los shaders cada vez que se actualizaba el juego.

Mi nota para este juego es: imprescindible. Aunque no es el mejor juego de esta lista, sí que considero que es lo suficientemente bueno como para que el 80% de la gente deba jugar a él.

Limbo (Epic Games)

Se trata de un plataformas bastante tétrico, con puzzles. En el juego no se menciona ni se habla en ningún momento de nada. El juego además solo usa escala de grises. Hay que ir descubriendo todas las mecánicas poco a poco. Tuvo bastante fama en su día.

He de decir que tengo sentimientos encontrados. Por un lado es un juego bastante inteligente en algunos aspectos, y sobre todo teniendo en cuenta cuándo salió. Pero realmente no me enganchó, me tuve que forzar un poco a seguir. La indefinición de la historia, y saber que quizá nunca se iba a definir, no me animaba a continuar. Además hubo un par de puzzles que aunque tenía la idea correcta, por culpa de los controles, un poco lentos y poco dados a maniobrar, tuve que revisar varias veces a ver si mi idea era la buena.

Mi nota para este juego: bueno. No es un mal juego pero por lo expuesto anteriormente no le puedo dar más.

RiME (Epic Games)

Juego precioso, rezuma belleza y a la vez esa sensación de misterio con sus ruinas de una civilización ya perdida (¡me encantan esos juegos!). El juego no tiene excesiva dificultad y se va pasando por diferentes etapas de un proceso de algo que se desvela al final. Un final catárquico, y a diferencia de Limbo, aquí si me dio la impresión de que avanzar saciaría mi curiosidad. Similar a Limbo, el juego se expresa sin palabras y habrá que ir descubriendo las mecánicas, aunque se basa en mucho lenguaje común de videojuegos para hacerte saber algunas cosas.

Mi nota para este juego: imprescindible.

Watch Dogs 2 (Ubisoft Connect)

Este juego es la continuación de Watch Dogs, esta vez en la zona de Silicon Valley. En mi opinión es muy superior al primero. En jugabilidad introduce bastantes mecánicas nuevas. Ya el primero se sentía muy bien, con la posibilidad de abordar las misiones de formas muy diferentes gracias al tema de los hackeos y las estrategias que pueden generar, y aquí, sobre todo al final, se multiplica gracias al uso de aparatos radio control. La historia además, pasa de ser tan oscura y seria como en el 1, para ser algo que no se toma en serio a sí mismo, con misiones tontas, con objetivos tontos y personajes cada cuál más loco que el anterior. En ese sentido me gustó. Como si de una comedia se tratase entra bien, y aunque no tiene la historia más profunda del mundo, me pareció divertido y entré en el mundo.

Como punto negativo, este juego tiene varios bugs en PC. El primero es que dependiendo del modo de pantalla completa que usemos, el juego crasheará en unos pocos minutos (o no). Descubrir el modo correcto fue frustrante. Además más adelante un objeto esencial no se me dió, por un bug, y tuve que añadírmelo usando mods. Solo así pude acabar la historia, ya que el juego creía que ya me lo había dado... Sobre las partes online del juego, no puedo opinar mucho, ya que no jugué apenas.

Mi nota para este juego: imprescindible.

Sherlock Holmes: Crimes & Punishments (Epic Games)

Quería un juego de detectives. Y este lo tenía en mi biblioteca. Sherlock Holmes parece interesante. Se trata de un juego en 3D donde deberemos ir encontrando pistas, interrogar a la gente, resolver algún minijuego de experimentación práctica y por último hacer deducciones, para sacar nuevas preguntas o para incriminar a alguien. Los casos sí que tienen bastante ramificaciones interesantes. El juego te da libertad, dentro que de que haya indicios lógicos, para acusar a cualquiera... Pero luego al acabar el caso te dice quién era de verdad. Y nunca explica por qué llega a sus conclusiones ni donde has cometido el fallo. Como es habitual en estos juegos, a veces te quedas bloqueado por no ver alguna pista clave. Pero en otros puntos el juego te guía bastante, diciéndote exactamente dónde debes contrastar la información.

A nivel técnico el juego tarda mucho en cargar entre zonas y tiene glitches gráficos recurrentes. La ambientación victoriana, típica de Sherlock Holmes, tampoco es mi preferida.

Es por ello que mi nota para este juego es: regular. Juégalo si este tipo de juegos te gustan mucho. Creo que no destaca positivamente en nada, en general es bastante mediocre.

Batman: Arkham Knight (Epic Games)

Este juego forma parte de la trilogía Batman Arkham. Unos juegos donde llevamos a Batman en una épica historia contra el mal. Batman es de los pocos superhéroes que me gustan, por el carisma de sus villanos así como la estética detectivesca/noir que tiene Gotham City. En estos juegos prima el sigilo, con mecánicas muy interesantes alejadas de simples coberturas, y el combate cuerpo a cuerpo, muy trabajado con multitud de combos. Batman no puede matar así que no hay armas de fuego pero sí que tiene multitud de artilugios. Los dos primeros juegos, Arkham Asylum y Arkham City los jugué hace tiempo. Me gustaron mucho. Este se quedó pendiente ya que mi ordenador de entonces no lo soportaba.

El juego es mucho más mundo abierto que los otros, con bastantes misiones paralelas a la misión principal. Tenemos al Batman más completo y potente de todos los juegos y aun así, me parece poco aprovechado. Creo que en los otros juegos de la saga se aprovechaba más a Batman. Y todo tiene un motivo. El batmóvil. En esta entrega se añade el batmóvil, el coche ultra-mega-super-guay de Batman. Y le añaden un montón de mecánicas. Muchas misiones acaban siendo misiones de llevar el Batmóvil (contra unos convenientes coches no tripulados, así podemos usar cohetes y metralletas). Incluso hay plataformeo de Batmóvil. Incluso para liberar a Catwoman, hay que hacer carreras de coches (y esto lo hace, ¿Riddler?, ¿en serio pilotar un coche a alta velocidad es un acertijo?). Otro punto negativo es que también lo noté más peliculero que otras entregas, más tétricas y "solitarias".

Mi nota es: notable. No es un mal juego, que no se me malinterprete. Pero creo que quitaron algunas de mis cosas favoritas de los otros juegos para dárselo al Batmóvil, empeorando el juego.

DiRT 4 (Steam)

Continuación del DiRT 3 de la saga DiRT de Codemasters. DiRT 3 me gustó mucho, fue mi iniciación a los juegos de rally. Juegos que me han gustado mucho. De hecho prefiero este género de carreras antes que las carreras en circuito, ya sea con IA u online. DiRT 4 es un juego más fácil que los DiRT Rally. No obstante sigue siendo muy divertido y un sitio ideal para empezar. Tiene variedad de coches, con manejos lo suficientemente diferentes como para notar diferencias, pero sin requerir mucho setup (algo en lo que soy terrible). Diferentes tipos de terreno y muchos tipos de curvas aunque al final, es un juego repetitivo, que en mi caso juego de vez en cuando para no pensar demasiado y nunca en grandes sesiones.

Mi nota es: notable. Dentro del mismo desarrollador hay juegos mejores. Pero no es mal juego.

Evoland (Epic Games)

Evoland es un RPG corto basado en la idea de que vas recorriendo la historia del género poco a poco. Empezando con los 8 bits de Zelda hasta llegar a los mundos prerenderizados de PS1. El juego en sí es un tributo, no destaca ni por historia, ni por dificultad ni por profundidad. El propósito es enseñarte como han ido evolucionando los juegos. He de decir que este tipo de RPGs de aventura los he jugado muy poco, con algún Zelda como única excepción, así que no tiene el componente nostálgico que a otras personas les puede parecer interesante. Hay una secuela, más larga y profunda pero no he jugado.

Mi nota es: bien. Si te gusta el género, te gustará aunque sea por las referencias. Si no, es un juego entretenido, cortito, pero nada excepcional.

Red Dead Redemption 2 (Steam)

Maravilloso juego de Rockstar Games, autores de Grand Theft Auto entre otras sagas. En este caso, vamos al viejo oeste a vivir la historia de Arthur Morgan. De primeras esperaba encontrarme un GTA del oeste. Aunque hay ciertos parecidos (mundo abierto, shooter en tercera persona, ...) hay bastantes diferencias. El juego es mucho más inmersivo, existe inventario de verdad, podemos robar cosas, hay muchos objetos con efectos, a su vez tanto nosotros como nuestro caballo tenemos diferentes medidores, los NPCs son muchísimo más profundos e inteligentes, la naturaleza es impresionante, el combate es mucho más espectacular, una historia digna de Óscar,... Y un ritmo mucho más lento. De hecho quizá una de las pegas es que el juego, con sus animaciones, su inmersividad, acaba teniendo un ritmo en ocasiones lento. Y encima el juego es largo. No obstante, el juego es una aventura que se disfruta por el camino. Y la atención al detalle es espectacular. No creo que sea el juego perfecto, pero sí un juego que todo el mundo debería experimentar.

Mi nota es: imprescindible. Todo el mundo debería probarlo.

Papers Please (GOG)

Un juego donde debemos ponernos en la piel de un trabajador del control fronterizo de la república de Arstotzka. Allí deberemos revisar la documentación de cada persona que intente entrar, para ver si está todo en regla o no. La jugabilidad es básicamente pedir documentos, mirar y contrastarlo con las normas que tenemos. Pero además tenemos un tiempo limitado, por cada persona que atendamos ganaremos monedas y necesitamos esas monedas para que nuestra familia sobreviva. La verdad es que esta burocracia se hace adictiva, y poner el sello es muy satisfactorio. El juego tiene diferentes finales, aunque hay alguno mejor que otro. No es muy largo llegar a ello.

Mi nota es: imprescindible. Este juego es muy interesante, tanto por el trasfondo que plantea como por su propia jugabilidad.

Stellaris (Steam)

Stellaris es un 4X de Paradox ambientado en el futuro, con civilizaciones interplanetarias, diplomacia cósmica, colonias interespecie y guerras con flotas interestelares. Dentro de Paradox es quizá de los más fáciles de entender de primeras. Aun así, tiene una gran curva de aprendizaje. Podemos controlar gran cantidad de detalles de nuestro imperio. Para mí el juego adolece de un problema al final de la partida. Al principio el juego es interesante cuando te vas expandiendo pero una vez las fronteras están establecidas, y hay alianzas entre imperios, el juego resulta algo tedioso (y tiene mal rendimiento). Con poca posibilidad de hacer cosas, solamente pudiendo esperar más a que acaben de producirse algunas cosas por si acaso, el juego se vuelve algo aburrido. No obstante le intentaré dar otra oportunidad más adelante. El juego tiene una política de DLCs muy agresiva que también le resta valor al juego si no tienes todos (y valen bastante).

Mi nota es: bien. Dentro del género creo que es un juego interesante, sobre todo si la temática histórica no te gusta tanto. Es un juego con gran curva de aprendizaje. Hay que dedicarle tiempo. Pero quizá es de los más accesibles dentro del universo Paradox.

OpenRCT2 (web del proyecto)

Una implementación open source del motor de Roller Coaster Tycoon 2. Actualmente, necesita los ficheros originales para funcionar (que en mi caso ya disponía legalmente). Todavía no ha salido la versión 1.0 pero aun así probé un escenario del juego y quedé sorprendido gratamente. Hay pequeños cambios, destinados a mejorar algunos aspectos menores del juego. Pero por lo demás, tiene la misma esencia del juego. Muy recomendable, aunque quizá más adelante cuando implementen todavía más cosas.

Mi nota es: imprescindible. RCT2 fue una obra maestra del género tycoon. Esta implementación alternativa del motor mejora algunos detalles y es más accesible. Aún así, la versión final no se ha publicado todavía.

Wasteland 3 (Steam)

¿Un RPG postapocalíptico en la zona de Colorado, con combate tipo XCOM? Claro que sí. Este juego me encantó, aunque lo jugué entre 2022 y 2023, lo voy a meter aquí. Es un juego muy abierto, con muchas posibilidades, pero con una historia definida. Llevamos un equipo que podemos ir mejorando, asignándoles armas y habilidades según nuestro criterio. Además una exploración muy interesante y decisiones que afectan al juego.

Mi nota es: imprescindible. Para mí uno de los mejores juegos que he jugado en estos últimos años. Quizá al principio algunas cosas resultan algo confusas al principio. Si te gustó Fallout: New Vegas, quizá te guste, aunque el combate es muy diferente.

DiRT Rally 2.0 (Steam)

Más complejo que DiRT 4, pero también más desafiante y satisfactorio cuando sale bien. Es quizá uno de los mejores juegos modernos de rally que se pueden comprar. Aquí siento que tiene más poder el tema de los setups, algo en lo que sufro. Las variaciones de coche y terreno se notan más, por lo que con algunos coches te sentirás más cómodo que con otros y preferirás unos terrenos a otros.

Mi nota es: imprescindible. Si te gustan los juegos de coches, tienes que probarlo.

Grand Theft Auto IV: The Lost and Damned (Steam)

GTA IV ya me lo había pasado. Pero con el interés creciente en GTA VI recordé que no había jugado a las expansiones que tuvo. Fueron The Lost and Damned y The Ballad of Gay Tony. The Lost and Damned nos pone al mando de Johnny, un motero del club The Lost de Alderney. Introduce mecánicas nuevas a GTA IV y vehículos extra (motos principalmente). La historia es interesante, bastante centrada y se entrelaza con la de GTA IV y la de TBOGT. No obstante, GTA IV siempre fue un juego donde el manejo de los vehículos se puede calificar como raro y las motos más todavía. Se siente raro manejar motos tal y como se hace en este juego.

Mi nota es: bien. En esencia son misiones extras al GTA IV con alguna mecánica innovadora.

Grand Theft Auto IV: The Ballad of Gay Tony (Steam)

La segunda expansión del GTA IV. Algo mejor, ya que expone algo muy interesante de Liberty City, su vida nocturna. Tiene ese toque fiestero que se echaba en falta desde Vice City. Las misiones son bastante espectaculares, los personajes son bastante carismáticos todos ellos y se añaden mecánicas secundarias interesantes como vigilar el local, contrabando de droga, saltos con paracaídas (en este juego se añaden además más vehículos aéreos), peleas, bailes/booty call...

Mi nota es: notable. Sigue siendo una extensión de GTA IV, pero mejor que la anterior y merece bastante la pena.

Filament (Steam)

Un juego de puzzles donde debemos arrastrar un cable (o "filamento") a través de columnas, siguiendo ciertas normas (hay varias diferentes). Es un juego entretenido, aunque me quedé bloqueado y no he podido acabarlo. La historia no me ha llamado la atención para nada. Es un juego de puzzles interesante y con variaciones sobre un concepto base simple. Tampoco destaca excesivamente en nada.

Mi nota: bien.

Bridge Constructor Portal (Steam)

Juego de construcción de puentes basado en Portal. Entretenido para pasar un rato pero hay poca motivación para continuar los puzzles.

Mi nota: bien.

Otros juegos

He seguido jugando a juegos de otros años, sin gran impacto, estos son: Project CARS 2, Asseto Corsa, Two Point Hospital, Mini Metro y Enter the Gungeon.

]]>
https://blog.adrianistan.eu/videojuegos-2023 Thu, 28 Dec 2023 15:07:35 +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
Scryer Prolog Meetup 2023 Notes (day 2) https://blog.adrianistan.eu/scryer-prolog-meetup-2023-notes-day2 https://blog.adrianistan.eu/scryer-prolog-meetup-2023-notes-day2 This is a continuation of the previous post. In this post, we'll see the talks that happened on day 2.

Verifying safety properties in dose escalation trial design with Prolog - David C. Norris

David Norris works in medical research. In particular, he works on finding the right amount of a new drug for patients. This was a joint work with Markus Triska.

When new drugs are introduced in oncology a dose-finding trial is needed with humans. They need to be done with unhealthy patients because they're too toxic. Also, we can't repeat the experiments with the same patients as the use of the drug modifies the condition of the patient itself.

Both purposes: therapeutic for patients where actual drugs are not working, and scientifically to learn about the correct doses. After 4 weeks, a binary decision. Have we reached Dose-Limiting Toxicity? (DLT)

Only the dose makes the poison - Paracelsus

Scientists like to use the 3+3 method. A simple but underspecified method for dose escalation. Difficult to find a precise description. We can find papers with errors implementing the method.

He implemented a trial workflow with DCGs. Also using constraints. It can generate all possible paths for a trial. With all paths, we can calculate exact properties without Monte Carlo statistical errors... EscRisk app. precautionary package (available for R)

Problems with this first version: not generalizable, lacks depth (doesn't explain previous trials)

Rewrote it to be more general and start from the point of view of making decisions incrementally and how many patients regret taking that dose

Use of reified predicates for decision making. Are they infeasible? Are they regrettable?

After we have this, and a DCG for running and generating paths. We can verify the safety properties of (all) trials and their recommended dose. Backward and forward reasoning thanks to monotonic constraints enables in clpz.

There's a problem with trial design in 3+3. Patients need to wait for other patients to reach the same status to proceed. Wasting time is not optimal. They added extra predicates to allow cohorts of size less than 3.

Current studies show a lot of fine print when doing 3+3 trials. But David can trust his results without needing fine print thanks to LP and the abstractions available in Scryer.

Q&A: A main concern was to model every possible case and not lose any correct solution. So we used clpz, reif, ... But reviewers complained that it (proving there are no mistakes) was slow. Different priorities :)

When declaring reified predicates, it's recommended to add _t to mark it's a reified predicate

Meta-predicates for the answer constraint semantics and constraint-based modelling in Prolog - Dr. François Fage

He started doing Prolog with an internship about unification in equational theories by Term Rewriting Systems in Prolog I.

Later worked on unification of finite and infinite terms (meet Prolog II team!)

Reactive programming with condition-action rules: ASP and semantics of Prolog with negation

Developed Meta(F) finite domain constraint library using freeze/frozen/setarg

Polymorphic subtyping with Prolog using CHR

Developed at INRIA a biological system with 2 languages and 4 semantics at the same time

But he talks about teaching Prolog. Several courses on CLP at École Polytechnique some years ago. But now resumed teaching Prolog. For the subject Constraint-Based Modeling & Algorithms for Decision-Making

They first used MiniZinc for modeling + Python for toy solvers

  • Pro: MiniZinc is a declarative language with mathematical variables and constraints
  • Pro: Very efficient solvers. MiniZinc is designed independent of the solvers.

  • Con: Difficult to debug (multiple transformations, black-box solver)
  • Con: It only supports solutions, you can't get constraint answers like Prolog
  • Con: No way to manually do search, you can only set some options that are send to the solvers
  • Con: Had to use Python for toy solvers. A different language.

So, they moved to Prolog in 2023.

  • same language for modeling and programming
  • libraries written in Prolog -> good error messages and better debugging
  • constraint answers
  • we can program search, do meta-interpreters

Some abstractions are not as nice: lots of focus on recursion and list-oriented constructs, no indexed mathematical variables.

Some Prolog metapredicates were defined before constraints and they have bugs or the way the work is not specified.

Some terminology is inherited and may not be the best one today

Some examples cited:

Bug in frozen/2 in SWI so they had to implement constraints using attr var

Abstractions like foreach loops lose constraints! Maybe add an error if you try to use it with attr var to signal it is not intended to work with it.

Predicates like bagof/3 duplicate constraints (both SWI and Scryer). Should we fix bagof/3, fix clpz or create a correct list_of predicate? Some systems like CHR and SMT simplify their constraints but clpz does not.

So, he developed a for_all library to express loops in a more declarative way using a DSL.

Introduces also a let predicate to allow functional expressions inside but it would be nice to have a mechanism to define functions in Prolog without needing an aux predicate.

He also developed an array library compatible with the for_all library.

He also developed a MiniZinc library which is a MiniZinc parser but solves the problem in Prolog -> useful for debugging.

In the Q&A section, it was debated that maybe the missing key abstraction in Prolog are List Comprehensions.

Prolog for Verification, Analysis and Transformation tools - Prof. Dr. Michael Leuschel

He didn't like Prolog at the beginning. However now we uses Prolog in ProB, a validation tool for high level specs. Like B, Z, Event-B, TLA+, Alloy, CSP, XTL (Prolog). Used in the industry.

B is a language based on set theory. You can execute ProB in a Jupyter notebook.

B uses Unicode symbols. Problems like Send More Money and N-Queens can be solved in B with intersections, set comprehensions, existential operators, etc

There's also a UI and models can run in real-time

Lots of industrial users in train systems.

He uses different ways of interfacing with other languages: C FFI API for Z3, ZeroMQ and sockets for Java UI, Java B code parser, ... Linda (SICSstus) was much slower than ZeroMQ.

They use coroutines extensively: freeze/2, when/2 and block declarations (SICStus). Blocks are less expressive than when but faster and less intrusive.

They're important to implement determinism first propagation (first solve deterministic stuff to reduce search space). Solvers combined via reification.

Process Algebras CSP (Tony Hoare) can be applied to Prolog very closely to formal language.

B requires Type Inference and it's easily encoded in Prolog.

B requires classical negation, not negation as failure. He implements constructive negation by having two interpreters, one for finding solutions, other for finding counter examples.

He showed a comparison between Java and Prolog in TLA+ Translators. Prolog code is much shorter and readable than the equivalent Java code.

Why not Prolog?

  • No static type checker
  • Few standard libraries
  • Limited support for parallel execution
  • Performance: some key algorithms are implemented in C++ instead.
  • Limited IDE support (especially outside Emacs. He uses a BBEdit plugin).

Why Prolog?

  • Nondeterminism and unification for encoding semantic/translation rules
  • Coroutines
  • Constraints
  • Tabling (although it doesn't work well with constraints)
  • Q&A: Trains don't run B when they're in production. There's a code generator from B to B0 and later to Ada, C, ... But as we can't prove the code generation is fine, trains run more than 1 system generated differently and they work in tandem.

    Prolog and algebra - Dr. David Stewart

    David Stewart is a mathematician from Manchester. He has been working together with David Cushing and George Stagg on several math papers where they use Prolog to help with the proofs.

    Thanks to CLP they could explore extremely big math objects. While other systems are just brute forcing, their use of CLP makes it work faster.

    The most famous paper they published with this strategy was: You need 27 tickets to guarantee a win on the UK National Lottery.It reached pop science websites and even appeared on the news. NBC television, for example, showed this "interesting" title while discussing the paper and their use of Prolog.

    In the National Lottery you have to guess 6 numbers of 59. To win something you need to guess at least 2. With 27 you can do it. But with 26 not. The difficult thing is trying to prove that wth 26 it was not possible and they used Prolog for that.

    He also discussed the paper: A Prolog assisted search for new simple Lie algebras. He then started to explain what a Lie algebra is and what is a simple Lie algebra

    He later shared his wishes about Prolog

    First, he wanted to have a matrix multiplication constraint. Right now, there's no matrix multiplication constraint. There's a scalar product constraint but it's not usable for their use case. It was discussed whether a specialized constraint in the library would yield more performance or not than than a naive custom one.

    David also said that better term rewriting would be useful as Some equations can be simplified a lot by doing the right variable substitution but CLP doesn't apply it.

    On the Q&A it was said that the lottery ticket scheme is perfect for money laundering

    Grants4Companies: A logic-based AI application in the public sector - Dr. Markus Triska

    Markus Triska is the author of The Power of Prolog book and YouTube channel. He also works for the Austrian government in a department for digital government. They don't develop directly software, however.

    He presents a project developed by the Austrian government that checks which grants apply to each company.

    First, he starts recommending two books:

    The Collapse of Complex Societies by Joseph Tainter. TL;DR societies collapse because of bureaucracy. At some point they reach a level of diminishing benefits

    Le Règne de la Quantité et les Signes des Temps. René Guénon. We're always oscillating between quantity and quality. And now we're in a quantity system.

    The use of AI in the administration must satisfy the three following properties.

    • transparency
    • traceability
    • predictability

    They cannot guess patterns (like statistical AI) since everything we do must be backed by some explanation according to the law.

    They also try to use the minimum needed data and publicly available data (to prevent misuse). They ask for permission before doing any calculation.

    Grants are translated from natural language to a machine-interpretable language. They mix company data and the AI shows applicable grants (and why not to other grants). They use Scryer Prolog although the format of the grants is similar to Lisp.

    As it was contracted work they could not choose everything by themselves./p>

    Current work: adding more grants, more data sources, explaining results, analysis of grants, UX improvements,...

    Grants are not applied automatically since they depend on other entities.

    They're very interested in ISO conforming systems since it's important for the government. We're also using Scryer to explore relations between grants (find contradictions, find subsets/supersets, ...)

    They're very interested in how the system explains why a grant could be applicable or not. He hopes in the future explainability could be more important and even required if a catastrophe in IT happens. The system right now is able to do it, marking in different colors the exact reason why a grant may be rejected

    They use Three Valued Logic (an unknown value) because they might not have the data (Strong Kleene Logic K3)

    Law as Code: Applying logic programming to formalize and analyze laws and regulations - Dr. Björn Lellmann

    Björn also works in the Austrian government like Markus, but his project is more general. He repeats the same stuff about what a public administration needs for its AI.

    A law in itself could be a publicly available logic program, with clearly defined semantics, and follows a formalized, logic-based, executable rule representation.

    >Right now, lots of laws are translated into IT systems by IT persons that are not experts and they can introduce mistakes. Ideally, both versions should be defined in parallel to reduce translation bugs. We can use the help of LLM to help translate the current legal texts into logic programs. But always verifying the output.

    Possible applications: Business grants (G4C), checking evidence requirements (Single Digital Gateway), HR processes, ...

    The aim of the project is to create a basis for technical implementation of laws based on Logical Programming.

    In the showcase they used a Rules Engine provided by Unisys. He shows the UI, it's like a table, very flat difficult to see the hierarchy and the relations of the rules.

    But they have another UI that shows the rules compared to the text. It allows working in parallel with the legal text and the logic program. Also allows exporting to RuleML and Prolog.

    The project is not about having a precise representation of the laws yet. It's more about a proof of concept and the technical environment.

    Using Law as Code while drafting the laws could enable:

    • Check logical inconsistencies
    • Check inconsistencies with other legal texts
    • Suggest rules from natural language
    • Suggest natural language words from rules (creating the law in a simpler, standardized language)
    • Check for suitability for digitalization (which is a requirement for all new laws in Austria)

    The end goal is not about removing judges and making everything automatic. The human factor needs to stay. Also, some laws are ambiguous as a feature and they do not intend to change that.

    Why Prolog?

    • We need a language that is predictable and standardized
    • Accessibility. Rules can be used by citizens and systems from different authorities
    • Vendor-independence: No contract with a single vendor is required for evaluating legal texts

    In the Q&A people asked about Catala, a language with similar goals. However, in their (small) experience, Catala was not as easy as Prolog to reason about the laws and rules (and not just executing something).

    End words

    Additionally, Dr. Tomas Veloz could not attend the meeting but he left a recording of his talk, so everyone can see it.

    And that's it! Those were my notes from the Scryer Prolog Meetup. Later we had more offline conversations and we tried the delicious Altbier from Düsseldorf. I hope you find them interesting and I hope there will be more meetups in the future.

    ]]>
    https://blog.adrianistan.eu/scryer-prolog-meetup-2023-notes-day2 Tue, 14 Nov 2023 17:19:36 +0000
    Scryer Prolog Meetup 2023 Notes https://blog.adrianistan.eu/scryer-prolog-meetup-2023-notes https://blog.adrianistan.eu/scryer-prolog-meetup-2023-notes The 9th and 10th of November was the Scryer Prolog Meetup 2023 in Düsseldorf. As a Scryer Prolog user and contributor, I was very excited to go to this meeting. Now, I'm back at home and I can write here a report of what happened in this event with the notes I've taken.

    But before starting with the talks, let's understand why this event took place. Officially, it is considered that Prolog, as a programming language, was born in 1972. That marked 2022 as a special year since it was its 50 anniversary! Several things were organized, a book was released and several papers were published on the matter. But the big celebration was the Prolog Day Symposium, which took place on November 10th in Paris. For some people like me, it was a special event in which we were surrounded by lots of the names behind what Prolog is. Several Scryer Prolog developers met there and after the good experience, we agreed it was a good idea to hold a meetup for Scryer Prolog on similar dates the next year.

    And it happened! Thanks to Christian Jendreiko, a professor in Hochschule Düsseldorf; Markus Triska and the association Prolog Heritage, the event took place. Many thanks to them!

    And now, let's start with the first day:

    Current developments in Scryer Prolog - Mark Thom

    Link to presentation

    Mark is the main developer of Scryer Prolog. In this talk, he talks about the past and future of Scryer Prolog. First, Mark wants to thank all contributors for their work in the past year. He also wants to mention that Scryer has now a proper webpage. Also, now there's a playground that, although limited, allows people to try Scryer Prolog without the need to install anything. This playground is based on the WASM work by @rujialiu. This WASM port also required several changes to support 32 bit targets.

    Since last year, two releases of Scryer happened (v0.9.2 and v0.9.3). They include lots of changes compared to v0.9.1. The main ones are:

    • Atom table concurrency (thanks to @Skgland). This will allow multithreading in the future.
    • clpz improvements (@notoria, @triska, etc)
    • CI improvements by @infogulch
    • HTTP improvements and foreign function interface basic support by @aarroyoc
    • Djot and documentation for most libraries by @aarroyoc and @triska
    • Moved from GMP to dashu thanks to @fayeed and @lucksus
    • The ability to use Scryer as a library by @fayeed and @lucksus
    • dif/2 improvements by @bakaq
    • #scryer IRC channel on Libera Chat by @rikardlang
    • Minor fixes and test cases (@gruhn, @UWN, @pmoura, @infradig, @flexoron, @haijinSk, @josd, @rujialiu, @triska, ...)

    He has been working on the following items:

    • Improved ISO standard conformance
    • call/N goal expension caching
    • Much better compilation of disjunctions
    • Lookahead indexing
    • Introduction of stackless iterators
    • Many bug fixes
    • Golden Roll #1 (a technical doc showing how Scryer Prolog internals work)

    He has also been working on several items that require a little bit more explanation:

    Garbage Collection

    He wanted to add it for 3 years already, but he was held back a little by Rust's youngness. The algorithm is based on two-finger mark+compactation from the paper Garbage Collection for Prolog Based on WAM. Some changes are required since, for example, Scryer Prolog features "partial strings". Partial strings are a response to some Prolog's inefficient use of strings. Partial strings were inspired by Lisp and provide a similar interface to lists while being stored more compactly.

    Arena allocation will follow a BiBoP scheme, collected in tandem with the heap.

    All these things will make it easier to track resources at the cost of being more verbose.

    Some obstacles that he found with Rust are the imposed RAII. It would be nice to slot a custom allocator abstraction into common Rust data structures, like Vec. That would leave us with more flexible options, which are required for a garbage collector.

    However, the allocator_api for Rust is not stable and only available on nightly releases.

    Also some data types will require finalization when freed.

    At this point, he is implementing some data structures from scratch to be able to continue working on the GC.

    Last but not least, Prolog allows dynamic modification of the program, so a GC should be added to the code space too.

    Partial String Heap Section

    Regarding partial strings, as mentioned, there must be some changes in how they work. This started a discussion about the trade-offs of different designs. Right now, partial strings are not freed on backtracking (most other data types do), so GC is the only option here. Adding support for free-on-backtracking will make choicepoints bigger in memory, though. Also, the current code needs to know in lots of places if we're dealing with a partial string or not.

    WAM Heap Cells Optimization

    Atoms that use 6 bytes or less are going to be stored in heap cells directly, without being written to the atom table. This is an optimization found in several programming languages and it'll make more sense now that the atom table is thread-safe and there's some synchronization overhead.

    Also, the tag space has been revised and now they use fewer bits. Most data types in the WAM go from 6 bits to 4 bits.

    JIT / AOT

    Current plan is to compile WAM using Cranelift. However, GC must be done first. Maybe Prolog in the future could be used to analyze Prolog programs and produce optimizations (Prolog compiler in Prolog?)

    Compilation speed-ups

    There's a bottleneck translating between heap terms and Term (eliminate Term). Making Instruction data type smaller.

    Compilation of Scryer itself is becoming a concern with Rust compiler taking more time and using more memory.

    Other improvements

    There's an effort to push for multithreaded Prolog. But still a lot of questions are unanswered. How to coordinate threads? Which concurrency model should we support?

    An even better disjunction compilation was also proposed for several cases.

    Inlining common predicates was also being worked on since a lot of WAM inefficiencies come from shuffling HeapCellValues around in registers. Some ISO predicates already have this inlining implemented like var/1.

    Writing more Golden Rolls as they serve both as reference documents and incite people to contribute. Open to subjects!

    Scryer Prolog is not cache friendly. Clobbering the most common sequences of instructions into a single instruction could help.

    Arithmethic performance can be improved even without native compilation

    Some questions were asked to Mark. To the question of how he keeps doing the project, he just replied he feels he's doing more meaningful work here than in other places. About benchmarks, he replied that Scryer still doesn't have any automatic benchmark suite. Also, it was remembered in the room that we should not trade off correctness for performance. About coroutines, it was said that Scryer supports freeze/2 and dif/2 (one of the few systems with a correct dif). But when/2 and block declarations are still missing.

    Foreign Function Interface - Adrián Arroyo Calle

    Since this talk was given by me I didn't take any notes :). You can still download the presentation

    How we use Prolog in ADAM and the Synergy Engine - Nicolas Luck

    Nicolas is the co-founder of the DAO Coasys. Coasys current products are three: Flux, ADAM and the Synergy Engine.

    ADAM is actually the most important thing. It's a layer upon new applications can be built. When you build your application on top of ADAM, all the communications are abstracted and you just focus on creating data and querying data, in the form of knowledge graphs.

    Flux is an app built using ADAM, a chat platform similar to Discord and Slack. It serves as a proof-of-concept for ADAM.

    Unlike other approaches with knowledge graphs like semantic web, ADAM takes an agent-centric perspective, since it's the only way data can scale to civilization levels. In today's society, companies need to host the information, and that makes them not objective by definition. Blockchains can solve some issues here but they're slow. With the agent-centric approach, each agent is responsible of their own knowledge.

    Each agent has its own data. Each agent can choose to define its data with their own ontologies. The data units for an agent are expressions, which can be expressed in different "languages" (different technical backends, like IPFS, blockchain, Twitter, ...) and are cryptographically signed.

    "Perspectives" are how we associate expressions between them.

    When we move this from just one agent to several ones, we have neighborhoods, which are perspectives shared by more than one agent. Those perspectives have a social DNA which we can use to interact and understand the data. This social DNA is Prolog code, although there are DSLs in other languages to generate these queries.

    Apps in ADAM take a perspective or a neighborhood and their respective social DNA and then they represent the data

    Apart from the ORM, there's also an editor to define classes in Prolog. There's also an LLM system to help people create the Prolog code from natural language.

    So, what is Synergy Engine? It's a search engine built on top of ADAM. User writes a query, the query gets translated via LLM to Prolog code, and it's executed on the local graph or a remote one trying to find an answer. When a node finds an answer that generates the correct information (this is cryptographically checked, so it's the right answer), it gets an optional reward, if the user set it at the beginning

    50 years of Prolog - A few things to keep in mind - Guy Narboni

    Guy worked in Prologia, in the team that developed Prolog IV. Now, he has a consultancy called Implexe and it's also the president of Prolog Heritage.

    At the beginning there were no numbers in Prolog. One could only symbolically express numbers, using Peano's arithmetic. Nowadays we can express integers using #= from clpz while preserving all logic features.

    However, it only works for integers. We need all numbers covered cleanly. That's where Relational Interval Arithmetic enters.

    It makes possible calculations with guaranteed precision, safe pruning deductions in logic and interval constraints generalize clpz.

    Computation that has Interval Arithmetic is compatible with logic and it's safe. That was the idea pushed by Prolog IV.

    Also, it was pushed that both =/2 and dif/2 should work for every data type, even if they're numbers. Using dif/2 is very useful since it often removes the need for cuts.

    Rationals, decimals, integers,... were numbers but also they were treated as trees

    Now we can get some of the benefits of Prolog IV with #= but in Prolog IV, that was normal unification. Guy presents the Perfect Rectangle Case Study from Prolog III

    It shows that a simple syntactic change like replacing H0 with 0 in the head would make different results, because unification in Prolog III, unlike in IV, doesn't support all datatypes.

    Prolog IV worked with several types. All of them can be thought as a tree, but they're handled differently. There's a preunification table for types.

    In Prolog IV reals are approximated by intervals. Every numerical value is represented by a rational with infinite precision where integers are as big as necessary.

    Since dif/2 is a generic constraint, you have to deal with open and closed intervals

    Prolog IV was a beautiful PoC. Industrializing Prolog IV would be a rich idea! Even more powerful than clpz

    Marseille Prolog benefitted from WAM-Prolog line, but WAM-Prolog line ignored Marseille Prolog line!

    Eclipse has some parts of Interval Arithmetic

    Guy's wishlist

    • Interval solver
    • Linear solver
    • Research issues: enhanced LP / IA communications

    Art, Design and DCGs: The generative power of Prolog - Prof. Christian Jendreiko

    I came from a different perspective. I'm not an engineer, or a mathematician. I studied Media Theory. I liked logic to understand medieval thinkers.

    In Systems Design. We try to work on interdisciplinary teams. I'm happy we can bring together artists and engineers. I think is important that also artists understand a little bit of how computers work.

    I teach Prolog to young artists and designers.

    Two reasons: helpful tool to understand your own ways of reasoning and it's a great tool to step into logical things. So they can use it in the creative process Get students to get in touch with the fundamental concepts of CS (graphs, search, permutations, ...) from a solid logic ground.

    From a didactic point of view: with Prolog you can deep dive into the big topics of CS without any distraction.

    I also use it for electronic art. The computer changes the status quo of the artist in society and changes how communication happens. (Max Bense Programmierung Des Schönen)

    Paul Klee: "making art of any kind means to think in sequences in order to create patterns". Context of the time: shaky grounds in physics, maths "Prolog was made to describe sequences"

    Together with the students we learn how to teach Prolog. What are the good ways to communicate Prolog to people?

    The course is not about using tools created by other people like Midjourney but about creating their own programs. And to really understand what programming really means.

    Prolog also helps thinking in backward reasoning in the arts: Michel Angelo (you have a goal). And differentiate it from forward reasoning: Miró. He went to the beach and out of the pieces he found he constructed his art

    When you're taking a photo, in what mode are you thinking? Backward or forward?

    Let artists work systematically, without resorting to drugs

    Now that there's a browser version, it's going to be very helpful for students since they have trouble setting up.

    I want to migrate to Scryer. We're using SWI.

    We also want to use the predicate format_/2 which is a big improvement.

    Example of poems generated by DCG. A DCG is like a casting mold.

    He wants with the help of the students create a database from the Pattern Poetry book. With all patterns in the database, we could start experiments, mixing, recombination,... We would use the great power of generation of Scryer Prolog. Also, we want to abstract the shape of the output.

    There were some questions:

    LLMs get boring because they all. They all recombine the same. You need to get into the basics, the structure to avoid boringness.

    You only use format_? Have you tried going 2D canvas or a 3D space? Yes we are trying with Tau because it can access JS and acccess to MIDI, graphics, ... I hope with Scryer web version we could also use that in a future. We also really want a MIDI interface to play with music format.

    ISO Prolog, a basis for Prolog extensions - Prof. Dr. Ulrich Neumerkel

    He's the convener of ISO standardization.

    Prolog systems often are interested in ISO but later they get creative and they break. One of the systems that has kept ISO Prolog conformance is Scryer Prolog. Even with "weird" examples.

    Then Ulrich starts to go over the history of Prolog:

    • 05/1972 Before Prolog existed: système OEdipe, not yet Prolog, first dif/2 (almost no documentation)
    • 1972 Prolog 0 with dif/2, boum/2 (similar to atom_chars/2), occurs-check option, many cut-like constructs (very obscure feature).
    • 1975 (date of manual, probably 1973) Prolog I, no constraints, but errors, cut
    • 1977 DEC 10, better syntax, no errors, mostly silent failure instead very influential, basis for ISO, started (deadly) speed race where systems tried to be faster and faster.
    • 1981 FGCS. They used Hungarian MProlog at the beginning, but later they went to committed choice languages. The decision to use MProlog was controversial (Iron Wall) so ESPRIT project started, and many Prolog systems were developed. Take into account Prolog systems were very expensive systems. For example, IF/Prolog cost 20.000 Deutsche Marks at their time. Need for standardization
    • 1984 BSI with Roger Scowen started standardization with BS6154
    • 1987 Proposal accepted to ISO, begin of WG17
    • 1995 ISO/IEC 13211-1 published

    Some of the highlights of the standard:

    • disambiguated DEC10 syntax (first systems worked fine but their docs were bad so other systems later did it wrong)
    • unification defined for the first time NSTO. But STO is not defined. Because there were problems with rational trees that should also be there.
    • multi octet character set handling (MOCSH). Bytes and characters are not the same
    • clean error system, separates between instantiation errors and type/domain errors (this was very controversial at the time, as lots of people liked silent failure approaches)
    • no modules, later another standard was developed (13211-2:2000) but it's weak and accepts lots of interpretations
    • no constraints, but... allows extension mechanism (5.5.11)

    Extensions are only allowed if there's a strictly conforming mode without it. Most things can be done with modules (constraints, builtins, ...) but some are more difficult (syntax extensions, ...). Constraints are allowed then!

    What was the state of the art of constraints at that time?

    First, freeze/2 and frozen/2. consistency only via labeling. Then in 1988, meta-structures but very unusable. Attr vars were created in 1990 as "better meta-structures" but it was module based! So couldn't join ISO because there was no place in the standard for modules.

    AttrVars are present in SICStus and Scryer and to a lesser degree Ciao, SWI. They have different models. Markus Triska says for library developers it's easier the SICStus/Scryer interface while probably it's more costly for the Prolog system too.

    What's the current WG17 work?

    • DCG (yes, still)
    • No work on attr var as it's too hard to specify them mechanically. So we prefer to work on a higher level with things like dif/2
    • dif/2 only works without bugs on three systems: Prolog IV, SICStus, and Scryer.
    • Unicode support
    • Prolog prologue - things like length/2. Still very surprising how very different behaviors are observed with these simple predicates.
    • clpfd/clpz. Far from finishing it
    • STO unification but rational trees originate problems again
    • Queries using answer descriptions (quad). First for documents, and later for tests.
    • Conformity testing. Continuous work

    Suggestions are welcome!

    That was the end of day 1. See the following post for day 2 where we'll see how Prolog helps in oncology, automated train systems, and the Austrian government.

    ]]>
    https://blog.adrianistan.eu/scryer-prolog-meetup-2023-notes Tue, 14 Nov 2023 17:18:07 +0000
    Can we lose technology? https://blog.adrianistan.eu/can-we-lose-technology https://blog.adrianistan.eu/can-we-lose-technology A few minutes ago I saw a news item saying that TSMC had to delay its microchip fab on US soil due to lack of talent. This reminded me of something I've been thinking about for a while and that is whether we can lose technology.

    And the answer is YES. Real life is not like Civilization or Age of Empires, where every time you discover something superior. In real life, technology has to be applied to stay alive. If technology is not applied, it dies.

    Think of ancient Rome. Rome was a civilization of great engineers. It would be unfair to say that they invented all the things they did, as similar works have been found much earlier, but they managed to replicate all that technology all over the Mediterranean. However, the civilizations that replaced Rome in the West could not do anything similar. Was it because these works were heretical to their religion? Or was there another reason? It was for another reason.

    In the fall of Rome in the West and the subsequent rise of different peoples (Visigoths, Goths, Suevi, Vandals, ...) what we had was a great period of political instability. In a period of political instability, it is not possible to build great engineering works. Why spend money on an aqueduct in this city if I might lose it in a month? Better to spend the money on reinforcing the army. This was the real reason why much of the technology needed to build great engineering works was lost. They were no longer being built and knowing about it became unnecessary for people.

    But what about books? Indeed, we can put our knowledge in books, but this is always incomplete. There are always things that the author leaves out because they are considered obvious in their context. There is some clumsiness and there are more or less fortunate descriptions. From the Romans, in fact, we preserve books where they describe the operation of various devices they used in their works. These devices were necessary to build works of the magnitude and precision they did. However, as Isaac Moreno Gallo comments in his video Ars Mensoria, for many centuries these book descriptions were misinterpreted, giving rise to machines that did not serve their purpose.

    Within the traditional trades, there is a man named Eugenio Monesma, who has dedicated years to the development of documentaries describing on video the steps of the traditional trades. It is a very noble and extremely interesting work if we do not want to lose that technology. But I don't know to what extent it is enough. A lot of information can be gleaned from a 30-minute video. But all the detailed information that an apprentice slowly made his own by following his master little by little over the years is still missing.

    ]]>
    https://blog.adrianistan.eu/can-we-lose-technology Sat, 22 Jul 2023 13:36:41 +0000
    ¿Podemos perder tecnología? https://blog.adrianistan.eu/podemos-perder-tecnologia https://blog.adrianistan.eu/podemos-perder-tecnologia Hace unos minutos he visto una noticia que decía que TSMC tenía que retrasar su fábrica de microchips en suelo estadounidense por falta de talento. Esto me ha recordado una cosa que llevo pensando un tiempo y es si podemos perder tecnología.

    Y la respuesta es que . La vida real no es como el Civilization o el Age of Empires, donde cada vez descubres algo superior. En la vida real, la tecnología tiene que aplicarse para seguir estando viva. Si no se aplica la tecnología, esta muere.

    Pensemos en la antigua Roma. Roma fue una civilización de grandes ingenieros. Sería injusto decir que inventaron las cosas que hacían, ya que se han encontrado obras similares mucho más antiguas, pero ellos consiguieron replicar toda esa tecnología por todo el Mediterráneo. Sin embargo, las civilizaciones que sustituyeron a Roma en occidente no pudieron hacer nada similar. ¿Era por que estas obras eran heréticas para su religión? ¿O hubo otro motivo? Fue por otro motivo

    En la caída de Roma en occidente y posterior ascenso de diferentes pueblos (visigodos, godos, suevos, vándalos, ...) lo que hubo era un gran periodo de inestabilidad política. En un periodo de inestabilidad política no se pueden construir grandes obras de ingeniería. ¿Para qué gastarme el dinero en un acueducto en esta ciudad si igual la pierdo dentro de un mes? Mejor gastarme el dinero en reforzar el ejército. Este fue el verdadero motivo de que se perdiese gran parte de la tecnología necesaria para la construcción de grandes obras de ingeniería. Ya no se construían y saber sobre ello se volvió algo innecesario para la gente.

    ¿Pero, y los libros? Efectivamente, podemos plasmar nuestro conocimiento en libros, pero esto siempre es incompleto. Siempre hay cosas que el autor obvia por considerar evidentes en su contexto. Hay despistes y hay descripciones más o menos afortunadas. De los romanos de hecho conservamos libros donde describen el funcionamiento de diversos aparatos que usaban en las obras. Estos aparatos eran necesarios para construir obras de la magnitud y precisión que hacían. Sin embargo, como comenta, Isaac Moreno Gallo en su vídeo Ars Mensoria, durante muchos siglos esas descripciones de los libros se interpretaron mal, dando lugar a máquinas que no servían para su función.

    Dentro de los oficios tradicionales, existe un señor llamado Eugenio Monesma, que se ha dedicado durante años a la elaboración de documentales describiendo en vídeo los pasos de los oficios tradicionales. Es una labor muy noble y extremadamente interesante si no queremos perder esa tecnología. Pero no sé hasta que punto es suficiente. De un vídeo de 30 minutos se saca mucha información. Pero sigue faltando toda esa información, detallada, que un aprendiz iba haciendo suya lentamente siguiendo a su maestro poco a poco durante años.

    ]]>
    https://blog.adrianistan.eu/podemos-perder-tecnologia Sat, 22 Jul 2023 10:47:08 +0000
    ¿Qué necesita tener un buen protector de pantalla de móvil? https://blog.adrianistan.eu/que-necesita-buen-protector-pantalla-movil https://blog.adrianistan.eu/que-necesita-buen-protector-pantalla-movil Los que me conocen en persona saben que llevo varios años usando el mismo móvil, un Huawei P30 Pro. Se trata de un teléfono de gama alta que me regalaron y he de decir que me ha gustado mucho. Este teléfono me hizo cambiar mi percepción de la gama alta, ya que he visto que estos teléfonos duran bastante más que los gama baja que solía utilizar como smartphone.

    El tema de este post no es ese, sino que al ser mi primer teléfono "caro" consideraba que necesitaba cuidarlo más de lo que había cuidado los anteriores. Nunca había usado fundas y en principio no quería usarlas ya que me parece que afean el teléfono. Mis amigos me recomendaron que si no quería funda, que optase al menos por un protector de pantalla, fácilmente accesible en una tienda de accesorios para móvil.

    Llegando a la pregunta del post, ¿qué necesita un buen protector de pantalla? Lo primero es que encaje en el teléfono. El Huawei P30 Pro tiene la pantalla ligeramente curvada en los laterales, por lo que un protector plano más o menos estándar no se acoplaría de forma adecuada. Esto haría que resistiese mucho peor los impactos, aparte del impacto estético de que entre polvo y suciedad por los huecos. En modelos relativamente populares es fácil encontrar protectores de pantalla especialmente diseñados para un teléfono en concreto.

    Lo segundo sería la resistencia a impactos. Aquí depende del material de fabricación. Existen protectores hechos de cristal templado, de plástico y de hidrogel. En general los de peor calidad serán los de plástico. Los de cristal templado serán por lo general más gruesos y consistentes y los de hidrogel, más finos y moldeables. El material también influirá en la colocación, siendo más sencilla en cristal templado y plástico y más engorrosa en el hidrogel (aunque nada fuera del otro mundo). El hidrogel tiende a ser más caro y si buscamos uno muy barato podemos acabar con uno muy fino que ofrezca poca resistencia.

    En tercer lugar, el tacto. Esto ya es más complicado de evaluar antes de comprarlo pero lo considero importante y de cara a tener en cuenta a futuro, sobre todo si como yo, vas a terminar usando varios protectores durante la vida del teléfono. El cristal templado, al estar hecho de un material más similar a la propia pantalla del teléfono puede ser más similar, pero eso no quiere decir que luego las pulsaciones se transmitan igual de bien. En general el punto más complicado es conseguir que los lectores de huellas funcionen perfectamente si es que tenemos un teléfono con lector de huellas bajo la pantalla. En mi caso, sí que lo conseguí y la uso a diario

    Por último mencionaría la suciedad que acumulan, como de sencilla es la instalación, incluso si incluye protector también de la cámara trasera. En mi caso agradezco mucho a mis amigos que me recomendaron que pillase un protector de pantalla ya que mi móvil ha caído al suelo en más ocasiones de las que me gustaría y he podido comprobar como el protector ha absorbido gran parte del impacto. Y por supuesto las ralladuras no se ven. En mi caso, al tener un golpe fuerte prácticamente tengo que cambiar el protector de nuevo ya que este se quiebra (absorbiendo el golpe) y pierde parte de las propiedades originales. Pero prefiero mil veces comprar varios protectores a tener que reparar pantallas todas esas veces.

    ]]>
    https://blog.adrianistan.eu/que-necesita-buen-protector-pantalla-movil Wed, 14 Jun 2023 21:05:35 +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
    Sorteo 2022: ¿Qué has aprendido en el blog? https://blog.adrianistan.eu/sorteo-2022-que-has-aprendido-blog https://blog.adrianistan.eu/sorteo-2022-que-has-aprendido-blog ¡Saludos, lectores! Dentro de nada entraremos en el 2022 y para celebrarlo, qué mejor que unos pequeños regalos.

    Voy a sortear entre todos los lectores participantes dos videojuegos: Tropico 5 - Complete Collection y Cities Skylines (base); ambos en versión de Steam. No son los juegos más modernos del mundo, lo sé, pero tenía las keys cogiendo polvo y mejor que alguien se aproveche de ellos.

    Ambos juegos me gustan mucho personalmente y he jugado a ellos bastantes horas. Los dos son juegos del género city builder (de mis preferidos). Trópico 5 tiene más componente político y de humor, aderezado con una gran banda sonora. Cities Skylines por otro lado es un city builder al estilo SimCity pero con bastante énfasis en el transporte. A nivel de dificultad es bastante sencillo y tiene una comunidad de mods impresionante.

    Participar en el sorteo

    Para participar en el sorteo tienes que responder una pregunta:

    ¿Qué has aprendido en este blog?

    Se trata de que respondas aquello que descubriste mientras leías este blog, ya sea este año o cualquier otro año anterior.

    La respuesta deberás escribirla en alguna red social, incluyendo si es posible un enlace al artículo donde sale lo que aprendiste (pero no es necesario). Red social entendida como concepto amplio, por supuesto también se admiten posts en otros blogs o menciones en vídeos de YouTube, soy bastante abierto al respecto.

    Una vez hayas publicado tu respuesta, debes escribir aquí, en esta entrada, un comentario con el enlace. Así sabré que estás participando.

    ¡Espero ver muchas respuestas!

    La fecha límite para participar es el 5 de enero

    ]]>
    https://blog.adrianistan.eu/sorteo-2022-que-has-aprendido-blog Thu, 30 Dec 2021 21:44:34 +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
    Estadísticas y felicitaciones de 2020 https://blog.adrianistan.eu/estadisticas-2020 https://blog.adrianistan.eu/estadisticas-2020 Este año ha sido peculiar en muchos aspectos. No me voy a enrollar mucho en lo que todo el mundo ya sabe, pero a nivel personal ha sido un año de cambios. Acabé la universidad y entré a trabajar en Telefónica. También estuve sustituyendo una baja por enfermedad en la universidad. Además pude centrarme en algunos proyectos para "acabarlos", todo un logro.

    Si estás leyendo esto te deseo un buen año 2021 y una buena década (sí, empieza ahora).

    Para cerrar el año, vamos a continuar la tradición de este blog y comentar las estadísticas. Este año solo va a haber estadísticas desde enero hasta noviembre ya que en diciembre desactivé Google Analytics y el nuevo sistema de estadísticas ha estado un poco inestable (espero arreglarlo pronto).

    Este año nos han acompañado 38.988 usuarios, son menos que en 2019 y que en 2018 (aunque en 2018 se contaba diciembre así que seguramene hubo algo más de usuarios que 2018).

    A nivel de países pocos cambios. Cada vez las visitas desde España representan menos porcentaje del total (en 2020 27%, en 2019 eran el 35%). El orden de países es bastante estable aunque Ecuador ha adelantado a Estados Unidos. Alemania ha caído del octavo puesto a mucho más abajo.

    >En cuanto a ciudades: Madrid sigue siendo la reina. Seguida de Bogotá, que adelanta a Santiago de Chile y cae al tercer puesto. Las siguen Ciudad de México, Medellín, Barcelona, Buenos Aires, Quito y Sevilla.

    Los navegadores más usados para acceder al blog fueron Chrome, Firefox y Safari, bastante en línea con estadísticas de otros sitios más importantes. Los sistemas operativos más usados fueron Windows, Android y Linux, sin perder de vista macOS y iOS, que son relevantes (13% de los usuarios).

    ¿De dónde venís? Principalmente accedéis por Google, aunque el número de fuentes se ha diversificado en 2020. Veo cada vez más gente usando buscadores que no son Google (Bing, DuckDuckGo, Ecosia). También veo alegre que cada vez tengo más fuentes que son sitios educativos, normalmente Aulas Virtuales tanto de Moddle como Google Classroom. Por otro lado, el correo ha caído como fuente para acceder al blog.

    Y por último, estos han sido los posts más vistos en 2020. La estadística en Python se vuelve a llevar la palma, aunque Rust, Prolog y CMake también tienen fuerza. Lo curioso es que ninguno de estos posts de esta lista han sido escritos en 2020. El post más visto en 2020 y escrito en 2020 es Testing en lenguaje natural con Gherkin y Behave, que ha sido el número 25.

    Que los posts de este año se han leído menos es un hecho. Las visitas han bajado y si se mantienen es por posts antiguos. Los motivos son muchos pero hay dos principales. El primero es que he decidido hablar de temas más de nicho (hay bastante de Prolog que yo mismo admito que a mucha gente no le interesará) y lo segundo es que en varios momentos he decidido dedicar mi tiempo a acabar proyectos (Mapaquiz, Space Pipes, mi TFG) más que a escribir en el blog. Aún así, tengo muuuchas cosas anotadas de las que me gustaría hablar y que espero que en 2021 les dedique tiempo.

    Un saludo, feliz año y gracias por estar allí, al otro lado de la pantalla.

    ]]>
    https://blog.adrianistan.eu/estadisticas-2020 Fri, 1 Jan 2021 17:09:54 +0000
    Nuevos horizontes (Pfizer) https://blog.adrianistan.eu/nuevos-horizontes-pfizer https://blog.adrianistan.eu/nuevos-horizontes-pfizer Es un honor comenzar una nueva etapa profesional y más en este año de locos

    Justo a punto de acabar el año me incorporo a la multinacional Pfizer para trabajar en nanobots en Prolog

    Se trata de un proyecto muy interesante, que lleva desarrollándose desde hace un tiempo gracias a la ayuda de la Fundación Bill Gates. Además tendremos también que usar tecnología 5G.

    ¿Lo malo del cambio? Que voy a tener que mudarme, pero de momento puedo seguir teletrabajando para evitar sospechas.















    PD: Evidentemente este post es una inocentada. ¡Feliz día de los inocentes!

    ]]>
    https://blog.adrianistan.eu/nuevos-horizontes-pfizer Mon, 28 Dec 2020 21:21:12 +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
    Un nuevo comienzo https://blog.adrianistan.eu/nuevo-comienzo https://blog.adrianistan.eu/nuevo-comienzo El 1 de julio va a ser un día importante en mi carrera profesional. Hace justamente un año empecé a trabajar como becario en Telefónica y ahora voy a pasar a forma parte de la plantilla con un contrato de verdad.

    Como becario

    ...estuve en un proyecto de una plataforma de Big Data para Telefónica. Era un proyecto con bastante gente, con varios subproyectos dentro del gran proyecto. Mi squad era el que se encargaba de que la plataforma se ejecutase correctamente en la nube (primero AWS, después Azure). Para ello usábamos Kubernetes, Ansible y posteriormente Terraform y un programa propio en Python.

    Terraform precisamente es una de las herramientas en las que me "especialicé", aunque durante todo el año, mis tareas fueron bastante variadas, ya que pude toquetear bastantes cosas, desde Grafana hasta Elasticsearch pasando por Sentry.

    A nivel personal, me sentí bastante a gusto. Mi oficina era la de Boecillo (Valladolid) pero muchos miembros del equipo eran de Madrid, Huesca o Valencia y no podía verles más que por Slack. Sin embargo, la gente de Boecillo era la más cercana a mis tareas. Me ayudaban en todo en lo que me atascaba y tenían buen sentido del humor. Además, dentro del equipo había gente de mentalidad curiosa, siempre descubriendo cosas nuevas. Aprendí mucho de ellos. La empresa por su parte también me dedicó horas con cursos, tanto presenciales como online. Si bien había alguno mejor que otro, hubo sesiones muy aprovechables. Todo esto sin olvidar que durante mi estancia hubo una pandemia global.

    Tras casi un año, ya conocía bastantes cosas de la empresa, sus pros y sus contras y si bien no podía comparar con otras empresas, decidí aplicar a un puesto. Después de unas cuantas entrevistas, finalmente llegué al puesto que empiezo el 1 de julio.

    El futuro

    El sitio al que me incorporo es en otro proyecto diferente, más enfocado a IoT y a hogares inteligentes. Sin embargo, no dejo de lado el backend y la nube. Espero que en esta nueva etapa pueda aprender más todavía y seguir conociendo gente tan buena como la que había en mi etapa de becario.

    ¿Qué nos deparará el futuro?

    PD: Durante mi estancia usé MacOS, pero no me ha acabado de convencer y ahora he pedido Ubuntu. Veremos si a nivel profesional cumple.

    ]]>
    https://blog.adrianistan.eu/nuevo-comienzo Mon, 29 Jun 2020 22:50:50 +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
    ¡Feliz año 2020! https://blog.adrianistan.eu/feliz-2020 https://blog.adrianistan.eu/feliz-2020 Si tomas los cuatro números primos consecutivos empezando con 17, los elevas al cuadrado y los sumas, ¿qué te da?

    17²+19²+23²+29² = 2020

    ¡Efectivamente! 2020. ¡Felicidades a todos los lectores del blog que me acompañáis! Sé que ya sois unos cuantos. Espero que el año que viene podamos seguir hablando aquí de programación, inteligencia artificial, computación y lo que surja.

    Antes de dar paso a la muerte definitiva a Python 2.7 vamos a repasar las principales estadísticas del blog.

    Este año habéis visitado el blog la friolera de 44.864 usuarios en 58.116 sesiones, un claro aumento respecto a 2018 (39.328 usuarios y 51.731 sesiones).

    Países

    Los tres primeros puestos son inamovibles, aunque España cada vez representa menos porcentaje de visitas. En el cuarto puesto Chile ha adelantado a Argentina, aunque siguen muy cerca el uno del otro. Los países europeos Francia y Reino Unido salen de la lista, tienen muchas menos visitas este año. 

    Las ciudades que más visitan mi blog son: Madrid, Santiago (de Chile), Bogotá, Ciudad de México, Barcelona, Buenos Aires y Medellín.

    Tecnología

    Resultados muy similares al año pasado

    Artículos

    Y el artículo más visitado en 2019 es:

    Estadística en Python: media, mediana, varianza, percentiles (Parte III)

    No me voy a extender mucho en esto porque con la Ley de Zipf se vieron los artículos más leídos

    Sin extenderme mucho más, ¡os deseo feliz año a todos!

    ]]>
    https://blog.adrianistan.eu/feliz-2020 Tue, 31 Dec 2019 19:48:00 +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
    Ley de Zipf en el blog https://blog.adrianistan.eu/ley-zipf-blog https://blog.adrianistan.eu/ley-zipf-blog Estaba yo leyendo uno de mis blogs favoritos, Los días y las frases, que pese a lo que se pueda pensar de mí, no trata, ni remotamente de tecnología, programación, etc sino de aforismos e historia. Muy entretenido, siempre leo sus artículos nada más salir. Hace unos meses ya, el autor publicó una entrada sobre la ley de Zipf. Como él lo explica mejor que nadie, voy a copiar literalmente el texto:

    George Kingsley Zipf fue un lingüista norteamericano de mediados del siglo XX que se dedicó a aplicar el análisis estadístico a las lenguas.
     
    Uno de los estudios que le reportó fama fue el descubrimiento de la ley que lleva su nombre, la "Ley de Zipf", según la cual la frecuencia con la que son utilizadas las palabras siguen una distribución estadística concreta. No entraremos en detalles técnicos de su formulación, pero básicamente nos dice que la palabra más usada en un idioma (the, en inglés) aparece el doble de veces que la segunda más usada (of), y el triple que la tercera, etc. 
     
    Pero esta ley de la frecuencia de las apariciones no ocurre solo con las palabras, su ámbito es mucho mayor. Por ejemplo, en el de las poblaciones de las ciudades de un país: la ciudad más grande suele tener el doble de habitantes que la segunda población de ese país. Y en general es aplicable a la ordenación de  grandes conjuntos de datos... E internetque no deja de ser una base de datos enorme,no podría ser menos, también se puede describir el número de visitas a las páginas individuales de Internet en un intervalo de tiempo dado... (Artículo https://diasyfrases.blogspot.com/2019/09/cumple-este-blog-con-la-ley-de-zipf.html)

    A continuación, prueba con los artículos del blog, según número de visitas, a ver si la popularidad sigue esta curiosa ley, a priori, relacionada con la lingüística. ¡Al parecer Los días y las frases sigue una distribución similar a la ley de Zipf! ¿Y mi blog, Adrianistán? ¿Seguirá también la ley de Zipf?

    Experimento

    Voy a tomar los datos del mes de octubre, ya que es el más próximo que ya ha acabado y considero que es un mes representativo, bastante normalillo. Además, las entradas que publiqué en octubre no parecen haber tenido demasiado impacto en general. También he decidido quitar la página de inicio, ya que no es un artículo como tal.

    El artículo más visto del mes es Estadística en Python Parte 3 con 1327 visitas. A partir de aquí podemos calcular las visitas estimadas según la ley, dividiendo progresivamente.

    Artículos Visitas Reales Visitas Zipf
    /estadistica-python-media-mediana-varianza-percentiles-parte-iii 1327 1327
    /estadistica-python-distribucion-binomial-normal-poisson-parte-vi 445 663.5
    /estadistica-python-pandas-numpy-scipy-parte-i 434 442.333333333333
    /rust-101-tutorial-rust-espanol 328 331.75
    /introduccion-a-prolog-tutorial-en-espanol 233 265.4
    /tutorial-de-cmake 199 221.166666666667
    /estadistica-python-analisis-datos-multidimensionales-regresion-lineal-parte-iv 162 189.571428571429
    /cosas-no-sabias-python 151 165.875
    /estadistica-python-ajustar-datos-una-distribucion-parte-vii 134 147.444444444444

    (veo que os gusta mucho la estadística con Python)

    Vemos que hay números muy próximos a la estimación, pero mejor hagamos un gráfico.

    Vemos que la curva real se ajusta relativamente bien a la curva estimada por la ley de Zipf. El punto donde más se aleja (tanto absoluto como relativamente) es el segundo artículo.

    Podríamos decir, que sí, en Adrianistán también se aplica la ley de Zipf. ¿Será, quizá, que esta ley se aplica en todos los sistemas de información? ¿Es parte intrínseca de la realidad? Os dejo reflexionar

    ]]>
    https://blog.adrianistan.eu/ley-zipf-blog Mon, 25 Nov 2019 21:47:21 +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
    Videojuegos de ordenador para el verano https://blog.adrianistan.eu/videojuegos-verano-ordenador https://blog.adrianistan.eu/videojuegos-verano-ordenador En este blog no he hablado mucho sobre videojuegos. Me gustan mucho, pero últimamente no juego tanto como antaño. Eso sí, cuando juego, suelo estar bastante tiempo. En la época de verano sin embargo, encuentro más tiempo para jugar. Hasta hace bastante tiempo no tenía un tipo de PC acorde a las exigencias de los juegos actuales. Hace unos años me construí un PC que ya me permitía jugar a bastantes triple A.

    Tiene un procesador AMD FX-4350 y una gráfica AMD R7 250. De RAM pusimos 8 GB y de disco tenía 1 TB de tipo HDD. No era lo mejor del momento, pero entraba dentro de nuestro presupuesto. El ordenador ha cumplido mis expectativas y me ha permitido jugar a los juegos que quería, cuando y como quería. En este artículo os voy a comentar algunos de los juegos que juego en verano. Por supuesto, más pronto que tarde necesitará una renovación, con los últimos procesadores de AMD y las gráficas de NVIDIA, que rompen récords cada vez que se presentan. También he podido comprobar la potencia de los SSD, que dentro de poco se convertirán en algo obligatorio.

    La mayoría de los juegos de la lista son compatibles con ordenadores bastante asequibles, no necesitarás un ordenador gaming de última generación para poder jugar satisfactoriamente a ellos.

    Starcraft II

    Leyendo las noticias de inteligencias artificiales como DeepMind que juegan al juego me entró curiosidad. Había jugado al primero unas pocas horas, así que sabía un poco por donde iba. No obstante, aprovechando que ahora es gratuito, convertido en free to play, me atreví a ponerme más en serio con este juego. Se trata de uno de los juegos más importantes de la esfera de los e-sports. No obstante, yo empecé con la campaña gratuita, que es un buen tutorial. Luego pasé a partidas multijugador 1vs1. El ritmo es diferente, es mucho más frenético, si no eres rápido, te comen. Siendo yo una persona más acostumbrada a ir más lentamente, construyendo las bases más detenidamente, me ha costado un poco entender la mentalidad. Todavía soy muy malo y fuera de la campaña es difícil de aprender mecánicas avanzadas.

    Factorio

    Factorio es un juego de automatización industrial. La idea es muy simple, pero tremendamente potente. Tienes que ir construyendo cosas, como si fuese una gran cadena de montaje. El objetivo es construir un cohete bastante complejo pero eso es lo de menos. Lo adictivo es intentar hacer tu fábrica lo más eficiente y productiva posible. Un juego muy recomendable para programadores. Existe modo de juego pacífico y con enemigos. Recomiendo empezar con el pacífico para saber de que va la cosa, y luego pasarse a enemigos, que es más interesante.

    Cities Skylines

    Cities Skylines es el city builder por excelencia de esta generación. Con una cantidad ingente de mods y DLCs, el juego completo es impresionante y me gusta bastante. Respecto a la simulación... A nivel económico y de satisfacción de la gente es muy fácil y lo único interesante es a nivel tráfico (no es casualidad que esté hecho por una empresa que se dedicaba a hacer juegos de transporte). No obstante, por defecto la gestión del transporte es justamente un poco mediocre. Por otro lado la calidad visual es muy buena.

    Enter the Gungeon

    Enter the Gungeon es un juego roguelike muy entretenido. Con contenido a rabiar (armas, salas, enemigos, objetos,...), la mecánica del juego es simple. Dispara y que no te den (sobre todo más lo segundo). Tiene un toque de humor que me encanta y el juego internamente es complejo, aunque no lo parezca. Debo decir que todavía no lo he acabado pero me sigue entreteniendo aunque nunca pase del cuarto piso.

    Batman: Arkham Asylum

    Batman: Arkham Asylum es un triple A con unos añitos ya. Batman es mi superhéroe favorito (por no decir, el único que tolero) y tenía ganas de probar este juego. Se trata de un juego de acción, sigilo y combate cuerpo a cuerpo en tercera persona. Me sorprendió gratamente, una ambientación perfecta, una historia interesante y una jugabilidad muy potente.

    Dirt 3

    Dirt 3 es uno de los mejores juegos de carreras que he podido jugar. Principalmente son carreras de rally, las cuáles son ligeramente diferentes. En primer lugar, suelen ser punto a punto, es decir, nunca repites la misma curva y además se intenta que no te la puedas llegar a aprender. En segundo lugar, son cronometradas, pocas veces tendrás que adelantar. El juego propone una conducción bastante realista (sin llegar a ser una simulación), con buenas físicas. Bastante contenido, con montones de coches de las marcas clásicas: Renault, Lancia, Mitsubishi, Mini, Peugeout, BMW, Subaru, ... Yo me pasé la campaña con marchas manuales y se lo recomiendo a todo el mundo, ya que es muy satisfactorio (en F1 puede ser más deseperante). El juego también tiene pruebas de circuito y de drifting, las cuáles no son tan buenas pero son minoritarias. En definitiva, un buen juego para quemar adrenalina, a velocidades tan bajas que te sorprenderás.

    Theme Hospital

    Este juego es quizá el más antiguo de esta lista, de hecho si lo compráis en GOG, lo recibiréis empaquetado junto con DOSBox para que funcione correctamente. Theme Hospital es un juego de tipo tycoon (gestión empresarial). En este caso estamos ante uno de los mejores títulos del género, donde gestionamos un hospital. El juego no pretende ser serio, de hecho todas las enfermedades son inventadas y estúpidas pero es entretenido y llegado a un punto, puede llegar a suponer un reto lograr que no te despidan. Juego muy creativo, en jugabilidad ha envejecido bien, pero hay algunos detalles un poco arcaicos.

    Trópico 5

    Trópico es una saga de city builders, mismo género que Cities Skylines, pero con otro enfoque, radicalmente diferente. En Trópico gestionamos una isla tropical siendo presidentes de esta. Aquí la política es más importante, ya que inspirándose en ciertas dictaduras, deberás mantenerte en el poder. Dentro de la isla habrá facciones, que tendrás que contentar y podrá haber rebeldes, que podrán iniciar la revolución. Para ello, tú que eres precavido, habrás construido un ejército lo suficientemente fuerte, con un salario aceptable. Además organizarás elecciones falsas para dar apariencia de democracia. El juego tiene un fantástico sentido del humor y Trópico 5 mejoró mucho la jugabilidad respecto al 3 (el anterior que he jugado). Por otro lado, cabe destacar la escala del juego. Si en Cities Skylines las ciudades son de cientos de miles de habitantes, en Trópico con una isla de 150 habitantes ya tendrás bastantes problemas. Es un juego mucho más individualizado, donde cada persona importa. Además en la versión 5, la historia del mundo influye.

    Mini Metro

    Mini Metro es un juego sencillo donde tenemos que ir construyendo líneas de metro. El juego tiene una estética totalmente minimalista, que junto a su mecánica simple hace que sea muy fácil aprender a jugar. Sin embargo, la dificultad de este juego según avanza el tiempo no es baladí y en algún momento perderás (se pierde cuando una estación está colapsada). Juego muy simple pero entretenido al que le he echado muchas horas. Como punto negativo, el ritmo. Al empezar el juego es demasiado simple y aburrido, para luego ya ponerse interesante. La intensidad no es nada regular.

     

    El resto

    Hay juegos que he jugado este verano que se quedan fuera de la lista, principalmente porque no me han gustado tanto. Algunos de ellos, sin orden en particular: Fórmula 1 2015, The Sexy Brutale, Super Meat Boy, Counter-Strike: GO, Fortnite, Maldita Castilla, Civilization V, Borderlands, ...

    Como siempre, cualquier aportación es bienvenida en los comentarios. Un saludo y hasta otro día.

    ]]>
    https://blog.adrianistan.eu/videojuegos-verano-ordenador Sat, 10 Aug 2019 17:24:35 +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
    Feliz Navidad 2018 https://blog.adrianistan.eu/feliz-navidad-2018 https://blog.adrianistan.eu/feliz-navidad-2018

     

    ¡Feliz Navidad a todos! Que los problemas de vuestra rutina no os amarguen el carbón de azúcar que os merecéis.

     

    Estas fechas además son apropiadas para hacer un repaso de lo que ha sido el blog durante 2018. Así que voy a abrir Google Analytics y veamos que ha ocurrido.

     

    Usuarios

     

    Este año hemos batido récord, con 39.328 usuarios únicos contabilizados, en 51.731 sesiones. Esto es casi el doble que el año pasado en ambas métricas.

     

     

    Respecto a vuestra edad, cada vez sois más jóvenes. Ha habido un crecimiento relativo importante en la categoría 18-24 y un retroceso en los otros grupos de edad, pero en general todos los grupos tienen más lectores. Respecto a vuestro sexo, este año ha habido un 18,65% de mujeres, respecto al año anterior de 12,3%. Recordad que todos estos datos demográficos no son nada precisos y es una estimación que hace Google Analytics.

     

    En cuanto a vuestros intereses, bastante similar a otros años, usuarios tecnológicos, bastante más software. Las categorías que más han crecido este año son Enterprise Software y Science/Mathematics, curioso.

     

    La mayoría tenéis configurado como idioma el inglés o el castellano, con el tercer idioma siendo el francés (pero a mucha distancia).

     

    En cuanto a vuestra ubicación, la mayoría seguís siendo de España, aunque latinoamérica ha subido este año bastante, algo que me ha alegrado mucho. El país que más ha aumentado su porcentaje de visitas respecto al año pasado a sido Nueva España México. Destacar que aunque en Venezuela y Estados Unidos ha aumentado el número de lectores, lo ha hecho a un ritmo tan bajo, que su porcentaje de lectores ha bajado, ¿tendrá que ver la situación política? Del Top 15 también señalar que Reino Unido es el único país donde ha bajado el número de lectores. Y este año se han recibido visitas desde 114 territorios diferentes del mundo.

     

     

    En cuanto a ciudades, la mayoría venís de la ciudad con el mejor agua del mundo, los mejores atascos, las mejores cervezas y las mejores catedrales: Madrid. Le sigue Barcelona y en tercer lugar, Santiago de Chile. El top 10 lo completan: Bogotá, Ciudad de México, Buenos Aires, Valencia, La Victoria, Medellín y Sevilla. Esta vez, mi propia ciudad, Valladolid, no está en el top 10.

     

     

    Se confirma la tendencia de que Chrome es el navegador más usado. El segundo es Firefox y el tercero es Safari. Como cosas curiosas, Edge ha superado a Internet Explorer y hay más usuarios de Opera que de Edge.

     

     

    En cuanto a sistemas operativos se vuelve a ver una dominancia de Windows y Android. Por otro lado me gustaría conocer a los que han entrado desde BeOS y desde Tizen.

     

    ¿Cómo llegáis a este blog?

     

    Ahora vamos a ver como accedéis a este blog. La gran mayoría llegáis a través de un buscador, principalmente Google aunque también Yahoo, Ecosia, Bing, Ask, DuckDuckGo y Yandex.

     

    De los que llegáis a través de otras páginas, la mayoría venís de Menéame, que este año se ha portado muy bien con el blog. La siguiente web es GitHub, ya que enlaza al blog en varios sitios (tutoriales de Rust, repos muy curiosos, ¡e incluso desde issues!). Mención especial a las tres instancias de Moodle de las que tengo constancia que enlazan a mi blog (una de un instituto de la República Checa, muy curioso). Bastantes visitas tambien desde Reddit.

     

    También hay quién nos sigue desde el canal de Telegram, un lector RSS, redes sociales y el correo (podéis encontrar más información en la barra lateral).

     

    Lo más visto

     

    Finalmente, lo mejor para el final, ¿qué contenido es el más popular del blog?

     

    He de decir que esto ha sido una total sorpresa, se trata de un artículo que no tuvo apenas repercusión cuando lo publiqué, pero parece que muchísima gente ha llegado a él a través de Google. Se trata de...

     

    Estadística en Python: media, mediana, varianza, percentiles (Parte III)

     

    La verdad es que me ha sorprendido mucho, pero al parecer mucha gente desea hacer estadística en Python, pero curiosamente no es el primer artículo de la serie que hice.

     

    El segundo artículo ha sido: IPFS, el futuro de la web descentralizada, que tuvo bastante repercusión e interés por vuestra parte desde el principio.

     

    El tercer artículo con más visitas ha sido: Estadística en Python: Parte 1 Como véis, la estadística ha tenido bastante éxito en el blog.

     

    Sin nada más, me despido. ¡Muchas gracias a todos los que seguís el blog! Este año cada vez sois más, y estáis más presentes. Yo me voy a ver ¡Qué bello es vivir! otra vez, que es una película que me gusta mucho.

    ]]>
    https://blog.adrianistan.eu/feliz-navidad-2018 Tue, 25 Dec 2018 00:00:00 +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
    Un muro en Cuzco https://blog.adrianistan.eu/un-muro-en-cuzco https://blog.adrianistan.eu/un-muro-en-cuzco
    Foto de https://livinmiami.wordpress.com/2012/04/27/cuzco-8/

    Este muro, célebre por la película Diarios de Motocicleta, ha sido llamado el muro de los incas y de los incapaces. Realmente el contraste es evidente. Quizá incluso nos pueda parecer que era imposible que los incas construyesen sus muros con esas piedras tan grandes, tan pesadas. Tenemos la misma mentalidad que la del muro construido por los españoles.

    Y es que es todo cuestión de mentalidad, de prioridades. Las prioridades de los españoles eran otras. Son otras, porque esas prioridades siguen siendo lo mismo y forman parte de lo más profundo de la cultura. La mentalidad del muro español refleja varias cosas.

    Rapidez, es importante construir algo rápidamente. El muro inca posiblemente tardó más en construirse que el español, hecho de bloques más pequeños. Más rápida su extracción, más rápido su transporte, más rápida su colocación. Si hacemos rápidamente una tarea, podemos empezar a hacer otra tarea antes y al final, hemos hecho más tareas.

    Flexibilidad, es importante poder reaprovechar el trabajo ya realizado. Si producimos bloques más pequeños, estos nos pueden servir para otros muros, podemos tapar otros huecos, no estamos limitados al muro en concreto que estamos construyendo.

    Pragmatismo, es importante centrarse en el objetivo final de nuestras tareas. Un muro cumple la función de separar la calle de la casa, aislarla del frío, los ladrones y las miradas furtivas. Si cumple esas cualidades, ya tenemos un buen muro y cuánto más simple sea el mecanismo por el cuál ese muro acaba construido, mejor. No estamos para complicarnos con mundanidades.



    Los incas tenían otra mentalidad cuando construyeron este muro.

    Durabilidad, es importante que las cosas que hagamos duren el mayor tiempo posible. Los bloques grandes aguantan más, son más difíciles de romper. Los muros con muchos bloques tienes más lugares de posibles grietas, donde puede entrar el agua. Al final con el tiempo, hemos perdido menos el tiempo con este muro que si lo hubiésemos hecho rápido, pero costó más de entrada.

    Seguridad, es importante que las cosas que construyamos generen el menor número de situaciones peligrosas. En la zona de Cuzco los terremotos son algo relativamente habitual. Los bloques grandes soportan mejor los terremotos y reducen las vibraciones que se transmiten.

    Naturaleza, es importante que nuestras obras sean similares a la naturaleza, de quien venimos, y fuente inagotable de sabiduría puesta en práctica. En la naturaleza encontramos grandes rocas por dóquier, son el día a día en el mundo fuera de la ciudad.

    Superación, el hombre siempre se pone metas para ver si es capaz de superarse. Cada momento en la vida es una oportunidad para hacerlo, da igual que se esté construyendo solo un muro, al final la magnificiencia se entrena. Construir con estas enormes piedras no es fácil, pero ahí estamos para superar esa dificultad y que la gente al verlo quede estupefacta.

    Pero ante todo, ambos imperios cuando construyeron este muro lo hicieron de la forma correcta, la que ellos consideraban natural, posiblemente nunca se cuestionaron a sí mismos porque hacían los muros así. Era algo evidente para ellos.

    Quizá nunca llegamos a entender la mentalidad de los incas y por qué construían sus muros así, quizá las razones que he dado aquí no tengan nada que ver con sus verdaderos motivos, su mentalidad y sus prioridades. De todos modos, ¿eres un inca o un incapaz?]]>
    https://blog.adrianistan.eu/un-muro-en-cuzco Wed, 4 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
    Te espero en la feria Espacios de Ingenio https://blog.adrianistan.eu/te-espero-la-feria-espacios-ingenio https://blog.adrianistan.eu/te-espero-la-feria-espacios-ingenio


    El viernes 16 por la mañana daré el taller (dos sesiones), Programar es fácil: ponte a ello. En el taller se iniciará a los alumnos en la programación con Python.

    Si tienes algún familiar que le pueda interesar, házselo saber al instituto/colegio para que le lleven a la actividad.

    Inscripciones y más información

    ]]>
    https://blog.adrianistan.eu/te-espero-la-feria-espacios-ingenio Wed, 21 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
    ¿Cómo recuperar archivos borrados? https://blog.adrianistan.eu/recuperar-archivos-borrados https://blog.adrianistan.eu/recuperar-archivos-borrados Papelera de Reciclaje, una forma de recuperar archivos que hemos borrado sin querer, dándonos otra oportunidad para pensar si realmente queremos borrar el archivo. No obstante, ¿qué pasa si has vaciado la papelera sin darte cuenta de lo que había dentro? O peor, hay pinchos USB que carecen de la opción de papelera. ¿Está todo perdido? No, todavía podemos probar un programa de recuperación. Existen varios programas como Recuva o Disk Drill. Yo voy a usar Recuva que es más popular, puedes descargar aquí Recuva. Se trata además de un programa gratuito, sin coste para el usuario (aunque existe una versión pro).

    Una vez la instalación haya sido correcta (el procedimiento es trivial), ejecutamos el programa con Run Recuva.

    Asistente de Recuva


    El modo por defecto, es un asistente de que nos va haciendo preguntas sobre el tipo de archivo que vayamos a recuperar.

    Si el archivo que queremos recuperar no forma parte de ninguno de estos tipos de archivos, seleccionamos la primera opción. Luego nos pregunta sobre la antigua localización física del fichero. Es decir, en qué disco duro estaba. Si nos acordamos, el tiempo de recuperación puede reducirse mucho, si no lo sabemos la efectividad es la misma, pero tarda más.

    Una vez le hayamos dado los datos, empezará la búsqueda de archivos que han sido recientemente borrados.


    Lista de elementos


    Una vez haya finalizado la búsqueda, tendremos una lista de elementos que se pueden recuperar. Si son imágenes, podremos ver una miniatura de ellas.

    Para recuperar el archivo lo seleccionamos y pulsamos Recuperar. Especificamos la carpeta donde van a ponerse los archivos extraídos de la recuperación y comenzamos. Si todo va bien, tendremos los archivos listos en la carpeta especificada. Tendremos que renombrar el archivo, ya que muchas veces suelen salir nombres que no tienen nada que ver con el original.

    La foto son unos puentes antiguos bajo tierra del río Esgueva que se encuentran en Valladolid y que han salido al hacer obras en un solar

    Recuva recomienda restaurar los archivos en unidades físicas diferentes para mejorar las probabilidades de éxito.

    Existe también un modo avanzado, que tiene más opciones, permite hacer búsquedas más selectivas, etc... Podemos acceder fácilmente a él, haciendo click en Cambiar al modo avanzado.

    ¿Cómo funcionan estos programas?


    ¿Por qué funcionan los programas como Recuva? Realmente funcionan porque los borrados en los discos duros y memorias USB son perezosos. Es decir, borrar algo simplemente es decir que esa zona del disco se puede usar sin temor para nuevos archivos, pero no modifica nada más. Esto hace que los borrados sean muchos más rápidos y programas como Recuva lo aprovechan para recupera archivos si no ha llegado un fichero nuevo a esa zona. Es por eso importante tener en cuenta, que a más archivos que hayamos creado después del borrado, menos probabilidad hay de encontrarlo.]]>
    https://blog.adrianistan.eu/recuperar-archivos-borrados Fri, 2 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ísticas del blog, versión 2017 https://blog.adrianistan.eu/estadisticas-blog-2017 https://blog.adrianistan.eu/estadisticas-blog-2017

    Usuarios


    En general 2017 ha sido un muy buen año. A fecha del 10 de diciembre he recibido 24.677 sesiones, de 19.618 usuarios y 29.925 visitas a páginas (al acabar diciembre serán más de 30.000 fácilmente). Todo esto sin contar a la gente que entra con un bloqueador de anuncios, que puede bloquear Google Analytics también. Según PageFair, en España el 19% de los usuarios de escritorio usan un AdBlocker. La gráfica que se dibuja es un poco rara sin embargo.

    Prácticamente se ve plana debido al gran pico que hubo a finales de julio. De esta gráfica solo se puede apreciar otro pico el 20 de abril. Estos picos se relacionan con dos de los artículso más leídos este año en el blog.

    El público no ha evolucionado mucho. Ha aumentado ligeramente el procentaje de mujeres y han aumentado los grupos de edad más alta, mientras que la franja 18-24 ha sufrido un descenso.

    En el idioma tampoco hay grandes sorpresas. La mayoría entráis con español, catalán o en-us (que no deja de ser una configuración por defecto muy común). Las visitas en alemán, francés las achaco principalmente a emigrantes y portugués de Brasil a que mucha gente de allí sabe algo de español.

    Si bien otros años estaba más compensada la cosa, este año España acapara la mayor parte de las visitas (15.227). Le sigue muy de lejos, pero de forma nada despreciable Méxio y Argentina (1.588 y 1.003 respectivamente). Para completar los 10 países que más visitan mi blogn tenemos a Colombia, Estados Unidos, Chile, Venezuela, Perú, Alemania y Reino Unido.

    Estados Unidos ya lleva siendo durante bastantes años un país hispano más, relativamente potente, en lo que es este blog y como siempre, me sorprende la falta de presencia de algunos países latinoamericanos como Uruguay o Ecuador, que podrían estar perfectamente por encima de Alemania o Reino Unido.

    Tecnología




    Respecto a los navegadores hay una tendencia clara en el uso de Chrome. Firefox se mantiene en segunda posición y Safari consolida su tercera posición. Vemos que Edge todavía no ha superado a Internet Explorer en este blog. El navegador más extraño con el que se ha accedido a mi blog es Puffin.

    En sistemas operativos no hay apenas cambios. Windows y Android siguen siendo los reyes. Mención especial a aquellos que usan Firefox OS y BlackBerry.

    Adquisición


    Esta es una de mis partes preferidas, saber de dónde venís. Menéame repite como una web desde las que entra más tráfico al blog (este año este blog llegó a portada). Google aporta muchas visitas, como el gran buscador. RSS en realidad es una categoría peligrosa, puesto que hay varios sitios que toman la fuente RSS (planetas, bots, ...). Finalmente, Facebook parece despegar, aunque Twitter sigue arriba. Mención especial a Instagram (sorprendente para mí, la verdad) y... sigo sin saber como se contabilizan tantas visitas desde ads.adpv.com, pero creo que es un error en algún sitio.

    Otras webs que me han enlazado: foroantiusura.org, hispachan.org, Pocket, iessanvicente.com, campusvirtual.uva.es, aulas.inf.uva.es, Mastodon, rust.libhunt.com, GNU Social, www3.gobiernodecanarias.org, ... Desde el infame DesdeLinux todavía llega gente a mi blog...

    Contenido


    Y si, llegó la hora de saber cuáles han sido las páginas más vistas. La ganadora indiscutible es Todo lo que debes de saber sobre las tarjetas de crédito, débito y prepago, un artículo algo diferente en el blog, sobre uno de los muchos temas secundarios que me interesan y que gustó mucho. Este artículo causó el pico de finales julio. A continuación un clásico, Tutorial de GnuCash, que fue publicada el año anterior y sigue atrayendo a miles de personas. ¿Os acordáis del pico de abril? Fue provocado por Novedades de C++17. Y es que, a la gente le interesa mucho C++. Y solo después de estos tres artículos tenemos a la portada del blog. El tutorial de Rust, de CMake y de WiX también han recibido bastantes visitas. Y el artículo de la Calculadora de 4 bits sigue recibiendo muchas visitas desde el año 2013 (es de los que estaban en el primer Blogger del principio).]]>
    https://blog.adrianistan.eu/estadisticas-blog-2017 Sun, 10 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
    Curve, una tarjeta para dominarlas a todas https://blog.adrianistan.eu/curve-una-tarjeta-dominarlas-todas https://blog.adrianistan.eu/curve-una-tarjeta-dominarlas-todas Curve, se trata de una tarjeta que te permite tener todas tus tarjetas combinadas en una única tarjeta. Así, con la misma tarjeta, puedes pagar una compra con tu tarjeta del BBVA y después pagar con la de Bankia.

    La tarjeta no tenía comisiones, así que decidí pedirla, aunque solo fuese por probarla. Me bajé la app, me registré y el envío llegó a España desde Reino Unido en unos días.

    Se trata de una tarjeta MasterCard, aceptada en multitud de establecimientos y cajeros. Tiene NFC, chip EMV y banda magnética. Para activarla debemos realizar una compra con chip, usando el PIN que se nos mostrará en la aplicación. Por su uso no nos cobran nada, pero no la recomiendo para retirar dinero de cajeros, pues en España nos cobrarán (para eso mejor Revolut o N26). La tarjeta la controlamos a través de la aplicación:



    Tarjeta de N26 en la aplicación de Curve

    En la aplicación podemos seleccionar entre las tarjetas que tenemos agregadas y ver los gastos que hemos realizados con ellas.

    Tarjetas que tengo agregadas a mi tarjeta Curve

    Para añadir una tarjeta hay varios métodos, dependiendo de la tarjeta en cuestión. El más habitual será hacer un cargo para posteriormente devolver la cantidad. Si la tarjeta no tiene protecciones de PIN 3D, nos pedirá que indiquemos un número que aparece en el concepto del cargo.

    No podemos usar una tarjeta hasta que no esté verificada

    Actualmente Curve dispone de un sistema de referidos. Por cada amigo que invites tú ganas 5£ y tu amigo gana 5£. Curve es una muy buena tarjeta, que permite controlar todas tus tarjetas desde un único sitio. Es la tarjeta que actualmente llevo en la cartera para pagar (y la de Revolut para los cajeros). Mi código de amigo, por el cual os darán 5£ al hacer vuestra primera compra es:

    ZC6PR


    Para haceros con la tarjeta necesitáis registraros desde la aplicación para Android o para iOS.]]>
    https://blog.adrianistan.eu/curve-una-tarjeta-dominarlas-todas Fri, 24 Nov 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
    Crónica de la 62 SEMINCI https://blog.adrianistan.eu/cronica-la-62-seminci https://blog.adrianistan.eu/cronica-la-62-seminci SEMINCI, la Semana Internacional de Cine de Valladolid. Este año he sido uno de los 5 miembros del jurado joven de la sección Punto de Encuentro. La sección reúne los primeros y segundos largometrajes de los directores.



    El sábado comenzamos el visionado de las películas en el Teatro Zorrilla con la película Paris la Blanche, que comentaré más tarde y con la gala inaugural en el Teatro Calderón.



    Fernando Colomo llega por la alfombra verde mientras hay un grupo que lucha por el pueblo palestino

    Ese mismo día, ya por la noche, tocó Haporetzet. Al día siguiente vi 4 películas: The Nile Hilton Incident, La Librería, Adiós Entusiasmo y Aala Kaf Ifrit.

    Mi acreditación

    Los días prosiguieron viendo todos los días películas por la mañana y por la tarde, algunas eran entretenidas, otras te hacían replantear tu existencia. Me costó bastante conseguir mi acreditación, por un fallo que tuvieron con los códigos QR. Adicionalmente pedí entrada para ver una película islandesa aleatoria que resultó ser Eldjall de Rúnar Rúnarsson (Islandia ha sido el país invitado al festival) y acudí a La Noche del Corto Español y a la exposición de cortometrajes de la ECAM/ESCAC.

    Teatro Zorrilla de Valladolid, aquí se proyectaban la mayoría de películas de Punto de Encuentro

    El viernes nos reunimos para decidir cuál era la mejor película que habíamos visto. Fue una votación difícil, con triple empate en la primera ronda. Finalmente elegimos Aala Kaf Ifrit de Kaouther Ben Hania.

    Ray Loriga leyendo el palmarés de Sección Oficial

    El último día acudí por la mañana a la lectura del palmarés. Quedé sorprendido por las decisiones de los otros jurados. Me hizo gracia que nada más acabar la lectura del palmarés, las azafatas empezasen a repartir revistas con todo el palmarés perfectamente explicado. La gala de clausura era por la tarde Originalmente no tenía entrada para la gala de clausura, pero 30 minutos antes de que empezara, me avisaron de que podía ir. A toda prisa me arreglé, para poder llegar a la alfombra verde y tomar asiento en el Teatro Calderón. Al finalizar la gala, se proyectó la película Sage Femme, que fue el broche cinéfilo.

    Posteriormente no me dejaron entrar al cóctel pero sí pude acceder a la fiesta privada. Allí, entre algún famoso, bailé un poco. Y así acabó la SEMINCI.


    Películas de Punto de Encuentro


    Ahora viene lo divertido, hablar de las películas. ¡Empezamos!

    Aala Kaf Ifrit, ganadora del premio joven, se trata de una película muy dura sobre un caso real de una violación sita en Túnez. La historia, contada en 9 planos secuencia, genera una sensación de impotencia que jamás había experimentado. Son los propios policías los que han violado a la joven. La historia tiene mucha fuerza, contada sin morbo y donde se ve una evolución. El espectador va descubriendo poco a poco qué ha ocurrido exactamente. La actriz principal, Mariam Al Ferjani es impresionante. Como puntos negativos: el personaje que acompaña a la protagonista se ve demasiado plano. Los violadores tienen un aspecto ligeramente cómico al final de la cinta, lo que a algunas personas les provocó cierta confusión. Se trata de una película muy recomendable, pero hace falta estómago para verla.

    Adiós Entusiasmo me hizo perder el entusiasmo por la vida. La película trata sobre una madre que está encerrada en su habitación, mientras sus 3 hijas y su hijo viven con ella. Se trata de una película inconexa, ideal para buscarle los 3 pies al gato. Los planos, unos son claustrofóbicos y otros sin sentido. Hay personajes de los que no sabemos absolutamente nada, solo aparecen de repente en casa de la protagonista. La conclusión final de la película sorprendentemente no esclarece nada. Vladimir Durán, no me ha gustado tú película. Mencionar que fue la única película de la SEMINCI en la que estuve que no se llevó aplausos al final, sino patadas al suelo. Con esta película empecé a desarrollar cierto temor ante el cine argentino. Como veremos más adelante, este temor no estaba infundado.

    Angels Wear White trata sobre una violación de unas menores. Se narran dos historias de forma paralela, por un lado las niñas, inconscientes de lo que ha ocurrido y que solo quieren volver a jugar y la limpiadora del hotel, testigo de lo sucedido pero que lucha por sobrevivir en China. En mi opinión es una película bastante normalita, los personajes se sienten bien y la historia atrapa. No obstante, las dos historias se alternan cada tanto tiempo que muchas veces perdemos el hilo de la otra historia.

    Arpón es otra película argentina. ¡Vaya! Lo cierto es que de las tres películas argentinas que se presentaron esta es la más aceptable. Trata sobre un director de colegio aficionado a las prostitutas que descubre que tiene que hacerse cargo de Cata, una joven del instituto. La película tiene demasiados fallos: la relación entre Cata y el director no se aclara nunca, hay cosas importantes que se quedan sin resolver (¿cómo pueder ser que después de casi matar a un proxeneta, para el tío esto no sea una preocupación?) y la película en general te deja con ganas de más. ¿Es esto solo lo que querías contar? La película podía tener más recorrido con el escenario de partida. ¿Por qué parece que todas las cosas interesantes que podían ocurrir no pasan?

    As Duas Irenes, premio del público, es una película brasileña, muy bien hecha, entretenida de ver, que te deja con buen sabor de boca. Se trata de dos hermanastras que no sabían que compartían padre y de como se hacen amigas. La protagonista es Irene, de clase alta, la hermana mediana, que además descubre gracias a su hermanastra que hay otro mundo fuera de lo que su familia considera normal. El argumento no es demasiado original, pero la película en general cumple. El final es un gran punto también para esta cinta.

    Ayúdame a pasar la noche fue una de mis favoritas. He de ser sincero, cuando leí sobre la película tenía toda la pinta de ser una tragedia pura y dura. Nada más lejos de la realidad, se trata de un drama disfrazado de comedia. Divertida de ver, trata el tema de una familia al borde de la bancarrota por culpa de la madre, que ha desarrollado ludopatía. Los personajes masculinos, cada uno de diferente edad, afrontan de forma paralela la misma situación de forma diferente. El propio director vino a Valladolid, José Ramón Chávez Delgado, donde pudimos mantener una conversación. Película muy recomendable, con un final con un toque cómico muy logrado.

    Haporetzet es sin duda una de las películas con un argumento más original de esta SEMINCI. Esta película alemana-israelí trata sobre la vida de Alex, cuya madre de repente desaparece. Para más inri, su casa sufre un robo, pero de ahí saca ideas para avanzar en el mundo. Muy buen trabajo de la actriz principal, Lihi Kornowski. Quizá el ritmo no es el más adecuado, pero personalmente fue una película muy interesante. Fue otra de mis favoritas.

    Hoy partido a las 3 es un despropósito. Se trata de una película en la que cuentas los minutos que quedan para irte. El argumento no tiene ningún sentido, no es más que un pretexto para que decenas de personajes interactúen entre ellos. No hay un personaje principal que hile la película. A ratos da la impresión de que estamos ante un documental grabado con un móvil. La película es soporífera y no aporta nada interesante al espectador. El peor cine argentino.



    Napadid Shodan es una película iraní que trata sobre una chica y su pareja y los problemas de acudir al servicio médico para asuntos sexuales sin estar casados. Se ve reflejada una Irán conservadora. La película es interesante pero no pasa de ahí. Los personajes no aportan mucho y el final me dejó un poco frío.

    Never Steady, Never Still nos cuenta la historia de una mujer enferma de párkinson. Su marido, apoyo incondicional, sufre un infarto y muere. Su hijo está en ese momento de la vida en el que quiere volverse adulto, independizándose. Es una película dura, con excesivo énfasis en el sufrimiento de Judy, la protagonista. Además, la historia paralela del hijo no termina de encajarme del todo en esta historia. Se trata de una película demasiado larga para lo que es.

    Paris La Blanche es una película que parte de una muy buena historia, pero no termina de encajar. Rekia, argelina, lleva sin ver a su marido 40 años. Este fue buscando trabajo y nunca volvió. Rekia, sigue los pasos de su marido y va a Francia, tratando de encontrarle. La película es demasiado lenta, los personajes secundarios no encajan muy bien (¿de dónde han sacado a esa gente?) y te hacen preguntarte qué propósito tiene que sean así. El personaje de Rekia es muy entrañable, pero el final me dejó frío.

    Špína, premio Punto de Encuentro, es una película eslovaca que trata también sobre una violación. En este caso, la joven es violada por su propio profesor de matemáticas y entra en un estado de shock que hace que la internen en un psiquiátrico. Al contrario que Aala Kaf Ifrit, esta película es mucho más fría, menos pasional que la tunecina. El espectador espera que la joven cuente lo sucedido, en una sociedad que la va a apoyar, y el hecho de que no lo haga genera impotencia. Fue una sorpresa que está película ganase el premio del jurado no-joven, pues sobre el mismo tema de la violación creemos que Aala Kaf Ifrit es mucho mejor, aunque sí que es cierto que son dos maneras de manejar la situación totalmente distintas. Por otro lado en Spina el espectador sabe desde el principio todos los detalles, mientras que en la tunecina, el espectador descubre poco a poco qué ha ocurrido exactamente.

    Stebuklas es una película lituana que nos narra la llegada de un americano al pueblo de sus ancestros. Allí, la protagonista regenta una granja de cerdos que ha visto días mejores. El pueblo en general está deprimido por la caída del comunismo y la crisis económica que ello ha provocado. Se trata de una película divertida a momentos, que explora los tópicos americanos (el americano tiene cierto parecido a Donald Trump) y amena. Con la mejor banda sonora de esta sección Punto de Encuentro. Su único y gran fallo es el final. Al principio la película promete mucho y es muy interesante, pero se desinfla al llegar al final. Demasiado. Un final decepcionante. Además, la película cuenta con unas referencias religiosas un tanto turbias, que podían sobrar. Fue una de mis favoritas.

    Šventasis, es una película lituana que nos cuenta la historia de un mecánico que pierde su trabajo por la crisis y como intenta mejorar su vida. Sin embargo, la mala suerte hace que vaya perdiendo la esperanza. La película parte de algo interesante, comienza correctamente pero en un determinado punto vemos que la película no va a avanzar más. Es bastante predecible y no es muy interesante. Además, la trama del chico de YouTube, que podía aportar algo, se resuelve de la manera más estúpida posible, hasta tal punto de que no hubiese afectado nada al desarrollo de la película esa subtrama.

    Con este resumen de las películas me despido.]]>
    https://blog.adrianistan.eu/cronica-la-62-seminci Sun, 12 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
    ¿Cómo aumenté las visitas a mi blog? https://blog.adrianistan.eu/como-aumente-visitas-blog https://blog.adrianistan.eu/como-aumente-visitas-blog
    El 2016 está en naranja y el 2017 en azul. Como se observa, el crecimiento es estable. ¿Qué pasos seguí para aumentar el número de visitas?

    • Mantén el blog vivo: es importante ir realizando entradas de forma más o menos constante. No te digo que te marques un calendario (yo no lo tengo) pero sí saber cuando llevas demasiado tiempo sin darle amor al blog. Se visual, usa una imagen como mínimo en cada post y dentro de ellos recurre a imágenes si es posible.


    En la constancia está la clave del éxito

    • Configura una lista de correo: el correo electrónico es algo que todo el mundo tiene. Un usuario más avanzado es capaz de configurar RSS, pero todos van a poder suscribirse a la lista de correo. Además, el email es algo que la gente suele revisar con frecuencia.


     

    • Conoce a tu audiencia: revisa los posts más populares y que más interacciones han generado. Para mí esto ha sido un dato muy valioso. Muchas veces para mí ha sido una sorpresa que ciertos posts tuvieran más éxito que otros. Aunque en mi caso es un blog personal y sigo haciendo lo que me da la gana la mayor parte del tiempo, sí que es cierto que muchas veces uso esta información para priorizar ciertos artículos. Para ayudarte en esta tarea existen los Data Management Platform, como Oracle Bluekai y Adobe Audience Manager.


    Conoce a los usuarios y sus intereses

    • Promociónate: este año he mejorado la presencia en Facebook, aunque no ha sido muy enriquecedor. Sin embargo, gracias a Telegram y los grupos he conseguido llegar a mucha gente. Para mí Telegram se ha convertido en un sitio ideal para promocionar mi blog. Para mí sorpresa también he recibido unas cuantas visitas desde Instagram, lo cuál es muy interesante, aunque el Instagram que tengo es más personal que otra cosa.


    Promociónate, muchas veces parece que nadie te ve pero en realidad estás consolidando y generando audiencia

    • Hazle la vida fácil a tus lectores: la web tiene que ser elegante y bonita, los botones para compartir en redes sociales deben estar bien situados, pero no insistas excesivamente, pues genera mala imagen. Comprueba que la web cargue rápido y que se visualice correctamente en todo tipo de dispositivos. Estas cosas cuentan para el ránking de Google. La publicidad está bien, pero no tiene que ser intrusiva. Esto quiere decir que: nada de popups y tampoco anuncios con sonido.


    Que tu blog y un laberinto no tengan nada en común ayuda bastante

    • Suerte: al fin y al cabo también necesitas algo de suerte. En mi caso este año publiqué el post de Novedades en C++17 que fue portada en meneame. Eso genera muchísimas visitas de golpe. Varios conocidos se pusieron en contacto conmigo al reconocer un artículo de mi blog gozando de tal popularidad momentánea. A día de hoy, ese artículo sigue generando visitas todos los días, por encima de otros artículos.


    Los cuadros son de los pintores Rob Gonsalves y Stephan Schmitz

     ]]>
    https://blog.adrianistan.eu/como-aumente-visitas-blog Thu, 2 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
    ¡Comienza la SEMINCI! https://blog.adrianistan.eu/comienza-la-seminci https://blog.adrianistan.eu/comienza-la-seminci Cine de autor.

    Este año, el festival, dentro del Premio de la Juventud, y más concretamente, en la sección Punto de Encuentro he tenido el honor de ser nombrado jurado. Para poder llevar a cabo mi tarea tendré que ver todas las películas de la sección Punto de Encuentro, aunque también tenemos derecho a ver las películas de sección oficial, así como asistir a la gala inaugural (pero no a la de clausura curiosamente).



    Las películas que se van a proyectar en la SEMINCI dentro de la sección Punto de Encuentro son:

    • Napadid Shodan

    • Paris La Blanche

    • Aala Kaf Ifrit

    • Haporetzet

    • Angels Wear White

    • Adiós Entusiasmo

    • Špína

    • Stebuklas

    • Ayúdame a pasar la noche

    • Never steady, never still

    • Hoy partido a las 3

    • Šventasis

    • As duas Irenes

    • Arpón


    Si estás por Valladolid estos días y vas a acurdir a alguna proyección de la SEMINCI, no dudes en contactar conmigo, seguro que estaré cerca. Podremos tomar unas cañas mientras debatimos asuntos de alta incurnia: ¿Tabs o espacios? ¿Ennio Morricone o Hans Zimmer?

    Así mismo animo a todas las personas a venir a Valladolid a disfrutar de la SEMINCI. Os esperamos.]]>
    https://blog.adrianistan.eu/comienza-la-seminci Fri, 20 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
    Premio Nobel de Medicina https://blog.adrianistan.eu/premio-nobel-medicina https://blog.adrianistan.eu/premio-nobel-medicina

    El español Francis Mojica ganará el Premio Nobel de Medicina de este año


    ]]>
    https://blog.adrianistan.eu/premio-nobel-medicina Sun, 1 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
    Y sigo sin entender nada pero da igual https://blog.adrianistan.eu/sigo-sin-entender-nada https://blog.adrianistan.eu/sigo-sin-entender-nada
    Desde entonces ha ido tratando de obtener respuestas, para llegar a la conclusión de que lo más interesante son las preguntas.



    ¿Y qué toca hacer hoy? Otros años, llegada esta fecha publicaba algo en lo que había estado trabajando, sin embargo este año no tengo nada que enseñar. Así que habrá que inventar una nueva tradición. ¡Escuchemos canciones de Monty Python todos juntos!



    Quizá este año haya sido uno en los que más haya crecido personalmente. Para ser sinceros, al cumplir años el año anterior creía que este sería mi año definitivo con las mujeres. No ha sido así, aunque por el camino he ido recopilando anécdotas que solo mis más íntimos amigos conocen y que el resto de la humanidad no conocerá hasta que me aproxime a mi lecho de muerte. Sin embargo, ahora mismo no me parece algo tan importante. Quizá la clave sea dejarle de dar la importancia que le estaba dando antes. Creo que he leído a Feynman en el mejor momento que podía de mi vida, y ha supuesto una enorme influencia en mí.

    Entrar en la asociación BEST, sin apenas saber mucho de ella, ha sido una de las cosas que más me ha cambiado: en mi manera de ver el mundo, en mi manera de relacionarme con la gente. Todavía tengo mucho por disfrutar de esta aventura, he conocido a multitud de gente. Curiosamente quedé por primera vez con ellos un 24 de septiembre, aunque las copas se alargaron hasta ya el día 25, mi cumpleaños. Qué curioso todo ¿verdad?



     ]]>
    https://blog.adrianistan.eu/sigo-sin-entender-nada Mon, 25 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
    ¿Por qué ha estado Adrianistán 10 días offline? https://blog.adrianistan.eu/ha-estado-adrianistan-10-dias-offline https://blog.adrianistan.eu/ha-estado-adrianistan-10-dias-offline


    Sí, ya sé que ahora mismo habréis perdido toda la confianza en mí, pero voy a detallaros qué es lo que pasó exactamente.

    tl;dr: La nota del servicio técnico de Vodafone que le pongo tiende asintóticamente a cero.


    Agosto


    Agosto es el mes favorito del año para toquetear los servidores.



    La primera quincena estuve de excavación arqueológica en Numancia, gracias a un campo de trabajo organizado por la Diputación de Soria. Así que durante esa quincena no hice nada. Si realmente os interesa, puedo explicar como fue la experiencia.

    Sin embargo al llegar ya podía empezar a tocar el servidor. Quizá algunos ya lo sabéis, pero esta web está alojada en mi casa, en una Raspberry Pi 2. Al menos eso era antes, gracias a páginas chinas y a suculentas promociones de bancos que han acabado vendidos a 1 € conseguí hacerme con una Orange Pi PC, una Raspberry Pi 3, así como switch y cables Ethernet. Me dispuse a montar un clúster para este blog y algún proyecto extra más que tengo entre manos. El resultado es Mastín de Jade.

    En un par de días tuve todo funcionando. Reinstalé todo de cero (¡los backups funcionan!) y de paso modifiqué la arquitectura general. Ahora WordPress está en la Pi 3, no en la 2 pero la base de datos MariaDB se ha quedado en la Pi 2.

    ¿Por qué te cuento esto? En realidad el hecho de modificar la arquitecura ligeramente ha provocada que no estuviese 100% operativa desde que se solucionase el verdadero problema.



     

    Septiembre


    Septiembre es un mes complicado para mí. El 1 de septiembre salí de casa para ser organizador de un curso BEST en Valladolid. Sí, es mi ciudad, pero eso no quiere decir que pasase/durmiese en casa. El 2 de septiembre llegó la mala noticia. El internet había dejado de funcionar en mi casa. Dejé de registrar visitas en Google Analytics. Algo pasaba. En principio se pensó que el router estaba mal. Pero no. El fallo parecía estar en el módem. ¿Cómo es que tengo módem a día de hoy? Para encontrar respuesta tendremos que retroceder en el tiempo...



     

    El pasado


    Hace muchos años, en Castilla y León existía una compañía de telecomunicaciones llamada Retecal. Esta compañía ofrecía servicios de televisión por cable entre otros. En su momento se contrató televisión por cable en mi casa y se montó una instalación. Esta instalación se basa en cables coaxiales. En un determinado momento (próximo a la llegada de la TDT), quitamos la televisión por cable pero la instalación no se desmontó.



    Este anuncio ya da bastante pena

    Años más tarde, decidimos abandonar Orange como ISP y nos pasamos a Vodafone-ONO. Contratamos fibra óptica, pero como la velocidad contratada podía pasar por el cable coaxial sin problemas nos dijeron que podían reutilizar la instalación de Retecal. Es decir, la fibra óptica llega hasta mi portal, lugar donde engancha al antiguo cable de Retecal para llegar a casa. Ese es el motivo por el que hace falta un módem aparte.

    Septiembre otra vez


    El módem no tiene encendidas las luces de siempre. Definitivamente el problema está por ese lado. Sin yo poder asistir a la ceremonia, el técnico visita la instalación y ¡sorpresa!, el módem tampoco está estropeado. El técnico verifica que el problema es que llega una señal muy débil al módem. Sin embargo en ese momento, la empresa subcontratada por Vodafone ya no se encarga de eso, hay que llamar a otra empresa subcontratada para verificar el cableado entre mi casa y la centralita.
    We Bare Bears



    A partir de ese momento, todo es infructuoso, pasan los días y no viene nadie. Llamadas a Vodafone todos los días que muchas veces se resumen en: ¿ha probado a reiniciar el router?.La situación era tan desesperante que ya buscábamos otra compañía. Si Vodafone no nos lo iba a arreglar, al menos contratábamos a otra empresa y que montase otra instalación.

    Finalmente llego a casa el 11 de septiembre y sigue sin ir. Mencionar que no solo no tenía Internet, sino que el teléfono fijo también estaba inoperativo por compartir instalación.



    Finalmente el día 13 aparece un técnico de otra empresa. No hay nadie en casa. Gracias a un vecino consigue entrar en los cuadros, ojo, no de la planta baja, sino del primer piso y encuentra el problema allí. Llego del Carrefour justo cuando el técnico ya casi ha terminado. Resulta que justamente de camino me había quedado unos minutos de más viendo como demolían una casa con una excavadora (superpoder de jubilado desbloqueado) y llegué por los pelos.

    ¡El caso es que ya iba todo! Le dimos las gracias y me puse a comprobar que todo siguiese en orden.



    Evidentemente todo lo relacionado con Mastín de Jade necesitaba reajustes, el router había perdido la IP asignada y me tocó modificar los dominios. También me tocó ajustar toda la LAN, ya que el router perdió la memoria y me asignó IPs distintas a cada pieza del clúster.

    En realidad hasta hoy mismo no ha funcionado de verdad, pues se me olvidó modificar una configuración de MariaDB y debido al cambio de IPs, no aceptaba conexiones entrantes desde el WordPress.

    Y esta ha sido la historia de por qué Adrianistán ha estado offline ni más ni menos que 11 días.

    Adivináis qué días estuvo Vodafone dándome largas

     ]]>
    https://blog.adrianistan.eu/ha-estado-adrianistan-10-dias-offline 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
    ¿Qué hacer si pierdes o te roban el móvil? https://blog.adrianistan.eu/pierdes-te-roban-movil https://blog.adrianistan.eu/pierdes-te-roban-movil si te roban el móvil o lo pierdes.

    Afortunadamente, los móviles actuales cuentan con sistemas que permiten localizar el móvil (siempre que el teléfono siga encendido claro está).

    Si tu dispositivo tiene Android, la opción más común es usar el Android Device Manager. Se trata de una aplicación integrada de serie en los dispositivos Android con Google Play. Adicionalmente existe en Google Play y en la página web una aplicación para localizar el dispositivo.

    Tiene varias opciones, entre ellas es muy interesante la de reproducir sonido si estamos cerca del teléfono y no lo vemos y la de borrar los datos, si vemos difícil recuperar el teléfono, que al menos no lleguen a cualquier mano.

    Para usuarios de iOS existe una opción similar llamada Find my iPhone. Se activa desde la web de iCloud. Su funcionamiento es muy similar a Android Device Manager aunque se integra también con Apple Pay, la plataforma de pagos de Apple, para bloquear los pagos.

    Estos métodos son los oficiales de los fabricantes para cada sistema operativo. No obstante, existen más aplicaciones de terceros. En dispositivos iOS tenemos la aplicación Life360 GPS Tracking, que nos permite conocer la ubicación GPS de familiares y amigos si ellos nos dan su autorización expresa. Puede ser interesante dejar configurado este servicio con un amigo para así obtener la ubicación precisa en caso de pérdida o sustracción. Una aplicación similar pero compatible con Android e iOS es Instamapper.

    Pero existen todavía más métodos, quizá no tengas más opciones ya que no configuraste tu teléfono anteriormente. En ese caso te preguntarás cómo rastrear un celular por número. Existen webs gratuitas, que afirman ser capaces de localizar los dispositivos. Algunas de ellas son: www.sat-gps-locate.com y www.themobiletracker.com. En estas webs solo hay que introducir el número completo de teléfono (con prefijo de país) y localizan el teléfono. Según las propias webs la precisión es de 10 metros en Europa y de 25 metros en Latinoamérica. Usan una combinación de tecnología GSM (que disponen todos los teléfonos) y GPS.

    Existen muchas aplicaciones, vamos a mencionar otra conocida, llamada SeekDroid. De funcionamiento a Android Device Manager, tiene algunas opciones extra que pueden interesarnos. Podemos enviar mensajes al dispositivo que tiene el ladrón y de ese modo advertirle de qué te has dado cuenta del robo. El inconveniente que tiene es que necesita conexión a Internet para operar. Si tu teléfono tiene contratado datos no es inconveniente, pero si no lo tiene solo podrás localizarlo cuando el ladrón se conecte por Wi-Fi.

    En todo caso recuerda usar estos métodos sabiamente y sin abusar. Rastrear a una persona mayor de edad sin su consentimiento puede ser un delito. Usa estos métodos solo para rastrear tu propio teléfono y el de familiares y amigos que te dejen.

     

     ]]>
    https://blog.adrianistan.eu/pierdes-te-roban-movil Fri, 7 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
    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
    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
    Rust 101, tutorial de Rust en español https://blog.adrianistan.eu/rust-101-tutorial-rust-espanol https://blog.adrianistan.eu/rust-101-tutorial-rust-espanol Rust es un lenguaje imperativo, orientado a objetos y funcional, con gran soporte a concurrencia y de propósito general. En este tutorial de Rust daremos nuestros primeros pasos en Rust, un lenguaje que nace de las necesidades de tener un lenguaje que fuese rápido, concurrente y seguro. Aprenderemos a amar/odiar al estricto compilador y veremos algunas de las grandes ideas detrás de Rust. Sin embargo, esto requiere mayor atención de nuestra parte ya que Rust no es otro lenguaje basado en C. Algunas de sus características son:

    • Abstracciones sin costo
    • Seguridad de memoria garantizada
    • Eliminación de las condiciones de carrera
    • Generalización basada en traits
    • Comparación de patrones
    • Inferencia de tipos

    Rust toma influencias de muchos lenguajes, para dar lugar a un estilo único de programación que costará más de lo habitual aprender a usar de forma efectiva. Rust toma influencia de C++, OCaml, Haskell y en menor medida de Erlang, Alef, Limbo, Swift y otros lenguajes menos conocidos.

    Si aprecias el trabajo detrás de este tutorial, no dudes en apoyarme a través de una donación vía PayPal o alguna criptodivisa (ver margen derecho). También se aceptan mejoras al texto, tan solo ponte en contacto conmigo.

    Índice del tutorial de Rust

    1. Instalando Rust
    2. Variables y tipos de datos en Rust
    3. Referencias y préstamos en Rust
    4. Funciones y closures en Rust
    5. Estructuras de control en Rust
    6. Structs, traits y POO en Rust
    7. Gestión de errores en Rust, Option y Result
    8. Concurrencia en Rust
    9. Box, Rc y RefCell, punteros inteligentes en Rust
    10. Cargo y módulos en Rust
    11. Tests en Rust
    12. Documentación con rustdoc
    13. Leer de teclado en Rust
    14. Crear ventanas y botones en Rust con GTK
    15. Leer y escribir JSON en Rust con Serde
    16. Yew, crea webapps al estilo Angular/React en Rust
    17. Construye un servidor web o una API con Rocket
    18. Programa juegos en Rust con Piston (actualmente recomiendo ggez antes que Piston)
    19. Futures y Tokio: programación asíncrona
    20. Diversión con punteros: bloques unsafe en Rust
    21. Bindings entre Rust y C/C++ con bindgen

    Otros artículos sobre Rust en el blog

    Instalando Rust

    Rust funciona en una gran variedad de sistemas operativos y emite código compatible con muchos otros. El compilador oficial de Rust usa LLVM de forma interna, por la que las mejoras que se realicen sobre LLVM mejoran de forma indirecta el compilador de Rust. Otros lenguajes que usan LLVM son: C++ (con el compilador Clang), Julia, Swift, FORTRAN (con el compilador Flang), D (con el compilador LDC), Haskell (a través de GHC) y muchos más con otros compiladores. Mencionar que también existe un compilador de Rust bajo la suite GCC pero es experimental y no se recomienda su uso. Para instalar el compilador oficial de Rust visitamos la página oficial de Rust. Hacemos click en Instalar. Descargamos el archivo y seguimos los pasos en pantalla.

    Lo que acabamos de hacer es usar rustup. Se trata de un programa que nos ayudará mucho y que nos permite instalar varias versiones de Rust en una misma máquina así como actualizar las mismas. Es una herramienta parecida a rvm de Ruby o nvm de Node.js. rustup nos instala rustc, cargo y rustdoc. Comprobemos que Rust se ha instalado correctamente, hagamos un Hola Mundo. Crea un archivo llamado hola.rs (rs es la extensión de los archivos de código fuente de Rust). Debe contener lo siguiente:

    fn main() {
        println!("¡Hola mundo!");
    }
    

    Podemos compilar con rustc.

    rustc hola.rs -o hola
    

    Como en GCC, la opción -o sirve para especificar el nombre del ejecutable de salida. println! es una macro que nos permite escribir en pantalla. Permite realizar interpolaciones, usando llaves. Por ejemplo: println!("Mi nombres es {}","Adrián"); Equivale a: println!("Mi nombre es Adrián");

    Usando rustup

    He aquí algunos comandos útiles para manejar rustup de forma efectiva.

    Mostrar que versión está actualmente activa

    rustup show
    

    Actualizar los entornos de Rust

    rustup update
    

    Instalar la versión nightly de Rust

    rustup install nightly
    

    Ejecutar esta un comando con un entorno determinado

    rustup run nightly rustc hola.rs -o hola
    

    Cambiar el entorno por defecto

    rustup default nightly / rustup default stable
    

    Añadir una plataforma al entorno (para cross-compiling)

    rustup target add arm-linux-androideabi (Android ARM)
    

    Con esto acabamos esta parte sobre como configurar el entorno. A continuación entraremos en profundidad ya en el lenguaje en sí.

    ]]>
    https://blog.adrianistan.eu/rust-101-tutorial-rust-espanol 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 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
    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
    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
    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
    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
    Compra ordenadores seminuevos y ayuda al medio ambiente https://blog.adrianistan.eu/compra-ordenadores-seminuevos-ayuda-al-medio-ambiente https://blog.adrianistan.eu/compra-ordenadores-seminuevos-ayuda-al-medio-ambiente
    Los ordenadores seminuevos son un claro ejemplo de un producto que puede aguantar muchos años más. Estos vienen frecuentemente de usuarios profesionales y exigentes con sus equipos. Cuando vamos al detalle nos encontramos de que son equipos con prestaciones muy decentes para el mercado actual y a unos precios considerablemente más bajos. Son productos de gama alta de la generación anterior, que en la generación actual, sin llegar a ser tope de gama, no dejan de ser un buen partido. Pero no solo eso, sino que ayudaremos a reducir el impacto medioambiental de nuestras compras.



    En el caso de los ordenadores de segunda mano, una ventaja que hay respecto a otras tecnologías es el gran impacto que tiene el software. Esto significa que los componentes de hardware (lo físico del ordenador) están muy condicionados por la capa de software (programas) que se ejecutan por encima. Si tenemos un sistema actualizado, con las últimas versiones de los programas, tendremos un equipo seguro, fiable y con nuevas características de poco en poco que mejoren lo ya existente. Actualmente, los sistemas operativos como Windows han reducido su nivel de consumo y no será un problema de ningún tipo tener la última versión de Windows 10 en estos equipos.

    No obstante, yo en lo personal recomiendo siempre que sea posible usar un sistema operativo Linux de forma habitual. Suelen estar mejor actualizados y su rendimiento es mejor, incluso en equipos más antiguos. El único requisito para dar el paso es asegurarse que todos los programas que vayamos a usar o bien sean compatibles con Linux o bien existan reemplazos. Si acostumbras a usar Adobe Photoshop, quizá Linux no sea para ti; si sin embargo solo realizas tareas de ofimática con Word y Excel, quizá puedas usar sin problemas LibreOffice, presente en Linux.



    A muchos nos puede asaltar la duda: "Vale, muy bien, los ordenadores seminuevos son más baratos, plenamente funcionales y ayudan al medio ambiente, ¿pero y si tengo algún fallo con el equipo?". Efectivamente, se trata de una duda razonable. Por suerte, actualmente existen vendedores que no solo ofrecen equipos de segunda mano sino que se hacen cargo de la garantía desde la fecha en que compramos el producto. Como si fuese un equipo nuevo.

    En definitiva, esta alternativa a comprar un equipo nuevo, es más respetuosa con el medio ambiente, con equipos igual de potentes y fiables que muchos en el mercado y a un precio reducido. En particulares, la diferencia se nota. Pero en empresas donde se compran ordenadores en más cantidades y muchas veces no se necesitan grandes equipos para la realización de tareas ofimáticas, la compra de ordenadores seminuevos puede suponer un gran ahorro.

     ]]>
    https://blog.adrianistan.eu/compra-ordenadores-seminuevos-ayuda-al-medio-ambiente Wed, 28 Jun 2017 00:00:00 +0000
    Cuando 'bad_style' se convierte en algo ofensivo https://blog.adrianistan.eu/cuando-bad_style-se-convierte-algo-ofensivo https://blog.adrianistan.eu/cuando-bad_style-se-convierte-algo-ofensivo esta issue. Básicamente viene a decir que usar el término bad para referirse a el estilo de programación de Rust no estándar es incorrecto. Que puede ofender a la gente. Particularmente lo encuentro jocoso, pero hay mucha gente que no. La discusión se ha bloqueado (alguien diría que censurado) y parece que finalmente se ha adoptado la decisión de eliminar bad por ser ofensivo.

    Con gran acierto el autor profetizó que esta issue traería mucho bikeshedding, aunque no únicamente en el nombre de la sustitución. Sin embargo me parece que este tema merece que le dedique un poco de tiempo.

    En primer lugar se culpa de que usar bad hace a la gente sentirse mal, que es un término pasivo-agresivo y que echa las culpas al programador por usarlo. ¿Qué hace bad_style? Es un lint del compilador de Rust que emite advertencias si usamos una convención distinta la habitual. Por ejemplo,si usamos camelCase en vez de snake_case obtendremos un warning por este linter.

    A mí en particular en condiciones normales me daría igual el término exacto de este linter, pero los argumentos que han dado me han parecido muy soprendentes. Supuestamente si algo tiene la palabra bad podría haber gente que se sienta mal y le afecte, que los principiantes se sientan bienvenidos en una comunidad con un lenguaje que no te hace usar la palabra bad cuando vas a saltarte la convención (y además les desmotiva).

    El caso es que me resulta difícil entender como el flag para que el compilador no ejecute el susodicho linter pueda herir los sentimientos de alguien. El compilador no es algo personal, no te está insultando a ti, es una herramienta, y sí, existen cosas malas. Si llevamos esto al extremo el compilador no debería dar errores sino "sintaxis malointerpretada" Programar en bad_style es una mala práctica porque reduce la interoperabilidad y legibilidad entre proyectos. No importa si es camelCase o snake_case pero todos estamos de acuerdo que si un lenguaje adopta una forma por convención es mucho mejor adherirse a ella, podremos leer código ajeno más rápidamente y mejor. Si realmente te afecta emocionalmente usar bad_style porque es bad entonces quizá tu problema sea otro, mucho más grave y que necesites ayuda de un profesional.

    Por otra parte la discusión presenta la más que alarmante falta de visión más allá del mundo angloparlante que presenta mucha gente. No es que les ignoran, es que creen que ciertas cosas que son aplicables a EEUU son aplicables al resto de culturas del mundo.

    Fue en otra conversación donde se propuso cambiar stupid por foolishrude, ya que era menos ofensivo. Como si estúpido en español fuese un gran insulto. Para mí estúpido puede llegar a ser un insulto tierno. Sin embargo el grupo de trabajo dedicado a revisar las palabras prefería abolir sus usos con la excusa de que nadie pudiera sentirse ofendido. Yo no lo sabía pero existen detectores automáticos de estas palabras y muchas otras que no deben ser usadas. Para mí todo esto me parece sacado de contexto de forma excesivo y aunque no es algo único a la informática, es aquí donde me lo encuentro más a menudo. Sobre este asunto estoy bastante de acuerdo con Slavoj Zizek.

    https://www.youtube.com/watch?v=5dNbWGaaxWM

    No sé donde quedó aquello de que no había libertad de expresión si no había libertad de ofender a la gente como decían Orwell o Chomsky. Cada vez más las comunidades que dicen ser welcoming me alejan más. Dije antes que este asunto no solo ha pasado en el mundo de Rust. Efectivamente, en Node.js también eliminaron suicide del módulo cluster porque era ofensivo también. No pudieron eliminar killhost porque eran ya términos históricos de UNIX, aunque les hubiese gustado.]]>
    https://blog.adrianistan.eu/cuando-bad_style-se-convierte-algo-ofensivo Tue, 20 Jun 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
    Polvo https://blog.adrianistan.eu/polvo https://blog.adrianistan.eu/polvo


    ¡Pero si no hay café! Me han traído una máquina de chucherías. ¿Qué clase broma es esta? Si tiene hasta condones, como si fuese a echar un polvo aquí en la oficina. El disgusto de Félix era tal que fue a hablar con su jefe. Un asunto que él consideraba de máxima prioridad. ¡Voy a ir al jefe a hablarle de este asunto, es de vital importancia tener una máquina de café! Al llegar al despacho descubrió que sí que había gente en la oficina que usaba los condones. Fingió no haber visto nada y fue a la planta de arriba. Quizá allí tendrían néctar negro.

    Al subir se encontró con Emma. Era la típica persona superficial e interesada. Ella fue quien le dijo que no cobraría paga extra en navidad por su baja productividad. Esta vez, y por primera vez en mucho tiempo, dijo algo interesante para Félix. Arriba han quitado la máquina de café. ¿Por qué lo primero que me ha dicho al verme es sobre el café? ¿Está insinuando que no hago nada más que tomar café? Instintivamente le propinó un golpe a Emma que la dejó inconsciente en el suelo. ¡Cómo puede pensar que soy un cafeinómano! ¡Y como se atrevió a decirme que mis niñas estarían mejor sin su padre! ¡Por fin muerde el polvo! Al poco tiempo recapacitó. ¡Dios! ¿Qué he hecho? ¿La habré matado? Tenía tanto miedo que no se paró a comprobarlo. Miremos el lado positivo, nadie me ha visto. Podría hacer pasar que fue un accidente. ¿Pero y si cuando recupera la consciencia me acusa a mí? ¿Y si me despiden? ¡Perderé todo lo que tengo!

    Ahora Félix no quería perder los faxes, los gritos y el estrés. En cierta parte empezó a sentir nostalgia. Son parte de mí. Si me despiden, ¿qué seré? ¡Tengo derecho a quedarme con mis problemas! Tengo que deshacerme de ella sin levantar sospechas. Entró en una sala vacía con cuidado de que nadie le viese. Dentro de la sala repleta de polvo, destacaba una máquina de café. Esa máquina no tenía polvo, ¡era la que estaba antes en su planta!

    Se apresuró a acabar con lo que quedaba de Emma. Cortó. Descuartizó. Aplastó. Ya no era él. Pero hacía mucho tiempo que ya no era él. Se empezó a dar cuenta que el cuerpo muerto de Emma sangraba y podía delatarle. Una bombilla se le iluminó y decidió escurrir la sangre en el tanque de la máquina de café.



    A la mañana siguiente volvió a aparecer la máquina de café. Al parecer había habido quejas. Como todos los días fue a por su café. Tenía un tono más rojo que de costumbre, pero estaba delicioso. Un compañero le comentó a Félix que Emma había desaparecido sin dejar rastro. Seguramente no andará muy lejos. Miró el café, sonrió y siguió trabajando. De todos modos nadie distingue el polvo en el viento y Emma ha volado.]]>
    https://blog.adrianistan.eu/polvo Wed, 26 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
    The Pitts Circus, la primera película financiada con Ethereum https://blog.adrianistan.eu/the-pitts-circus-la-primera-pelicula-financiada-ethereum https://blog.adrianistan.eu/the-pitts-circus-la-primera-pelicula-financiada-ethereum Ethereum. Uno de las primeras apliaciones que surgieron fue el crowdfunding distribuido. ¿Te imaginas un Kickstarter P2P? Pues con Ethereum es posible implementarlo.

    Siguiendo estas ideas surge The Pitts Circus, la primera película financiada con Ethereum.


    La película


    Se trata de una cienta de terror-comedia que incorpora las habilidades, talento cómico y naturaleza única de una familia de circo real de Australia. La familia circense Pitts ha estado de gira por los cinco continentes con su espectáculo. Cecil Pitt también es un músico consumado, forma parte de la banda musical Barkers Vale Brothers. Él se hará cargo de la banda sonora de la película ya que participó en todos los episodios de Karnydale (otro proyecto de los productores de la cinta) La familia contará con: los dos padres, un chico de 11 años, una niña de 6 años y un niño de 1 año. Disfruta de este cuento de hadas circense, ambientado en el remoto oeste de Australia y Suiza, con acróbatas, payasos, un sacerdote, niños diabólicos y caníbales.

    ¿Cómo funciona?


    Inicialmente se han creado 666 acciones del proyecto, estas se compran vía Ethereum y el smart contract asociado se encargará de repartirnos los beneficios que genere la película (si los hubiera) durante 20 años. Para evitar situaciones similares a lo ocurrido con la DAO, el crowdfunding no se basa exclusivamente en Ethereum sino que hay un respaldo legal en Suiza. La compañía productora es aKenEvilThing, una empresa fundada en 2014 en Suiza con varios proyectos anteriores.

    Si quiéres colaborar con la financiación del proyecto lo único que tienes que hacer es enviar una cantidad determinada de Ether según el número de acciones que quieras comprar (1 acción = 10 ETH) a la dirección que aparece en su página web.

    Ahora mismo el equipo está de rodaje y se espera que para finales de 2017 ya pueda presentarse a algunos festivales de cine. Algunos actores confirmados aparte de la familia Pitts son: Carlos Henriquez y Matto Kämpf.



     

    Entrevista con Tony Caradonna


    Pregunta: Tony, ¿puedes contarnos un poco acerca de la película?
    Tony Caradonna: “The Pitts Circus es una película de horror-comedia ficticia protagonizada por los “Pitts”; una familia real de un circo a las afueras de Australia, justo donde las primeras escenas están siendo filmadas antes de viajar a Suiza para terminar el rodaje. En 2018 la película independiente se presentará en festivales de cine antes de su lanzamiento en cines selectos en todo el mundo y por Video-on-Demand. Lo que realmente nos emociona es que la película es la primera del mundo que se financia completamente con Ethereum ".
    Pregunta: Como realizaron esto? Y por qué decidieron financiar el film con Ethereum?
    Tony Caradonna: "La película es financiada por 666 acciones de crowdfunded, con un costo por acción de diez Ethereum (que son aproximadamente 97 dólares americanos para el momento en que se escribió esta nota). Los accionistas recibirán el 50% de los ingresos de la película por un período de 20 años garantizados por contratos inteligentes: contratos auto ejecutables basados en leyes suizas que utilizan protocolos informáticos basados en la cadena de bloques de Ethereum para verificar y hacer cumplir las cláusulas acordadas por sus firmantes. Esto se traduce en un acuerdo de contrato más rápido y seguro para todos. El Pitts Circus es uno de los primeros proyectos rentables en el mundo real que está conectado a la cadena de bloques, por lo que a mi parecer hace que sea una gran manera de mostrar a la gente común la propuesta de valor en blockchain y moneda digital”.
    Pregunta: ¿Qué más podemos esperar de esta película y la compañía productora?
    Tony Caradonna: "Queremos ver más participación de la comunidad en la producción de películas, desde el casting hasta el argumento. Hemos desarrollado nuestro propio activo accionario para ayudar a conseguir lo que llamamos EMV-coin (Ethereum Movie Coin). Nuestras monedas EMV se distribuirán gratuitamente a todos nuestros accionistas, con una ICO planeada para adquirir monedas adicionales en el futuro y que podría aumentar la participación de los consumidores en futuras producciones. Las monedas permitirán a los consumidores agregar o votar por propuestas, nuevos contenidos y la dirección estratégica de futuras producciones. Estamos creando un caso real de negocios a escala mundial que muestra lo que es posible hacer con la plataforma de Ethereum ".
    Pregunta: ¿Entonces la moneda EMV es un activo que nos da la posibilidad de decidir sobre futuras películas permitiéndonos votar para evitar historias exageradas y finales que dan paso a nuevas experiencias en pantalla? ¿Las monedas EMV también benefician el ROI de la película?
    Tony Caradonna: "Sí, exactamente. Este enfoque de toma de decisiones de producción significa que los consumidores ven lo que quieren ver. Los contribuyentes pueden intercambiar nuevas ideas con la posibilidad de que la comunidad vote para que se produzca alguna de ellas. Esto equivale a menores costos de producción para la película que sugiera la comunidad, y mayores ventas, ya que las ideas han sido previamente validadas por el consumidor. La combinación de presupuestos de producción más bajos con mayores ingresos recompensa a los inversionistas con mejores beneficios. Los poseedores de monedas EMV se benefician del contenido que ellos mismos ayudan a generar”.
    Pregunta: ¿Qué puedes ver para la industria cinematográfica en el futuro después del lanzamiento de The Pitts Circus?
    Tony Caradonna: "Puedo prever que muchas películas se producirán con un modelo similar al nuestro a través de la contribución de la comunidad. Los presupuestos de producción más bajos que estas películas exigen llevarán a que muchas nuevas ideas puedan llegar a la pantalla grande, y que cualquiera pueda aparecer en los créditos de una película. Si Ethereum continúa en su actual avance, espero que el modelo de producción en comunidad de EMV se vuelva muy popular, con muchas personas deseando comprar monedas EMV en los intercambios para también poder participar en el modelo que hemos creado. Esto resultaría muy favorable para el precio en que se cotice en el mercado el EMV ".
    Pregunta: ¿Otros planes para el futuro?
    Tony Caradonna: "Hemos realizado varias alianzas de negocio con compañías relacionadas al área de las criptodivisas que ayudarán a apoyar la red de monedas EMV incluyendo: Ledger Wallet, Trezor y Ether Card para que el intercambio de monedas de EMV y Ether sea seguro y simple, COVAL para cifrar Bitcoin y Ether en archivos de audio con su plataforma 'Vocal'. Estas alianzas permitirán la colocación de productos digitales en el film”.
    Sobre Tony:

    Tony Caradonna tiene un Master en Física y Filosofía. Financió sus estudios haciendo espectáculos como comediante y artista de circo. Estuvo de gira con varios circos en todo el mundo. Allí conoció a Ken Fanning, un director y artista de circo. También conoció a la Familia Pitts, una Familia Australiana de Circo. Su primer encuentro fue en una gira con otro circo australiano 2000/2001. Han trabajado juntos regularmente en varias ocasiones desde entonces. Actualmente, el financiamiento del Swiss National Language Cooperation Arts funding ya está concedido y aún está pendiente un Financiamiento del Swiss National Film Arts]]>
    https://blog.adrianistan.eu/the-pitts-circus-la-primera-pelicula-financiada-ethereum Fri, 17 Feb 2017 00:00:00 +0000
    ¿Está usted de broma Sr. Feynman? https://blog.adrianistan.eu/esta-usted-broma-sr-feynman https://blog.adrianistan.eu/esta-usted-broma-sr-feynman
    Se trata de ¿Está usted de broma Sr. Feynman? y es una especie de autobiografía de la vida del físico Richard Feynman. El libro se estructura en anécdotas que va contando que si bien suelen tener un elemento de inicio cronologicamente ordenado con el resto, cada anécdota puede estructurarse de forma diferente.

    El libro es muy divertido y derrocha originalidad. Es un claro ejemplo de por qué prefiero los libros de no ficción, ya que superan a la ficción y por mucho.

    La vida de Richard Feynman, uno de los grandes físicos del siglo XX, es una completa inspiración. Una invitación a ser curioso, a no tener miedo al qué dirán, a dejar de preocuparse y a disfrutar de las cosas de la vida.

    Algunas anécdotas interesantes:

    • La apertura de cajas fuertes en Los Alamos

    • Cabrear a la censura de Los Alamos

    • Introducirse en el mundo de la pintura y llegar a realizar un cuadro para un burdel

    • Tocar la frigideira en una banda de Río de Janeiro

    • Recibir un premio Nobel

    • Dar un seminario de biología en Harvard (acabando justo él de terminar física)

    • Hablar en un idioma inventado en una actuación de scouts

    • Ligar en un local de carretera

    • Y muchas más


    Mi más sincera recomendación. Yo ahora empiezo con la segunda parte ¿Qué te importa lo que piensen los demás?

     ]]>
    https://blog.adrianistan.eu/esta-usted-broma-sr-feynman Thu, 16 Feb 2017 00:00:00 +0000
    ¿Cómo borrar el historial? https://blog.adrianistan.eu/como-borrar-el-historial https://blog.adrianistan.eu/como-borrar-el-historial
    Un rastro digital que dejamos cuando navegamos por la web es el historial. Como si fuese Pulgarcito, el navegador va guardando las páginas que hemos visitado y su orden. Esto puede ser interesante si queremos volver a visitar una página que visitamos hace unos días pero no nos acordamos de su nombre exacto.

    El botón que da acceso al historial en Firefox. En otros navegadores es similar.

    Como puedes observar es fácil saber en qué páginas has estado antes. Incluyendo redes sociales.

    Pero no creas que el historial se guarda solo en los navegadores (Explorer, Chrome, Firefox, Opera, Safari, ...) sino que ciertas webs también construyen sus propios historiales. YouTube o Google construyen sus propios historiales que construyen si el usuario ha iniciado sesión en el sistema.

    Estos historiales también se pueden borrar como veremos más adelante.

    No obstante, el navegador guarda otros datos de nuestra navegación. Cualquier persona que accediese a nuestro ordenador. Estos datos son las cookies (muy famosas por los insistentes avisos), la caché, los fomularios y las contraseñas que hayamos decidido guardar para nuestra comodidad.

    Las cookies son necesarias para la navegación web tal como la entendemos hoy, sin embargo puede ser interesante borrarlas de forma regular. ¿Sabes por qué Google implementó la navegación privada en Chrome? Porque quería evitar que la gente borrase sus cookies cuando visitaban sitios de los que no querían dejar rastro en su ordenador. De ese modo Chrome seguía teniendo las cookies y los usuarios entran en un modo de incógnito. No obstante, eliminar las cookies de forma manual sigue siendo algo recomendable.

    La caché la forman archivos (estilos, imágenes, fuentes,...) que se descargan la primera vez que visitamos una página. Estos se guardan en nuestro ordenador para que una vez accedamos de nuevo a esa web esta tarde menos en cargar. Es por ello que la caché está llena de archivos que se supone que nunca van a cambiar. Aun así, esta caché puede delatar las páginas que visitamos.

    Para obtener más información los compañeros de http://borrar-historial.com se han pegado un currazo dando métodos efectivos y fiables de como borrar un historial.

     ]]>
    https://blog.adrianistan.eu/como-borrar-el-historial Wed, 15 Feb 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
    Feliz añ... ¡Dame los datos! https://blog.adrianistan.eu/feliz-an-dame-los-datos https://blog.adrianistan.eu/feliz-an-dame-los-datos


    Pero no nos pongamos emotivos todavía. Queda lo mejor. Analizar los datos de Google Analytics de esta y otras webs que he creado.

    Un poco de historia


    Como muchos sabréis ya, el blog ahora mismo funciona con WordPress, más concretamente en una Raspberry Pi 2 con Raspbian y WordPress se ejecuta sobre PHP7, con MariaDB de base de datos y nginx de servidor web. Antes no era así. Originalmente era un blog en Blogger muy simple. Después lo pasé a Jekyll con GitHub Pages usando un tema que cree sobre la marcha. Este tema tenía un diseño "peculiar" y tenía algunos problemas. Por eso inicié la transición a un tema nuevo basado en Material Design. Pero aquello no duró mucho, era más lento y daba más fallos. Al final decidí pasarme a WordPress y de paso comprar el dominio adrianistan.eu. Esta transición la hice en junio y hay que tenerla en cuenta ya que cambié las propiedades de Analytics en el proceso. El blog antiguo además no ha dejado de funcionar (para no romper enlaces) y lo tenéis en http://adrianistan.eu/blog/

    blog.adrianistan.eu - Muy satisfecho


    El flamante nuevo blog, usando WordPress y en chupando electricidad en mi casa ha tenido 3982 sesiones con 5213 visitas a páginas.

    El pico más alto corresponde al 14 de julio y es el día siguiente a la publicación del Tutorial de GnuCash, artículo que como veremos ahora sigue siendo de los más visitados. El segundo pico, ya el 21 de noviembre fue el día siguiente a la publicación del artículo sobre Iron, un framework web escrito en Rust.

    Respecto a los idiomas, nada raro, todas las variaciones de español que existen, algunos idiomas como Catalán, Gallego, Euskera o Portugués que pueden ser perfectamente de gente que entiende ambos idiomas. Inglés también está muy alto, teniendo en cuenta que es la configuración por defecto de muchas cosas tampoco es raro.



    Analytics ha contabilizado accesos desde 70 países. La mayor parte del tráfico viene de España, con mucha diferencia, seguido de México y en tercer lugar Estados Unidos. En cuarto lugar Argentina. Le siguen Colombia, Venezuela, Chile, Perú, Ecuador y el top 10 lo termina Brasil. Me parece curioso, en undécimo puesto encontramos a Alemania. Y me ha sorprendido, no esperaba que estuviese tan arriba. Esta por encima de otros países latinoamericanos, supongo que esto se debe a los españoles que se han ido a trabajar a Alemania.

    La mayoría de vosotros entráis con Google Chrome. En segundo lugar se posiciona Firefox. Con un porcentaje ya bastante reducido está Safari, que supongo que será sobre todo tráfico de iOS, aunque como veremos más adelante recibo más visitas desde macOS que desde iOS. Y este año hemos dado el sorpasso, el blog recibió más visitas con Edge que con Internet Explorer. Mención especial a NetNewsWire Browser, que me hizo tener que buscarlo en Google.

    En sistemas operativos, primero Windows, después Android y a continuación Linux de escritorio. Esta tendencia la tengo en las otras webs y es algo que se lleva dando desde hace algún tiempo. Me sorprende que macOS posicione por encima de iOS. Se ve que cada vez hay más Macs en el mundo hispanohablante.

    ¿De dónde vienen las ovejas descarriadas que entran aquí?


    Analytics dice que un alto porcentaje vienen de búsquedas en Google, Menéame aporta bastantes visitas, Google+ sigue trayéndome gente, Reddit también y Facebook ha empezado a traer usuarios de forma más constante. Pero lo más importante es el RSS y Feedly. Mucha gente viene a través de esos canales, lo cuál me alegra pues la gente que accede por allí suelen ser lectores que ya han decidido quedarse. El correo electrónico parece funcionar bien, aunque hay pocos suscriptores. Y me sorprende que al menos 16 personas hayan accedido al blog a través de mi perfil de Instagram. ¿En serio?

    ¿Y en qué corral artículo caen?


    Los 3 tutoriales: CMake, WiX y GnuCash han sido muy populares. La calculadora de 4 bits lleva desde el 2013 siendo uno de los artículos más leídos siempre en mi blog.

    Las conclusiones que saco es que Rust interesa, Bitcoin y Ethereum no tanto pero también. Esos dos temas van a ser importantes para este año 2017.

    adrianistan.eu - El año del boom


    adrianistan.eu reúne por un lado el blog hasta antes de junio, las descargas de Kovel, los complementos de Firefox y alguna cosilla más.

    14889 sesiones y 12532 usuarios. El artículo de enero de 2016 titulado Cómo programar en C (en 2016) fue un absoluto éxito, llegando a la portada de Menéame. Fue además enlazado desde sitios como CyberHades o ElHacker. El artículo era una mera traducción a la que añadí un par de cosas al final. No recuerdo ahora mismo como lo encontré, pero me pareció bueno y a la vez el típico artículo que a veces pasa desapercibido. Le pedí si podía hacer la traducción y me dijo que sí. Hubo mucho debate sobre el artículo lo que además le dio repercusión.

    En el mapa veo ya empiezo a ver cosas raras. España es el país líder, pero el segundo es... ¡¿Rusia?! Eso dice Analytics, pero creo que es mentira o mejor dicho, no es exactamente verdad. Esta propiedad ha recibido muchos hits desde unos dominios y mi teoría es que todos vienen de Rusia y son en realidad bots. El tercer país es Estados Unidos, seguido de México, Alemania, Argentina, Francia, Colombia, Venezuela y Perú. En total se han registrado datos de 153 países. Puntos extra para los que han entrado desde la isla de Mayotte y la isla de Guam.

    Aquí gana Firefox, algo obvio debido a que parte del sitio está dedicado a alojar complementos de Firefox. Y aquí Rusia me vuelve a intrigar. YaBrowser es un navegador ruso y tiene un porcentaje considerable de visitas. Muy extraño todo. No aparecen en la imagen pero me han llamado la atención estos tres navegadores: Coc Coc, Dooble y PlayFreeBrowser.

    Según mi teoría, site-auditor.online, rank-checker.online y monetizationking.net han estado generando tráfico falso. La verdad es que es muy intrigante. Las instalaciones de complementos siguen trayendo un buen tráfico.

    Vamos con otra web.

    Phaser.js Hispano - Una comunidad interesada


    La web ha tenido muy buen recibimiento con gente interesada. Me da pena no poder escribir artículos para Phaser.js Hispano porque realmente hay gente que los está buscando y los necesita. Para paliar estos previsibles vacíos de contenidos he creado Gamedev Hispano, un foro donde se puede ir aportando poco a poco. El foro no ha tenido una buena acogida inicialmente, pero es un proyecto que puede tener futuro y no me voy a rendir todavía.

    TucTum - Muchos usuarios desisten


    TucTum es un nodo de GNU Social que te paga por la cantidad de me gustas que reciban tus publicaciones. El programa funciona pero la gente no se hace a TucTum. Entiendo que la interfaz no es tan sencilla pero el principal problema es, a mi modo de ver, que no hay gente suficiente como para retener a la gente que se va hace cuenta. Muchos se hacen cuenta y al ver que no hay nadie con quien hablar se van. No tengo nada preparado para TucTum este 2017.

    Gaming on Haiku - El sitio que me plantea cambios


    No voy a hablar mucho de Gaming on Haiku (2550 sesiones y 1933 usuarios) pero este sitio me ha hecho plantearme cosas. Primero, fue el lugar donde probé WordPress antes de usarlo en este blog. Además ha sido un sitio en inglés y mi impresión ha sido que con mucho menos esfuerzo he logrado mucha más repercusión. Enlaces en muchas más páginas y mayor interacción social. Pero también es un sitio de nicho, mucho más fácil de categorizar que este blog. La pregunta es: ¿qué parte de su éxito corresponde al idioma y qué parte a la temática de la página?

    Llegados a este punto, ¿merece la pena convertir Adrianistán en un blog en inglés? Sin duda, algo que me voy a plantear muy seriamente en este 2017, por supuesto, vuestras opiniones me importan y podéis decir que pensáis al respecto.]]>
    https://blog.adrianistan.eu/feliz-an-dame-los-datos Sat, 31 Dec 2016 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
    Fundamentalismo ateo https://blog.adrianistan.eu/fundamentalismo-ateo https://blog.adrianistan.eu/fundamentalismo-ateo
    fundamentalismoNada más amigos porque me voy a la puta mierda porque soy borrico pedazo de estúpido desinformado, con falta de cultura.

    Reflexión seria: ¿Por qué tengo la impresión de que está surgiendo una especie de fundamentalismo ateo? ¿Por qué atacar tan fervientemente? A mi modo de ver, personas como Cristian50088 son la antítesis de lo que debe ser un buen científico. Deprenden arrogancia y caen en falacias dignas de fundamentalistas religiosos.

    Hace tiempo que llevo viendo que las secciones de comentarios de ciertos vídeos de YouTube están llenos de ateos exaltados. Personas que aprovechan a la mínima para despotricar contra cualquier persona que quiera hacer una mínima observación. Y lo peor de todo es que para mí son hipócritas. Richard Dawkins admitió en un vídeo que el "ser ateo" no es en sentido estricto una mejora sobre "ser creyente" ya que en ambos casos se presupone la existencia o falta de ella de un Dios. Sin embargo, en el asunto Dios no es que haya muchas pruebas que digamos. Por tanto, la posición idónea es el escepticismo y no la completa negación de Dios, porque estaríamos cayendo en el mismo error de los creyentes de afirmar/negar sin pruebas. Un escéptico, un agnóstico, ... va a vivir su vida de forma prácticamente igual que el ateo. Pero existe esa sutil diferencia sobre como ha llegado a ello.

    Lo de las faltas de respeto, lo dejo para otro momento.

     

     ]]>
    https://blog.adrianistan.eu/fundamentalismo-ateo Wed, 21 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
    Viernes de Complot en Adrianistán https://blog.adrianistan.eu/viernes-complot-adrianistan https://blog.adrianistan.eu/viernes-complot-adrianistan
    [video width="480" height="270" mp4="https://files.adrianistan.eu/giphy.mp4" loop="true" autoplay="true"][/video]

    Pero en Adrianistán también existe el dinero y a los adrianistaníes (sobre todo los que no forman parte del complot definitivo) les gusta ahorrar. Los muggles celebran su Black Friday, nosotros celebramos el Viernes de Complot. Por una vez al año, Adrianistán sufre de un consumismo compulsivo y todo se vuelve publicidad.

    [video width="480" height="480" mp4="https://files.adrianistan.eu/complot.mp4" loop="true" autoplay="true"][/video]

    Si compras algo de lo que hay en este artículo que sepas que estarás ayudando económicamente a Adrianistán. Todos los libros han pasado lso filtros del comité de actividades anti-42.

    Libros


    51mpcaohrl-_sx333_bo1204203200_ ¿Está usted de broma, Sr. Feynman?

    sapiens Sapiens, una breve historia de la humanidad

    profeta El Profeta

    pendulo El péndulo de Foucault

    La Meta La Meta

    La Divina Comedia La Divina Comedia

    Vuelos


    AirFrance ha preparado unas ofertas de Viernes de Complot muy interesantes. ¡Aprovecha y visita a tu familia si vives lejos! AirFrance ha preparado unas ofertas de Viernes de Complot muy interesantes. ¡Aprovecha y visita a tu familia si vives lejos!

    Vueling, una compañía aérea que ofrece grandes destinos a bajo precio Vueling, una compañía aérea que ofrece grandes destinos a bajo precio

    Hoteles


    Accor Hoteles ofrece descuentos especiales en hoteles por todo el mundo. Accor Hoteles ofrece descuentos especiales en hoteles por todo el mundo.

    Mini PCs y microcontroladores


    Raspberry Pi 3 Raspberry Pi 3

    orangepi Orange Pi 2

    ODROID C2 ODROID C2

    BeagleBone Black BeagleBone Black

    Banana Pi Banana Pi

    Pine A64+ Pine A64+

    ESP8266 (NodeMcu) ESP8266 (NodeMcu)

    Arduino UNO Arduino UNO

    Arduino Nano Arduino Nano

    TI Launchpad MSP430 TI Launchpad MSP430

     

    Dominios


    1&1 es un veterano en Internet. Dominios y servidores a precios competitivos. 1&1 es un veterano en Internet. Dominios y servidores a precios competitivos.

    GoDaddy, uno de los registrar DNS más populares del mundo GoDaddy, uno de los registrar DNS más populares del mundo.

    Otras promociones


    MiniInTheBox, uno de las tiendas online de gagdets más grandes de Internet te ofrece descuentos de Viernes de Complot MiniInTheBox, uno de las tiendas online de gagdets más grandes de Internet te ofrece descuentos de Viernes de Complot.

    TomTop, con el código 3TTEU obtendrás 3 euros de descuento TomTop, con el código 3TTEU obtendrás 3 euros de descuento.

    Intimissimi, 20% de descuento en gran parte de su lencería Intimissimi, 20% de descuento en gran parte de su lencería con motivo del Viernes de Complot.

     ]]>
    https://blog.adrianistan.eu/viernes-complot-adrianistan Thu, 24 Nov 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
    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
    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
    Tutorial de GnuCash en castellano https://blog.adrianistan.eu/tutorial-gnu-cash https://blog.adrianistan.eu/tutorial-gnu-cash GnuCash es un programa de contabilidad y finanzas para familias y PYMES, software libre perteneciente al proyecto GNOME. Es un programa muy potente, por desgracia su uso puede no ser muy intuitivo.

    GnuCash

    Creando un libro


    Todo absolutamente todo se guarda en un único archivo, que puede ser de varios tipos (XML, sqlite3, MySQL, PostgreSQL). Cuando iniciemos GnuCash veremos este asistente que nos ayudará a crear un libro.

    NuevaCuenta

    Lo primero que deberemos elegir será la moneda principal. Lo habitual es que elijáis la moneda oficial de vuestro país, en mi caso, el Euro.

    SeleccionarMoneda

    Ahora aparecerán las opciones del libro de contabilidad. Estas opciones normalmente solo se aplican a empresas y están destinadas a generar facturas o hacer presupuestos. La única opción que quizá os interese es la de Trading accounts que es interesante si vais a manejar cuentas en varias monedas. Ahora tenemos que seleccionar una plantilla para tener cuentas ya generadas. Esto puede ser algo confuso, ya que es obligatorio seleccionar al menos una plantilla. Yo no recomiendo ninguna y prefiero empezar de cero, para eso selecciona la plantilla más básica que hay Childcare expenses y continúa hasta que estes ya en la vista principal de GnuCash.

    Plantillas

    VistaPrincipalGnuCash

    A continuación vamos a dejar el libro vacío. Pulsa botón derecho sobre Expenses y luego clic en Delete account... Se te preguntará si quieres mover o borrar, selecciona la opción Delete all subaccounts.

    Creando cuentas


    GnuCash usa un sistema de doble entrada. Quiere decir que todos los gastos e ingresos deben ir anotados en dos partes. Por ejemplo, si anotamos en Gastos una comida en un restaurante, tenemos que anotar también el gasto en la cuenta de Metálico, que es de donde provino el dinero. Naturalemente no lo tenemos que hacer manualmente, cuando anotemos un ingreso o gasto en GnuCash este se encargará de anotarlo donde sea oportuno.

    Ahora que tenemos el libro en blanco vamos a ir creando unas cuentas maestras y unas subcuentas. GnuCash dispone de los siguientes tipos de cuentas:

    • Asset (Activos)

      • Son aquellas cosas que tienen valor y pueden usarse para pagar cosas. En esta categoría entra desde el dinero en metálico hasta las propiedades inmobiliarias.



    • Bank (Banco)

      • Este subtipo se usa para anotar los fondos de una cuenta bancaria (que puede tener un interés) y para las tarjetas de débito.



    • Cash (Metálico)

      • Este subtipo de cuenta nos permite anotar el dinero que poseemos en metálico



    • Credit Card (Tarjeta de crétido)

      • Este subtipo de cuenta es específico para tarjetas de crédito



    • Equity

      • Este tipo está destinado a ofrecer los saldos de apertura de las cuentas. Como GnuCash es un sistema de partida doble es necesario que el dinero provenga de algún sitio. Normalmente vendrá de Income pero cuando abrimos una cuenta en la que ya teníamos dinero antes y queremos que GnuCash la contabilice, añadir el saldo de apertura a ingresos no es práctica recomendada. Así Equity permite tener saldos de apertura en las cuentas que no habían sido contabilizados antes por GnuCash.



    • Expense (Gastos)

      • Aquí se anotan los gastos. Esta cuenta y sus subcuentas son las que más usaremos en el día a día.



    • Income (Ingresos)

      • Aquí se anotan los ingresos. Es la segunda que más usaremos después de Expense.



    • Liability (Obligaciones)

      • Aquí podemos anotar deudas que tenemos pendientes de pago.



    • Mutual Fund (Fondo de Inversión)

      • Una cartera de valores de inversión compuesta de Stocks.



    • Stock (Acción)

      • Un subtipo de cuenta que representa las acciones que se poseen de una empresa.




    ¿Son estas todos los tipos de cuenta que permite GnuCash? No, hay algunas más de tipo interno. Sin embargo con estos tipos tendremos suficientes para construir el árbol de cuentas de nuestro libro.

    Creamos una nueva cuenta New account...

    NuevaCuentaActivos

    Lo primero es decidir el Account Type, cuando crees tu libro yo recomiendo hacer la primera la de Activos, del tipo Asset. ¿Qué nos pregunta el formulario?

    • Account name. Un nombre para la cuenta.

    • Account code. Permite asignar un código alfanumérico a la cuenta. Es usado para determinar el orden de las cuentas cuando se generan informes. Además si trabajas con contabilidades más complejas sabrás que es habitual que en muchos sitios incluso pidan los gastos en números de cuenta estandarizados (en la administración por lo menos es así, así ellos pueden combinar todas las cuentas del mismo número y sacar datos globales). Es habitual que las cuentas padre acaben en cero. Es totalmente opcional y si usas GnuCash en casa simplemente deja ese campo vacío.

    • Description. Una descripción de la cuenta

    • Security/Currency. Si nuestra cuenta no es Stock o Mutual Fund sirve para indicar la moneda de la cuenta. Si sí que es una cuenta Stock o Mutual Fund sirve para indicar la acción en sí. Cabe destacar que en GnuCash es posible añadir subcuentas dentro de una cuenta cuya moneda sea distinta (una subcuenta en dólares dentro de la cuenta Activos que es en euros). Luego veremos como ajustar la tasa de conversión para los informes. También hay gente que prefiere que todas las cuentas sean de la misma moneda, en ese caso crean Activos en Euros y Activos en Dólares.

    • Smallest fraction. La fracción más pequeña de la moneda con la que estemos trabajando. En general es habitual dejarlo por defecto, pero este ajuste nos será de utilidad con las criptodivisas.

    • Account Color. El color de la cuenta. Uso puramente estético.

    • Notes. Podemos guardar notas. Este campo lo considero de gran utilidad pues me permite guardar los códigos IBAN y BIC dentro de las cuentas bancarias.

    • Hidden. Si quieres que no se vea. Yo lo quito siempre, pues me gusta ver el árbol completo.

    • Placeholder. Una cuenta placeholder es específicamente diseñada para tener subcuentas. Quiere decir que no es posible anotar en ellas directamente, solo a través de alguna de sus subcuentas. En el caso de las cuentas principales recomiendo que todas sean placeholder, así siempre especificaremos la subcuenta donde se ha producido el ingreso/gasto.

    • En la pestaña superior Opening balance podemos indicar el saldo de apertura. Se anotará también en una cuenta de tipo Equity.


    Creamos la cuenta y listo. Ahora vamos a crear muchas cuentas hasta que se adapte a nuestra situación. Mis consejos para diseñar el árbol son:

    • La cuenta Activos puede tener activo circulante o propiedades, sin embargo no recomiendo añadir propiedades, ni otros activos que no sean dinero en sí (obras de arte, etc) salvo casos muy específicos.

    • Dentro de la cuenta Activos suelo crear otra cuenta para cada banco. Luego dentro de la cuenta de cada banco creo la correspondiente Bank account de las cuentas bancarias.

    • Hay sistemas de pago (PayPal) y tarjetas (Revolut) que permiten más de una divisa simultánea. En ese caso yo suelo crear subcuentas, una en cada divisa que permita y de la que preveo tener fondos. Por ejemplo, en PayPal creo la cuenta de tipo Asset placeholder con dos subcuentas, PayPal EUR y PayPal USD.

    • Las criptodivisas como Bitcoin pueden ser manejadas por GnuCash de manera extraoficial. Los desarrolladores se han negado a añadir el código de la moneda (BTC o XBT) hasta que aparezca en el estándar ISO. Sin embargo GnuCash viene con un montón de otras monedas. La moneda XXX, que no respresenta a ninguna real puede hacer de Bitcoin. Pero para que funcione bien es importante que el valor de Smallest Fraction sea el que permita más digitos decimales. Aun así GnuCash tendrá problemas con tantos decimales a los que acostumbra Bitcoin por lo que puede ser interesante que XXX sean Milibitcoins o Satoshis.


    ArbolDeCuentas

    Cambios de divisa


    GnuCash permite que creemos cuentas en muchas divisas. Sin embargo, como puedes apreciar en la imagen superior, en la cuenta de PayPal, la moneda extranjera no aparece convertida y no es tenida en cuenta. Esto es porque por defecto GnuCash aplica un tipo de cambio 0, es decir, la moneda extranjera no vale nada. Puedes dejarlo así o puedes añadir el cambio. Hay dos formas, una manual y otra automática.

    Vamos a Tools -> Price Editor. Veremos una ventana vacía con botones a la derecha. Si tenemos Perl instalado en nuestro ordenador con el módulo Finance::Quote veremos el botón Get Quotes activado. Ese botón sirve para actualizar los precios de los cambios de divisa y de las acciones. Ese es el modo automático. Si necesitas instalar Perl en Windows puedes usar ActivePerl o Strawberry Perl.

    Si queremos hacer el cambio manualmente iremos a Add.

    PriceEditor

    En este caso vamos a añadir el precio para pasar de dólares USD a euros. El namespace es CURRENCY, security es la moneda a la que queramos hacer la transformación a nuestra moneda. Currency la dejamos tal cual. La fecha estará puesta a día de hoy. Esto es muy cómodo porque GnuCash permite guardar el historial de tipos de cambio. Así, cuando haya un nuevo tipo de cambio simplemente vuelves a dar a Add... y se guardará en una fecha distinta. GnuCash será lo suficientemente listo para saber que tipo de cambio ha de usar en cada momento. Por último Price es el precio de 1 dólar en euros. Hay muchos sitios de donde sacar esta información. En Google simplemente con escribir "1 USD to EUR" tendremos el resultado.

    USDtoEUR

    Anotar ingresos y gastos


    Cuando usemos GnuCash en nuestro día a día lo más normal es que anotemos ingresos y gastos. Es necesario haber creado cuentas de tipo Income y Expense para anotar ingresos y gastos respectivamente. Dentro es habitual crear subcuentas, cada una de una categoría de ingreso o gasto (sueldo, pensión, multas, restaurantes, ...).

    Selecciona la cuenta y haz doble click o pulsa el botón Open, se abrirá en una nueva pestaña, donde podremos ver algo parecido a un libro de contabilidad del MundoReal. Allí podemos revisar las anotaciones anteriores y editarlas (aunque no es recomendable editar la contabilidad, ejem,...). Pero por defecto el foco se centra en la fila inferior, donde podemos introducir una nueva entrada al libro de contabilidad.

    LibroGnuCash

    La fecha la podemos ajustar a cuando se realizó el ingreso/gasto. El número es solo necesario si eres una empresa y las facturas están numeradas. La descripción es un campo libre, donde explicamos el ingreso/gasto. La siguiente columna es Transfer donde debemos de elegir la cuenta de Activos donde se ingresará/eliminará la cantidad. Allí, en la cuenta del Activo también se guarda una copia. Recuerda GnuCash es un sistema de partida doble. Con el botón superior Jump es posible ir a la cuenta del Activo y viceversa, aunque no es necesario. La R es un indicador de reconciliación, no se toca. Charge son cargos que pudiera tener e Income es el beneficio. En una cuenta Expense las columnas son Expense y Rebate, de funcionamiento exactamente igual. Ambas tienen Balance que es el balance total.

    Si tienes una transacción compleja donde los ingresos y los gastos ocurren en varios lugares (pagar el sueldo a un trabajador) puedes hacer una transacción partida o Split.

    Transferencias entre cuentas


    TransferenciaGnuCash

    Las transferencias entre cuentas de activos son sencillas de realizar. Simplemente busca en Actions -> Transfer. En caso de que las cuentas tengan distinta divisa es posible ajustar el cambio. El botón Fetch Rate solo funcionará si tienes el plugin de Perl instalado. La columna de la izquierda es la cuenta de origen y a la derecha se sitúa la cuenta de destino.

    Programar operaciones


    Si sabemos que un ingreso o pago va a realizarse de manera periódica podemos automatizarlo. Cuando GnuCash se abra comprobará si desde que abriste el programa por última vez ha habido algún día con una operación programada y la realizará.

    Vamos a Actions -> Schedule -> Scheduled transactions editor. En una nueva pestaña veremos un calendario y una lista de operaciones vacía. Creamos una nueva en New.

     

    En Frequency ajustamos cuando queremos que esta operación se realice. Otra opción es hacer una operación en el libro y desde allí crear una operación programada siguiendo los mismos parámetros que la ya realizada.

    Nomina

    GnuCash no es solo esto, también dispone de informes y presupuestos, pero eso se verá en otro momento.

     ]]>
    https://blog.adrianistan.eu/tutorial-gnu-cash Wed, 13 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
    Valladolid Hyperlapse https://blog.adrianistan.eu/valladolid-hyperlapse https://blog.adrianistan.eu/valladolid-hyperlapse
    ]]>
    https://blog.adrianistan.eu/valladolid-hyperlapse Wed, 11 May 2016 00:00:00 +0000
    El Universo Mecánico https://blog.adrianistan.eu/el-universo-mecanico https://blog.adrianistan.eu/el-universo-mecanico El Universo Mecánico.

    eluniversomecanico

    La serie se organiza en 52 episodios, todos ellos muy interesantes, con una misma estructura. El profesor de CalTech (doctor David L. Goodstein) desde su clase con alumnos hará una introducción al tema en cuestión. Puede que haga un experimento, cuente una historia o incluso lea un poema. Después un narrador nos explicará la teoría, apoyándose de ejemplos, ecuaciones en pantalla y gráficos 3D (revolucionarios para la época).

    El Universo Mecánico no es una serie superificial, no incide mucho en las ecuaciones, pero los conceptos pueden ser algo avanzados, dependiendo del nivel que tenga el espectador. De todos modos, la serie cuenta con un par de capítulos instrumentales donde se explican los conceptos de vector, derivada e integral, que pueden servir al espectador que no domine estos conceptos matemáticos.

    movingcircles

    La serie no se deja prácticamente nada de la física, veremos desde partículas elementales hasta las ecuaciones de Maxwell pasando por óptica, temperatura, magnetismo, electricidad, gravitación, conservación del momento, ondas, movimiento armónico simple, ...

    Aprenderemos no solo fórmulas y ecuaciones con sentido (o sin él) sino que comprenderemos la genialidad intrínseca de cada teoría. Veremos que no hay verdades inamovibles, que nunca podemos olvidar la historia y que aunque al final del día esa magnífica ecuación nos sirva para mandar esa nave a la luna, lo subyacente, lo real en sí, no es eso. La realidad es la realidad y nosotros nos intentamos acercar a ella con modelos matemáticos. Y como muchas veces hemos visto que dos cosas bien distintas (aparentemente) en realidad son el mismo efecto de la realidad. ¿Qué es una fuerza? Solamente un concepto teórico, que no existe en la realidad, para explicar algo que sucede en nuestro universo.

    Pero aprenderemos además que todo está relacionado con todo, el arte, la poesía, la política, la filosofía y la física no son más que divisiones, en cierto modo arbitrarias que hacemos los humanos dentro de una realidad que no tiene divisiones, que no "colecciona sellos".



    Para mí, una de las mejores series documentales de ciencia.]]>
    https://blog.adrianistan.eu/el-universo-mecanico Tue, 10 May 2016 00:00:00 +0000
    The Everything Building https://blog.adrianistan.eu/the-everything-building https://blog.adrianistan.eu/the-everything-building The Everything Building es un juego que fue presentado al Ludum Dare 34. Ludum Dare es una game jam, la más popular quizá. Se trata de hacer un juego en 72 horas. Normalmente hay un tema para el juego y si te ciñes a él podrás optar a ganar en más categorías. El tema de esta vez fue "Two game controls" (dos controles). The Everything Building quedó segundo en la general aunque en la categoría de diversión ganó.

    elevator

    ¡Probadlo! A mí me ha encantado, es un gran juego. Con solo dos controles (flecha arriba, flecha abajo) controlamos un ascensor. Tenemos que llevar a la gente de una planta a otra. Si hay demasiada gente espereando perdemos. Hasta ahí parece simple pero el tipo de personas que montan en el ascensor tiene efectos sobre el resto. Por ejemplo, existen las parejitas, con un corazón encima, que no van a revelar su planta hasta que no se encuentren con su pareja. Están los zombies que ahuyentan a la gente. Los perros ocupan poco espacio y entran 4 mientras que coches solo entran 1. Si llevas payasos con globos te será más fácil subir y más difícil bajar. Este comportamiento a la inversa si coges a un fortachón. Hay un personaje que puede cambiar el tipo de establecimiento de esa planta (que en definitiva es lo que genera los personajes especiales) y que nos puede servir para jugar táctico.

    En el apartado técnico no hay ningún reproche, es ligero, tiene un diseño pixel art bellísimo, la música acompaña perfectamente y la jugabilidad está ajustada, ¡perderás en el mejor momento!

    En definitiva, jugadlo, es de lo mejorcito que he visto en HTML5.



    Mis felicitaciones a Olli Ethuaho, Kimmo Keskinen, Sakari Leppä, Valtteri Heinonen y Anastasia Diatlova. El código está en GitHub: https://github.com/Oletus/elevator]]>
    https://blog.adrianistan.eu/the-everything-building Mon, 9 May 2016 00:00:00 +0000
    ¿Qué ocurrirá con Bitcoin y los ordenadores cuánticos? https://blog.adrianistan.eu/ocurrira-bitcoin-los-ordenadores-cuanticos https://blog.adrianistan.eu/ocurrira-bitcoin-los-ordenadores-cuanticos
    La computación cuántica es inevitable. Ya vemos como Google y NASA están trabajando conjuntamente en modelos como D-Wave 2 (aunque este modelo está pensado para Machine Learning no para criptografía). Así que si partimos de que la computación cuántica va a llegar, solo queda estar preparados para cuando el salto ocurra, que creo que será dentro de poco.

    En StackExchange Bitcoin plantean esta pregunta

    bitcoin-stackexchange

    Los ordenadores cuánticos afectarán a parte de la estructura Bitcoin.

    El algoritmo ECDSA estará roto. Los ordenadores cuánticos encontrarán una manera sencilla de sacar la clave privada a partir de una clave pública. Significa que podrán acceder a tu cuenta con Bitcoins. Pero no es tan grave si se modifica el uso que le damos a Bitcoin y usamos las direcciones una única vez. La clave pública solo se envía cuando ya hemos gastado los Bitcoins así que si no reutilizamos la dirección nadie podrá acceder a tus Bitcoins.

    Otro problema es el tema de los hashes. Actualmente se usa SHA256, el cuál es lo suficientemente seguro en computación tradicinal aunque con la potencia que tendrá la computación cuántica sería similar a descifrar SHA128 en un ordenador tradicional (algoritmo de Grove). En ese caso Bitcoin tiene un procedimiento para reemplazar el algoritmo rápidamente por un nuevo algoritmo diseñado con ordenadores cuánticos en mente que actualmente están en desarrollo.

    Pero lo que puede que además ocurra sea una hiperinflación, una centralización de la red. Puesto que en cuanto un ordenador cuántico entre en la red a minar, la dificultad aumentará drásticamente y el resto de mineros no podrán competir. El problema de la centralización lleva acarreado un problema de pérdida de confianza en la red ("si solo unos pocos controlan la red, ¿cómo sé que no están compinchados entre ellos?"). Si superan el 51% de la potencia de la red, pueden controlar toda la red. Podrá realizar transacciones inversas que él haya mandado. Puede bloquear las transacciones de tener confirmaciones. Puede bloquear al resto de mineros de minar un bloque válido que la red acepte.

    El atacante no podrá, sin embargo, revertir las transacciones de otras personas, bloquear las transacciones (pueden tener 0 confirmaciones pero aparecerás), generar monedas por arte de magia, enviar monedas que nunca le han pertenecido. Así que en la mayoría de casos no sería tan rentable y en muchos casos lo que más dinero te de sea seguir las normas.

    Conclusión, los ordenadores cuánticos pueden afectar a Bitcoin pero ya hay soluciones en marcha y no serán muy difíciles de implementar llegado el momento. Además el atacante podría no tener interés en llevar a cabo dicha acción pues el gasto que le llevaría seguiría siendo superior a lo que obtendría.

    ¿Qué opinas tú? ¿Crees que me he equivocado en algo?

    Fuentes:
    ]]>
    https://blog.adrianistan.eu/ocurrira-bitcoin-los-ordenadores-cuanticos Sat, 7 May 2016 00:00:00 +0000
    Sobre la Física - Parte 2 - ¿Qué es la luz? https://blog.adrianistan.eu/la-fisica-parte-2-la-luz https://blog.adrianistan.eu/la-fisica-parte-2-la-luz
    Antes de nada, puede que en algún sitio haya cometido un error garrafal de terminología o de concepto, en ese caso indicádmelo

    Contexto


    La dualidad onda-partícula hace referencia a la naturaleza de la luz. ¿Qué es la luz? ¿De qué está formada? Para explicarlo, tenemos que remontarnos al siglo XVII, tiempo de Newton y Huygens.

    En esta época, la física despega y cada vez se proponen nuevas leyes para explicar fenómenos observados desde la antigüedad, pero que en la cultura clásica grecorromana y posteriormente en filosofía escolástica se trataban sin referencia a las matemáticas. Este concepto actual de relacionar y aplicar las matemáticas a la naturaleza es un pensamiento que nace en el renacimiento.

    Al tratar el tema de la luz se realizan experimentos con conclusiones muy dispares, lo que genera gran controversia entre los pocos científicos que había en la época. Básicamente distinguimos dos teorías, no voy a explicar sus experimentos, solo el concepto.

    Por un lado, Isaac Newton, propone que la luz es una partícula (una especie de pelotita) y actúa como tal. Tiene experimentos que lo corroboran.

    Por otro lado, Huygens propone que la luz es una onda y actúa como tal. Tiene experimentos que lo corroboran.

    Sin embargo los modelos de partícula y onda son imcompatibles entre sí y lo que explica una teoría no puede ser explicado por la otra y viceversa. Gran problema.

    Maxwell


    Entonces llega Maxwell, ya en el siglo XIX y en un atisbo de genialidad, saca a relucir sus ecuaciones electromagnéticas. Estas ecuaciones son un punto de inflexión en la física, unifican mucho contenido disperso de un asunto que traía de cabeza a los físicos como era la electricidad y el magnetismo. Demuestra que estan relacionadas estas propiedades (o que realmente son lo mismo, según la interpretación) y realiza un curioso hallazgo y es que la relación entre campo eléctrico y campo magnético es... la velocidad de la luz. Esto servirá de punto de partida para que Einstein para su teoría de la relatividad, en la cual la velocidad de la luz es constante y no puede ser superada. Volvemos a Maxwell. Esa relación parece indicar que la luz es en realidad una onda electromagnética. ¿Parece que Huygens tenía razón, no?

    maxwell

    maxwell-god

    Efecto Fotoeléctrico


    Pues tampoco, porque a principios de siglo XX se observa el efecto fotoeléctrico. Este efecto no se puede explicar de ninguna manera por la teoría electromagnética y Einstein en 1905 revoluciona el campo de la concepción materia-energía con una teoría cuantificada. Volvamos atrás al concepto de cuantificado.

    FotoElectrico

    Max Planck propone un modelo cuántico, el primero, para tratar de explicar comportamientos relacionados con el cuerpo negro. La teoría supone un cambio drástico porque supone admitir que no existen todos los valores de una variable, sino que las magnitudes físicas van a saltos. Estos saltos son los cuantos, de ahí el nombre de la física cuántica.

    Einstein toma el concepto de los cuantos y los usa para explicar la naturaleza de la luz en el efecto fotoeléctrico. Para él la luz sigue siendo una onda pero a la vez se transmite en una especie de paquetes. Esos paquetes los llama fotones. Entonces llega el Efecto Compton, que a nivel de electrones demuestra que el fotón tiene comportamiento de partícula. Pero esto ya se vuelve difícil de explicar.

    Las teorías de Maxwell funcionaban muy bien y habían sido puestas en práctica con una asombrosa precisión. Ahora estos nuevos experimentos contradecían la teoría de Maxwell. Y lo peor es que aunque se ha definido un comportamiento de partícula, resulta imposible tratar de encontrar la masa de la luz. ¿Cuántos gramos tiene la luz?

    Desde entonces se habla de dualidad onda-partícula, la luz es partícula y onda a la vez.

    Conclusión


    La conclusión a la que podemos llegar (seguro que hay más) es que la física no explica como ES la realidad, sino que planeta modelos que se ajustan a la realidad, pero el universo no es un ordenador o una calculadora. No es una gran ecuación. El universo es el universo, el universo no entiende de la razón y de lógica. Simplemente ES. Y nosotros podemos aplicar modelos, pero esos modelos no SON la realidad.

    Así pues la luz es la luz y se manifiesta de formas distintas en nuestros modelos simplificados, reduccionistas. La pregunta entonces no tiene sentido. En general dudo que podamos llegar hasta el "final de la física" puesto que no tendremos nunca la certeza de que el universo se comporta de forma lógica y racional o se comporta de manera irracional, aunque tengamos modelos matemáticos que puedan predecir la realidad con una asombrosa precisión. No caigamos en el dogma del empirismo. No sobrepasemos los límites de la razón.]]>
    https://blog.adrianistan.eu/la-fisica-parte-2-la-luz Thu, 14 Apr 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
    Sobre la Física - Parte 1 https://blog.adrianistan.eu/la-fisica-parte-1 https://blog.adrianistan.eu/la-fisica-parte-1


    ¿Qué es la física? No es algo sencillo. Realmente no hay una respuesta universal. ¿Qué es la naturaleza, qué es la realidad? No son preguntas fáciles. La mayoría de la gente hoy día es empirista, de un grado u otro, una posición correcta, pero no es la única válida. Platón, aún cuando ha pasado mucho tiempo, sigue teniendo una teoría válida para explicar la realidad, alejada totalmente del empirismo. Y el empirismo radical conlleva al escepticismo como avisaba Kant. No poder estar seguros de nada, ¡terminamos en un punto peor que el de partida! Tenemos que asumir que la naturaleza es uniforme en sus comportamientos, pero eso es algo que nunca podrá ser demostrado. Dudas, dudas.

    Ajedrezado

    ¿Y las matemáticas? ¿Fueron descubiertas o fueron inventadas? Ahora mi opinión. La física no es la realidad. La física es un modelo matemático, muy preciso, pero alejado de lo real. "El Universo se puede expresar en una ecuación" es falso. La física no es más que una interpretación humana usando matemáticas. La física entonces no es más que una invención humana, no nos dice porque la realidad es tal como es. Dudas, dudas.

    Lo más triste de todo es que hay gente que cree en la ciencia como un dogma, ajena a cualquier objeción. Pasamos del dogma bíblico al dogma de "El Método Científico". Salimos de una cueva y entramos en otra. Muy poca gente, muy pocos científicos pueden estar realmente fuera de la caverna. Dudas, dudas.

    Y en estos temas la gente obvia el asunto de Dios. Asunto para nada cerrado. No como un Dios personalista, que juzga a la gente, pero ¿y como un estado ideal de perfección? Pero antes tenemos que definir la perfección. ¿O es Dios acaso otra cosa? Dudas, dudas.

    ¿Qué opináis? Me encantará oír vuestras opiniones al respecto y por supuesto, sois libres de criticarme. Intentaré desarrollar cada punto por separado, porque es extenso, de momento dejamos el asunto planteado.]]>
    https://blog.adrianistan.eu/la-fisica-parte-1 Sun, 27 Mar 2016 00:00:00 +0000
    Análisis de The Witness https://blog.adrianistan.eu/analisis-the-witness https://blog.adrianistan.eu/analisis-the-witness Jonathan Blow. Me lo había recomendado un buen amigo y al poco me enganché. Voy a hacer un spoiler mínimo, lo justo para que os quedeis con los dientes largos.

    Nota musical:


    Si te has pasado el juego al 100% lo entenderás

    The Witness se inicia de manera enigmática para el jugador acostumbrado a la jugabilidad Nintendo. Con jugabilidad Nintendo me refiero a la típica de juegos como Mario Party donde antes de cada juego se explican las instrucciones por escrito y se puede incluso practicar. La propia pantalla de carga produce intriga al jugador.

    TheWitnessLoad

    Aparecemos en un pasillo, en una isla, al principio solo podemos avanzar hacia delante. El juego nos indica que podemos usar las teclas WASD para movernos y que usemos el ratón. Es la única ayuda que nos dará.

    TheWitness

    Para avanzar tendremos que resolver puzzles. Todos los puzzles poseen la misma estructura, empezamos por un círculo gordo y tenemos que dibujar una ruta por la cuadrícula hasta el final, una línea que se sale de la cuadrícula. Pero no vale con llegar, tendremos que cumplir las condiciones que los símbolos geométricos y el entorno nos impongan. Al principio estos símbolos no aparecen pero según vayamos avanzando, empezaran a aparecer hexágonos, cuadrados amarillos, cuadrados azules, cuadrados blancos, estrellas rosas, triángulos y el entorno también influirá en la resolución del puzzle.



    Una vez atravesamos la primera parte, se nos deja sueltos en la isla y entonces podemos hablar de un auténtico sandbox de puzzles.

    TheWitnessApple

    El significado de cada símbolo, de porque un puzzle se resuelve de esta manera y no de otra es algo que queda para nosotros. Tenemos que resolver los puzzles, pero además tenemos que saber qué nos pide el puzzle. Y muchas veces creemos haber llegado a una conclusión que es incorrecta, y tenemos que replantearnos todo lo anterior. Muy bien pensado. Cuando ya sepamos el significado de los símbolos y como influye el entorno en el puzzle el reto ya no será descifrar su significado, sino resolverlo, puesto que serán más complejos. Un trabajo de científicos...



    El vídeo es un desbloqueable que encontramos en el juego si somos avispados, ya que las piezas del gran puzzle que es encontrar los vídeos se encuentran desperdigadas por la isla. En este fragmento vemos a James Burke al final de la serie Connections de la BBC (1978). The Witness está plagado de filosofía empirista por todas partes. Además de estos vídeos (que hay 6) nos encontramos con 43 audios con frases, explicaciones y pensamientos de Albert Einstein, William Faulkner, Feynman y gente relacionada con la filosofía de la ciencia y otros personajes de la filosofía zen moderna, como Gangaji y David Golding. Algunos audios son más estimulantes que otros, algunos te hacen pensar y otros son un poco rollo. Lo cierto es que crea una atmósfera que mezcla elementos zen y el empirismo.

    El zen es tan importante que en un determinado momento nos preguntaremos si todo esto es un kōan, es decir un problema que el maestro plantea al alumno para comprobar sus progresos, muchas veces un problema absurdo que para ser resuelto el alumno debe desligarse del pensamiento racional común para así entrar en un sentido racional más elevado y así aumentar su nivel de conciencia para intuir lo que en realidad le está preguntando el maestro, que trasciende al sentido literal de las palabras. Una explosión mental en toda regla que nos acerca, en esos momentos de explosión al contacto más directo con la realidad, sin pasar por el pensamiento, pues este ha sido destrozado.

    Sin embargo toda esta filosofía que planté a el juego no acaba bien, por lo menos desde mi punto de vista. El juego parece que según vas avanzando llegarás a una conclusión final, que te hará replantearte tu vida. Esto no ocurre. Quizá sea un reflejo de la vida. Creemos que tiene algún sentido, que algún día comprenderemos. Pero al final solo hemos dado unas pinceladas aquí y haya, pero seguimos sin tener "eso". "Eso" que acabaría con "aquello". Así que el final no es malo, pero quizá cuando lo veas por primera vez, después de haber estado jugando te sepa a poco. Es lógico. Pero tras un razonamiento, haciendo analogías con la vida, no me parece un final tan malo.

    El apartado estético es alucinante, cada vez que lo pienso más me asombra. No es especialmente realista, podríamos llamarlo impresionista, con formas toscas, esquinas gigantes pero una iluminación y un colorido espectacular. Aparte de la belleza de por sí de la naturaleza y los edificios tenemos juegos de sombras:

    TheWitnessMalabarista

    En este caso por ejemplo, vemos la estatua de un pobre hombre, parece que gritando al cielo, una gran desgracia la ha acontecido. Pero su sombra, que combina con las piedras del suelo nos deja otra imagen, se encuentra haciendo malabares, un símbolo de felicidad. No todo es lo que parece.

    TheWitnessGranEstatua

     

    En esta imagen vemos la estatua una mujer tallada en la montaña. Tiene un porte de grandeza, se va a comer el mundo, es fuerte y mira al cielo, aspirando a lo más alto.

    TheWitnessEstatuaAyuda

    En cambio, desde el punto de vista correcto, la situación cambia por completo, la mujer está siendo ayudada por otra, parece que va a morir, que las cosas le van mal y la otra mujer, por encima de ella la intenta subir a donde está ella, un lugar mucho más estable.

    TheWitnessPuzzleAmbiental

     

    Y dejamos lo mejor para el final, los puzzles ambientales. Se trata de puzzles que aparecen en la naturaleza. En la imagen vemos uno muy sencillo, el del río, que además cuenta con panel explicativo. Cuando veamos uno de ellos simplemente nos dirigimos al punto gordo y vamos hasta el final. Veremos estrellitas resplandecer y si finalizamos la ruta se hará un gran estruendo. Hay muchísimos. Y muchos donde no te los esperas. Por haber hay hasta uno dentro de una película del cine, que, os adelanto, dura una hora el recorrido que tenemos que hacer.

    TheWitnessEnvironmentalPuzzle

    ¡Están por todas partes!

    TheWitnessCastleBefore

    TheWitnessCastleAfter

    Mi recomendación final es que un juego magnífico, llegar al final estándar no es muy difícil, pero el jugador que le entre el gusanillo puede aspirar a conseguir todos los vídeos, entrar al nivel de la cueva y desvelar el final secreto.

    Comprar en eBay Comprar en Amazon Comprar en Steam]]>
    https://blog.adrianistan.eu/analisis-the-witness Fri, 25 Mar 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
    Bienvenido 2016, bienvenido kaizen https://blog.adrianistan.eu/bienvenido-2016-bienvenido-kaizen https://blog.adrianistan.eu/bienvenido-2016-bienvenido-kaizen
    2016 Imagen de Marcela

    ¿Cómo mejorar el blog?


    Cada vez me interesa más este blog, cada vez le tengo más cariño, pero creo que no se está aprovechando su potencial. Así que, y siguiendo las pautas de la mejora continua (KAIZEN), os voy a pedir unas cosillas:

    • Rellenar la encuesta que se encuentra más abajo

    • Compartir el blog en redes sociales

    • Suscribirte por correo

    • (y ya si donas por PayPal, ChangeTip o Flattr, eres un fiera)


    El objetivo es que el blog crezca, no en visitas, lo cual me importa pero no tanto como en la comunidad, una comunidad activa alrededor del blog.

    ]]>
    https://blog.adrianistan.eu/bienvenido-2016-bienvenido-kaizen Fri, 8 Jan 2016 00:00:00 +0000
    Ya he logrado dominar el mundo https://blog.adrianistan.eu/ya-he-logrado-dominar-mundo https://blog.adrianistan.eu/ya-he-logrado-dominar-mundo
    Piramide

    Quiero agradecer a todos aquellos que han hecho posible mi dominio. En especial a los illuminati, a la cienciología (ahora se llama Scientology™), a los programas de telebasura (bueno, en realidad a la televisión general puesto que TELEVISIÓN EN EL MUNDO = TELEBASURA GLOBAL), a la gente que da opiniones sin informarse antes (hay que majos son, que tierna es su manipulación), a las diversas sectas de nuestro bonito panorama internacional, a los aburridos (muy aburridos estaban), a los habitantes de Isla Malvada y por supuesto a mi kit de Dominium mundi, que le faltaban piezas (concretamente el Santo Grial, reclamé a los templarios pero no hicieron ni caso "No, nosotros no sabemos. Lo andábamos buscando también, fíjate que casualidad").

    Mundo

    No voy a negarlo, me ha sido muy fácil dominar el mundo, tomar el control de todas las cosas y personas del orbe para poder ejercer poder y tiranía sin límites. Mi plan era absurdo pero eso lo comentaré otro día.

    DominarMundo

    Ahora voy a empezar a aplicar mi poder. Algunas ideas que tengo son:

    • Hacer una ola con todas las personas de la Tierra. Tengo mis dudas si en el hemisferio sur y debido al efecto Coriolis las personas girarán sus brazos en sentido opuesto.

    • Registrar los silbidos y cobrar un canon

    • Quitarles los caramelos a los niños

    • Instaurar el culto a 42

    • Inventarme un mito tipo La Eneida o Gilgamesh, donde yo soy el héroe supremo

    • Hacer un pacto con los delfines, a fin de poner límites a los imperios. Ellos el agua, yo la superficie

    • Introducirme en la organización de los Anunaki e ir escalando puestos socialmente poco a poco.

    • Añadir una sustancia de control mental en la vacuna de la gripe, y si no funciona, reeducacioncilla (si, al estilo Ned Flanders)

    • Publicar en una página web todas las cosas tiránicas que hago a modo de denuncia. Seré a la vez tirano y salvador. Pero usaré un pseudónimo guay tipo Sr. X

    • Ir al pasado y matarme para así evitar la destrucción del mundo, pero no podré ir al pasado por las paradojas de "¿cómo llegaste al pasado para matarte, jovencito? ¿no estarías ya muerto y en ese caso no podrías haber ido al pasado a asesinarte?".


    DominarMundo-2

    ¿Sabías qué...


    ... la mayoría de los que han intentado dominar el mundo han fracasado?

    ... yo no?

    ... feliz día de los Santos Inocentes, aunque si te lo estabas creyendo quizá te habías tomado drogas de más?

    Patos]]>
    https://blog.adrianistan.eu/ya-he-logrado-dominar-mundo Mon, 28 Dec 2015 00:00:00 +0000
    El pájaro que nunca deja de beber https://blog.adrianistan.eu/pajaro-bebedor https://blog.adrianistan.eu/pajaro-bebedor
    Hace poco me encontré en una feria con el dichoso pájaro y no me pude resistir. ¡Me lo compré!

    [video width="360" height="360" webm="https://files.adrianistan.eu/PajaroBebedor.webm"][/video]]]>
    https://blog.adrianistan.eu/pajaro-bebedor Wed, 23 Dec 2015 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
    Hercólubus o planeta rojo https://blog.adrianistan.eu/hercolubus-planeta-rojo https://blog.adrianistan.eu/hercolubus-planeta-rojo Hercólubus o planeta rojo, de V. M. Rabolú.

    Hercolubus

    El peor libro que he leído en mucho tiempo. Llamarlo libro es faltar el respeto. Ideas descabelladas desmentidas por la ciencia con pruebas hace ya muchos años (no como el autor del libro que lo sabe todo por conocimiento místico). Técnicamente comete fallos gramaticales y no hay una cohesión entre los capítulos, pasando a hablar de temas que no tienen nada que ver.

    En definitiva, si queréis pasároslo bien leyendo estupideces, leeros este libro. Yo no he podido parar de reír.]]>
    https://blog.adrianistan.eu/hercolubus-planeta-rojo Sat, 21 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
    La gran lista de BitCoins gratis https://blog.adrianistan.eu/la-gran-lista-bitcoins-gratis https://blog.adrianistan.eu/la-gran-lista-bitcoins-gratis Esta página se mantiene por motivos históricos pero no creo que alguno de los enlaces funcione ahora (Marzo 2018)
    ¿Quieres algún BitCoin extra? En estas páginas encontrarás metódos gratuitos para aumentar los números de tu monedero BitCoin. Las páginas se encuentran agrupadas por categorías y en ninguna es necesario registrarse.

    Bitcoin

    Visitar páginas



    Faucets


    3 minutos



    4 minutos



    5 minutos



    10 minutos



    15 minutos



    20 minutos



    30 minutos



    45 minutos



    1 hora



    24 horas


    ]]>
    https://blog.adrianistan.eu/la-gran-lista-bitcoins-gratis Sun, 4 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
    NexCono, tu sitio viral preferido https://blog.adrianistan.eu/nexcono-sitio-viral-preferido https://blog.adrianistan.eu/nexcono-sitio-viral-preferido Firefox OS y Ubuntu Phone.

    NexCono

    NexCono se ha dividido en las siguientes secciones:

    Y podemos encontrar artículos muy interesantes y variados como los Pensamientos de Thomas Edison en París o una demostración interactiva del teorema del seno

    Participación de los usuarios


    Para NexCono es muy importante la participación de los usuarios. Todas las páginas tienen comentarios de Disqus. Además de tener un formulario para proponer temas. NexCono dispone de un newsletter para recibir las novedades del sitio directamente en tu buzón de correo. Las secciones Tests y Preguntas están diseñadas especialmente para fomentar la participación

    Monetización


    NexCono ha sido diseñado para tener el menor coste posible de infraestructura. De hecho, ahora mismo no estoy pagando nada. Uso AppEngine de Google como hosting pero solo como almacenamiento estático pues el sitio está construido en Jekyll. Mucha gente me hubiese recomendado WordPress, pero decidí quedarme con Jekyll por los costes y porque es una plataforma que ya domino. Los ingresos son por la publicidad. Estoy probando muchas combinaciones de anuncios: Adpv, AdPrimary, PropellerAds, ExoClick, YesAdvertising, Anonymous Ads, etc aunque todavía no tengo muy claro con quien quedarme.

    Mi pregunta


    Os pido a vosotros, los lectores de esta entrada que entreis a NexCono y me digais lo que debería mejorar en cualquier apartado (diseño (la fuente, la disposición), contenido (más largo, más corto, otros temas, más imágenes), categorías, etc) para así poco a poco ir mejorando ese portal.]]>
    https://blog.adrianistan.eu/nexcono-sitio-viral-preferido Mon, 13 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
    Literatura Adrianistaní, un libro ligero de relatos cortos https://blog.adrianistan.eu/literatura-adrianistani-libro-ligero-relatos-cortos https://blog.adrianistan.eu/literatura-adrianistani-libro-ligero-relatos-cortos Literatura Adrianistaní para comprar. Se trata de un libro de recopilación de algunos relatos cortos que teníamos por ahí.



    Se trata de 11 relatos, algunos mucho más largo que otros. De ellos 3 son de Raúl y el resto son míos. La idea de este libro surgió después de perder un concurso literario donde había un generoso premio económico. Entonces decidimos vender nuestros propios relatos, muchos presentados a otros concursos, simplemente para ver que éramos capaces.

    Prefacio


    Este libro es una colección de pequeños relatos que hemos ido componiendo en nuestro paso por la faz de la Tierra. Un pequeño trocito de nosotros directo a vosotros. - Adrián Arroyo Calle

     
    En este pequeño libro están escritos los mejores relatos, compuestos de la mejor creatividad, y en ellos, hacemos lo posible por transportaros a la más inimaginable realidad para que al menos por un ratito os hagamos sentir irreales, y a la vez igual de dichosos que nosotros nos sentimos al presentar esta colección. Disfrutadlo. Raúl Izquierdo Buznego

    Comprar


    He subido el libro a un par de tiendas, ninguna de las copias tiene DRM y en la mayoría de tiendas está en formato EPUB. Si lo deseas, puedes enviarme un correo para realizar la transacción sin intermediarios.

    Comprar Literatura Adrianistaní]]>
    https://blog.adrianistan.eu/literatura-adrianistani-libro-ligero-relatos-cortos Sun, 14 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
    Aprobar consiste en entrar en el juego https://blog.adrianistan.eu/aprobar-consiste-en-entrar-en-el-juego https://blog.adrianistan.eu/aprobar-consiste-en-entrar-en-el-juego

    Interesante reflexión]]>
    https://blog.adrianistan.eu/aprobar-consiste-en-entrar-en-el-juego Wed, 8 Apr 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, documentación e investigación https://blog.adrianistan.eu/secta-sectarium-documentacion-e-investigacion https://blog.adrianistan.eu/secta-sectarium-documentacion-e-investigacion

    La idea


    Siempre me he sentido atraído por las sectas y todos sus extraños ritos fundamentos en nada. Y quizá por ello me gustan más las sectas que no copian descaradamente ideas de religiones ya establecidas dándoles otra interpretación.

    SectaTriptico

    Últimamente he tenido más información acerca de las sectas y cuanta más información obtenía era necesario hacer algo que expresase todo este misterioso mundo.

    ScientologyDVD

    ScientologyDVDInterior

    Por ejemplo este es un DVD que he recibido de Scientology (la Iglesia de la Cienciología) conocida por tener entre sus miembros a personalidades de Hollywood. Esta secta fue fundada por Lafayette Ronnald Hubbard, un escritor de ciencia ficción estadounidense que escribió Dianética y dio comienzo a la revolución. L. Ron Hubbard vivió una vida intensa relacionada con los viajes y el ejército. Finalmente se suicidó y dejó una fortuna de 600 millones de dólares.

    L-Ron-Hubbard

    ¿Qué es una secta?


    El diccionario de la RAE define secta como:

    1. Conjunto de seguidores de una parcialidad religiosa o ideológica.

    2. Doctrina religiosa o ideológica que se diferencia e independiza de otra.

    3. Conjunto de creyentes en una doctrina particular o de fieles de una religión que el hablante considera falsa.


    Estas definiciones no me gustan mucho puesto que son demasiado abiertas a cualquier cosa y no introduce el significado que para muchos significa. Para mí, la diferencia entre religión y secta tiene que ver claramente con el control que ejerce sobre el individuo. Así pues a la pregunta ¿Es la Iglesia Católica secta? debo de decir un rotundo no. La Iglesia Católica no ejerce prácticamente ningún control sobre sus miembros. Es cierto que hay sacramentos como el bautismo y el matrimonio pero no son comparables con los que una secta en condiciones debería hacer, controlar las cuentas bancarias de sus miembros por ejemplo.

    Las religiones son el mayor producto cultural de la humanidad, el más perfecto y muchas veces el más infravalorado. Una religión es una filosofía de vida.

    Funcionamiento de una secta


    Las sectas pueden tener muchos tipos de funcionamiento. Principalmente son fundadas por un líder carismático que ha tenido alguna revelación. Para conseguir atraer fieles se usan diversas técnicas. En el tríptico que poseo se dan 24 técnicas de manipulación que no voy a escribir pero las podeis leer aquí

    TripticoInterior

    Una buena manera de iniciar a alguien en una secta sería en una conferencia sobre un tema intrigante y misterioso (he visto carteles de sectas que hablaban de electrones y sus enseñanzas metafísicas), un DVD o un bonito libro de reflexión.

    LibroSecta

    Arquitectura


    Ya centrándonos más en el juego de Secta Sectarium surge la duda arquitectónica. No todas las sectas tienen campos de trabajo, aunque en el juego tendrán que tener para dar más interés al asunto. Un estilo arquitectónico que se adapta muy bien a estas cosas es el panóptico. Se trata de que todas las personas estén en todo momento vigiladas en su interior. Estos diseños han sido llevados con éxito a cárceles, escuelas y manicomios desde el siglo XIX. El libro Vigilar y Castigar aportará más información al respecto. La Isla de Hashima en Japón, aporta un diseño interesante para sectas marinas. Llamaremos a este concepto Isla Malvada

    Hashima

    Más inspiraciones


    Los Simpson, una de mis series favoritas tiene un episodio divertidísimo sobre los mvimientarios; una secta que se afinca en Springfield. El episodio se llama  The Joy of the Sect o La alegría de la secta en español.

    TheJoyOfTheSect

    La secta del banco Triodos Bank. Sí, ese banco ético fantástico se dedica a financiar su propia secta llamada la antroposofía, la cual también está basada en las geniales escuelas Waldorf que sacan tan buena calificación en PISA. Este tema es complejo y hay muy buena documentación en español en el blog de El Retorno de los Charlatanes.

    También podríamos hablar como inspiraciones las inofensivas agrupaciones como

    Otras fuentes que merece la pena ser mencionadas

    El Soneto de la Secta


    Admiraba yo a mi líder genial
    una persona sencilla, compleja
    que su nombre se te queda en la ceja
    no puede haber nadie más fenomenal

    Iremos a un planeta divertido
    donde animales música cantan
    y nuestros miedos humanos se espantan
    y nadie vuelve a hablar del temido

    Nuestro canto es de alabanza suprema
    somos guiados por la fe, la razón
    hablando tranquilamente del tema

    Progresamos rápido cual neutrón
    ¿Quieres venir? Coge la barca, rema
    Aquí te esperamos con ilusión

    -- Adrián Arroyo Calle

     ]]>
    https://blog.adrianistan.eu/secta-sectarium-documentacion-e-investigacion Sun, 8 Mar 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
    Prosperidad https://blog.adrianistan.eu/prosperidad https://blog.adrianistan.eu/prosperidad

    Hechos


    Empecemos por los hechos que hemos acumulado

    ¡Empieza la monetización!


    En 2013 ya había recibido algún dinero por PayPal pero 2014 ha sido el año de la monetización. El auge de BitCoin también contribuyó a ello y en consecuencia ahora veis bonitos anuncios de AdSense. No ha sido fácil llegar a AdSense por unos problemas que tenía con Google Sites y su API propia de AdSense. También he probado CoinURL.com, Adpv.com y Anonymous Ads. Las donaciones via PayPal también han crecido y tuve la oportunidad de probar un servicio llamado Tran.sl en el que te pagan por traducir textos.

    ¡Vamos a Turquía!


    En 2014 nuestro equipo de Orientación ganó el Campeonato de España de Orientación de Centros Escolares. Esto quiere decir que en 2015 competiremos en Turquía en el Campeonato del Mundo de Centros Escolares.


    Competiciones


    Este año participé en varios concursos/competiciones a parte del Campeonato de España de Orientación. A destacar la Olimpiada Matemática (donde pasé a la fase provincial), el Google Code-In 2013 (y 2014 que esta siendo ahora mismo) y el programa Ubuntu Pioneers (el cual fue una gran sorpresa).

    Aprendizaje continuo


    Este 2014 he aprendido nuevas tecnologías que creo que podrán tener alguna utilidad. Compré un Arduino UNO, use OpenShift y Heroku con Node.js, también eche un vistazo a TypeScript e hice alguna cosita en Rust. Mejoré mi destreza en Blender y pude hacer un pequeño juego completo en Ogre3D (Lumtumo). También fue el año de GameJS para hacer Norzarea.

    Norzarea

    MEME Manía


    La MEMEManía alcanzó un gran esplendor. Tanto tiempo perdido haciendo memes... Pero alguno hay bastante graciosete que aun hoy me hacen reír.

    Dogma42

    Cultura


    Blogs


    En 2013 cerró Google Reader y me pasé a Feedly. La verdad es poco a poco acumulaba algún que otro sitio interesante pero este año el crecimiento ha sido exponencial. Voy a recomendaros algunos:

    Libros


    Ahora toca recomendar libros. Creo que podemos empezar por un buen clásico: De Ratones y Hombres de John Steinbeck. Una lectura fácil y aparentemente inocente pero con mucho trasfondo.

    Si avanzamos más podemos mencionar El Quark y el Jaguar de Murray Gell-Man, un gran libro sobre física cuántica, pero que no os asuste el tema, el libro en general es bastante ameno.

    ElQuarkYElJaguar

    Si quieres hacer pasar un mal rato a vuestros enemigos recomendadle Paz en la Guerra de Miguel de Unamuno. Lo siento, pero es pesadísimo de leer. Sé que Unamuno está considerado un gran autor de la generación del 98 pero con esta obra no sé que pensar de él.

    MiguelDeUnamuno

    Un buen libro que seguro os dará algo que pensar es El Conde Lucanor de Juan Manual

    ¿Un libro de motivación? Quizá The Game Jam Survival Guide de Christer Kaitila, es cortito pero te pone las pilas en cuanto a empezar a hacer juegos.

    Videojuegos



    • Grim Fandango

    • Trópico 3

    • Half-Life 2

    • Portal


    GrimFandango

    Y como siempre, los clásicos de siempre:

    Objetivos


    Vamos a repasar los objetivos principales. Mi agenda se desbordó hace tiempo de cosas por hacer y ahora mismo tengo claro una cosa. Es mejor hacer pocos proyectos, hacerlos bien, que el público pueda disfrutar de ello que muchos proyectos en estado dudoso y sin utilidad práctica real.

    Así pues he intentado reducir los proyectos al mínimo y aunque parezcan vagos aquí, en mi cabeza son algo más tangibles:

    • Publicar más en el blog (típica mentira que se cuenta al estilo de voy a ir al gimnasio o este es el año de Linux en el escritorio)

    • Desarrollar más juegos para el 2015. El desafío #OneGameAMonth promete 12 en un año, pero ya os digo yo que con ciertos meses no espereis nada.

    • Colaborar más con Haiku y FreeRCT.

    • Ir finalizando proyectos antiguos abiertos. Esto sin duda va a ser lo más difícil de cumplir, lo dejamos como algo "a intentar".

    • Actualizar los complementos de Firefox y crear alguno nuevo. Ya han venido múltiples personas contactando conmigo con ideas, algunas de ellas interesantes.

    • Avanzar paso a paso hacia la excelencia. Para esto es importante el Círculo de Deeming y el feedback que recibo del público.


    Conclusión


    Creo que podemos sacar unos valores que debemos almacenar en nuestros cerebros. Unas frases célebres de esas que tanto gustan a la gente nos hablará

    • Si todos piensan igual, entonces nadie está pensando

      • George S. Patton o Winston Churchill, lo cierto es que no he encontrado el autor exacto pero tengo medianamente claro que fue alguien de la primera mitad del siglo XX. Esta frase se ha convertido en mi máxima y creo que no está de más recordarla



    • La vida es como montar en bicicleta. Para mantener el equilibro hay que seguir pedaleando

      • Atribuida a Albert Einstein, es una frase que refleja que no debemos quedarnos parados, siempre tenemos que tener metas, objetivos que alcanzar



    • Nunca discutas con un idiota, te rebajará a su nivel y te ganará por su experiencia

      • Dicha por Mark Twain. Conviene recordarla muy a menudo en estos tiempos.



    • Una mentira repetida adecuadamente mil veces se convierte en una verdad

      • De Joseph Goebbels. Tiene usos positivos y negativos, aunque...



    • No hay buenos o malos, solo hay puntos de vista

      • No sé si lo dijo alguien, pero esto también es importante



    • Piensa globalmente, actúa localmente

      • El mundo no es tu ciudad y tu país. El mundo es mucho más grande, si no pensamos en esa gente nos estamos perdiendo el gran pastel. Y luego decimos que el pastel es una mentira.



    • Publicar es una característica

    • Ponte objetivos cortos, nunca parés y alcanzarás grandes metas


    Finalizando


    Os deseo a todos vosotros un feliz año 2015 que sea todavía mejor que 2014

     ]]>
    https://blog.adrianistan.eu/prosperidad Fri, 26 Dec 2014 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
    La mística relación entre el 9N y Firefox https://blog.adrianistan.eu/la-mistica-relacion-entre-el-9n-y-firefox https://blog.adrianistan.eu/la-mistica-relacion-entre-el-9n-y-firefox Firefox se abrió ante mis ojos cuando vi esta valoración de mi complemento en el sitio de complementos de Firefox.

    9N-Firefox
    He intentado por tres golpes instalar este complemento, pero, como ya es fuerza normal, si no eres de MADRID ("Villa y Corte") no funciona. Puedes entrar a "Opciones", pero el identificador de municipio NO ADMITE un primer número que sea el "0". Por lo tanto, Barcelona, que es id 08019 , no funciona. SIEMPRE IGUAL. 9N. (Traducción hecha por Apertium)

    Os pongo en situación. El comentario en cuestión se refiere al complemento El Tiempo en España y la valoración anterior (solo había una antes) le daba 5 estrellas sobre 5. ¿Qué ha pasado? Resulta que cuando hice el complemento (es bastante viejo) use el control numérico que ofrece XUL y almacené la preferencia como un entero. Esto funcionaba con todos los códigos que había probado pero fallaba con los que empezaban con 0. ¿Por qué? La respuesta es muy sencilla y es que al estar en un control numérico al introducir un número a la izquierda este se nos quita tan rápidamente como introdujamos la siguiente cifra. El código de Barcelona empieza por 0 y claro, en esa ciudad no funcionaba el complemento. En eso le doy la razón al autor de la valoración. Pero de ahí a relacionar el que no funcione con un compot contra los catalanes por parte de Madrid me parece excesivo y un sin sentido. ¿Cómo alguien puede relacionar un bug con un complot político? (encima de un complemento que es open source)]]>
    https://blog.adrianistan.eu/la-mistica-relacion-entre-el-9n-y-firefox Wed, 8 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
    Probando Jekyll https://blog.adrianistan.eu/probando-jekyll https://blog.adrianistan.eu/probando-jekyll otro blog]]> https://blog.adrianistan.eu/probando-jekyll Tue, 5 Aug 2014 00:00:00 +0000 GAJSE, una carta de presentación https://blog.adrianistan.eu/gajse-una-carta-de-presentacion https://blog.adrianistan.eu/gajse-una-carta-de-presentacion https://blog.adrianistan.eu/gajse-una-carta-de-presentacion Mon, 28 Jul 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
    La Semana en Adrianistán (IV) https://blog.adrianistan.eu/la-semana-en-adrianistan-iv https://blog.adrianistan.eu/la-semana-en-adrianistan-iv
    • GAJSE ha añadido soporte para eventos cuando el usuario choque con un objeto.
    • DivCity ha mejorado en lo relativo al display de las Cells e incluye un nuevo concepto para modificar las Cells, las Tools.
    • DivPacker, un nuevo proyecto, que espero que sea simple y divertido
    • Tengo nueva página oficial. Mi nueva página pasa a estar disponible en http://adrianarroyocalle.github.io Basada en una temática de una ciudad, es muy colorida.

    ]]>
    https://blog.adrianistan.eu/la-semana-en-adrianistan-iv Tue, 17 Jun 2014 00:00:00 +0000
    La Semana en Adrianistán III https://blog.adrianistan.eu/la-semana-en-adrianistan-iii https://blog.adrianistan.eu/la-semana-en-adrianistan-iii ]]> https://blog.adrianistan.eu/la-semana-en-adrianistan-iii Mon, 9 Jun 2014 00:00:00 +0000 La semana en Adrianistán II https://blog.adrianistan.eu/la-semana-en-adrianistan-ii https://blog.adrianistan.eu/la-semana-en-adrianistan-ii
    Un sencillo juego que nos muestra al principio un número de 0 a 9 durante un segundo. Después empiezan a pasar los números en orden y tenemos que pulsar el botón cuando estemos en el número del principio. Si acertamos, lucecita verde, si no lucecita roja. Además he incluido sonido con el zumbador.

    También se han producido avances en DivCity que ahora luce un nuevo sistema de clases para los edificios. Poco visible por el momento, pero bastante código potencialmente útil. Además se ha añadido un curioso script que mejorará la distribución del TAR.GZ binario en Linux. Como medida de seguridad, ahora DivCity guarda todo el código de las librerías en las que depende. Ahora mismo estoy trabajando en el sistema para detectar los edificios seleccionados con el ratón.
    ]]>
    https://blog.adrianistan.eu/la-semana-en-adrianistan-ii Mon, 2 Jun 2014 00:00:00 +0000
    La semana en Adrianistán I https://blog.adrianistan.eu/la-semana-en-adrianistan-i https://blog.adrianistan.eu/la-semana-en-adrianistan-i El proyecto GAJSE se ha llevado la mayor parte de mis esfuerzos. He añadido a GAJSE las siguientes funcionalidades:
    •  Audio posicional. Basándome en Geometría Analítica básica y el Gain Node de la API WebAudio he conseguido crear un audio posicional que depende de donde estes situado. Funciona bastante bien.
    • Sistema de iluminación. No ha supuesto mucho esfuerzo pues simplemente llamamos a Three.js con los mismos parámetros
    • Sistema básico de personajes. Ya se permite la creación de personajes en una escena, pero faltan muchas cosas. Ha sido necesario para implementar lo siguiente:
    • Guiones hablados. Ahora ya se pueden hacer conversaciones basadas en ScriptedText. En este método las conversaciones son un callback de una función. Me ha parecido que era la más fácil de implementar y funciona bastante bien. Sin embargo para ello he tenido que modificar el módulo de mensajes y hacer uno basado en un Stack de mensajes (debido a que JavaScript es asíncrono). Todavía no se permiten elecciones basadas en el texto, pero trabajaré en ello.
    Y esta ha sido la primera semana en Adrianistán. La próxima semana contaré más novedades.

      ]]>
      https://blog.adrianistan.eu/la-semana-en-adrianistan-i Mon, 26 May 2014 00:00:00 +0000
      Introducción a la orientación https://blog.adrianistan.eu/introduccion-a-la-orientacion https://blog.adrianistan.eu/introduccion-a-la-orientacion

      ¿Qué es la Orientación?


      La Orientación (o deporte de orientación) es un deporte en el que varias personas deben llegar cuanto antes a  la meta pasando por una serie de puntos marcados. Estos puntos no están unidos entre sí y para llegar a ellos se te proporciona un mapa y una brújula. Los puntos por los que se debe pasar se llaman balizas y son como en la foto inferior.


      Una baliza suele ser de color naranja y lleva un número. Este número es el identificador de la baliza. Arriba tenemos dos sistemas diferentes de comprobar que has pasado por allí, una pinza roja que taladra un dibujo sobre el papel, y una baliza SportIdent, un sistema más moderno y usado que permite guardar tiempos y parciales. El número de balizas en una carrerar puede variar, pero suele haber más de 10. El mapa que nos dan en la salida (normalmente la carrear es cronometrada, se sale escalonadamente) está dibujado siguiendo unas normas. Aquí tenemos un trozo del tramo de una baliza en un mapa:

      El mapa que se nos da viene todo el terreno (bosque, pradera, ciudad, etc) por el que se corre. Este recorrido nos indica el camino para la baliza 10. He aquí una pequeña tabla de colores para empezar:



































      ColorSignificado
      BlancoBosque de penetrabilidad media
      VerdeBosque denso (hay niveles)
      NaranjaClaro, no hay bosque
      Líneas marronesNos indica la altura de una zona, son las curvas de nivel
      AzulAgua
      NegroPiedra o artificial
      MarrónTierra

      Estos colores tiene a su vez símbolos más concretos (la V es hoyo, puede ser negra o marrón; el "peine" es un cortado, puede ser negro o marrón).

      Suena bien, ¿dónde puedo ir?


      Para empezar deberías a ir a un club de orientación, allí te enseñarán y te llevarán a mapas reales, primero en categorías pequeñas, después mejorarás. Hay clubes por todo el mundo, pero es cierto que si vivieses en Finlandia conocerías más este deporte.]]>
      https://blog.adrianistan.eu/introduccion-a-la-orientacion Sun, 15 Dec 2013 00:00:00 +0000
      Territorios españoles olvidados https://blog.adrianistan.eu/territorios-espanoles-olvidados https://blog.adrianistan.eu/territorios-espanoles-olvidados
      • Parte de la Península Ibérica
      • Islas próximas a la parte de la Península Ibérica correspondiente.
      • Ceuta
      • Melilla
      • Archipiélago de Baleares
      • Archipiélago de Canarias
      • Islas Chafarinas (Isla del Congreso, Isla de Isabel II e Isla del Rey)
      • Isla de Alborán (e Isla de las Nubes)
      • Isla de Perejil
      • Islas Alhucemas (Isla de Tierra, Isla de Mar y Peñón de Alhucemas)
      • Peñón de Vélez de la Gomera
      • Isla de Guedes
      • Isla de los Pescadores
      • Isla de Ceas
      • Isla de Coroas
      • Llívias
      • Islas Salvajes
      • Isla de los Faisanes
      Es importante conservar estos territorios, sobre todo los que pequeños ya que son los más fáciles de atacar o dejar olvidadas. No incluyo en esta lista las islas próximas a la costa de la península pues a no ser una excepción todas deben pertenecer a España (Islas Cíes, etc). Tampoco incluyo las concretas de los archipiélagos de Baleares y Canarias ya que al ser grandes contienen muchos peñones e islotes similares.
      ]]>
      https://blog.adrianistan.eu/territorios-espanoles-olvidados Thu, 10 Oct 2013 00:00:00 +0000
      Esperanto, Interlengua y cía. https://blog.adrianistan.eu/esperanto-interlengua-y-cia https://blog.adrianistan.eu/esperanto-interlengua-y-cia
      1. Una lengua viva
      2. Una lengua artificial
      3. Una lengua muerta
      Analicemos punto por punto las opciones que presenta cada grupo.

      Una lengua viva
      Puede que esto suene la opción más práctica y viable pero es la que desataría más rencillas entre países. Cada país querría que su lengua, la que ya saben, sea impuesta sobre las demás y esas luchas no llevarían a ninguna parte. Si tenemos que dar ejemplos concretos, el inglés últimamente goza de esa superioridad, pero no nos engañemos, solo es por el dominio de los EEUU. Hace tiempo este mismo puesto lo ocupó el francés y mucho antes el español, el latín y el griego. En el futuro quizá el idioma más importante sea el ruso, el chino o el árabe. ¿Quién sabe? Sin embargo ningún país querría poner un idioma actual como idioma internacional.

      Una lengua artificial
      Los idiomas artificiales fueron diseñados para, entre otros propósitos, usarse internacionalmente. Son idiomas muy bien diseñados en los que prima su facilidad de aprendizaje y su falta de excepciones. Su principal problema es la falta de cultura y de hablantes. Entre estos idiomas encontramos el esperanto e interlengua. Ambos son idiomas muy interesantes.

      Una lengua muerta
      Una lengua muerta puede parecer una buena opción pues es conocida por muchos estudiosos de la materia y no beneficiaría a ningún país. El mayor rechazo estaría en que versiones de la lengua muerta habría que implantar. En este grupo puede entrar el latín y el griego.
      ]]>
      https://blog.adrianistan.eu/esperanto-interlengua-y-cia Mon, 7 Oct 2013 00:00:00 +0000
      Algunos proyectos open-source con los que colaboro https://blog.adrianistan.eu/algunos-proyectos-open-source-con-los-que-colaboro https://blog.adrianistan.eu/algunos-proyectos-open-source-con-los-que-colaboro
      • Haiku: El sistema operativo libre no basado en Linux ni UNIX sino en BeOS
      • FreeRCT: Un juego libre que intenta ser el clon de libre del famoso juego Rollercoaster Tycoon 2
      • Mozilla Firefox: No creo que tenga que explicarlo, aunque quizá este sea el proyecto open-source donde más gente colabora.
      Por supuesto también hay más proyectos pero en los que colaboro no tan regularmente y dicho esto me despido.
      ]]>
      https://blog.adrianistan.eu/algunos-proyectos-open-source-con-los-que-colaboro Mon, 7 Oct 2013 00:00:00 +0000
      Inicio del nuevo curso. Novedades interesantes https://blog.adrianistan.eu/inicio-del-nuevo-curso-novedades-interesantes https://blog.adrianistan.eu/inicio-del-nuevo-curso-novedades-interesantes
      • La Reflexión del día - Hablaré sobre un tema dando mi opinión
      • Kuriosidad con K - Hablaré sobre una curiosidad que me haya llamado la atención
      • Programación - Contaré como implementar algo en algún lenguaje de programación que me haya costado
      • Crítica - Hablaré sobre una película, libro o videojuego que haya podido disfrutar (o aborrecer) y daré mi opinión
      Además no se quedarán fuera otros artículos que me parezcan interesantes pero que no tengan una categoría. Y dicho esto me despido de vosotros para desearos un feliz día (si lo quereis lo tomais y si no, pues que se le va a hacer) y un hasta luego.
      ]]>
      https://blog.adrianistan.eu/inicio-del-nuevo-curso-novedades-interesantes Thu, 3 Oct 2013 00:00:00 +0000
      Azpazeta 2.0 Juno. Disponible a partir de hoy. https://blog.adrianistan.eu/azpazeta-2-0-juno-disponible-a-partir-de-hoy https://blog.adrianistan.eu/azpazeta-2-0-juno-disponible-a-partir-de-hoy

      Diseñado desde cero


      Este es el titular para Azpazeta 2.0 Juno que al contrario de lo que el número de versión sugiere es un juego completamente nuevo. Se ha diseñado teniendo en cuenta modularidad y que sea una plataforma para añadir rápidamente nuevas características; en este aspecto Azpazeta original fallaba bastante con un código relativamente desordenado. Otra característica importante es el rendimiento de Azpazeta que ha mejorado de una manera vertiginosa. Antes con la implementación de gráficos original, el muñeco se movía lento. Ahora y gracias a su renderizado por hardware OpenGL y GLSL es mucho más rápido. No tenemos tests pero la diferencia es abismal. Otra novedad importante es el sistema de mapas y scripts que unidos sirven para crear aventuras increíbles en el universo Azpazeta. Relacionado con lo anterior tenemos la tienda online de mapas, un servicio online gratuito que provee de mapas para descargar desde Azpazeta. Otro detalle importante es el formato de partidas que sufre un resiseño (no muy grande) para ofrecer mejores características. Quizá un detalle que pase desapercibido al principio es la ventana de opciones que incluye muchas opciones (algunas sin uso actual) que siempre se agradece. Un detalla también importante internamente pero difícil de ver es la división del juego en Cliente-Servidor lo que proveerá una jugabilidad más amplia en el futuro, aunque según se está viendo, puede ser causa de problemas al intentar jugar a Azpazeta, sobre todo en plataformas Windows.

      Y lo que llegará...


      Azpazeta 2.0 viene sin mapas, os recomiendo pasaros por Azpazeta Market para descargaros unos cuantos, si tienes una idea para hacer un mapa, coméntanosla, nosotros te asesoraremos de como hacerlo mejor y como subirlo a Azpazeta Market gratuitamente. Si quieres ayudar en la plataforma, también eres bien recibido, hay muchas tareas que implementar. Por último comentar que ya se han definido las novedades que traerá Azpazeta 2.1 LINK, entre ellas: mayor estabilidad, sonido y audio, implementación de policía y tiendas y pequeños mini-concursos. A esto se sumarán seguramente más novedades pequeñas igualmente agradecidas.

      A disfrutar


      Bueno y llegado el momento, los links de descarga. Azpazeta lo podreis encontrar en muchas webs, pero yo os recomiendo ir a la que hemos creado para el lanzamiento.
      ]]>
      https://blog.adrianistan.eu/azpazeta-2-0-juno-disponible-a-partir-de-hoy Wed, 25 Sep 2013 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
      Preparando el nuevo curso https://blog.adrianistan.eu/preparando-el-nuevo-curso https://blog.adrianistan.eu/preparando-el-nuevo-curso
      PD: Supuestamente este año me toca ser miembro del Consejo Escolar el instituto, veremos como sale todo... y de regalo el trailer de Azpazeta que ya se puede descargar si sabeis mirar:
      ]]>
      https://blog.adrianistan.eu/preparando-el-nuevo-curso Mon, 26 Aug 2013 00:00:00 +0000
      Calculadora de 4 Bits https://blog.adrianistan.eu/calculadora-de-4-bits https://blog.adrianistan.eu/calculadora-de-4-bits


      El funcionamiento es algo retorcido (faltan conversores de decimal a binario y viceversa). Primero ponemos los sumandos activando los interruptores de los sumandos que queremos sumar. Podemos activar 1,1,2,2,4,4,8 y 8. Pero hay que tener cuidado si sumamos más de 15, pues habrá un overflow un el circuito en cuestión será físicamente dañado. Una vez ajustados los interruptores presionamos el botón al lado de la pila de 2V y veremos el resultado en los LEDs. Para obtener el resultado deberemos sumar un valor diferente por cada LED encendido. En el siguiente orden: 1,2,4 y 8. Así podemos obtener hasta 15. Digo que este modelo es fácilmente extensible, pues podemos añadir más módulos como el de los 2 y los 4. Así aumentaremos la capacidad de procesamiento. Por cierto, dentro de poco vereis más novedades sobre mi videojuego.

      Planos originales: http://www.instructables.com/id/4-Bit-Binary-Adder-Mini-Calculator/step5/Building-It-On-A-Breadboard/]]>
      https://blog.adrianistan.eu/calculadora-de-4-bits Tue, 28 May 2013 00:00:00 +0000
      La Fuente Q y el problema sinóptico https://blog.adrianistan.eu/la-fuente-q-y-el-problema-sinoptico https://blog.adrianistan.eu/la-fuente-q-y-el-problema-sinoptico problema sinóptico. Quizá a muchos no os suene pero es un tema importante en la teología (habéis leído bien, la ciencia de las religiones).

      ¿Qué es el Problema Sinóptico?


      Como muchos sabéis en la religión católica hay 4 evangelios oficiales (correctamente llamados canónicos). Además existen los evangelios apócrifos que son aquellos que por su contenido no están en la Biblia. El problema sinóptico ocurre cuando 3 de los 4 evangelios son muy parecidos en algunas partes (usando los mismos párrafos algunas veces). Estos evangelios son los de Marcos, Mateo y Lucas. Esto tiene una explicación lógica pues el de Marcos fue primero y los de Mateo y Lucas se inspiraron en él. Se le llama la Fuente M. Sin embargo hay partes coincidentes entre Mateo y Lucas que no aparecen en Marcos (y son bastante abundantes). Se sabe que ni Mateo conoció a Lucas ni Lucas a Mateo. Este es el problema sinóptico que ocurre además con algunos evangelios apócrifos.

      La Teoría de las dos fuentes


      Hay una teoría muy aceptada que dice que tanto Marcos, como Lucas tuvieron acceso a una fuente hoy en día desconocida, esta fuente se la llama la Fuente Q (del alemán "queller", fuente). La Fuente Q explicaría las similitudes entre los diversos evangelios

      La Fuente Q


      Mientras la Fuente M se la conoce como la una lista de hechos y milagros de Jesús, la Fuente Q sería una recopilación de dichos de Jesús. Las bienaventuranzas y el Padre Nuestro solo aparecen por ejemplo en los evangelios con Fuente Q. Actualmente no se conoce ninguna referencia a la Fuente Q en la antigüedad por lo que ha sido criticada por expertos que simplemente dicen que no existe. Sin embargo, la teoría de las dos fuentes que explica la existencia de la Fuente Q es la más sencilla y como nos dice la navaja de Ocam (ya explicaré), lo más probable es que esta teoría sea cierta.]]>
      https://blog.adrianistan.eu/la-fuente-q-y-el-problema-sinoptico Thu, 16 May 2013 00:00:00 +0000
      Intercambio con Francia https://blog.adrianistan.eu/intercambio-con-francia https://blog.adrianistan.eu/intercambio-con-francia https://blog.adrianistan.eu/intercambio-con-francia Fri, 19 Apr 2013 00:00:00 +0000 OpenGL y su gran futuro https://blog.adrianistan.eu/opengl-y-su-gran-futuro https://blog.adrianistan.eu/opengl-y-su-gran-futuro

      GPU, Windows XP y Khronos


      Por el año 2000 salieron las primeras GPU y por entonces también salió Windows XP. También se formó el Khronos Group. Empecemos por Windows XP donde el soporte claro de Microsoft era DirectX. Con el gran lanzamiento de DirectX 9 una API que ya era superior a OpenGL se desarrollaron muchos juegos. OpenGL no evolucionaba y además solía ser más lento ya que si el fabricante no proporcionaba drivers de OpenGL (algo no obligatorio) este funcionaba por software en vez de por hardware. Entonces SGI y otras empresas fundan el Khronos Group que incorpora entre sus objetivos velar por mejorar OpenGL y otras tecnologías.

      Windows Vista, DirectX 10 y el iPhone


      La siguiente versión de DirectX 10 avanzaba y solo sería compatible con Windows Vista. Todo el mundo sabe que Windows Vista fracasó y por tanto DirectX 10 perdía fuelle y que hicieron los de OpenGL... Reescribir partes de la API para que fuese igual. Así pues OpenGL no lo aprovechó. Sin embargo las cosas cambian y ese día llegó cuando se lanzó el iPhone. No soy partidario de Apple pero el iPhone y todos sus gráficos funcionaban con OpenGL, concretamente con OpenGL ES(ES significa sistemas empotrados). OpenGL volvía a resurgir. Android también nació y también usó OpenGL ES. OpenGL volvía a ser importante.

      OpenGL ES 2, la revolución


      Entonces se vió que OpenGL ES 1 era un poco malo y lento y para ello el Khronos Group definió una nueva API completamente diferente, sin retrocompatibilidad y centrada en la potencia y simpleza. Nació OpenGL ES 2, la versión de OpenGL más innovadora del momento. Eliminaba partes típicas de la API y las reemplazaba por otros sistemas. Destacan los shaders tanto de vértices como de fragmento. OpenGL ES 2 además tiene muy pocas funciones por tanto la simpleza manda sin dejar de ser potente y profesional. OpenGL ES 2 se diseña como un subconjunto de OpenGL por tanto OpenGL también necesita una renovación. Será OpenGL 3 y 4 las grandes precursoras del cambio que dejan atrás rotaciones de matrices por vertex shaders. La ventaja de los shaders es que se compilan para la GPU y dejan más rendimiento para la CPU.

      WebGL y OpenGL ES 3


      Las especificaciones de HTML5 ya comtemplaban la etiqueta canvas que permite dibujar gráficos. Primero se introdujo el 2D, más tarde se pensó en el 3D y así surgió WebGL, una API gráfica para HTML/JavaScript. Se basa en OpenGL ES 2 y por ello hace uso extensivo de matrices de vértices y shaders. También surge OpenGL ES 3 que añade mejoras a OpenGL ES 2, sin embargo actualmente no ha sido muy adoptado debido a que es bastante reciente. Actualmente OpenGL es una gran API gráfica libre y multiplataforma que funciona sobre Linux, Mac, Windows (un poco mal, observad el proyecto ANGLE), Haiku, Android, iOS, Firefox OS, BlackBerry OS, Ubuntu Phone, PlayStation 3 y más. OpenGL tiene un gran futuro sobre todo cuando se implementen las otras especificaciones del Khronos Group como OpenCL, WebCL, OpenMAX, OpenWF, OpenML, OpenVG, COLLADA y más APIs que no son tan comunes pero que se integran muy bien con OpenGL.]]>
      https://blog.adrianistan.eu/opengl-y-su-gran-futuro Thu, 14 Mar 2013 00:00:00 +0000
      Los números aleatorios https://blog.adrianistan.eu/los-numeros-aleatorios https://blog.adrianistan.eu/los-numeros-aleatorios

      Aleatoriedad en el cerebro humano


      Este punto es interesante y es importante aclarar primero para evitar futuros errores. El cerebro no piensa bien aleatoriamente. Si tuviésemos una máquina de números aleatorios y los oyesemos veríamos que algunos se repetirían o creeríamos ver patrones. Esto es una falsa sensación de no-aleatoriedad. Esto ocurrió con el iPod, cuando la gente seleccionaba Reproducción aleatoria veían que algunas canciones se repetían y la gente se quejó de falta de aleatoriedad. Al final se modificó el algoritmo para que fuese menos aleatorio pero no los repitiese y la gente pensó que era más aleatorio

      Generar números aleatorios


      Estará pensando como generar números aleatorios con una máquina. Si usted ha pensado poco dirá que un algoritmo, si ha pensado más verá que necesitaremos valores de entrada diferentes para ese algoritmo y no se puede hacer aleatoriedad. Realmente hay 2 métodos pseudo-aleatorios. El primero se basa en mediciones de radiación, es normal que varíe bastante pero es muy lento, el segundo ampliamente usado en la computación es el tiempo. Cada milisegundo es un número que con unas cuentas puede ser muy diferente al del milisegundo anterior. Así se hace aleatoriedad en los ordenadores actualmente. Esto tiene un inconveniente y es que si viésemos todo eso algún día se repetiría y se verían patrones por tanto es imposible generar números realmente aleatorios. Existe investigación en el campo de la física cuántica y los ordenadores cuánticos pero todavía no ha dado sus frutos

      ¿Es el universo aleatorio?


      Podríamos pensar que sí, pero hemos dicho que nos ha resultado imposible hacerlo con ordenadores (ordenadores que son capaces de simular ciertas tareas del universo). Así pues, ¿existen los números aleatorios? Cuando nosotros pensamos un número ¿estamos obedeciendo un patrón?. Es posible pero también hay que pensar en el rango de ese patrón ya que no hay un límite de números en el universo porque los números son infinitos. Si los números son infinitos ¿como serían los números aleatorios reales? ¿Habría algún tope?

      Conclusión


      Los números aleatorios no existen en los ordenadores y es posible que tampoco en el universo. Pero esto plantea grandes dudas sobre nuestro futuro y si lo podremos predecir usando patrones. El tiempo dirá]]>
      https://blog.adrianistan.eu/los-numeros-aleatorios Mon, 11 Mar 2013 00:00:00 +0000
      Hablando sobre Google Code-in 2012 y proyectos https://blog.adrianistan.eu/hablando-sobre-google-code-in-2012-y-proyectos https://blog.adrianistan.eu/hablando-sobre-google-code-in-2012-y-proyectos https://blog.adrianistan.eu/hablando-sobre-google-code-in-2012-y-proyectos Wed, 6 Feb 2013 00:00:00 +0000 Los navegadores del 2012 https://blog.adrianistan.eu/los-navegadores-del-2012 https://blog.adrianistan.eu/los-navegadores-del-2012 Este artículo está escrito originalmente para el IES Zorrilla donde colaboro de vez en cuando

      Internet Explorer -- Microsoft -- HTML: Trident -- JavaScript: Chakra
      Internet Explorer es uno de los navegadores más importantes. Su éxito radica en que desde su primera versión, ha estado incluido en Windows (y este tenía el 98% de los ordenadores). Internet Explorer ha desarrollado su propia tecnología web con los controles ActiveX, Visual Basic Script y etiquetas HTML extrañas. Por eso tiene fama de extraño y apartado debido a que no cumple el estándar web y todas las webs se tienen que modificar para funcionar con él. Además no es multiplataforma por tanto sería imposible ver webs de Internet Explorer con Mac OSX o con Linux.
      SonrisaBuena integración con Windows
      TristeMonopoliza la web
      Mozilla Firefox -- Mozilla Foundation -- HTML: Gecko -- JavaScript: SpiderMonkey
      Mozilla Firefox es la continuación de el navegador Netscape. Funciona en Windows, Mac OSX, Linux, Android y tiene ports para sistemas como NetBSD, Solaris o Haiku. Además se está desarrollando un sistema operativo de smartphone basado en Firefox, Firefox OS, donde todo es una página web. Mozilla Firefox es famoso por su respeto a los estándares web y a su filosofía de mantener la privacidad del usuario. Además a sido pionera en tecnologías ya implantadas y cuenta con una capacidad de personalización increíble gracias a extensiones (tiene tienda oficial con todo gratis), compementos y temas. Actualmente se encuentran desarrollando las aplicaciones de Firefox que servirán para Firefox, Firefox para Android y para Firefox OS. Tiene fama de que gasta mucho, pero actualmente gasta muchísimo menos que antes y esta a igual nivel que Chrome
      SonrisaSiempre funcionará con estándares.
      TristePuede tener fallos en tecnologías propietarias
      Google Chrome -- Google -- HTML: WebKit -- JavaScript: V8
      El navegador de Google ha crecido muy rápido. Está disponible de manera oficial en Windows, Mac OSX y Linux. Además el navegador Chromium que es Chrome pero con código abierto (como Firefox). Tiene un motor de JavaScript muy rápido y usa muchos hilos. Esto provoca que parezca que consuma poco aunque no es así. Además hace que páginas muy pequeñas y muy sencillas tarden lo mismo que las grandes. Su capacidad de personalización es muy inferior pero cuenta con la Chrome Web Store. que proporciona apps.
      SonrisaMuy rápido en JavaScript
      TristePoca seguridad y privacidad
      Opera -- Opera -- HTML: Presto -- JavaScript: Carakan
      Un desconocido en España pero muy usado en otros países. También usa estándares es también usado en móviles y es el navegador de la Wii y la Nintendo DSi. Poca capacidad de personalización, pero líder en algunas cosas.
      SonrisaAlternativa a los otros navegadores
      TristeSe usa muy poco
      Safari -- Apple -- Motor: WebKit -- JavaScript: Nitro
      Desarrollado por Apple se lleva muy bien con Mac OSX e iOS. Usa el mismo motor de Chrome (realmente lo creo Safari). En Windows tiene resultados pésimos y no hay versión de Linux. Se lleva bien-mal con los estándares porque algunas cosas acepta y otras no.
      SonrisaEl mejor navegador para Mac OSX
      TristeMuy cerrado y algo retrasado en estándares
      El resto
      Estos son los 5 navegadores principales, ahora bien, existen muchos más navegadores, sobre todo derivados de estos 5 y alguno independiente.
      Konqueror -- KHTML -- KJS
      Epiphany -- WebKit y Gecko -- ?
      Midori -- WebKit -- ?
      Maxthlon -- Trident y WebKit -- ?
      Así pues decídete por el que creas conveniente, yo uso Mozilla Firefox ¿y tú?
      ]]>
      https://blog.adrianistan.eu/los-navegadores-del-2012 Sat, 29 Dec 2012 00:00:00 +0000
      Consejo Escolar https://blog.adrianistan.eu/consejo-escolar https://blog.adrianistan.eu/consejo-escolar  
      Consejo escolar es el nombre que en distintos sistemas educativos se da a una institución colegiada cuyas funciones se extienden al control de la gestión de los centros escolares, teniendo su mayor nivel decisiorio en cuestiones no estrictamente docentes.

      En mi instituto se celebraban elecciones este año y decidí presentarme. Fui a secretaría y allí, con bastante desorden consiguieron que pudiese participar. Más tarde vi los candidatos, estos eran 3 (conmigo). No les conocía, pero enseguida conocí al primero, vino a ver las listas al mismo tiempo que yo. El otro no le conocí y sigo sin conocerle. Doy la noticia y hablo con profesores y alumnos (una especie de campaña) y llega el día de la verdad, día 19 de Noviembre. A primera hora entregan papeletas por las clases (sin el papel, solo el sobre por una falta de organización tremenda). A mi clase nos toca votar sobre las 11:45. Se pueden elegir 2 candidatos (pues hay dos plazas libres) y somos 3. La mayoría de la gente vota a 2 personas y todos a los que conozco me votan. Hoy ya han sido contados los votos y veo una cosa bastante...296 votos lo que quiere decir que no solo no soy elegido sino que el que no conozco (y nadie por lo visto) ¡me ha ganado! La verdad es que es bastante igualado pero no me he quedado satisfecho con el resultado aunque sea suplente. Dentro de 2 años me volveré a presentar...]]>
      https://blog.adrianistan.eu/consejo-escolar Tue, 20 Nov 2012 00:00:00 +0000
      Presentación https://blog.adrianistan.eu/presentacion https://blog.adrianistan.eu/presentacion https://blog.adrianistan.eu/presentacion Thu, 25 Oct 2012 00:00:00 +0000 FAQs https://blog.adrianistan.eu/faqs https://blog.adrianistan.eu/faqs En esta sección respondo a las preguntas frecuentes (y no tan frecuentes) que surgen respecto al blog.

      ¿Qué es esto?

      ¡Adrianistán! Se trata del blog en español donde me dedico a hablar de cosas poco interesantes pero que me entretienen. Principalmente de programación, pero también hay algo de filosofía, física y República de Adrianistán.

      ¿Quién eres?

      Soy Adrián Arroyo Calle, soy de Valladolid (Castilla). Actualmente estudio Ingeniería Informática en la Universidad de Valladolid. Creo que el mundo es impresionante y me gustaría conocer todo sobre él. Me gusta mucho viajar, conocer datos estúpidos

      ¿Cuál es tu lenguaje de programación favorito?

      La verdad es que no tengo un lenguaje favorito, aunque ahora mismo el lenguaje en el que soy más productivo es JavaScript. También he programado en Java, Python, Rust, C, C#, SQL, C++ (hace tiempo) y siempre trato de ir aprendiendo cosas nuevas (Haskell, Prolog).

      ¿Dónde trabajas?

      Actualmente estudio Ingeniería Informática en la UVa. También soy miembro de la asociación BEST Valladolid.

      ¿Tienes más blogs?

      Sí, también escribo en Phaser.js Hispano, aunque no escribo tanto allí

      ¿Qué música te gusta?

      La verdad es que me gusta la música en general, sin demasiadas distinciones. Sin embargo, si tengo que elegir me quedaría con mis bandas sonoras: Ennio Morricone, Vangelis, Alexandre Desplat,  Yann Tiersen, etc (John Williams no). También me gusta bastante Mike Oldfield y el género del Latin Jazz. Pero en mi biblioteca musical hay de todo: Nino Bravo, Nina Simone, Nach, Paco Pil, Philip Glass, Pink Floyd, Bach, Johnny Cash, Soleá Morente, ... Muchas veces depende del momento.

      ¿Película preferida?

      Algunas de mis películas favoritas son: El Sentido de la Vida de los Monty Python, La Naranja Mecánica, Intocable, The Royal Tenenbaums, ¡Bienvenido Mr. Marshall! y Qué bello es vivir. También fui jurado joven del festival de cine de Valladolid, la SEMINCI.

      ¿Vim o Emacs?

      Vim claramente (¿o acaso tenéis 20 dedos en cada mano?). Aunque suelo programar en Visual Studio Code.

      ¿Windows o Linux?

      Suelo trabajar con Linux (concretamente con Debian) porque me gusta más, pero he de reconocer que Windows no es tan mal sistema operativo.

      ¿Qué escritorio usas en Linux?

      GNOME 3, con algunas extensiones por defecto. Lo considero uno de los escritorios más productivos que existen actualmente y con buen diseño.

      ¿Espacios o tabuladores?

      Tabuladores que en el editor se convierten a espacios

      ¿En quién te inspiras?

      Aunque intento seguir mi propio camino, he de decir que hay ciertos personajes que me han inspirado durante mi vida. Richard Feynman, Bertrand Russell, mis compañeros de BEST Valladolid, ...

      ¿Puedo publicar en Adrianistán?

      Sí, claro, cualquiera puede aportar. Si eres una empresa, tendrás que pagar. Contacta conmigo para más detalles.

      ¿Puedo anunciarme en Adrianistán?

      Sí por supuesto. Contacta conmigo y según el tamaño/tipo del anuncio veremos el precio.

      ¿Puedes trabajar para mí?

      Actualmente estudio y actualmente no estoy buscando trabajo

      ¿Qué fue de Divel?

      Fue como empecé con la programación, mi empresa. Actualmente ya no uso más ese pseudónimo para publicar mis programas.

      ¿Qué pasó con NextDivel?

      El objetivo de NextDivel era ver hasta donde podía llegar programando un sistema operativo de cero. Ahora que he estudiado más el funcionamiento de los sistemas operativos solo puedo decir que NextDivel hace demasiadas pocas cosas y las que hace, no las hace bien del todo.

      ¿Cada cuánto publicas?

      En Adrianistán no hay un horario fijo de publicaciones. A veces hay publicaciones muy seguidas unas de otras y luego un largo tiempo sin ellas. Realmente todo depende de si tengo algo interesante que contar y de si tengo tiempo y ganas para ello.

      ¿Cuándo vas a hacer un post sobre X?

      Cuando sea interesante para mí poder contaros algo sobre X. No suelo seguir demasiado las modas tecnológicas, así que el blog estará condenado siempre a recibir menos visitas que otros.

      ¿Podemos quedar y charlar?

      Sí, claro. Vivo en Valladolid y normalmente puedo quedar. Una meta que me puse hace tiempo era la de conocer cada día a una persona distinta, algo que nunca ha ocurrido, pero por algo se les llama metas. Si hablas inglés bien, mejor, así puedo practicar.

      ¡Tengo más preguntas!

      ¡Bien! Usa el formulario de contacto y hablamos.

      ]]>
      https://blog.adrianistan.eu/faqs Sat, 1 Jan 2000 00:00:00 +0000