Categoría: Programación

  • Es posible especificarle al comando sort la columna por la que se quiere ordenar un archivo, en vez de utilizar el criterio por defecto, el de ordenar al fabéticamente por el contenido de toda la fila. Esto resulta útil, por ejemplo, para ordenar archivos de log tabulares por una columna concreta que no sea la primera. Hay que usar la opción -k, que tiene un comportamiento similar a la opción -f de cut cuando se delimita por espacio.

    cat prueba.txt
    3 miércoles
    1 lunes
    2 martes
    4 jueves
    
    sort prueba.txt
    1 lunes
    2 martes
    3 miércoles
    4 jueves
    
    sort -k2 prueba.txt
    4 jueves
    1 lunes
    2 martes
    3 miércoles
  • Super buenos consejos para escribir código Elixir más manejable.

    Las funciones con side-effects suelen devolver resultados tipo {:ok, term()} | {:error, term()}. Si estás trabajando con funciones que tienen side-effects, no las conectes con la siguiente función con un pipe. Es mejor tratar los resultados con un case o con un with.

    Super de acuerdo en esto. Recuerdo que hace no demasiado me pareció «superapropiado» hacer un fmap monádico en Elixir para poder escribir luego código fancy a lo Haskell:

    # Just don't. Not a very smart idea despite what it looks like
    def fmap({:ok, x}, f), do: {:ok, f.(x)}
    def fmap(whatever, _), do: whatever
    
    def readline(path),
      do:
        File.read(path)
        |> fmap(&String.split(&1, "\n"))
        |> fmap(&Enum.filter(&1, fn line -> line != "" end))

    Pero desde que conozco el with, no sólo no me parece necesario sino que oh dios mío por qué pude pensar que esto era buena idea. En caso de que deje de cumplirse con la secuencia de condiciones, with evalúa el bloque completo al valor devuelto por la primera función que salió mal, dejandolo todo más limpio.

    def readline(path),
      do:
        with {:ok, contents} <- File.read(path),
             {:ok, lines} <- String.split(contents, "\n"),
             {:ok, trimmed} <- Enum.filter(lines, fn line -> line != "" end) do
          trimmed
        end

    Supongo que no será la única epifanía ocurrida en este mundo al conocer la palabra clave with.

  • Al hilo del programa que escribí hace poco para visualizar el fractal de Mandelbrot, puede resultar interesante a simple vista que haya escogido Racket como lenguaje de programación. Fundamentalmente lo elegí porque quería hacer programación visual, y en Racket esto resulta fácil de hacer desde su GUI de forma interactiva. Se puede hacer un (require racket/draw), y ya se pueden usar funciones para dibujar sobre mapas de bits que hasta se pueden visualizar dentro del propio DrRacket. Tengo un curso de Racket en mi canal de YouTube, pero está a medias.

    Racket es una implementación de Scheme, pero existen muchas otras. GNU Emacs es conocido por tener una implementación de Scheme (Emacs Lisp), con la que se pueden hacer hasta plugins; como una alternativa al Vimscript. También se puede activar el modo scratch y programar en Emacs Lisp desde dentro de Emacs, para darle más lore al meme de que GNU Emacs es un propio sistema operativo dentro de otro.

    Sin embargo, GNU tiene otra implementación alternativa de Scheme llamada GNU Guile, que también tiene características interesantes. Guile es como Lua. Se puede incrustar soporte para Guile en otro programa y utilizarlo como una herramienta para fabricar plugins o extensiones. Se puede pedir a código C que interactúe con Guile.

    Además, también Guile tiene un modo independiente para usarlo como una herramienta de línea de comandos más, e incluso para interpretar programas escritos en archivos de código fuente. Lo normal es que tengan la extension .scm (aunque, como siempre, en un entorno GNU esto suele dar igual), y que se invoque GNU Guile en este caso con guile -s codigo.scm.

    ~ $ guile
    GNU Guile 2.2.4
    Copyright (C) 1995-2017 Free Software Foundation, Inc.
    
    Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
    This program is free software, and you are welcome to redistribute it
    under certain conditions; type `,show c' for details.
    
    Enter `,help' for help.
    scheme@(guile-user)> (+ 2 2)
    $1 = 4
    scheme@(guile-user)>

    Existen algunas cosas que me gustan de GNU Guile viniendo de Racket. La mayoría de palabras clave funcionan igual. En GNU Guile existe la primitiva define y la primitiva let. No tienen nombres especiales o diferentes. También existen algunas carencias en GNU Guile, pero son fáciles de subsanar. Por ejemplo, muchas de las funciones para trabajar con listas (foldl o reduce) están declaradas en la librería SRFI-1, por lo que tienen que ser importadas antes de poder usarse con un (use-modules (srfi srfi-1)).

    ~ $ guile
    GNU Guile 2.2.4
    Copyright (C) 1995-2017 Free Software Foundation, Inc.
    
    Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
    This program is free software, and you are welcome to redistribute it
    under certain conditions; type `,show c' for details.
    
    Enter `,help' for help.
    scheme@(guile-user)> (use-modules (srfi srfi-1))
    scheme@(guile-user)> (reduce (lambda (x a) (+ x a)) 0 '(1 2 3 4 5))
    $1 = 15
  • Ayer cerré un stream en Twitch en el que intenté programar la típica representación gráfica del conjunto de Mandelbrot. No lo terminé porque se me alargó, pero lo continuaré. Dejo aquí algunas notas para cuando haga la segunda parte.

    El conjunto de Mandelbrot es el conjunto de números complejos donde se cumple que la evolución de la serie definida por la función f(c, n) = f(c, n-1)^2 + c, siendo f(c, 0) = 0, no tiende al infinito. Por ejemplo, para c=1 se obtiene la serie 0,1,2,5,26…, no acotada, pero para c=-1 se obtiene la serie 0,-1,0,-1,…, que sí está acotada. (Por supuesto, c=1 y c=-1 son ejemplos muy simples, pero esta fórmula se usará con números con parte imaginaria como 0.2265+0.331i.)

    Para facilitar las cosas, lo normal es asumir que si la magnitud del número complejo supera en algún punto de la serie el valor 4, entonces con seguridad no se acota. Como no podemos pedirle al ordenador precisión infinita, si después de un número máximo de iteraciones sigue sin tender al infinito, podemos asumir que sí se acota.

    O sea, que al final en un programa de ordenador repetiremos la función hasta que se superen 50, 100, 1000 iteraciones (lo cual nos diría que está acotada), o hasta obtener algún valor con un absoluto mayor a 4, lo que nos deja interrumpir la ejecución asumiendo que no se acota. Cuantas más iteraciones apliquemos, más precisa será la evaluación, ya que puede ocurrir que una serie para un complejo tarde más tiempo en divergir, aunque también tomará más tiempo.

    En cuanto a la clásica imagen del fractal generado a partir del conjunto de Mandelbrot, que seguramente muchos habremos visto alguna vez, lo que vemos es la representación en un sistema de coordenadas 2D del valor de esta función para todos los números complejos. El eje X representa la parte real del complejo y el eje Y, la parte imaginaria.

    En el programa de ordenador generaríamos la imagen transformando del sistema de coordenadas de la imagen (por ejemplo píxeles del (0,0) al (640,480)) a una interpolación más aceptable en el rango de los complejos que vayamos a comprobar (como (-1,-1) a (1,1), aunque podríamos reducir el área para hacer zoom), y luego consultando si ese número complejo está en el conjunto o no. Si está en el conjunto, lo podemos representar de negro. Si no está en el conjunto, lo típico es crear algún tipo de paleta de colores para representar con un color diferente aquellos complejos que escapen antes al infinito de aquellos que escapan más tarde.

     Representación del conjunto de Mandelbrot
    La clásica foto del conjunto de Mandelbrot tomada de Wikipedia.
  • Una de las cosas bonitas de la GDPR es que casi obliga a las redes sociales y sitios similares a tener un sistema de exportado de datos. Y casi siempre lo hacen con algún formato estructurado como CSV, JSON o XML, fácil de procesar por un ordenador.

    Paralelamente, WordPress tiene el viejo confiable XML-RPC para crear posts de forma programática. Si no tienes mucha idea, recomiendan cerrar ese endpoint al exterior para evitar problemas. Pero en verdad, si sabes protegerlo, resulta muy práctico para crear posts a golpe de petición HTTP. (En teoría existe la API REST, pero bueno…)

    Con la ayuda de un cliente XML-RPC para WordPress que hay en NPM y de la API de Hacker News, hice en Node.js un script de un solo uso que crease una entrada por cada comentario y enlace enviado para enlazar hacia el comentario o la historia, siguiendo el modelo de sindicación PESOS de IndieWeb (Publish Elsewhere, Syndicate to Own Site). Le he puesto la etiqueta hn-import a la colección. ¿Cuál será el próximo archivo que importe?

    En sí la librería no es muy complicada de utilizar. Una vez tienes un cliente conectado es muy fácil chutarle un array de posts a crear (por ejemplo, procesar fila a fila un CSV o item a item un JSON o un XML). Primero se crea un cliente:

    const { createClient } = require("wordpress")
    
    const client = createClient({
      url: "https://example.com",
      username: "mi user",
      password: "mi password"
    })

    Luego definiendo el payload. Aquí es donde encuentro más cómodo crear un objeto JSON que declarar todo el chorizo XML de XML-RPC:

    const payload = {
      title: "El título de mi entrada",
      content: `
        <p>El contenido de mi entrada.</p>
        <blockquote>Aceptamos HTML.</blockquote>
        <p><a href="https://www.example.com">Y enlaces</a></p>
      `.trim(),
      status: 'publish', // podría ser 'draft' o 'private'
      termNames: {
        category: ['Categoría'],
        post_tag: ['tag 1', 'tag 2', 'tag 3'],
      },
      date: '2006-01-02 15:04:05',
      format: 'link' // podría ser 'aside', 'status', 'photo', ...
    }

    Y ya postear usando newPost. La librería es previa a promesas así que su único punto malo es que está orientada a callbacks.

    client.newPost(payload, (err, id) => {
      if (err) {
        console.error(err)
      } else {
        console.log(`ID del nuevo post: ${id}`)
      }
    })
  • En la mayoría de mis repositorios Git, correr git branch suele suponer abrir un cubo de basura bien grande. Cuando una rama de Git desaparece en el remoto (por ejemplo, en GitHub cuando se borra desde la interfaz web automáticamente), luego te tienes que acordar en local de borrar también tu rama. De lo contrario, vas a acabar con ramas stale que son aquellas de las que se hizo git push para abrir PR y que quedan ahí.

    Una forma de identificar estas ramas es hacer un git fetch --prune, manteniendo ese prune para que se ocupe de detectar qué ramas han desaparecido del remoto, seguido de git branch -vv | grep gone. En el modo verbose de branch, las ramas locales que hacen tracking de un remoto que ya no está se identifican porque aparece [gone] en su línea de terminal. Por lo que esta pipeline lista únicamente esas ramas locales que han desaparecido del repositorio remoto.

    Cortando la primera columna (mejor con awk '{ print $1 }' aunque con cut también se pueda hacer), puedes listar únicamente los nombres de las ramas. Y si estás de acuerdo con la salida de git branch -vv | grep gone | awk '{ print $1 }', (y sólo si estás de acuerdo, porque ya sabes, no refunds), entonces puedes envolver todo en un git branch -D $(git branch -vv | grep gone | awk '{ print $1 }') para cargarte todas esas ramas de un plumazo.

  • Compartí por el Discord de mi comunidad de YouTube mi post del otro día en el que compartía un atajo para copiar y pegar de Vim al portapapeles. Aparentemente no a todo el mundo le funcionó a la primera.

    Para poder utilizar compartir el portapapeles, Vim tiene que haber sido compilado con soporte para el mismo. Esto se puede saber ejecutando vim --version y comprobando que en la salida del comando aparece la opción +clipboard (tiene que ser un +, porque un -clipoboard precisamente avisa que no). Las versiones de Vim que trae Homebrew en macOS lo suelen tener. En otros UNIX y en GNU/Linux, es posible que haya que cambiar el paquete vim por vim-x11 o por una versión de Vim más completa.

    Probando en mi instalación de FreeBSD con X11, funciona tanto para el portapapeles CLIPBOARD ("+) como para el portapapeles PRIMARY (el del botón central del ratón, "*).

  • Recientemente en Ruby on Rails incorporaron un cambio que genera un aviso al arrancar la aplicación en modo producción si el driver de base de datos que está configurado es el de SQLite. El aviso se puede desactivar cambiando un flag de configuración en el production.rb para confirmar que no es un accidente, sino una decisión.

    You are running SQLite in production, this is generally not recommended.

    Debo romper una lanza a favor de SQLite. Tiene sus issues, claro está, pero muchos de ellos pueden entenderse si se lee la documentación técnica.

    Por ejemplo, si nunca te has leído su guía sobre tipos de datos y afinidad de columnas probablemente te sorprenda lo blando que es para validar los tipos de datos durante el INSERT (puedes guardar enteros en columnas creadas como VARCHAR, y viceversa).

    No vale para todo, pero lo he visto muchas veces en producción en circunstancias en las que tiene sentido que esté en producción, y si está justificado, seguiré defendiéndolo con fuerza como alternativa ligera.

  • Pues esta es fácil pero como todo en Git nunca aparente. Va a pasar cuando haga cambios al Gemfile en dos ramas a la vez.

    git checkout HEAD -- Gemfile.lock

    Esto es para traerme el Gemfile.lock que había antes de obtener mi conflicto. Lo importante es que podemos confirmar con un git status que se preserva el Gemfile, así que los cambios en las versiones o librerías siguen ahí.

    Si he metido o quitado una dependencia del Gemfile, ahora ejecuto bundle para que se descargue o se retire del lock por encima de la última versión que había en la rama en la que estoy intentando hacer el merge.

    Si he actualizado la versión de una dependencia del Gemfile, ahora ejecuto bundle update para que se vuelva a reflejar ese cambio en el lock por encima de la última versión que había en la rama en la que estoy intentando hacer el merge.

  • El registro de Vim + (PLUS) interactúa con el portapapeles del sistema operativo (el que te permite luego hacer Ctrl-V en otra aplicación).

    Si hago un yank en Vim poniendo "+ primero (por ejemplo, "+yy para copiar una línea o una selección visual), luego puedo pegarlo en otra aplicación gráfica de macOS con Cmd+V. Es bidireccional, así que puedo pegar del portapapeles del sistema si he copiado de otra app con "+p.

    Hasta ahora utilizaba el modo selección y luego ejecutaba !pbcopy para enviarle mi buffer a pbcopy (un comando de terminal de macOS que envía su stdin al portapapeles), pero esto es más cómodo y no me borra el buffer.