Facundo Batista: Enseñando Python

El último finde terminó el segundo Seminario de Python armado con Onapsis.

El primero había sido hace dos años, muy introductorio, para alrededor de cuarenta asistentes.

El que acabo de completar, durante cuatro sábados de Junio, tuvo un contenido más avanzado. También fue en oficinas de Onapsis, pero en las nuevas, porque justo se mudaron hace un par de meses. Y fue para más de sesenta personas, lo cual ya per se es un desafío.

La grupal, el último día

Aunque había más gente, estaba más cómodo que la vez pasada porque al ser un espacio más grande, permitía moverme mejor. Un factor clave: ¡di todo el curso parado! Es crítico para futuros casos similares conseguirme una mesita alta para poder usar la laptop parado, esta vez funcionó de maravillas.

Y además el contenido en si del curso fue más interesante, porque fueron temas que en general no doy en los cursos, y más desafiante porque otros temas los recorrí con una mayor profundidad. Esto, sumado a que los tiempos eran super justos, me obligó a preparar bien las clases y ejecutarlas sin mucho margen.

Pero salieron bien, lo cual me deja muy contento. Un experimento exitoso.

Por otro lado, para seguir cambiando cosas e intentar cosas nuevas, estamos armando con Leandro la idea de unos cursos no sólo teóricos sino también con una parte práctica importante.

Vamos a ver si hacemos un par básicos, y si nos gusta y funciona, podemos seguir con temas más avanzados. O ir cambiando horarios, o días, o la duración del curso en sí.

Queremos ir probando distintas alternativas, pero para entender mejor qué quieren les posibles asistentes, armamos esta encuesta, no dejes de llenarla si te interesaría asistir.

Facundo Batista: Python Argentina, actualidad y futuro

A nivel PyAr estamos teniendo un presente movidito. Venimos de un meetup en Trocafone y tenemos un PyDay en Resistencia (Chaco) a fin de mes.

El meetup en Trocafone estuvo bárbaro. Fue diferente a los que veníamos armando de un par de charlas y algo social tipo after office en la semana: lo que hicimos fue armar una mañana de charlas, y luego sprint/taller, o al menos esa era la idea.

En la práctica, los meetups son más relajados que un PyDay o conferencia más grande, y nos permite acomodarnos a lo que va saliendo. Las charlas iniciales estuvieron muy bien, después hablé un poco de los proyectos de la tarde, comimos la picada bárbara cortesía de Trocafone, y después jugamos dos veces a eso que llamo "coding dojo rotativo rápido", y aprendimos cómo hacer bots de Telegram con Python de forma super sencilla.

A nivel futuro estamos planeando un meetup en Rosario (algún día de semana, primeros días de Agosto? ¡tenemos que definir!), y luego va a realizarse un PyDay también en esa ciudad, con fecha a confirmar.

Y ya tenemos definidas las fechas para la PyCon Argentina 2019: va a ser el 4, 5, y 6 de Diciembre (en el Centro Cultural General San Martín, CABA, como el año pasado).

Asociación Civil, próximos meses

Específicamente hablando de la Asociación Civil Python Argentina, tengo planes armados para los próximos meses que presenté en una reunión para Comisión Directiva y amigues hace unas semanas en oficinas de Satellogic.

Los punteo a continuación, pero tampoco entro en demasiado detalle para no aburrir. Sepan que si quieren más info de cualquiera de estos temas pueden preguntarme directamente o a través del grupo de Telegram de Amigues de la AC.

Terminar bootstrap de la Asociación Civil a nivel infraestructura

  • Sistema de socies, tenerlo 100% funcional: para ello tenemos que terminar de cerrar estos issues, todo lo que necesitamos está cargado ahí... no falta tanto, avanzamos mucho los últimos meses, laburando yo solo, o con mucha ayuda en el último PyCamp, y gracias a Gilgamezh por todo lo que es infraestructura ya lo tenemos en producción (ejemplo: les nueves socies pueden anotarse acá).
  • Sistema de eventos completo: este no lo estamos haciendo a pulmón porque lo necesitamos más o menos rápido (es más, ya deberíamos estar empezándolo a usar), sino que se lo encargamos a Devecoop, cooperativa amiga (y socia benefactora) que nos hizo precio con el laburo. Vamos a ver cuanto llegamos a hacer, pero está tomando cuerpo de forma genial, y a lo sumo nos quedarán algunos detalles a terminar a futuro.
Gestión de Eventos

Que la AC haga break even: para esto básicamente tenemos que tener una entrada mensual mayor a los gastos fijos que tenemos (abogado, contador, gastos bancarios, gastos operativos, etc.); para lograr esto queremos hacer foco en:

  • Juntar socies humanes y de organizaciones benefactoras: para la primera parte vamos por todos lados contando sobre la asociación y bajamos la barrera de entrada con el sistema de socies; para la segunda parte ya me estuve moviendo bastante antes de lanzar la PyCon, y pasamos de dos socias benefactoras a seis, lo cual casi da por cumplido ese objetivo (si conseguimos alguna más en el 2019 mejor, pero para el año estamos hechos).
  • Al menos dos entradas por eventos PyAr y por eventos externos: esto nos da una pequeña entrada de guita que ayuda al movimiento de la Asociación Civil, y aunque no es grande, pega directamente en el propósito de la asociación: ayudar a que se hagan eventos.
  • La PyCon (Argentina) nos tiene que dar un extra importante, aunque no la deberíamos contemplar como guita para el movimiento operativo de la asociación: por un lado nos debería "blindar" para la próxima conferencia grande, y por el otro tenemos que usar ese extra para consolidar infraestructura del grupo (comprar proyector, micrófonos, etc...).

Terminar bootstrap de la AC a nivel legal

  • Tener exención de ganancias: esto ya lo ejecuté hace algunas semanas, y es un ejemplo de lo que decía arriba de gastos operativos que necesitamos afrontar en el día a día: solamente las copias autenticadas de papeles internos para presentar en la AFIP costaron $1500. Sí, una luca y media :/.
  • Tener los papeles internos normalizados: para esto vamos a armar una o dos reuniones en el corto plazo donde nos vamos a sentar a llenar "libros legales", que es algo que tenemos medio (bastante) atrasado.
  • Tener acceso del contador a sistemas bancarios: tenemos que ponernos con Leandro a toquetear permisos de plataformas...
Libros, papeles, presentaciones

