Facundo Batista: Mi cuerpo también es político

Hace rato que uno viene interesado en la pelea de las mujeres (y otros colectivos que podríamos englobar en un "no-hombres-cis") por estar reconocidos como sujetos de derecho a la par de los hombres cisgénero.

Seguramente habrán estarán al tanto de las marchas #niunamenos o el debate que se dió semanas atrás en el Congreso sobre la despenalización del aborto (lo cual se apoya desde estas lineas). Y si no están al tanto, salgan del termo.

Particularmente, vengo siguiendo estos temas desde hace bastante, antes de que explotara mediáticamente. Como ejemplo basta un botón: hace muchos años que decidí empezar a comprar el Página 12 también los viernes (sólo lo compraba los domingos) solamente porque ese día salen los suplementos Las 12 y Soy.

También vengo empujando desde hace casi una década un movimiento en el grupo de Python Argentina cuya arista más sobresaliente es la de "diversidad dentro del grupo", pero que también versa sobre los derechos de las mujeres en la industria, como cobrar el mismo salario por la misma tarea o destruir el techo de cristal... en general mejorar las condiciones en todos los ámbitos: laboral, obvio, pero también en todos los otros espacios, como conferencias, reuniones, etc.

¿Qué tiene que ver todo esto con mi cuerpo?

Yo, como hombre cisgénero heterosexual, reconozco que me encuentro entre el grupo privilegiado. Dice Malena Pichot en un texto que les recomiendo: "Quienes somos cis heterosexuales, con alguna serie más de privilegios encima, tenemos la absoluta responsabilidad de hacer algo, de no ser cómplices del sistema que nos beneficia a nosotros en detrimento de otros".

Por mi parte, hace rato que tomé la decisión de no tener más hijos. A Felipe y Malena los amo con el alma, los disfruto, los adoro, pero no quiero tener más hijos. Entonces, entra en juego el control de la natalidad. Y acá es donde nos metemos en un terreno que también históricamente fue dominación de los hombres: el cuerpo de las mujeres.

Es que cuando de control de natalidad se habla, siempre se hace hincapié en las mujeres y en formas de que no puedan procrear, desde la ligadura de trompas, pasando por la colocación de un DIU hasta las pastillas anticonceptivas con todos las implicaciones a nivel hormonal que tienen.

Lo importante es tomar la responsabilidad sobre las decisiones. YO no quiero tener más hijos. Entonces, YO tengo que actuar en consecuencia. Lo que corresponde es hacerme una vasectomía. Y me hice una vasectomía.

¿Qué es una vasectomía? Una operación sencillita que se basa en cortar el tubito que lleva los espermatozoides (producidos en la vesícula seminal) al testículo (que produce el resto del semen). Entonces, el semen producido no contiene más espermatozoides! A lo bruto, seguís disparando como siempre pero con balas de salva :p.

Yo pedí turno con un urólogo, me saqué todas las dudas (un poco trata de disuadirte), me mandó a hacer un par de estudios pre-operatorios de rutina, y luego la operación, que es ambulante y bastante rutinaria. Una semana con los testículos hinchados, y otra sin poder hacer ejercicio. Un control un par de semanas después, y otro un par de meses después. Y listo.

Se siente bien poner el cuerpo donde uno tiene la ideología.

De yapa, les dejo un hilo de twitter muy gracioso de Emanuel Rodríguez (a quien yo "sigo desde Cemento") sobre este tema.

Mariano Guerra: Riak Core Tutorial Part 9: Persistent KV with leveled backend

The content of this chapter is in the 07-leveled-kv branch.

https://gitlab.com/marianoguerra/tanodb/tree/07-leveled-kv

Implementing it

Until now we have an in memory key-value store, what do we have to do to make it a persistent one?

We would need to implement a new kv backend, that implements the same API as tanodb_kv_ets but using a library that persists to disk.

For this we are going to use leveled a pure erlang implementation of leveldb.

Being pure erlang means it's easy to build on any platform and easy to understand and contribute since it's all erlang!

The changes will involve making room for configurable KV backends, for that we will keep the backend module in a field called kv_mod in the vnode state:

-record(state, {partition, kv_state, kv_mod}).

On init we will pass an extra field to the KV backend init function with the base path where it can safely store files without clashing with other vnodes in the same node:

init([Partition]) ->
        DataPath = application:get_env(tanodb, data_path, "."),
        KvMod = tanodb_kv_leveled,
        {ok, KvState} = KvMod:new(#{partition => Partition,
                                                                data_path => DataPath}),
        {ok, #state { partition=Partition, kv_state=KvState, kv_mod=KvMod }}.

We are getting the base path to store data from an environment variable (tanodb.data_path), to make it configurable we need to add it to our cuttlefish schema on priv/01-tanodb.schema:

%% @doc base folder where data is stored
{mapping, "paths.data", "tanodb.data_path", [
  {datatype, directory},
  {default, "{{platform_data_dir}}/vnodes"}
]}.

Then we need to replace all the places in tanodb_vnode where we used tanodb_kv_ets to use the value of kv_mod from the state record.

On rebar.config we need to add the leveled dependency, since it doesn't have any release and it's not on hex.pm we will reference the master branch from the github repo:

{deps, [cowboy, jsx, recon,
        {riak_core, {pkg, riak_core_ng}},
        {leveled, {git, "https://github.com/martinsumner/leveled.git", {branch, "master"}}}
]}.

We specify in the release to load leveled and its dependency lz4:

{relx, [{release, { tanodb , "0.1.0"},
                 [tanodb,
                  cuttlefish,
                  cowboy,
                  {leveled, load},
                  {lz4, load},
                  jsx,
                  sasl]},

At this point in time, to be able to compile leveled on Erlang 20.3, we need to add an override to remove the warnings_as_errors option in erl_opts:

{override, leveled,
        [{erl_opts, [{platform_define, "^1[7-8]{1}", old_rand},
                {platform_define, "^R", old_rand},
                {platform_define, "^R", no_sync}]}]}

The code for apps/tanodb/src/tanodb_kv_leveled.erl:

-module(tanodb_kv_leveled).
-export([new/1, get/3, put/4, delete/3, keys/2, dispose/1, delete/1,
                 is_empty/1, foldl/3]).

-include_lib("leveled/include/leveled.hrl").

-record(state, {bookie, base_path}).

new(#{partition := Partition, data_path := DataPath}) ->
        Path = filename:join([DataPath, "leveled", integer_to_list(Partition)]),
        {ok, Bookie} = leveled_bookie:book_start(Path, 2000, 500000000, none),
        State = #state{bookie=Bookie, base_path=Path},
        {ok, State}.

put(State=#state{bookie=Bookie}, Bucket, Key, Value) ->
        R = leveled_bookie:book_put(Bookie, Bucket, Key, Value, []),
        {R, State}.

get(State=#state{bookie=Bookie}, Bucket, Key) ->
        K = {Bucket, Key},
        Res = case leveled_bookie:book_get(Bookie, Bucket, Key) of
                          not_found -> {not_found, K};
                          {ok, Value} -> {found, {K, Value}}
                  end,
        {Res, State}.

delete(State=#state{bookie=Bookie}, Bucket, Key) ->
        R = leveled_bookie:book_delete(Bookie, Bucket, Key, []),
        {ok, State}.

keys(State=#state{bookie=Bookie}, Bucket) ->
        FoldHeadsFun = fun(_B, K, _ProxyV, Acc) -> [K | Acc] end,
        {async, FoldFn} = leveled_bookie:book_returnfolder(Bookie,
                                                        {foldheads_bybucket,
                                                                ?STD_TAG,
                                                                Bucket,
                                                                all,
                                                                FoldHeadsFun,
                                                                true, true, false}),
        Keys = FoldFn(),
        {Keys, State}.

is_empty(State=#state{bookie=Bookie}) ->
        FoldBucketsFun = fun(B, Acc) -> [B | Acc] end,
        {async, FoldFn} = leveled_bookie:book_returnfolder(Bookie,
                                                                                                           {binary_bucketlist,
                                                                                                                ?STD_TAG,
                                                                                                                {FoldBucketsFun, []}}),
        IsEmpty = case FoldFn() of
                                  [] -> true;
                                  _ -> false
                          end,
        {IsEmpty, State}.

dispose(State=#state{bookie=Bookie}) ->
        ok = leveled_bookie:book_close(Bookie),
        {ok, State}.

delete(State=#state{base_path=Path}) ->
        R = remove_path(Path),
        {R, State}.

foldl(Fun, Acc0, State=#state{bookie=Bookie}) ->
        FoldObjectsFun = fun(B, K, V, Acc) -> Fun({{B, K}, V}, Acc) end,
        {async, FoldFn} = leveled_bookie:book_returnfolder(Bookie, {foldobjects_allkeys,
                                                                                                                                ?STD_TAG,
                                                                                                                                {FoldObjectsFun, Acc0},
                                                                                                                                true}),
        AccOut = FoldFn(),
        {AccOut, State}.

% private functions

sub_files(From) ->
        {ok, SubFiles} = file:list_dir(From),
        [filename:join(From, SubFile) || SubFile <- SubFiles].

remove_path(Path) ->
        case filelib:is_dir(Path) of
                false ->
                        file:delete(Path);
                true ->
                        lists:foreach(fun(ChildPath) -> remove_path(ChildPath) end,
                                                  sub_files(Path)),
                        file:del_dir(Path)
        end.

Trying it

From the user perspective nothing changed other than the fact that the data will persist between restarts.

To test it redo the "Trying it" sections from the Handoff and Coverage Calls chapters.

Facundo Batista: Cinéfilo 2018

Otra tanda de películas vistas y las anotadas para ver en un futuro.

No hay grandes descubrimientos pero las cuatro +1 que hay son sólidas.

  • Casi leyendas: -0. Tiene sus partes simpáticas y emotivas, y muchos chistes hacen reir. Pero es demasiado "liviana", casi transparente.
  • Collateral Beauty: +1. Hermosa película sobre la complejidad en algunos casos de lidiar con el tiempo, el amor y la muerte.
  • Dunkirk: -0. No está del todo mal... pero no me gustan las pelis de guerra, no me atrae casi nada, lo desastroso de todo alrededor, conceptualmente, hace que no llegue a ver lo interesante...
  • Ghost of New Orleans: +0. Arranca casi como un cliché caminando, pero después pega algunos giros interesantes que hace que valga la pena.
  • Girl on a Bicycle: +0. Es una comedia romántica tontona, pero linda y divertida, tiene puntos extras por estar hablada todo el tiempo en una mezcla de inglés, alemán, italiano y francés :).
  • Guardians of the Galaxy Vol. 2: -0. Tiene sus momentos, pero hay una cadencia de chistes o gags que no terminan de ser graciosos, y la película no es mucho más que eso...
  • Kiki, Love to Love: +0. Comedia liviana pero divertida con eje en distintas parafilias.
  • Life: +1. Una visión diferente sobre el encuentro de la raza humana con otra vida, en una peli con giros raros, pero no clásicos.
  • Logan: +1. Me gustó mucho, por todo el lado "real" del asunto; no es una peli de superheroes, es una película dramática normal donde un par son mutantes (y obvio, tiene mucha acción y violencia, pero lo que rescato es lo otro).
  • Passage to Mars: +1. Es un documental, un poco lento a veces, pero si te interesa como es la exploración de verdad, imperdible.
  • Personal Shopper: -0. Con partes muy interesantes, y una historia que está bien, pero medio inconexa, muchos agujeros, y le falta como un cierre o propósito.
  • Pirates of the Caribbean: Dead Men Tell No Tales: -0. Me aburrió bastante. Es más de lo mismo, no hay nada nada nuevo :(
  • Spider-Man: Homecoming: +0. Divertida, no mucho más. 100% pochoclera.
  • T2 Trainspotting: +1. Muy buena música, muy buena fotografía. La historia también está muy bien, pero no sé si se entiende todo si no viste la primera.
  • The Red Turtle: -0. Es linda, pero muy lenta! Nos costó llegar hasta el final :/
  • Thor: Ragnarok: +0. La típica de acción de superheroes, pero me divertí bastante, estuvo buena :)
  • Vampyres: -1. No encontré nada nuevo, ni la terminé de ver aburrido de los sinsentidos de la trama y lo malo de las actuaciones.
La desolación, en el Ártico o en Marte

Estas son las nuevas anotadas. Estoy viendo que desde hace un tiempo estoy incorporando bastante películas "no en inglés", pero más que nada porque escucho de ellas! Esto es parte de las mejoras que hablé en algún otro post sobre donde encontrar trailers... que todavía no es perfecto, sin embargo... que me estoy perdiendo trailers (como el de "First man", acá abajo, que me la recomendó Chaghi), pero prefiero perderme trailers de películas mainstream, que luego me entero por otro lado, que de películas menos promocionadas...

  • Camarón: Flamenco y Revolución: (2018; Documentary) - [D: Alexis Morante; A: Camarón de la Isla, Juan Diego]
  • Chavela: (2017; Documentary, Biography, History, Music) Through its lyrical structure, Chavela will take viewers on an evocative, thought-provoking journey through the iconoclastic life of game-changing artist Chavela Vargas. Centered around never before-seen interview footage of Chavela shot 20 years before her death in 2012, and guided by the stories in Chavela's songs, and the myths and tales others have told about her - as well as those she spread about herself - the film weaves an arresting portrait of a woman who dared to dress, speak, sing, and dream her unique life into being.::Aubin Pictures [D: Catherine Gund, Daresha Kyi; A: Pedro Almodóvar, Elena Benarroch, Miguel Bosé]
  • Eva: (2011; Drama, Fantasy, Sci-Fi) Set in 2041, Alex Garel is a well-known robot programmer who after 10 years returns to his home town to work in his old university when his friend Julia brings him a project to create a new line of robot child. There Alex meets his brother David, Lana (Alex's former lover and David's current wife), and Eva, Alex's 10-years-old niece. Looking for inspiration, Alex asks Eva to be the muse of the new robot, watching her attitude and behavior during the time they spend together, making emotional tests to configure its personality. The relationship with his niece gives Alex doubts about finishing the project and awakens old feelings for Lana. At the same time he starts suspecting that perhaps the lovely and imaginative Eva is hiding an important secret about Lana and herself.::Chockys [D: Kike Maíllo; A: Daniel Brühl, Marta Etura, Alberto Ammann]
  • Extraordinary Tales: (2013; Animation, Horror, Mystery) An animated anthology of five tales adapted from Edgar Allan Poe's stories. [D: Raul Garcia; A: Christopher Lee, Bela Lugosi, Julian Sands]
  • First Man: (2018; Biography, Drama, History) A Biopic on the life of the legendary American Astronaut Neil Armstrong from 1961-1969, on his journey to becoming the first human to walk the moon. Exploring the sacrifices and costs on the Nation and Neil himself, during one of the most dangerous missions in the history of space travel.::Matthew Villella [D: Damien Chazelle; A: Claire Foy, Ryan Gosling, Pablo Schreiber]
  • Impulse: (2018; Action, Sci-Fi) 16-year-old Henry Coles is an outsider in her new town of Reston, New York. With a major chip on her shoulder and no friends, she remains withdrawn and isolated, but everything changes when a traumatic encounter with a classmate triggers something deep within Henry- unleashing a power she cannot control. [D: ; A: Maddie Hasson, Sarah Desjardins, Enuka Okuma]
  • The Quietude: (2018; Drama, Thriller) Two sisters, as close as they are different, find themselves after a long separation. [D: Pablo Trapero; A: Martina Gusman, Bérénice Bejo, Edgar Ramírez]
  • Perfect Strangers: (2016; Comedy, Drama) On a warm summer evening, the loving couple of Rocco, a plastic surgeon, and Eva, a therapist, are expecting their good friends to share a pleasant evening over dinner. Everything is in order: The first course is ready, the roast is in the oven, the table is set, and without a doubt, this is going to be a gathering of true friends. Before long, the group will begin the feast; however, in this nice but somewhat ordinary dinner, there's certainly something missing. Perhaps, if everyone placed their mobile phones on the table--and like a dangerous Russian roulette--shared whatever arrived (texts, WhatsApp messages and calls), it would spice things up. Clearly, this uncommon truth-or-dare game has no point among friends who share everything with each other; nevertheless, when the phones start ringing, who will be the one with the sweatiest palms?::Nick Riganas [D: Paolo Genovese; A: Giuseppe Battiston, Anna Foglietta, Marco Giallini]
  • Sicario: (2015; Action, Crime, Drama, Mystery, Thriller) When drug violence worsens on the USA Mexico border, the FBI sends an idealistic agent, Kate Macer (Emily Blunt) on a mission to eradicate a drug cartel responsible for a bomb that had killed members of her team.::Gusde [D: Denis Villeneuve; A: Emily Blunt, Benicio Del Toro, Josh Brolin]
  • Temple Grandin: (2010; Biography, Drama) Biopic of Temple Grandin, an autistic woman who overcame the limitations imposed on her by her condition to become a Ph.D. and expert in the field of animal husbandry. She developed an interest in cattle early in life while spending time at her Aunt and Uncle's ranch. She did not speak until age four and had difficulty right through high school, mostly in dealing with people. Her mother was very supportive as were some of her teachers. She is noted for creating her "hug box", widely recognized today as a way of relieving stress in autistic children, and her humane design for the treatment of cattle in processing plants, which have been the subject of several books and won an award from PETA. Today, she is a professor at Colorado State University and well-known speaker on autism and animal handling.::garykmcd [D: Mick Jackson; A: Claire Danes, Julia Ormond, David Strathairn]
  • Under the Silver Lake: (2018; Comedy, Crime, Drama, Mystery, Thriller) Sam (Andrew Garfield) is a disenchanted 33-year-old who discovers a mysterious woman, Sarah (Riley Keough), frolicking in his apartment's swimming pool. When she vanishes, Sam embarks on a surreal quest across Los Angeles to decode the secret behind her disappearance, leading him into the murkiest depths of mystery, scandal, and conspiracy in the City of Angels.::A24 [D: David Robert Mitchell; A: Topher Grace, Sydney Sweeney, Riley Keough]
  • The House with a Clock in Its Walls: (2018; Comedy, Family, Fantasy, Horror, Mystery, Sci-Fi, Thriller) Lewis Barnavelt, after losing his parents, is sent to Michigan to live with his uncle Jonathan. He discovers his uncle is a warlock, and enters a world of magic and sorcery. But this power is not limited to good people: Lewis learns of Isaac Izard, an evil wizard who wanted to cause the Apocalypse so that he could see what happened afterwards. To do this, he constructed a magical clock with black magic, as long as it exists it will keep ticking, counting down to doomsday. He died before he could finish the clock, but he hid the clock in his house, where Uncle Jonathan now lives. Now Lewis and Jonathan must find the clock before it's too late, and before Isaac's wife, Selena, gets to it. [D: Eli Roth; A: Cate Blanchett, Jack Black, Lorenza Izzo]

Finalmente, el conteo de pendientes por fecha:

(Feb-2013)    2
(Jun-2013)   11   2
(Sep-2013)   16   8
(Dic-2013)   12  12   4
(Abr-2014)    8   8   3
(Jul-2014)   10  10  10   5   1
(Nov-2014)   22  22  22  22   7
(Feb-2015)   13  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
(Jun-2017)                           23  23  21  18
(Dic-2017)                               19  19  18
(May-2018)                                   22  22
(Sep-2018)                                       12
Total:       94  91  89 100  94  97  94  89  84  79

Facundo Batista: Pesos y dólares

El otro día en el canal de Telegram de la familia tiraron este video, que es bastante interesante y cortito, titulado ¿Por qué el contexto actual nos recuerda el 2001?.

Lo miré, y al toque, mientras lo miraba, me puse a escribir un párrafo de respuesta. Que fueron dos. Que fueron varios. Los rescato acá (los revisé, aclaré cosas, agregué una o dos oraciones) para que no se pierda :)

A mí me da terror el FMI no sólo por lo que hizo en el pasado en nuestro país sino por lo que hace en todos lados (miren Grecia) y lo que está volviendo a hacer ahora acá: pedir el achicamiento y desmantelamiento del Estado con el único propósito de garantizar que la plata se va a devolver a los prestamistas, sin importar que el país entre en un espiral descendiente donde nunca más tenga recuperación del sistema industrial, ni que la gente quede desprotegida a nivel de salud, etc...

El video tiene una GRAN falla en el análisis, que es al minuto 2:36 donde muestra que el FMI nos da pesos y nosotros le devolvemos pesos, entonces para tener esos pesos debemos achicar el Estado. El FMI nos da dólares, pero el Estado para funcionar internamente necesita pesos, no dólares. En ese sentido los pesos no son críticos, porque es la moneda que emite el mismo Estado.

¿A dónde van los dólares que el FMI nos presta, entonces? Cuatro grandes grupos:

  • fuga de capitales, por gente que se lleva los verdes a sus cuentas de afuera
  • fuga de capitales, por empresas que en vez de reinvertir acá se llevan las ganancias a sus empresas matrices (Telefónica, Claro, etc)
  • gastos en el exterior (turismo, principalmente)
  • importaciones, ya sea para consumo final (un container de zapatillas) como para otras cosas (un torno para una fábrica, una locomotora para un tren)

Entonces, hay que cortar esa falacia ahí, y darse cuenta que (como sugiere el video al final) la forma de que el Estado funcione mejor es justamente haciendo crecer la economía, que la gente pueda comprar más, gaste más y pague más impuestos.

Banco Central de la República Argentina

Pero los dólares, para el funcionamiento normal (y pagar la inmensa deuda que este gobierno de mierda nos dejó) tiene que salir de algún lado... ¿de dónde? Y a esto es lo que se le llama la "restricción externa"... no es algo que el Estado pueda manejar internamente, depende de determinadas acciones para que entren dólares, para poder usar esos dólares para comprar productos o servicios en el exterior.

¿Por qué dólares, y no euros, libras esterlinas o yuanes? Porque el comercio internacional lo domina Estados Unidos, pero es algo que va a cambiar con el tiempo. Como ejemplo, es totalmente ridículo que nuestras importaciones y exportaciones desde/a Brasil, por ejemplo, tengan que pasar por el dólar.

Pero me fui, perdón, estaba hablando sobre el detalle de dónde se pueden sacar dólares: Exportaciones principalmente, ya sea de productos o servicios, entonces tenemos que tratar de exportar lo más posible... y lo MEJOR posible... esto es, tratar de exportar un cinturón, y no cuero. O sea, exportar con valor agregado! Pero ese modelo es el de un país industrialista, y la derecha en Argentina siempre buscó lo contrario.

Y por otro lado, tenemos que bajar esos cuatro grupos a donde se van los dólares:

  • fuga de capitales, por gente que se lleva los verdes a sus cuentas de afuera: esto hay que restringirlo, y ponerle impuestos... pero ojo, Kristina intentó esto y te dejaba comprar sólo 5 mil dólares por mes, y vieron el quilombo que se armó (vayan a comprar 5 mil dólares ahora por mes).
  • fuga de capitales, por empresas que en vez de reinvertir acá se llevan las ganancias a sus empresas matrices: esto se controla con "cupos" (pueden sacar hasta tanto por año) y por impuestos
  • gastos en el exterior (turismo, principalmente): acá también hay que restringir, pero sin afectar la dinámica de la gente que viaja, porque no te mueve el número, se puede hacer encareciendo esas transacciones
  • importaciones, ya sea para consumo final (un container de zapatillas) como para otras cosas (un torno para una fábrica, una locomotora para un tren): este ítem es crítico, porque va de la mano de la reindustrialización del país... porque de la misma manera que arriba decía que hay que exportar cinturones y no cuerto, eso evita que compremos cinturones afuera, y por otro lado, siempre hay que permitir las importaciones para "formación de infraestructura" (el torno, la locomotora, etc); en este punto, también recuerden (a la hora de restringir importaciones boludas) el ESCÁNDALO que se armó con el gobierno pasado porque "no había vasitos de Starbucks"...

Ahora no hay vacunas contra la meningitis para chicos de 11 años.

Cambiamos.

Mariano Guerra: Creemos en la Web: Nombrando cosas

En la sección anterior (Creemos en la Web: Una potente calculadora) aprendimos a usar javascript como una calculadora, probemos usarla para eso convirtiendo de grados Celsius a Fahrenheit.

En la mayoría de los países se usan los grados centígrados para temperatura, pero en algunos otros se usan los grados Fahrenheit, buscando en google como es la formula de conversión, google me dice esto:

°F = °C x 1.8 + 32

Donde °F significa "el resultado de la conversión, grados fahrenheit" y °C significa "el valor en grados centígrados a convertir".

Como podemos ver, la formula "nombra" valores que van a variar según que queremos convertir, probemos calcular cuanto es 36 grados centígrados a fahrenheit:

36 * 1.8 + 32
< 96.8

El código es bastante similar, pero tiene un par de problemas:

Primero, si nos encontramos con la expresión 36 * 1.8 + 32 en el medio de otro código, como sabemos que partes son "parámetros", es decir, valores que podemos cambiar y cuales son valores fijos de la formula.

Segundo, como sabemos formula de que es? puede ser cualquier calculo.

Tercero, si queremos usar esa formula muchas veces para distintos valores, tenemos que escribir la formula cada vez? Osea que tenemos que recordarla siempre o buscarla siempre? Esto puede hacer que en algún momento nos equivoquemos y la escribamos mal o si necesitamos hacer un cambio tenemos que buscar todas las veces que la usamos y cambiarla.

Todos estos son indicios de lo mismo, nos gustaría ponerle un nombre a la formula y cada vez que la queramos usar simplemente mencionamos el nombre y le proveemos los valores que requiera. En este caso el nombre podría ser "centigradosAFahrenheit".

... silencio ...

Te preguntas porque ese nombre todo junto?

Resulta que en javascript podemos nombrar cualquier cosa siempre que el nombre cumpla con un par de condiciones:

  • Tiene que empezar con una letra, minúscula o mayúscula del alfabeto ingles
  • Luego puede tener cualquier combinación de letras minúsculas, mayúsculas o números

Nombres validos:

  • A
  • b
  • Ab
  • aB
  • a1
  • diaDelMes
  • parametro9

Nombres inválidos:

  • 1a
    • empieza con numero
  • a#
    • tiene un carácter que no es ni una letra ni un numero
  • centígrados a fahrenheit
    • tiene espacios y tilde, no esta permitido
  • año
    • tiene ñ

Otro detalle es que en javascript, las formulas se llaman funciones, como en matemáticas, aunque las funciones de javascript son mas generales que las funciones en matemáticas, funcionan de una manera similar:

  • Una función tiene un nombre
  • Una función recibe cero o mas valores de entrada o parámetros
  • Una función devuelve un valor de salida

Veamos como definir una función bien simple en javascript, una función que no recibe ningún parámetro y siempre devuelve el número 7:

function siete() {
    return 7;
}

Vamos por partes:

Para definir una función en javascript, empezamos escribiendo la palabra "function", que significa función en ingles.

Luego indicamos el nombre de la función, en este caso "siete".

Luego viene la lista de parámetros entre paréntesis, separados por coma si hay mas de uno, en este caso no necesitamos ningún parámetro, así que simplemente abrimos y cerramos los paréntesis.

Luego viene "el cuerpo" de la función, que es donde hacemos los cálculos necesarios, cada calculo se separa del siguiente con un punto y coma ";".

Cuando queremos terminar de ejecutar la función y devolver un valor lo indicamos empezando la linea con la palabra "return" (retornar en ingles) y el valor que queremos devolver, en este caso el numero 7.

Como usamos nuestra primera función? la nombramos y le indicamos los parámetros que le queremos pasar, en este caso no hay parámetros así que de nuevo es simplemente abrir y cerrar paréntesis.

siete()
< 7

Volviendo al caso de la formula de conversión, tenemos un parámetro que es el valor en centígrados que queremos convertir.

Como ese parámetro va a tomar distintos valores para cada conversión, le vamos a poner un nombre, propongo ponerle "gradosC".

Veamos de definir nuestra función:

function centigradosAFahrenheit(gradosC) {
    return gradosC * 1.8 + 32;
}

Como veras seguimos la misma estructura que antes, la palabra "function", seguida del nombre de la función a definir, seguida de la lista de parámetros entre paréntesis, en este caso uno solo, llamado "gradosC", seguido del cuerpo de la función entre llaves.

El cuerpo de la función tiene una sola linea que "retorna" el resultado de calcular gradosC * 1.8 + 32.

Como veras cuando queremos usar un parámetro simplemente lo nombramos y javascript reemplaza el valor que se le asigno en la llamada en ese lugar, por lo que si llamamos a la función con el valor 36:

centigradosAFahrenheit(36)

Nos devuelve:

< 96.8

Lo que hace javascript es reemplazar el valor del parámetro en esta llamada dentro del cuerpo de la función, así que:

return gradosC * 1.8 + 32;

En esta llamada se convierte en:

return 36 * 1.8 + 32;

Vamos viendo que nombrar cosas hace todo mas legible y reusable, dentro de una función podemos nombrar cualquier valor si hace las cosas mas claras.

En este ejemplo es mas o menos claro, pero si quisiéramos ser mas explícitos podríamos ponerle un nombre al resultado como la formula inicial:

°F = °C x 1.8 + 32

Eso lo hacemos con otra palabra especial, cuando queremos nombrar una función nueva usamos la palabra "function", cuando queremos nombrar un parámetro simplemente lo nombramos en la lista de parámetros, pero cuando queremos nombrar un valor en el cuerpo de una función, tenemos que usar la palabra "let", una palabra que podríamos traducir como "sea", veamos su uso así queda mas claro:

function centigradosAFahrenheit1(gradosC) {
    let gradosF = gradosC * 1.8 + 32;
    return gradosF;
}

La linea:

let gradosF = gradosC * 1.8 + 32;

Podríamos traducirla como "sea gradosF igual a gradosC * ...".

En la siguiente linea nombramos gradosF, que es reemplazado por el valor que se le asigno en la linea anterior.

Con esto aprendimos que en javascript hay al menos 3 cosas que podemos nombrar:

  • Funciones: una forma de reusar pedazos de código sin repetirnos
  • Parámetros: una forma de indicar partes del código que pueden variar
  • Variables: una forma de nombrar cálculos intermedios sin repetirnos

Estas 3 formas de nombrar nos permiten hacer nuestro código mas claro, hay un dicho muy conocido en la programación que dice:

Hay solo dos cosas difíciles en la programación: nombrar cosas e invalidar caches.

Ya aprendimos a nombrar cosas, lo difícil es ponerle el nombre indicado a cada cosa para que luego cuando se lea el código de nuevo quede clara la intención.

Sobre caches vamos a aprender eventualmente, alguna vez cuando una pagina no funcionaba bien alguien te habrá dicho que limpies el cache de la computadora, esa es la razón :)

Mariano Guerra: Riak Core Tutorial Part 8: HTTP API

The content of this chapter is in the 06-http-api branch.

https://gitlab.com/marianoguerra/tanodb/tree/06-http-api

How it Works

We are adding a simple HTTP API to our system, it will run on all nodes and allow us to interact with it from the outside.

We will use the Cowboy 2.0 HTTP Server.

Implementing it

We need to add cowboy as a dependency on rebar.config and tanodb.app.src.

Then in tanodb_app.erl we need to start the HTTP API:

setup_http_api() ->
  Dispatch = cowboy_router:compile([{'_', [{"/kv/:bucket/:key", tanodb_h_kv, []}]}]),

  HttpPort = application:get_env(tanodb, http_port, 8080),
  HttpAcceptors = application:get_env(tanodb, http_acceptors, 100),
  HttpMaxConnections = application:get_env(tanodb, http_max_connections, infinity),

  lager:info("Starting HTTP API at port ~p", [HttpPort]),

  {ok, _} = cowboy:start_clear(tanodb_http_listener,
    [{port, HttpPort},
     {num_acceptors, HttpAcceptors},
     {max_connections, HttpMaxConnections}],
    #{env => #{dispatch => Dispatch}}),

  ok.

We get the configuration from the environment, which is set by cuttlefish.

The API handler module tanodb_h_kv's main code:

init(ReqIn=#{method := <<"GET">>}, State) ->
    {Bucket, Key} = bindings(ReqIn),
    reply(ReqIn, State, tanodb:get(Bucket, Key));
init(ReqIn=#{method := <<"POST">>}, State) ->
    {Bucket, Key} = bindings(ReqIn),
    {ok, Value, Req1} = read_all_body(ReqIn),
    reply(Req1, State, tanodb:put(Bucket, Key, Value));
init(ReqIn=#{method := <<"DELETE">>}, State) ->
    {Bucket, Key} = bindings(ReqIn),
    reply(ReqIn, State, tanodb:delete(Bucket, Key)).

Trying it

Single Node

We are going to first test it on a single node.

Get key k1 in bucket b1, which doesn't exist yet:

curl -X GET  http://localhost:8098/kv/b1/k1
{
  "replies": [
    {
      "bucket": "b1",
      "error": "not_found",
      "key": "k1",
      "node": "tanodb@127.0.0.1",
      "ok": false,
      "partition": "1301649895747835411525156804137939564381064921088"
    },
    {
      "bucket": "b1",
      "error": "not_found",
      "key": "k1",
      "node": "tanodb@127.0.0.1",
      "ok": false,
      "partition": "1278813932664540053428224228626747642198940975104"
    },
    {
      "bucket": "b1",
      "error": "not_found",
      "key": "k1",
      "node": "tanodb@127.0.0.1",
      "ok": false,
      "partition": "1255977969581244695331291653115555720016817029120"
    }
  ]
}

Put key k1 in bucket b1 with content hi there:

curl -X POST  http://localhost:8098/kv/b1/k1 -d 'hi there'
{
  "replies": [
    {
      "node": "tanodb@127.0.0.1",
      "ok": true,
      "partition": "1301649895747835411525156804137939564381064921088"
    },
    {
      "node": "tanodb@127.0.0.1",
      "ok": true,
      "partition": "1278813932664540053428224228626747642198940975104"
    },
    {
      "node": "tanodb@127.0.0.1",
      "ok": true,
      "partition": "1255977969581244695331291653115555720016817029120"
    }
  ]
}

Get key k1 in bucket b1, which now exists:

curl -X GET  http://localhost:8098/kv/b1/k1
{
  "replies": [
    {
      "bucket": "b1",
      "key": "k1",
      "node": "tanodb@127.0.0.1",
      "ok": true,
      "partition": "1301649895747835411525156804137939564381064921088",
      "value": "hi there"
    },
    {
      "bucket": "b1",
      "key": "k1",
      "node": "tanodb@127.0.0.1",
      "ok": true,
      "partition": "1278813932664540053428224228626747642198940975104",
      "value": "hi there"
    },
    {
      "bucket": "b1",
      "key": "k1",
      "node": "tanodb@127.0.0.1",
      "ok": true,
      "partition": "1255977969581244695331291653115555720016817029120",
      "value": "hi there"
    }
  ]
}

Delete key k1 in bucket b1:

curl -X DELETE  http://localhost:8098/kv/b1/k1
{
  "replies": [
    {
      "node": "tanodb@127.0.0.1",
      "ok": true,
      "partition": "1301649895747835411525156804137939564381064921088"
    },
    {
      "node": "tanodb@127.0.0.1",
      "ok": true,
      "partition": "1278813932664540053428224228626747642198940975104"
    },
    {
      "node": "tanodb@127.0.0.1",
      "ok": true,
      "partition": "1255977969581244695331291653115555720016817029120"
    }
  ]
}

Get key k1 in bucket b1, which shouldn't exist anymore:

curl -X GET  http://localhost:8098/kv/b1/k1
{
  "replies": [
    {
      "bucket": "b1",
      "error": "not_found",
      "key": "k1",
      "node": "tanodb@127.0.0.1",
      "ok": false,
      "partition": "1301649895747835411525156804137939564381064921088"
    },
    {
      "bucket": "b1",
      "error": "not_found",
      "key": "k1",
      "node": "tanodb@127.0.0.1",
      "ok": false,
      "partition": "1278813932664540053428224228626747642198940975104"
    },
    {
      "bucket": "b1",
      "error": "not_found",
      "key": "k1",
      "node": "tanodb@127.0.0.1",
      "ok": false,
      "partition": "1255977969581244695331291653115555720016817029120"
    }
  ]
}

Cluster

We are going to test it on a cluster now, notice that the port changes, we are sending each request to a different node.

You can see each node's port on the logs at startup:

[info] Starting HTTP API at port 8198

Get key k1 in bucket b1, which doesn't exist yet:

curl -X GET  http://localhost:8198/kv/b1/k1

Notice the node name on the partition field, it may change for you depending on the state of handoff or how vnodes were distributed.

{
  "replies": [
    {
      "bucket": "b1",
      "error": "not_found",
      "key": "k1",
      "node": "tanodb2@127.0.0.1",
      "ok": false,
      "partition": "1301649895747835411525156804137939564381064921088"
    },
    {
      "bucket": "b1",
      "error": "not_found",
      "key": "k1",
      "node": "tanodb1@127.0.0.1",
      "ok": false,
      "partition": "1278813932664540053428224228626747642198940975104"
    },
    {
      "bucket": "b1",
      "error": "not_found",
      "key": "k1",
      "node": "tanodb1@127.0.0.1",
      "ok": false,
      "partition": "1255977969581244695331291653115555720016817029120"
    }
  ]
}

Put key k1 in bucket b1 with content hi there:

curl -X POST  http://localhost:8298/kv/b1/k1 -d 'hi there'
{
  "replies": [
    {
      "node": "tanodb1@127.0.0.1",
      "ok": true,
      "partition": "1255977969581244695331291653115555720016817029120"
    },
    {
      "node": "tanodb1@127.0.0.1",
      "ok": true,
      "partition": "1278813932664540053428224228626747642198940975104"
    },
    {
      "node": "tanodb2@127.0.0.1",
      "ok": true,
      "partition": "1301649895747835411525156804137939564381064921088"
    }
  ]
}

Get key k1 in bucket b1, which now exists:

curl -X GET  http://localhost:8398/kv/b1/k1
{
  "replies": [
    {
      "bucket": "b1",
      "key": "k1",
      "node": "tanodb1@127.0.0.1",
      "ok": true,
      "partition": "1278813932664540053428224228626747642198940975104",
      "value": "hi there"
    },
    {
      "bucket": "b1",
      "key": "k1",
      "node": "tanodb1@127.0.0.1",
      "ok": true,
      "partition": "1255977969581244695331291653115555720016817029120",
      "value": "hi there"
    },
    {
      "bucket": "b1",
      "key": "k1",
      "node": "tanodb2@127.0.0.1",
      "ok": true,
      "partition": "1301649895747835411525156804137939564381064921088",
      "value": "hi there"
    }
  ]
}

Delete key k1 in bucket b1:

curl -X DELETE  http://localhost:8198/kv/b1/k1
{
  "replies": [
    {
      "node": "tanodb2@127.0.0.1",
      "ok": true,
      "partition": "1301649895747835411525156804137939564381064921088"
    },
    {
      "node": "tanodb1@127.0.0.1",
      "ok": true,
      "partition": "1278813932664540053428224228626747642198940975104"
    },
    {
      "node": "tanodb1@127.0.0.1",
      "ok": true,
      "partition": "1255977969581244695331291653115555720016817029120"
    }
  ]
}

Get key k1 in bucket b1, which shouldn't exist anymore:

curl -X GET  http://localhost:8298/kv/b1/k1
{
  "replies": [
    {
      "bucket": "b1",
      "error": "not_found",
      "key": "k1",
      "node": "tanodb1@127.0.0.1",
      "ok": false,
      "partition": "1278813932664540053428224228626747642198940975104"
    },
    {
      "bucket": "b1",
      "error": "not_found",
      "key": "k1",
      "node": "tanodb1@127.0.0.1",
      "ok": false,
      "partition": "1255977969581244695331291653115555720016817029120"
    },
    {
      "bucket": "b1",
      "error": "not_found",
      "key": "k1",
      "node": "tanodb2@127.0.0.1",
      "ok": false,
      "partition": "1301649895747835411525156804137939564381064921088"
    }
  ]
}

Mariano Guerra: Creemos en la Web: Una potente calculadora

En :doc:`datos-con-javascript` aprendimos sobre los distintos tipos de datos en javascript (normalmente abreviado js).

System Message: ERROR/3 (<string>, line 1); backlink

"datos-con-javascript" slug doesn't exist.

Ahora vamos a aprender como realizar operaciones sobre algunos de esos datos, lo suficiente para tener una muy potente calculadora a nuestra disposición.

Ya que mencionamos la calculadora, empecemos por los que están disponibles en una.

Operadores Matemáticos

Suma y resta

Para números enteros (normalmente se le dicen int/ints) y decimales (float/floats):

Con enteros el resultado es otro entero:

2 + 3
< 5
2 - 3
< -1

Como veras si ponemos el menos delante de un numero indicamos que es negativo:

2 - -3
< 5

También podemos poner un mas, aunque no hace mucho efecto:

2 - +3
< -1

Cuando la operación mezcla enteros y decimales, el resultado "se promueve" a decimal:

2 - 3.5
< -1.5

Podemos tener mas de una operación en la misma linea:

2 - 3.5 + 4 - -2
< 4.5

Multiplicación y división

El símbolo de la multiplicación y división son distintos a los de la calculadora o los que escribimos a mano, la multiplicación es * y la división es /, veamos algunos ejemplos:

2 * 3
< 6
10 / 4
< 2.5

Fijate que la división devuelve decimales aunque los números sean enteros.

Mezclemos un poco:

2 * 3 / 4
< 1.5

Dividiendo por cero nos da un resultado raro:

10 / 0
< Infinity

Y su par:

10 / -0
< -Infinity

Precedencia

Vimos suma y resta separado de multiplicación y división porque esos operadores tienen distinta precedencia, es decir, se evalúan en un orden establecido, veamos un ejemplo, si te digo que adivines el resultado de:

2 + 3 * 4

Cual crees que es?

  • 14
  • 20

El resultad es 14, porque la multiplicación se "evalua" antes que la suma, si no especificamos explícitamente la precedencia con paréntesis, javascript "inserta" los paréntesis según cierto orden preestablecido.

2 + 3 * 4

Es lo mismo que

2 + (3 * 4)

Yo siempre prefiero poner los paréntesis para que quede clara la precedencia aun cuando no es necesario, esto va a ser aun mas importante cuando aprendamos a un mas operadores, si de hecho queremos el otro resultado, simplemente usamos paréntesis para forzar el orden deseado:

(2 + 3) * 4
< 20

Resto de división

Si en lugar del resultado de la división queremos saber el resto, usamos el operador %:

10 % 3
< 1

Operadores de comparación

Con los operadores que vimos hasta ahora ya tenemos una potente calculadora, pero si recordas el tipo bool del capítulo anterior, recordaras que dije que es el resultado de comparaciones y operaciones lógicas, ahora vamos a ver algunas de ellas.

Igual y desigual

Lo mas fácil que podemos comparar es si dos cosas son iguales, pero como todo en la programación, las cosas son un poco mas complicadas de lo necesario, vamos por partes:

Comparando por igualdad:

1 es igual a si mismo

1 == 1
< true

1 no es distinto a si mismo

1 != 1
< false
1 == 2
< false

1 no es distinto a si mismo

1 != 2
< true

Hasta acá todo bien, pero ya sabemos que en javascript hay dos tipos para indicar ausencia de datos, null y undefined, hace mucho tiempo, intentando hacernos un favor, los creadores de javascript decidieron que ciertas cosas eran iguales cuando claramente no lo son:

null == undefined
< true
"1" == 1
< true

Si, viste bien, el texto con el contenido "1", igual al numero 1, eso les pareció una ayuda, pero el 99.9% de las veces termina siendo un problema, razón por la cual introdujeron otros dos operadores de igualdad mas estrictos, veamos:

null === undefined
< false
"1" === 1
< false
null !== undefined
< true
"1" !== 1
< true

Ahora te doy un consejo, que mas que consejo es orden, siempre usa === y !==, porque cuando veas código usando las versiones mas flexibles, no sabes si hay un posible error en ese código o si la persona realmente quiere hacer una comparación mas flexible.

Mayores y Menores

Si queremos saber si un numero es menor o mayor a otro usamos los operadores de comparación correspondientes:

a > b
es a mayor que b?
a >= b
es a mayor o igual que b?
a < b
es a menor que b?
a <= b
es a menor o igual que b?
1 < 2
< true
1 <= 1
< true
1 > 2
< false
1 >= 1
< true

Precedencia

La siguiente linea:

1 + 2 * 3 < 3 * 4 + 5
< true

Es equivalente a:

(1 + (2 * 3)) < ((3 * 4) + 5)
< true

Pero antes de seguir intentando memorizarte el orden, mas fácil es siempre usar paréntesis :)

Operadores logicos

En la vida real, cuando tomamos decisiones, usualmente decimos cosas como:

Si el precio del tomate es menor que 5 y el precio de la lechuga es menor
que 6 o el precio del ajo es mayor a 7, entonces...

En esa expresión ya sabemos como expresar los números y las comparaciones, lo que nos falta expresar son los "y" y los "o", vamos a por ellos.

Conjunción (y)

La conjunción es cuando el resultado de una expresión es cierta/verdadera/true si ambas partes son verdaderas. En javascript el símbolo que usamos para expresarla es &&, veamos todos los casos de este operador en una tabla:

Operación Resultado
true && true true
true && false false
false && true false
false && false false

Como podemos ver, solo es verdadero si ambos lados son verdaderos, veamoslo en javascript

true && true
< true
true && false
< false
false && true
< false
false && false
< false

En ambos lados podemos poner cualquier expresión que evalúe a true o false:

1 === 1 && 1 !== 1
< false
1 === 1 && 1 !== 2
< true

Como con los otros operadores, podemos tener mas de uno:

1 === 1 && 1 !== 2 && 2 === 2
< true

Disyunción (o)

La disyunción es cuando el resultado de una expresión es cierta/verdadera/true si al menos una de las partes son verdaderas.

En javascript el símbolo que usamos para expresarla es ||, veamos todos los casos de este operador en una tabla:

Operación Resultado
true || true true
true || false true
false || true true
false || false false

Veamoslo en javascript

true || true
< true
true || false
< true
false || true
< true
false || false
< false

En ambos lados podemos poner cualquier expresión que evalúe a true o false:

1 === 1 || 1 !== 1
< false
1 === 1 || 1 !== 2
< true

Como con los otros operadores, podemos tener mas de uno:

1 === 1 || 1 !== 2 || 2 === 2
< true

Negación (no)

En español, si decimos "no es verdadero", nos referimos a que es falso, y al revés, "no es falso", indica que es verdadero. En lógica este operador se llama "not" (no en ingles), en js el operador es ! y se pone delante de un valor para "negarlo":

!true
< false
!false
< true

Podemos negar cualquier expresión, si es mas que un valor, lo ponemos entre paréntesis:

!(1 === 1) || (1 === 1)
< true
!(!(1 === 1) || (1 === 1))
< false
!!true
< true

Sobre valores símil falsos y cortocircuitos

Si te gusta romper todo lo que cae en tus manos como yo, se te habrá ocurrido "que pasa si pongo valores raros en esos operadores", bueno, javascript intenta ayudarnos, pero a veces sus ayudas terminan siendo poco útiles, veamos algunos ejemplos.

Si negamos dos veces un booleano, nos da el mismo valor:

!!true
< true

Que pasa si negamos otros valores? veamos:

!0
< true
!1
< false

Como le pedimos que niegue un valor y js espera un booleano, al darle otra cosa lo "convierte" (coherse en ingles) a booleano siguiendo ciertas reglas, todos los tipos tienen un valor que evalúa a falso y todos los otros a verdadero, si negamos dos veces cualquier valor obtenemos su valor booleano, por lo que podemos decir que cero es "falsy" (es una expresión en ingles que podríamos traducir como "similar a false") y cualquier otro numero es verdadero:

!!0
< false
!!1
< true
!!-1
< true
!!42
< true
!!""
< false
!!"0"
< true
!![]
< true
!!{}
< true
!!null
< false
!!undefined
< false

Entonces podemos decir que 0, el texto vacío, null y undefined, cuando son evaluados en operaciones que esperan un valor booleano, actúan como false, todos los otros, como true.

Y los cortocircuitos?

En los operadores && y || hay casos donde viendo el lado izquierdo de la operación, javascript ya sabe cual es el resultado, veamos de nuevo las tablas de ambos operadores:

Operación Resultado
true && true true
true && false false
false && true false
false && false false

Si el lado izquierdo es false, el resultado va a ser false, por lo cual para que el código se ejecute mas rápido, javascript no evalúa el lado derecho y devuelve directamente false.

Operación Resultado
true || true true
true || false true
false || true true
false || false false

Si el lado izquierdo es true, el resultado va a ser true, por lo cual el lado derecho no se ejecuta y devuelve directamente true.

Esto no es importante aun porque todavía no vimos funciones, pero imaginate que javascript encuentra la expresión:

false && lanzarMisil()

Por la lógica de "cortocircuito", el misil no se va a lanzar, ya que la función no va a ser evaluada.

Mariano Guerra: Riak Core Tutorial Part 7: Coverage Calls

The content of this chapter is in the `05-coverage` branch.

https://gitlab.com/marianoguerra/tanodb/tree/05-coverage

How it Works

Since bucket and key are hashed together to decide to which vnode a request will go it means that the keys for a given bucket may be distributed in multiple vnodes, and in case you are running in a cluster this means your keys are distributed in multiple physical nodes.

This means that to list all the keys from a bucket we have to ask all the vnodes for the keys on a given bucket and then put the responses together and return the set of all responses.

For this Riak Core provides something called coverage calls, which are a way to handle this process of running a command on all vnodes and gathering the responses.

In this chapter we are going to implement the tanodb:keys(Bucket) function using coverage calls.

In this case we call tanodb_coverage_fsm:start({keys, Bucket}, Timeout), which is a new module, it implements a behavior called riak_core_coverage_fsm, short for riak_core_coverage finite state machine, it implements some predefined callbacks that are called on different states of a finite state machine.

The start function calls tanodb_coverage_fsm_sup:start_fsm([ReqId, self(), Request, Timeout]) which starts a supervisor for this new process.

When we start the fsm with a command {keys, Bucket} and a timeout in milliseconds, it starts a supervisor that starts the finite state machine process, it first calls the init function which initializes the state of the process and returns some information to riak_core so it knows what kind of coverage call we want to do, then riak_core calls the handle_coverage function on each vnode and with each response it calls process_result in our process.

When all the results are received or if an error happens (such as a timeout) it will call the finish callback there we send the results to the calling process which is waiting for it.

The handle_coverage implementation is really simple, it uses the ets:match/2 function to match against all the entries with the given bucket and returns the key from the matched results.

You can read more about ets match specs in the match spec chapter on the Erlang documentation.

Implementing it

Code in tanodb.erl is really simple:

keys(Bucket, Opts) ->
    Timeout = maps:get(timeout, Opts, ?TIMEOUT),
    tanodb_coverage_fsm:start({keys, Bucket}, Timeout).

In tanodb_vnode.erl we need to implement the handle_coverage callback:

handle_coverage({keys, Bucket}, _KeySpaces, {_, RefId, _},
                State=#state{kv_state=KvState}) ->
    {Keys, KvState1} = tanodb_kv_ets:keys(KvState, Bucket),
    {reply, {RefId, Keys}, State#state{kv_state=KvState1}};

We add two new modules:

tanodb_coverage_fsm
The FSM implementation for the coverage call.
tanodb_coverage_fsm_sup
The supervisor for the FSM processes.

We also add the tanodb_coverage_fsm_sup to the tanodb_sup supervisor tree.

Trying it

Nums = lists:seq(1, 10).
Buckets = lists:map(fun (N) -> list_to_binary("bucket-" ++ integer_to_list(N)) end,
Nums).
Keys = lists:map(fun (N) -> list_to_binary("key-" ++ integer_to_list(N)) end, Nums).

GenValue = fun (Bucket, Key) -> [{bucket, Bucket}, {key, Key}] end.

lists:foreach(fun (Bucket) ->
        lists:foreach(fun (Key) ->
                Val = GenValue(Bucket, Key),
                tanodb:put(Bucket, Key, Val)
        end, Keys)
end, Buckets).

{ok, Items} = tanodb:keys(<<"bucket-1">>).
[{Partition, Node, Keys} || {Partition, Node, Keys} <- Items, Keys =/= []].
[{296867520082839655260123481645494988367611297792,
  'tanodb@127.0.0.1', [<<"key-10">>]},
 {365375409332725729550921208179070754913983135744,
  'tanodb@127.0.0.1', [<<"key-4">>]},
 {137015778499772148581595453067151533092743675904,
  'tanodb@127.0.0.1', [<<"key-8">>]},
 {707914855582156101004909840846949587645842325504,
  'tanodb@127.0.0.1', [<<"key-9">>]},
 {45671926166590716193865151022383844364247891968,
  'tanodb@127.0.0.1', [<<"key-2">>]},
 {753586781748746817198774991869333432010090217472,
  'tanodb@127.0.0.1', [<<"key-9">>]},
 {274031556999544297163190906134303066185487351808,
  'tanodb@127.0.0.1', [<<"key-10">>]},
 {822094670998632891489572718402909198556462055424,
  'tanodb@127.0.0.1', [<<"key-5">>]},
 {319703483166135013357056057156686910549735243776,
  'tanodb@127.0.0.1', [<<"key-4">>,<<"key-10">>]},
 {342539446249430371453988632667878832731859189760,
  'tanodb@127.0.0.1', [<<"key-4">>]},
 {68507889249886074290797726533575766546371837952,
  'tanodb@127.0.0.1', [<<"key-2">>]},
 {799258707915337533392640142891717276374338109440,
  'tanodb@127.0.0.1', [<<"key-5">>]},
 {91343852333181432387730302044767688728495783936,
  'tanodb@127.0.0.1', [<<"key-2">>]},
 {730750818665451459101842416358141509827966271488,
  'tanodb@127.0.0.1', [<<"key-9">>]},
 {159851741583067506678528028578343455274867621888,
  'tanodb@127.0.0.1', [<<"key-8">>]},
 {182687704666362864775460604089535377456991567872,
  'tanodb@127.0.0.1', [<<"key-8">>]},
 {844930634081928249586505293914101120738586001408,
  'tanodb@127.0.0.1', [<<"key-5">>]},
 {867766597165223607683437869425293042920709947392,
  'tanodb@127.0.0.1', [<<"key-3">>]},
 {890602560248518965780370444936484965102833893376,
  'tanodb@127.0.0.1', [<<"key-3">>]},
 {1050454301831586472458898473514828420377701515264,
  'tanodb@127.0.0.1', [<<"key-6">>]},
 {913438523331814323877303020447676887284957839360,
  'tanodb@127.0.0.1', [<<"key-3">>]},
 {1118962191081472546749696200048404186924073353216,
  'tanodb@127.0.0.1', [<<"key-7">>,<<"key-1">>]},
 {1164634117248063262943561351070788031288321245184,
  'tanodb@127.0.0.1', [<<"key-7">>]},
 {1027618338748291114361965898003636498195577569280,
  'tanodb@127.0.0.1', [<<"key-"...>>]},
 {1096126227998177188652763624537212264741949407232,
  'tanodb@127.0.0.1', [<<...>>]},
 {1073290264914881830555831049026020342559825461248,
  'tanodb@127.0.0.1', [...]},
 {1141798154164767904846628775559596109106197299200,
  'tanodb@127.0.0.1',...}]