Fortalecer/renovar/modernizar infraestructura del grupo

  • Tener una forma primaria de comunicación asincrónica moderna, mantenida: vamos a cambiar la "lista de correo pura" que tenemos hoy por algo más nuevo; esto puede ser ir a una "lista de correo con foro decente", como nos brindaría mailman3, o "un foro que igual el que quiera lo pueda manejar como lista de correo", como Discourse, o algo por el estilo. En esto está Gilgamezh, más que nada supervisando/participando en una discusión similar que está teniendo la PSF sobre esto mismo... ellos le están poniendo mucho análisis, así que lo más probable es que nosotros "reutilicemos sus conclusiones" y vayamos para el mismo lado.
  • Imagen más moderna y pensada: vamos a cambiar el logo! sí, después de 15 años, vamos a ir a un logo pensado/armado por un diseñador :) ... ¡y es una buena oportunidad para rehacer la bandera!
  • Tener un sistema de eventos que sirva para pydays/pycon/meetups: esto es Eventol, servido por nosotros, el cual ya funciona para pydays/pycon, pero faltan hacerle detalles y un cambio un poco más grande para soportar meetups o juntadas (que tiene el modelo de suscripción invertido con respecto a las conferencias normales).
  • Tener un sitio actualizado, elegante: un poco la imagen va a cambiar (por lo que decía arriba), y tenemos que corregirle cosas de la infraestructura que lo sirve ahora... no sólo a nivel de la migración que quedó por la mitad, sino también Waliki que se rompe seguido... ¿quizás cambiaremos la estructura que tiene el sitio en sí?

Marcos Dione: osm-tile-tools-v1.0

Today I finally sat down and spun off generate_tiles.py to its own repository, so people can follow its development without having to clone my own osm-carto fork. This happened just after I finished making the storage thread optional, because I usualy have as many rendering threads as cores I have, so that extra thread was not only compiting with them, but also it spent some time de/marshaling the metatiles between processes, so I'm not sure it's worth it. Maybe if I had more cores?

The new repo is here. There are some other tools there (hence the tools in the repo's name), but they're not so polished or documented. You're free to look and ask :)

What if I do a release, I hear (it must be the voice in my head)? Why not! I even decided to go bold and tagged it as v1.0.


openstreetmap pyhton

Marcos Dione: osm-render-tools-v1.0

Today I finally sat down and spun off generate_tiles.py to its own repository, so people can follow its development without having to clone my own osm-carto fork. This happened just after I finished making the storage thread optional, because I usualy have as many rendering threads as cores I have, so that extra thread was not only compiting with them, but also it spent some time de/marshaling the metatiles between processes, so I'm not sure it's worth it. Maybe if I had more cores?

The new repo is here. There are some other tools there (hence the tools in the repo's name), but they're not so polished or documented. You're free to look and ask :)

What if I do a release, I hear (it must be the voice in my head)? Why not! I even decided to go bold and tagged it as v1.0.


openstreetmap

Facundo Batista: A todo Python, noticias, eventos

Curso(s)

Hace unas semanas anunciaba por acá (y por otros lados) que iba a dar un Seminario Python nivel medio/avanzado con apoyo de Onapsis, abierto a la comunidad.

Explotó, en cuatro días tenía el 80% de las vacantes con la reserva confirmada, en siete días estaba todo cerrado. No esperaba un éxito tan rotundo, pero estoy muy contento. El lado gris es que un par se me enojaron porque no llegaron a entrar en el curso porque se demoraron en pagar (la mayoría lo entendió perfectamente).

Seguramente haré otros cursos durante el año. SPOILER ALERT: quizás algo introductorio primero y medio/avanzado luego, pero no sólo teoría sino también práctica, en conjunto con otro docente. Ya habrán novedades.

Charla

Ayer sábado fui invitado por Ana Vélez Rueda a dar una charla de introducción a la programación primero e introducción a Python luego, en la Universidad de Quilmes, para futuros docentes de Python. Luego de pensarlo bastante estos últimos días mezclé un poco ambas charlas, y creo que salió muy bien. La gente estuvo super interesada, y aunque fue largo y cansador (¡sábado a la tarde!) se la bancaron.

Próximos eventos de PyAr

Hay dos juntadas planeadas a futuro. La primera, bastante confirmada y que será anunciada estos días es el martes 21 a la tarde/noche, en J.P. Morgan, y va a ser una charla/taller sobre testing, más la parte social de siempre.

La segunda, todavía en etapa de "ver qué forma le damos" va a ser en Trocafone, el sábado 15 de Junio. Como es sábado, la idea es hacer algo más tipo sprint. O sea, ponernos a trabajar en algo (puntual o no), aunque seguramente abriremos el día con un par de charlas, quizás trabajar a la tarde luego del almuerzo, etc. Como les decía, todavía hay que definir la dinámica de ese día, pero está bueno volver cada tanto a la juntada de sábado, para hacer cosas :)

Anunciaremos detalles sobre ambas juntadas tanto en mi tuiter como en el de Pyar, pero la coordinación la haremos a través del meetup de Buenos Aires así que también se pueden sumar ahí.

Facundo Batista: Seminario Python, nivel medio/avanzado

Por segunda oportunidad voy a dar un Seminario de Python abierto al público en general, coordinado con Onapsis, aunque esta vez no va a ser introductorio sino que va a tener un nivel de medio a avanzado.

El contenido apunta a aquelles que ya tienen una base de Python y buscan formalizar conocimientos y terminar de establecer una estructura que les permita explorar temas más avanzados. De esta manera sólo voy a hacer un repaso rápido de los temas más simples, y haré foco en cuestiones más avanzadas o poco conocidas del lenguaje.

En otras palabras, asumiré un conocimiento de las estructuras más simples y usadas del lenguaje (que voy a repasar de forma breve) y voy a profundizar en otros temas del lenguaje y sus bibliotecas. El programa es super variado, me entusiasmé y metí un montón de temas, así que el ritmo de las clases va a ser interesante. Vengan despiertes :p

Algunos títulos (¡no todos!) son:

  • Generadores
  • Scopes
  • Closures y decoradores
  • Clases, y métodos especiales
  • Virtualenvs
  • Unit testing, logging, serialización
  • Concurrencia y paralelismo, threading y async
  • Context managers

Les decía que el Seminario es abierto al público en general, y será de 16 horas en total, cuatro sábados de Junio, durante la mañana, en CABA.

El costo es súper accesible, $1400, ya que parte lo cubre Onapsis, y la idea es hacerlo barato para que pueda venir la mayor cantidad de gente posible, y encima van a haber algunas becas. Ojo que los cupos son limitados (la oficina tiene un límite), por lo que cuanto antes consigan reserva la posición, mejor... tengan en cuenta que para la primera edición hubo gente que quedó afuera.

Al final del Seminario entregaré un certificado de asistencia y la totalidad del curso en formato electrónico.

Para realizar la reserva deben enviarme un mail así les confirmo disponibilidad y les paso los datos necesarios para realizar el pago (que podrá ser por depósito, transferencia bancaria, tarjeta de crédito, débito, etc.).

Acá están todos los detalles del curso.

Mariano Guerra: search erlang & efene code by pattern matching code with "holes"

Why and what?

One day, for weird reasons I was looking for a data structure that would allow me to pattern match it quick without being confused by existing data, I thought "nobody would have a tuple inside a one item tuple!", but I wasn't sure.

This wasn't the first time I wanted to search code by structure, not by text.

What I mean by "by structure"?

You may want to search for stuff like:

  • Calls to function f1 on module m1 with 3 arguments
    • Same but where some argument has a specific value
  • Tuples with a specific number of items where the first item is a given atom

I guess you get the idea, you want to match some expression's structure, but not the text, because of formatting, new lines, spacing and also because some of the values are not important, you would like to match any expression in some places.

Given that I have experience generating Erlang AST (abstract syntax tree) and walking AST trees (for parse transforms and some macro magic in efene)

I decided to give it a try, and the result worked, a year later I decided to write a blog post about it :)

erlplorer search "{{_@0, _@1, _@2}}" **/src/*.erl
asn1/src/asn1ct.erl:1366 {{decode,{Module,Type,Value},Error}}
dialyzer/src/dialyzer_plt.erl:675 {{M,_F,_A}}
kernel/src/pg2.erl:300 {{local_member,Name,Pid}}
kernel/src/pg2.erl:302 {{pid,Pid,Name}}
kernel/src/pg2.erl:343 {{local_member,Name,'$1'}}
kernel/src/pg2.erl:354 {{pid,Pid,'$1'}}
mnesia/src/mnesia_locker.erl:263 {{Tid,Oid,Op}}
mnesia/src/mnesia_locker.erl:266 {{Tid,Oid,read}}
mnesia/src/mnesia_locker.erl:269 {{Tid,Oid,write}}
mnesia/src/mnesia_locker.erl:309 {{Tid,Oid,{queued,Op}}}
mnesia/src/mnesia_locker.erl:508 {{Tid,Oid,{queued,Op}}}
mnesia/src/mnesia_locker.erl:524 {{'$1','_','_'}}
mnesia/src/mnesia_locker.erl:536 {{Tid,'_','_'}}
parsetools/src/yecc.erl:1075 {{From,Sym,Next}}
parsetools/src/yecc.erl:1164 {{From,Sym,To}}
reltool/src/reltool_server.erl:764 {{'$1','$2','$3'}}

Welp, yes, there are.

But what was that?

erlplorer is a command line tool built in efene that allows to search for Erlang and efene code by providing expressions with holes.

The first argument "{{_@0, _@1, _@2}}" is an Erlang expression, in this case a one item tuple holding a 3 item tuple, the weird looking variables are a specially named variables that erlplorer interprets as "match any AST node here, I don't care", they start with _@, any other variable will match that variable name in the code.

We can see that by searching for places that match a 3 item tuple ignoring the 3 places:

erlplorer search "{_, _, _}" **/src/*.erl
asn1/src/asn1ct_check.erl:917 {_,_,_}
... to many results to show

We can use this "meta variables" to do more pattern matching, let's search for 3 item tuples that have the same thing in the 3 places:

erlplorer search "{_@, _@, _@}" **/src/*.erl
asn1/src/asn1ct_check.erl:917 {_,_,_}
...
asn1/src/asn1ct_constructed_ber_bin_v2.erl:701 {[],[],[]}
...
asn1/src/asn1ct_constructed_per.erl:401 {false,false,false}
...
common_test/src/ct_framework.erl:1130 {_,_,_}
...
common_test/src/ct_logs.erl:1492 {"","",""}
...

Or search for identity functions:

erlplorer search "fun(_@) -> _@ end" **/src/*.erl
asn1/src/asn1ct.erl:2099 fun(D) -> D end
asn1/src/asn1ct_gen_ber_bin_v2.erl:650 fun(V) -> V end
common_test/src/test_server.erl:2067 fun(T) -> T end
compiler/src/beam_a.erl:147 fun(L) -> L end
compiler/src/beam_jump.erl:289 fun(Old) -> Old end
...

Places that add 0 to something:

erlplorer search "_@ + 0" **/src/*.erl
asn1/src/asn1rtt_per_common.erl:187 N + 0
asn1/src/asn1rtt_per_common.erl:197 N + 0
stdlib/src/ms_transform.erl:90 16 + 0
stdlib/src/ms_transform.erl:92 17 + 0
stdlib/src/ms_transform.erl:97 22 + 0
stdlib/src/ms_transform.erl:102 18 + 0
stdlib/src/ms_transform.erl:106 23 + 0
stdlib/src/ms_transform.erl:111 24 + 0
stdlib/src/ms_transform.erl:167 20 + 0
stdlib/src/ms_transform.erl:170 19 + 0
stdlib/src/ms_transform.erl:174 21 + 0

Places that add the same thing:

erlplorer search "_@ + _@" **/src/*.erl
dialyzer/test/small_SUITE_data/src/maps_redef2.erl:18 A + A
stdlib/src/dets_utils.erl:1138 1 + 1
stdlib/src/dets_utils.erl:1226 1 + 1
stdlib/src/rand.erl:1464 Y + Y
stdlib/src/zip.erl:1315 Sz + Sz

You get the idea...

How to use it

You need Erlang and rebar3 installed and in your $PATH

git clone https://github.com/marianoguerra/erlplorer
cd erlplorer
rebar3 escriptize
# ~/bin or any other folder in your $PATH
cp _build/default/bin/erlplorer ~/bin

How is it implemented?

Wrap the expression passed in a function [1]

Compile the module to Erlang AST [2]

Extract the AST body of the dummy function [3]

"abstract the AST", that is, I take an AST as Erlang data and I generate an AST that when compiled will generate that Erlang AST, I need that because I will put that AST in a pattern match position to pattern match AST nodes as I walk Erlang ASTs [4]. yeah, meta and too many AST references

An example is worth many of my words:

% the 42 is a fake "line" where the code was supposedly parsed
1> erl_parse:abstract({foo, 100, "hi"}, 42).
{tuple,42,[{atom,42,foo},{integer,42,100},{string,42,"hi"}]}

We have to take care of two things:

  • vars must match an AST node for a var with that name, not act as vars that will be bound on first match and pattern matched on successive matches
  • vars that start with _@ will be compiled to actual vars that behave as vars in a pattern match, that's how we can use them to pattern match

Then compile the abstracted AST into a module with a function we can pass to ast_walk/3 [5]

Load the compiled module [6]

Parse the files passed as last argument [7]

And walk the parsed AST with our compiled matcher [8]

For each match, since we have the AST, try to pretty print it [9]

Give it a try and let me know what you think.

PS: the code is a hack I did to use it when I needed it, don't judge efene by the code you see on that project :P

[1] https://github.com/marianoguerra/erlplorer/blob/0bdd56057dfeb399b9961ae6322f74ccabc2cc5a/src/erlplorer.fn#L53
[2] https://github.com/marianoguerra/erlplorer/blob/0bdd56057dfeb399b9961ae6322f74ccabc2cc5a/src/erlplorer.fn#L56
[3] https://github.com/marianoguerra/erlplorer/blob/0bdd56057dfeb399b9961ae6322f74ccabc2cc5a/src/erlplorer.fn#L57
[4] https://github.com/marianoguerra/erlplorer/blob/0bdd56057dfeb399b9961ae6322f74ccabc2cc5a/src/erlplorer.fn#L58
[5] https://github.com/marianoguerra/erlplorer/blob/0bdd56057dfeb399b9961ae6322f74ccabc2cc5a/src/erlplorer.fn#L64
[6] https://github.com/marianoguerra/erlplorer/blob/0bdd56057dfeb399b9961ae6322f74ccabc2cc5a/src/erlplorer.fn#L13
[7] https://github.com/marianoguerra/erlplorer/blob/0bdd56057dfeb399b9961ae6322f74ccabc2cc5a/src/erlplorer.fn#L152
[8] https://github.com/marianoguerra/erlplorer/blob/0bdd56057dfeb399b9961ae6322f74ccabc2cc5a/src/erlplorer.fn#L156
[9] https://github.com/marianoguerra/erlplorer/blob/0bdd56057dfeb399b9961ae6322f74ccabc2cc5a/src/erlplorer.fn#L76

Facundo Batista: Sincronizando (una historia de "soltar")

En una charla nerds con amigos surgió el tema de cómo guardar "pequeñas notas" de forma piola (que se replique, que sea posible buscar, que sea más o menos simple, etc.). En su momento barajamos varias opciones, y a mí me quedó dando vueltas en la cabeza un clientito que se instala y trabaja contra Nextcloud.

Nextcloud es una suite de servicios alrededor de sincronizar archivos, notas, tareas, etc, y ofrece formas de armar grupos de colaboración para trabajar con todo eso. Yo puntualmente estaba interesado en lo que es sincronización de archivos, y potencialmente notas.

Para mí es todo un viaje, porque tomé la decisión de que si Nextcloud funcionaba correctamente dejaba de usar Magicicada (ex Ubuntu One). Aunque estoy emocionalmente atado a ese pedazo de software, y todo lo que representó en mi carrera laboral y como evolución en Python, en algún punto entendí que NUNCA voy a tener tiempo de agregarle los features que quería para que cumpla con todo lo que quiero tener, en parte porque el tiempo que sí tengo prefiero (o preferiría) dedicárselo a otras cosas, incluso a otros proyectos de software, u otros hobbies no tan relacionados con la informática.

La decisión

Entonces, me voy de Magicicada. Cuando internamente terminó de madurar ese proceso adentro mío, me puse a laburar seriamente con Nextcloud. Lo instalé en mi servercito casero, y funcionó. Luego, toda la fase de cómo usarlo desde cada computadora. Quise probar que sincronice archivos desde el desktop, y a priori no anduvo.

Investigué un poco, creo que es por algo de la URL, y para dejar 100% como dice el manual del server le tengo que instalar SSL. Puedo para eso usar el servicio de LetsEncrypt, pero para que funcione (según entendí y probé) tengo que tener el puerto 80 accesible desde afuera. El tema es que Claro (mi proveedor de internechi) no me enruta más puerto por puerto, con lo que tendría que armar una DMZ acá en mi intranet, lo cual no es trivial (y asumiendo que el cambio de config de Claro no me rompa otra cosa y me deje en pelotas y en la vía).

Pero... pero.... pero.... pero... Me dí cuenta que NO tengo ganas de crear todo un universo sólo para finalmente tener una tarta de manzana :)

Alta tarta de manzanas

Y, pensé en Dropbox...

Hasta ahora venía esquivando Dropbox porque es una empresa yanqui, y meter todos mis datos ahí siempre me dio escozor. Como uno no tiene control sobre la encriptación de los archivos (se encriptan al transmitirse, y teóricamente cuando están guardados, pero Dropbox posee la clave para abrirlos) pueden ver todo lo que tengo. ¿En qué me jodía? Por un lado tengo mucha música en mp3, y aunque toda esa música la bajé de CDs que legalmente compré (!) no quiero que el día de mañana me rompan las pelotas porque les parezca que estoy infringiendo copyright. Por otro lado, tengo un montón de datos sensibles que no me gustaría que queden expuestos al mundo en caso de un fallo de seguridad de Dropbox.

Pero, estos dos aspectos ahora no me joden tanto. Con respecto a la música, es un riesgo que puedo correr. Con respecto a mis datos privados, hoy por hoy los tengo todos bajo KeePassX, así que todo bien ahí.

Por otro lado, al mismo tiempo NO quiero todo encriptado, porque ahí ya no podría acceder a mis archivos desde el teléfono o desde la web directamente, en caso de necesitarlo, y este es un feature que sí quiero... y una de las razones fuertes para irme de Magicicada.

El nuevo mundo

Habiendo sopesado estas variables, terminé de tomar la decisión. Me voy a Dropbox. La cuenta gratuita no me alcanza, pero la más barata sí, porque me ofrecen un terabyte de almacenamiento (puedo meter todas mis fotos y videos caseros, entre los cuales no llego a los 300GB), y cuesta USD 8.25, que por todo lo que yo uso este servicio, se justifica.

Igual, antes de "tirarme a la pileta", tenía que hacer otra verificación. Dropbox, ¿funciona?

Ejecuté una batería de pruebas desde mi computadora de escritorio. Funciona bien con Vim, que es complicado porque mueve/graba/mueve todo el tiempo. Funciona bien con LibreOffice, que maneja cada documento como un directorio comprimido con montón de archivitos adentros. También le abrí un archivo .tar de pocos archivos inmensos, y otro de montonazo de pequeños archivos en una árbol grandote de directorios, y se la bancó.

Y después ya en el baile le puse toda mi música, y todos mis archivos "simples" (en otras palabras, todo lo que sincronizo normalmente, excepto fotos y videos).

En todos los casos, fui comprobando los hashes de todo contra lo que el mismo Dropbox bajaba en la laptop. Se la bancó.

Yo, tirándole cosas a Dropbox hasta asegurarme que funca

Bien, ¡funciona! ¿Tiene algunos puntos débiles o negativos? Encontré tres grandes cosas que me molestan...

Por un lado, tenés que tener todo dentro del directorio $HOME/Dropbox. Con Ubuntu One y Magicicada yo siempre tuve el directorio default de cada uno, más una de Música, una de video, y una de fotos, que eran externas. Pero Dropbox no te deja hacer eso, tiene que estar todo adentro de la misma raíz.

La solución/parche que implementé es tener un .externals en ese directorio raíz, y ahí adentro la de música, fotos y videos, y desde los otros lugares hice un enlace simbólico. No es lo que más lindo queda, pero funciona. Un enlace simbólico al revés NO funciona, ya que Dropbox sincroniza el archivo del symlink en sí, y no el directorio al que apunta.

Otro punto negativo que me pareció HORRIBLE al principio pero luego no me pegó tanto (aunque tuve que tocar un par de archivos) es que Dropbox no respeta diferencias entre mayúsculas y minúsculas. O sea, que si vos tenés un archivo barco y otro Barco, Dropbox NO te sincroniza ambos, sino que uno de los dos te lo renombra para indicarte la colisión. Entiendo que se debe a que algunos sistemas de archivos de Windows tienen este mismo problema, pero Dropbox debería ser más inteligente y como yo tengo en todos lados Linux, no exponerme a esa limitación.

Finalmente, Dropbox es bastante flojo con respecto a sus notificaciones y data que tira al usuario. En mi sistema operativo (KDE Neon sobre Ubuntu Bionic) me pone un ícono en el systray que tiene un tilde verde, y cuando está sincronizando titila algo azul. Si lo abro, junto con otra info me dice en qué estado está: normalmente en "Actualizado", a veces mostrando la operación actual, pero de forma bastante resumida.

Desde linea de comandos puedo hacer dropbox status que me muestra esa misma info, pero me es más accesible. También es útil el dropbox ls o dropbox filestatus pero le falta información realmente sobre qué hizo, algo detallado que muestre en qué estado está con cada cosa, o donde poder revisar qué pasó media hora atrás... o sea, logs.

Quiero ver qué está haciendo, dejame ver qué está haciendo, mostrame qué está haciendo

Entiendo que a la mayoría de los usuarios finales no les importe más información que esa, pero yo en algunos casos necesito tener más control de lo que está haciendo o pasó.

¿Y las pequeñas notas?

Para cerrar el ciclo con el arranque del artículo, ¿qué onda sincronizar notas? Mariano me dijo que usaba bastante Zim Wiki, que es 100% texto y guarda las cosas en archivos sincronizados por, claro, Dropbox.

Él lo usa con el cliente que proveen en Linux y Windows, o directamente tocando el archivo de texto correspondiente en el teléfono. Me comentó también que tiene un "buscar" que funciona, y permite agregarle etiquetas (tags) a las notas.

Lo voy a probar.

Mariano Guerra: Elixir protocols, how do they work? the erlang perspective

We will start by creating a new project to learn more about Elixir protocols:

mix new learn --module Learn
cd learn

note: I'm using Erlang 21.3 and Elixir 1.8.1 and I never coded in Elixir before :)

I searched for Elixir protocols and found the official documentation with an example for a Size protocol I added it to the lib/learn.ex file and added some calls on the hello function to try it, it ended up looking like this:

defmodule Learn do
  @moduledoc """
  Documentation for Learn.
  """

  @doc """
  Hello world.

  ## Examples

  iex> Learn.hello()

  """
  def hello do
    Learn.Size.size("asd")
    Learn.Size.size(%{})
    Learn.Size.size({1, 2, 3})
  end

  defprotocol Size do
    @doc "Calculates the size (and not the length!) of a data structure"
    def size(data)
  end

  defimpl Size, for: BitString do
    def size(string), do: byte_size(string)
  end

  defimpl Size, for: Map do
    def size(map), do: map_size(map)
  end

  defimpl Size, for: Tuple do
    def size(tuple), do: tuple_size(tuple)
  end

end

Compiled the project:

mix compile

Opened an Elixir shell:

iex

Wrote a little script to decompile all beam files to Erlang (warning: Elixir flavored Erlang ahead!):

for f <- :filelib.wildcard('./_build/dev/lib/*/*/*.beam') do
  result = :beam_lib.chunks(f,[:abstract_code])
  {:ok,{_,[{:abstract_code,{_,ac}}]}} = result
  code = :erl_prettypr.format(:erl_syntax.form_list(ac))
  out_path = :string.replace(f, '.beam', '.erl')
  :file.write_file(out_path, code)
end

The results:

$  tree
.
├── _build
│   └── dev
│       └── lib
│           └── learn
│               ├── consolidated
│               │   ├── Elixir.Collectable.beam
│               │   ├── Elixir.Collectable.erl
│               │   ├── Elixir.Enumerable.beam
│               │   ├── Elixir.Enumerable.erl
│               │   ├── Elixir.IEx.Info.beam
│               │   ├── Elixir.IEx.Info.erl
│               │   ├── Elixir.Inspect.beam
│               │   ├── Elixir.Inspect.erl
│               │   ├── Elixir.Learn.Size.beam
│               │   ├── Elixir.Learn.Size.erl
│               │   ├── Elixir.List.Chars.beam
│               │   ├── Elixir.List.Chars.erl
│               │   ├── Elixir.String.Chars.beam
│               │   └── Elixir.String.Chars.erl
│               └── ebin
│                   ├── Elixir.Learn.beam
│                   ├── Elixir.Learn.erl
│                   ├── Elixir.Learn.Size.beam
│                   ├── Elixir.Learn.Size.BitString.beam
│                   ├── Elixir.Learn.Size.BitString.erl
│                   ├── Elixir.Learn.Size.erl
│                   ├── Elixir.Learn.Size.Map.beam
│                   ├── Elixir.Learn.Size.Map.erl
│                   ├── Elixir.Learn.Size.Tuple.beam
│                   ├── Elixir.Learn.Size.Tuple.erl
│                   └── learn.app

From the result it seems that it "consolidates" the protocols into the consolidated folder and then puts the modules at ebin (with protocol implementations named like the protocol plus the type they handle).

It's also clear that all Elixir modules are prefixed with Elixir., also that if I declare a protocol inside a module the protocol "belongs" to the module, in this case the "full qualified name" of the protocol is Elixir.Learn.Size.

Let's start exploring what code is generated by inspecting the main module we wrote (I will cleanup unneeded code from the examples):

-module('Elixir.Learn').
-export([hello/0]).

hello() ->
    'Elixir.Learn.Size':size(<<"asd">>),
    'Elixir.Learn.Size':size(#{}),
    'Elixir.Learn.Size':size({1, 2, 3}).

We can see that calling a function from a protocol implies calling the desired function on the consolidated module for the protocol itself.

Let's now see what the Elixir.Learn.Size module does:

-module('Elixir.Learn.Size').
-export(['__protocol__'/1, impl_for/1, 'impl_for!'/1, size/1]).

'impl_for!'(__@1) ->
    case impl_for(__@1) of
      __@2 when __@2 =:= nil orelse __@2 =:= false ->
      erlang:error('Elixir.Protocol.UndefinedError':exception([{protocol,
                                    'Elixir.Learn.Size'},
                                   {value,
                                    __@1}]));
      __@3 -> __@3
    end.

size(__@1) -> ('impl_for!'(__@1)):size(__@1).

struct_impl_for(_) -> nil.

impl_for(#{'__struct__' := __@1})
    when erlang:is_atom(__@1) ->
    struct_impl_for(__@1);
impl_for(__@1) when erlang:is_tuple(__@1) ->
    'Elixir.Learn.Size.Tuple';
impl_for(__@1) when erlang:is_map(__@1) ->
    'Elixir.Learn.Size.Map';
impl_for(__@1) when erlang:is_bitstring(__@1) ->
    'Elixir.Learn.Size.BitString';
impl_for(_) -> nil.

'__protocol__'(module) -> 'Elixir.Learn.Size';
'__protocol__'(functions) -> [{size, 1}];
'__protocol__'('consolidated?') -> true;
'__protocol__'(impls) ->
    {consolidated,
     ['Elixir.Map', 'Elixir.BitString', 'Elixir.Tuple']}.

The exported function for the protocol (size/1) does a simple thing, it asks the impl_for!/1 function for the module that knows how to handle Learn.Size.size/1 for the given argument and then calls that module's size/1 function:

size(__@1) -> ('impl_for!'(__@1)):size(__@1).

impl_for!/1 just calls impl_for/1 with the argument and handles the case where the value doesn't have a known implementation, in that case it raises an exception (Elixir.Protocol.UndefinedError), otherwise it just returns the module name.

impl_for/1 starts by checking if the argument is an Elixir struct, which underneath is just a map with a "well known" key __struct__ that contains the type of the struct as an atom:

impl_for(#{'__struct__' := __@1})
    when erlang:is_atom(__@1) ->

if it's a struct it calls struct_impl_for/1 with the struct type as argument:

struct_impl_for(__@1);

In our example, there's no struct that implements this protocol so the implementation of struct_impl_for/1 is simple:

struct_impl_for(_) -> nil.

After that it starts trying to find the implementation for non protocol types (mostly Erlang types), it tries to match using guards to check for the types, if none match, it returns nil like struct_impl_for/1:

impl_for(__@1) when erlang:is_tuple(__@1) ->
    'Elixir.Learn.Size.Tuple';

impl_for(__@1) when erlang:is_map(__@1) ->
    'Elixir.Learn.Size.Map';

impl_for(__@1) when erlang:is_bitstring(__@1) ->
    'Elixir.Learn.Size.BitString';

impl_for(_) -> nil.

Now that we got the module that handles the protocol function for each type, let's see their implementations:

Elixir.Learn.Size.BitString:

size(_string@1) -> erlang:byte_size(_string@1).

Elixir.Learn.Size.Map:

size(_map@1) -> erlang:map_size(_map@1).

Elixir.Learn.Size.Tuple:

size(_tuple@1) -> erlang:tuple_size(_tuple@1)

Now that we got the basic call and dispatch sequence let's try adding two structs and implement this protocol to see how it works for them:

I added two structs to the lib/learn.ex module:

defstruct name: "John", age: 27

defmodule User do
  defstruct name: "John", age: 27
end

Added calls to Size.size/1 in the hello/0 function:

def hello do
  Learn.Size.size("asd")
  Learn.Size.size(%{})
  Learn.Size.size({1, 2, 3})
  Learn.Size.size(%User{age: 27, name: "John"})
  Learn.Size.size(%Learn{age: 27, name: "John"})
end

And implemented the protocol Size for both structs:

defimpl Size, for: Learn do
  def size(learn), do: learn.age + 1
end

defimpl Size, for: User do
  def size(user), do: user.age + 2
end

Compiled with mix compile and inside iex pasted the script again, let's see what changed.

The hello world function looks like this:

hello() ->
        'Elixir.Learn.Size':size(<<"asd">>),
        'Elixir.Learn.Size':size(#{}),
        'Elixir.Learn.Size':size({1, 2, 3}),
        'Elixir.Learn.Size':size(#{age => 27,
                                   name => <<"John">>,
                                   '__struct__' => 'Elixir.Learn.User'}),
        'Elixir.Learn.Size':size(#{age => 27,
                                   name => <<"John">>,
                                   '__struct__' => 'Elixir.Learn'}).

Which confirms that Elixir structs are maps with a special __struct__ key.

Checking the generated files, there's a new file for our User struct (Elixir.Learn.User.erl), the other struct is defined inside Elixir.Learn.erl.

The module code relevant for the struct doesn't have anything specific to the protocols it implements:

-module('Elixir.Learn.User').
-export([_struct__'/0, '__struct__'/1]).

'__struct__'() ->
        #{'__struct__' => 'Elixir.Learn.User', age => 27,
          name => <<"John">>}.

'__struct__'(__@1) ->
        'Elixir.Enum':reduce(__@1,
                         #{'__struct__' => 'Elixir.Learn.User', age => 27,
                           name => <<"John">>},
                         fun ({__@2, __@3}, __@4) ->
                                 maps:update(__@2, __@3, __@4)
                         end).

Almost the same code is inside Elixir.Learn.erl for the other struct.

This shows that each struct has two "constructors", one without arguments that returns a struct with the default values for all fields and one that merges the arguments on the default values.

Let's see what changed on the consolidated protocol module:

struct_impl_for('Elixir.Learn.User') ->
        'Elixir.Learn.Size.Learn.User';
struct_impl_for('Elixir.Learn') ->
        'Elixir.Learn.Size.Learn';
struct_impl_for(_) -> nil.

Each struct type returns the module where the protocol is implemented, let's see both implementations:

Elixir.Learn.Size.Learn.User.erl:

size(_user@1) ->
        case _user@1 of
          #{age := __@1} -> __@1;
          __@1 when erlang:is_map(__@1) ->
          erlang:error({badkey, age, __@1});
          __@1 -> __@1:age()
        end
          + 2.

Elixir.Learn.Size.Learn.erl:

size(_learn@1) ->
        case _learn@1 of
          #{age := __@1} -> __@1;
          __@1 when erlang:is_map(__@1) ->
          erlang:error({badkey, age, __@1});
          __@1 -> __@1:age()
        end
          + 1.

Summary:

Elixir protocols are compiled to its own module whose content is the consolidated dispatch logic for it.

This logic is created by getting all the defimpl statements for it and adding a function clause to the struct_impl_for/1 function if the target type is an Elixir struct and a clause to the impl_for/1 function if the target type is any other type.

The function (impl_for!/1) returns the module that has the protocol implementation for the provided type.

Each protocol function asks for the module via impl_for!/1 and calls it with the given arguments.

This is just guessing, but the module indirection must be there to allow hot code reloading protocol implementations for each type independently without requiring also reloading the protocol consolidation. The struct_impl_for function is there to destructure the map only once.

I don't see traces of dynamic dispatch in case a module is loaded with a protocol implementation that was not known at consolidation time, I need to research this further.

An extra guess, this logic on the struct field to get the age field:

case _learn@1 of
  #{age := __@1} -> __@1;
  __@1 when erlang:is_map(__@1) ->
  erlang:error({badkey, age, __@1});
  __@1 -> __@1:age()
end

May be because Elixir allows to call a struct "method" without parenthesis and that's why it looks for the field first and the function with the same name second? I'm not entirely sure since my Elixir knowledge is basically non existent :)

If you have any questions or corrections I'm @warianoguerra my other accounts here: https://keybase.io/marianoguerra

Facundo Batista: Películas, duda, series

De nuevo vi bastante más que las que anoté para ver. Pero no le estoy dedicando mucho tiempo a ver películas (aunque aproveché bastante los vuelos largos en el último viaje), con lo cual creo que lo que pasa es que estoy anotando pocas nuevas.

¿Por qué? Creo que porque no hay mucho que me interese de lo que está saliendo (por ejemplo, ignoro todo lo que sean películas basadas en guerra, o de terror "normal" (o sea, aburrido), o la trigésimo novena de los avengers, etc...).

¿O quizás estoy perdiéndome de ver trailers que están buenos? ¿Hay películas que están por salir que piensan que están buenas y NO estoy anotando? ¿Mejores lugares para buscar trailers que IMDb? Propongan.

Por otro lado, estuve arrancando y cerrando un par de series. Recomiendo fuerte Borgen, enterita. Es muy buena. La primera temporada de Fargo es genial, la segunda pinta muy bien (estoy en eso). También estoy viendo Girls, que zafa. Y para que vean que no son todas rosas, tenía bajadas para ver y no pude pasar del primer capítulo (y picoteos de otros capítulos, para asegurarme de no llevarme una impresión equivocada) de Flight of the Conchords (no es mi tipo de humor, no me da nada, muy aburrida) y Dark Net (odio las series medio en primera persona onda "yo soy un muchacho tranquilo, crecí en oklahoma, conocí mi chica online" mientras lo muestran llevarle pasto a una vaca o una boludez así).

Bueno. A los bifes...

  • Absolutely Anything: +0. Una idea no nueva, pero encarada de un lado interesante y divertida.
  • Atomic Blonde: +0. No deja de ser una de espías en la época de la guerra fría, pero atrapa, divierte, y convence (si es que te interesa ese tipo de películas).
  • Atomica: -0. El contexto y parte de la historia eran interesantes, pero las actuaciones bleh, la peli en general no suma.
  • Black Snow: -0. La historia zafa y las actuaciones están buenas, pero es muy lenta por partes, y no te deja gran cosa.
  • Elle: -0. Es una historia chata, en el sentido que va contando cosas que poquito a poquito suma un relato, pero nunca te das cuenta cual es el nudo de la cuestión, entonces como que no vas entendiendo mucho, pero algo se forma; más allá de eso, no me entusiasmó nada.
  • Frank & Lola: +0. La película está bien, no es gran cosa pero cierra y se pasa un buen rato. Compensan que sí me gustan las películas "de chefs" y que no me gustan las películas donde las parejas tienen relaciones tóxicas.
  • Future Man: No es una peli, sino una serie! Ahí la reencolé donde corresponde, sacándola de acá.
  • MindGamers: -0. Tiene partes muy interesantes, pero el estilo de filmación la hace medio insoportable, y aunque una notable suma de sinsentidos parece acomodarse luego, no llega a funcionar.
  • Murder on the Orient Express: -0. No me terminó de enganchar... No sé exactamente qué es lo que hizo que la peli se me haga muy anodina.
  • Passengers: +0. Divertida, con efectos interesantes, con una trama liviana pero dinámica y se la pasa bien.
  • Ready Player One: +0. Está buena por los efectos y por todas las referencias... luego la historia no me llamó demasiado la atención.
  • Rooster's Blood: +0. Casi la saco cuando habían pasado veinte minutos y la historia no apuntaba a ningún lado, pero terminó zafando y está buena. Hay que bancarse algunos planos lentos interminables, pero va.
  • The Assignment: -0. Me gustó la forma gráfica de contar y mostrar la historia, y la historia misma no está mal, con un par de vueltas que sorprenden y te llevan hasta el final. Pero está armada como si fuese el suceso épico del cine y se queda muy corta, y eso hace que le reste un montón.
  • The Autopsy of Jane Doe: +1. Con la base de la historia muy conocida (se trató miles de veces), la peli logra darle un giro totalmente "real" pero sorprendente, lo cual más allá de que la peli es en sí de terror, tiene ese misterio que te lleva hasta al final y vale la pena.
  • The Circle: +0. La historia zafa, los paralelos con la realidad son evidentes; es una muy buena película para que muchos tomen conciencia de a donde nos dirigimos.
  • The Comedian: -0. La historia zafa, pero no va a ningún lado, las actuaciones están bien, pero a la peli le falta como "consistencia"...
  • The Discovery: +0. El concepto base de la historia está bueno, la trama alrededor también, y la vuelta del final es inesperada. Está bien.
  • The Little Hours: +0. Muy divertida, muy atípica. Ligeramente basada en el Decamerón, es una excusa para hilar una historia que lleva bien la peli.
  • The Recall: -1. Es muy mala. Así y todo la terminé de ver, porque no es que haya algo que sea insoportable, sólo es muy mala de forma parejita :).
  • The Whole Truth: +0. Está bien, se nota que la trama es más compleja y está resuelta con la voz en off, pero se sigue bien y se arma todo bien, con un desenlace no esperado y que cambia un poco la perspectiva de lo anterior.
  • Unlocked: +0. No deja de ser una de espías/acción, pero las idas y vuelta que tiene la hacen interesante. Pierde un poco la gracia el final como apostando a secuelas, pero bueh, la primera zafa, si hacen una continuación no me anoto...
  • What Happened to Monday: +0. Muy rara, pero entretenida. Lo que labura la actriz con los múltiples papeles es loable.
  • Zero Days: -1. No la pude terminar de ver de lo embolante que era. Hay toda una parte interesante que es, justamente, el tema del virus. Y todo lo relacionado con la planta atómica también está bueno. Pero si estirás eso a casi dos horas de película, encima en ese formato de "entrevistas", se hace insoportable.
Jane Doe

De las pocas nuevas...

  • Hellboy: (2019; Action, Adventure, Fantasy, Sci-Fi) Based on the graphic novels by Mike Mignola, Hellboy, caught between the worlds of the supernatural and human, battles an ancient sorceress bent on revenge.::Dominic Henriott AND ABDUL [D: Neil Marshall; A: David Harbour, Milla Jovovich, Ian McShane]
  • Yesterday: (2019; Comedy, Drama, Fantasy, Music, Musical, Romance) A struggling musician realizes he's the only person on Earth who can remember The Beatles.::a_clockwork_alpaca [D: Danny Boyle; A: Ana de Armas, Lily James, Kate McKinnon]
  • All Inclusive: (2018; Comedy) Pablo and Lucia live together. Pablo buys an All-Inclusive in Brazil online as a surprise for Lucia but his boss firing him, so Pablo tries to cancel the trip but he can't. They travel anyway, but Brazil waits with nothing but trouble.. [D: Diego Levy, Pablo Levy; A: Alan Sabbagh, Julieta Zylberberg, Mike Amigorena]
  • Aniara: (2018; Drama, Sci-Fi) A spaceship carrying settlers to Mars is knocked off course, causing the consumption-obsessed passengers to consider their place in the universe. [D: Pella Kagerman, Hugo Lilja; A: Emelie Jonsson, Bianca Cruzeiro, Arvin Kananian]
  • Asher: (2018; Drama, Thriller) An aging hitman's last job goes sideways, forcing him to redeem himself. [D: Michael Caton-Jones; A: Famke Janssen, Ron Perlman, Richard Dreyfuss]
  • Brexit: (2019; Biography, Drama, History) Political strategist Dominic Cummings leads a popular but controversial campaign to convince British voters to leave the European Union from 2015 up until the present day. [D: Toby Haynes; A: Benedict Cumberbatch, Sarah Belcher, Malcolm Freeman]
  • Escape Room: (2019; Action, Adventure, Drama, Horror, Mystery, Sci-Fi, Thriller) Six strangers find themselves in a maze of deadly mystery rooms, and must use their wits to survive. [D: Adam Robitel; A: Taylor Russell, Logan Miller, Jay Ellis]
  • Green Book: (2018; Biography, Comedy, Drama, Music) A working-class Italian-American bouncer becomes the driver of an African-American classical pianist on a tour of venues through the 1960s American South. [D: Peter Farrelly; A: Viggo Mortensen, Mahershala Ali, Linda Cardellini]
  • Incontrol: (2017; Drama, Sci-Fi, Thriller) 4 university students hook up to a machine allowing them to become one of their fellow students - e.g. allowing them each to party as one of the rich and beautiful. The long hook-ups start seriously affecting their normal selves.::Scott Filtenborg [D: Kurtis David Harder; A: Sarah Troyer, Anja Savcic, Valerie Planche]
  • Pet Sematary: (2019; Horror, Thriller) Louis Creed, his wife Rachel, and their two children Gage and Ellie move to a rural home where they are welcomed and enlightened about the eerie 'Pet Sematary' located nearby. After the tragedy of their cat being killed by a truck, Louis resorts to burying it in the mysterious pet cemetery, which is definitely not as it seems, as it proves to the Creeds that sometimes, dead is better. [D: Kevin Kölsch, Dennis Widmyer; A: John Lithgow, Jason Clarke, Amy Seimetz]
  • Spider-Man: Into the Spider-Verse: (2018; Animation, Action, Adventure, Family, Sci-Fi) Teen Miles Morales becomes Spider-Man of his reality, crossing his path with five counterparts from other dimensions to stop a threat for all realities.::Chockys [D: Bob Persichetti, Peter Ramsey, Rodney Rothman; A: Shameik Moore, Jake Johnson, Hailee Steinfeld]
  • The Irishman: (2019; Biography, Crime, Drama, History, Thriller) A mob hitman recalls his possible involvement with the slaying of Jimmy Hoffa. [D: Martin Scorsese; A: Anna Paquin, Robert De Niro, Al Pacino]
  • Get Her... If You Can: (2019; Comedy) Roberto (Javier Rey) and Daniela (Amaia Salamanca) are siblings, both wealthy business people, with very different styles. Roberto's plot to return her sister her sense of humour gets unexpectedly out of hand. [D: Inés de León; A: Amaia Salamanca, Hugo Silva, Javier Rey]

Finalmente, el conteo de pendientes por fecha:

(Jun-2013)    2
(Sep-2013)    8
(Dic-2013)   12   4
(Abr-2014)    8   3
(Jul-2014)   10  10   5   1
(Nov-2014)   22  22  22   7
(Feb-2015)   13  13  13  10
(Jun-2015)   16  16  15  13  11   1
(Dic-2015)       21  19  19  18   6   1
(May-2016)           26  25  23  21   9
(Sep-2016)               19  19  18  14   1
(Feb-2017)                   26  25  23  21   9   1
(Jun-2017)                       23  23  21  18   5
(Dic-2017)                           19  19  18  16
(May-2018)                               22  22  22
(Sep-2018)                                   12  12
(Mar-2019)                                       13
Total:       91  89 100  94  97  94  89  84  79  69