├── README.md ├── cuentos-el-paseo.md ├── cuentos-la-sopa.md ├── edge ├── README.md ├── conclusions.md ├── concurrency.md ├── correctness.md ├── development-cycles.md ├── features.md ├── images │ └── 058.svg ├── intro.md └── readability.md ├── images ├── 002.svg ├── 003.svg ├── 005.jpg ├── 006.png ├── 007.png ├── 008.svg ├── 009.svg ├── 010.svg ├── 011.svg ├── 013.svg ├── 014.png ├── 053.svg ├── 054.svg ├── 055.svg ├── 056.svg └── 057.svg ├── imaginar-y-vivir.md ├── immutable-avl-trees.md ├── intent-oriented-interfaces.md ├── journaling.md ├── kitchen.md ├── leadership.md ├── lean.md ├── load-shedding-surprises.md ├── mentor.md ├── recommendations.md ├── sardegna.md ├── smoothing.md ├── software-correctness.md ├── synthetic-aging.md ├── xkbcomp.md ├── zettelkasten.md └── zurich-shorts.md /README.md: -------------------------------------------------------------------------------- 1 | # weblog 2 | 3 | This repository contains articles that I generate from collections of public notes from my Zettelkasten in the hope that others may find them useful. 4 | I intend to add more in the near future. 5 | 6 | You can reach me at alefore@gmail.com. I'd be happy to hear from you, though I can't commit to answering technical questions nor providing support. 7 | -------------------------------------------------------------------------------- /cuentos-el-paseo.md: -------------------------------------------------------------------------------- 1 | # Cuentos: El paseo 2 | 3 | En la casa de Oscar sonó el timbre una, dos y tres veces. 4 | Oscar abrió la puerta y vio a un mico que sonreía. 5 | 6 | –Uy, ¡Miguelito! –dijo Oscar. 7 | 8 | –¡Quiubo, Osquitar! ¡Tiempos! 9 | 10 | Oscar le dio a Miguel un abrazo de oso. 11 | 12 | ☙ 13 | 14 | –Es un día estupendo –dijo Miguel–. Vamos a caminar. 15 | 16 | –Um, es muy temprano –murmuró Oscar, frotándose los párpados. 17 | Tengo un sueño de oso. 18 | 19 | –¡No! –dijo Miguel, mirando su reloj–. 20 | Camina, ¡va a ser un día buenísimo! 21 | 22 | Oscar dio un bostezo de oso, 23 | pero se puso su chaqueta y sus zapatos 24 | y salieron al bosque a caminar. 25 | 26 | ☙ 27 | 28 | Caminaron por un sendero rodeado de árboles tan altos 29 | que parecían tocar el cielo. 30 | Sus hojas susurraban canciones al viento. 31 | 32 | –¡Hola, Miguel! ¡Hola, Oscar! 33 | –dijo una garza blanca que se había posado en un árbol. 34 | La garza les hizo una reverencia con su elegante plumaje. 35 | 36 | –¡Hola, Graciela! 37 | –saludaron Miguel y Oscar, alzando sus manos–. 38 | ¿Qué tal? 39 | 40 | –Todo bien –sonrio Graciela–. ¡Qué día!, ¿no? 41 | 42 | –Sí –dijo Miguel–, 43 | es el día perfecto para un paseo. 44 | 45 | ☙ 46 | 47 | Miguel y Oscar siguieron su camino 48 | bajo la sombra de muchos árboles. 49 | Cuando llegaron al río, 50 | se sentaron a la orilla a descansar. 51 | El agua murmuraba suavemente a sus pies. 52 | 53 | –¿Tienes hambre? –preguntó Miguel y sacó dos sánduches de su mochila. 54 | 55 | –¡Tengo un hambre de oso! ¡Qué rico! 56 | 57 | Miguel le dio un sanduche a Oscar. 58 | 59 | –Buen provecho –dijo Miguel. 60 | 61 | –Buen provecho. 62 | 63 | ☙ 64 | 65 | Una rana verde se acercó a saltos mientras disfrutaban la comida. 66 | 67 | –¡Hola, Raul! 68 | –dijeron Miguel y Oscar. 69 | 70 | –¡Hola, Oscar! ¡Hola, Miguel! 71 | –dijo la rana. 72 | 73 | –¿Cómo estás? –preguntó Oscar. 74 | 75 | –¡Super! –dijo Raul–. ¡Qué día!, ¿no? 76 | 77 | –¡Sí! 78 | –dijo Miguel–. 79 | Es perfecto para sentarse a la orilla del río. 80 | 81 | ☙ 82 | 83 | Después de comer, Miguel y Oscar emprendieron el camino de regreso. 84 | Oscar silvaba mientras pasaban bajo la sombra de los árboles. 85 | 86 | –¡Mira! –dijo Oscar, señalando con la mano–. 87 | ¡Qué bonito! 88 | 89 | Miguel miró y vio el hermoso magnolio lleno de flores rosadas. 90 | 91 | –Bellísimo. ¡Cómo huele de rico! 92 | 93 | Oscar cerró los ojos y respiró profundo. 94 | 95 | –¡Delicioso! 96 | 97 | ☙ 98 | 99 | Cuando empezaba a oscurecer, llegaron a la casa de Oscar. 100 | 101 | –Chao –dijo Miguel–. ¡Que descanses! 102 | 103 | –Chao, Miguel. –dijo Oscar mientras se quitaba los zapatos en la entrada. 104 | 105 | –¡Ha sido un día genial! 106 | –dijo Oscar–. 107 | El día perfecto para caminar por el bosque, 108 | sentarse a comer a la orilla del río 109 | y admirar el magnolio. 110 | 111 | –Sí, ha sido un día estupendo. 112 | ¡Vamos a ver cómo será mañana! 113 | 114 | -------------------------------------------------------------------------------- /cuentos-la-sopa.md: -------------------------------------------------------------------------------- 1 | # Cuentos: La sopa 2 | 3 | Miguel, Oscar y Jimena cocinaban una sopa de tomate. 4 | Su aroma se había apoderado de la cocina. 5 | 6 | Miguel revolvía con una cuchara de palo. 7 | –¿Me alcanzas la sal? –dijo, señalando con su cola. 8 | 9 | Oscar abrió un cajón lleno de pequeños frascos de mil colores y sabores 10 | y sacó la sal. 11 | 12 | –Hay tener cuidado 13 | –dijo Miguel, sacudiéndola sobre la sopa–. 14 | Se te puede ir la mano. 15 | 16 | Jimena cerró los ojos y respiró profundo. 17 | –Huele riquísimo –dijo. 18 | 19 | Oscar llevó platos y cucharas a la mesa del jardín. 20 | Al regresar, traía hierbas del huerto 21 | que Miguel echó en la sopa. 22 | 23 | ☙ 24 | 25 | La sopa estaba casi lista. 26 | Oscar y Jimena se sentaron en el jardín. 27 | 28 | –La sopa de Miguel es deliciosa –dijo Jimena. 29 | El día era tan agradable 30 | que se había quitado su bufanda de lana verde. 31 | 32 | –Tengo un hambre de oso –dijo Oscar. 33 | 34 | Miguel, en la cocina, apagó la estufa 35 | y tomó la pesada olla con las dos manos para llevarla al jardín. 36 | Tal y como había dicho Jimena, olía muy bien. 37 | 38 | ☙ 39 | 40 | Al salir al jardín, Miguel no vió una pequeña caja de madera 41 | en el suelo, al lado de la puerta, y… ¡tropezó! 42 | 43 | –¡Ay! –dijo Jimena. 44 | 45 | –¡No! –alcanzó a decir Oscar. 46 | 47 | El tiempo pareció detenerse. 48 | Los animales vieron la olla volar por los aires 49 | y aterrizar con un estruendo. 50 | 51 | ☙ 52 | 53 | –¿Estás bien? –preguntó Jimena, corriendo hacia Miguel. 54 | 55 | –Sí, gracias –dijo Miguel, levantándose. 56 | Tenía el rostro rojo de rabia y frustración. 57 | –Pero… ¡la sopa! 58 | 59 | La sopa se había convertido en un gran charco rojo. 60 | En la mitad había quedado la olla, vacía. 61 | 62 | Miguel estaba muy bravo; 63 | necesitaría unos minutos para calmarse. 64 | 65 | ☙ 66 | 67 | –Creo que podemos olvidarnos de la sopa 68 | –dijo Miguel finalmente. 69 | sentándose junto a Jimena y Oscar. 70 | 71 | –Lo siento mucho –dijo Jimena. 72 | 73 | –Todo ese esfuerzo… ¡para nada! –dijo Miguel. 74 | 75 | –Lo siento mucho –dijo Oscar. 76 | 77 | –¿Esa caja qué es? –preguntó Jimena, 78 | inclinando su largo cuello hacia la pequeña caja de madera. 79 | 80 | –Mis herramientas –dijo Miguel con un suspiro. 81 | 82 | –¡Vieras la cara que pusiste! –dijo Oscar. 83 | 84 | ☙ 85 | 86 | Oscar se puso de pie y tomó la manguera. 87 | –Bueno, ¡vamos a limpiar! 88 | 89 | Miguel sacó un trapero. 90 | 91 | Jimena llevó la caja de herramientas al garage. 92 | Allí no estorbaría. 93 | 94 | –¡Hiciste una cara! –dijo Oscar mientras limpiaba, riéndose cada vez más. 95 | 96 | Miguel sonrió. 97 | 98 | El chorro de agua de la manguera era poderoso 99 | y el jardín pronto quedó reluciente. 100 | 101 | ☙ 102 | 103 | –No habrá sopa, pero otra cosa encontraremos en la cocina 104 | –dijo Miguel–. Podemos hacer un picnic. 105 | 106 | Oscar y Jimena lo siguieron a la cocina. 107 | 108 | –¿Tienes nueces? –preguntó Oscar–. Me encantan. 109 | 110 | Miguel abrió la despensa y le dió un frasco lleno de nueces a Oscar. 111 | Después se puso a cortar tajadas de pan. 112 | 113 | Oscar llenó tres tazas de granola y Jimena les echó yogur. 114 | 115 | –Esta granola me encanta –dijo Miguel–. A ver cómo les parece. 116 | 117 | Jimena abrió la nevera. 118 | –¡Zanahorias! ¡Son mi comida favorita! –dijo. 119 | Sacó cuatro zanahorias, un pepino cohombro, y un montón de fresas. 120 | 121 | ☙ 122 | 123 | Los animales volvieron al jardín. 124 | Cada uno tenía una bandeja con comida. 125 | 126 | –¡Me encantan los picnics! –dijo Oscar. 127 | 128 | –Oscar tiene toda la razón 129 | –sonrió Jimena, y le dio un mordisco a una zanahoria–. 130 | ¡Pusiste una cara! 131 | 132 | Los tres se sentaron en el pasto. 133 | La sopa se había arruinado, 134 | pero eso no les impidió disfrutar el hermoso atardecer. 135 | 136 | -------------------------------------------------------------------------------- /edge/README.md: -------------------------------------------------------------------------------- 1 | # Edge: Lessons 2 | 3 | TODO: I'm still breaking down this text into separate articles. 4 | 5 | * [Introduction](https://github.com/alefore/weblog/blob/master/edge/intro.md) 6 | * [Concurrency](https://github.com/alefore/weblog/blob/master/edge/concurrency.md) 7 | * [Correctness](https://github.com/alefore/weblog/blob/master/edge/correctness.md) 8 | * [Readability](https://github.com/alefore/weblog/blob/master/edge/readability.md) 9 | * [Development cycles](https://github.com/alefore/weblog/blob/master/edge/development-cycles.md) 10 | * [Features](https://github.com/alefore/weblog/blob/master/edge/features.md) 11 | * [Conclusions](https://github.com/alefore/weblog/blob/master/edge/conclusions.md) 12 | 13 | -------------------------------------------------------------------------------- /edge/conclusions.md: -------------------------------------------------------------------------------- 1 | # Edge: Lessons: Introduction: Conclusions 2 | 3 | It's crazy: I've continued working on Edge 4 | for ten years. 5 | I couldn't have known how far I'd go, 6 | how many features I would have implemented. 7 | 8 | I think the main lesson I learned 9 | is to **resist the urge to compromise on correctness**. 10 | This is behind a lot of the lessons in this article, 11 | such as: 12 | 13 | * Digging deep in order to fix bug categories 14 | (rather than only specific manifestations) 15 | 16 | * Making expectations and invariants explicit 17 | –through judicious use of strong types, 18 | runtime validations, 19 | and unit tests. 20 | 21 | I've invested a lot of energy into developing Edge. 22 | I'm glad I've done so. 23 | Starting this effort and continuing to invest in it has been very satisfying. 24 | I've learned a lot on this journey. 25 | 26 | ## Edge: Lessons: Conclusions: Ideas that worked well 27 | 28 | > Las grandes hojas del abedul: 29 | > unas se van, fugaces, con el viento; 30 | > otras caen solas, dando vueltas en el aire frio. 31 | 32 | ### Edge: Lessons: Ideas that worked well: Principles 33 | 34 | The following are a few *ideas or principles* that 35 | influenced my work in Edge, 36 | which worked better than I had initially anticipated: 37 | 38 | * The **most precious resource is your user's mental energy**. 39 | Computers are fast, and 40 | –even if Moore's law is dead– 41 | keep getting faster and cheaper; 42 | if you can spend a few million cycles to 43 | save the user from 44 | having to mentally compute something 45 | (that distracts them from their goal), 46 | do it, go the extra mile. 47 | Never force the user to wait for the results of an operation. 48 | 49 | * If the user is typing a command to be executed 50 | (*e.g.*, fork out an "ls" or "grep" command, kick off a regexp search), 51 | display a preview of the results. 52 | For example, as the user types the regexp, 53 | you can already display "this would match 26 locations". 54 | As the user types an "ls" or "grep" command 55 | (which have no side effects), 56 | you can already display the first lines of the output they'd get. 57 | 58 | * **Being able to check if a program expression 59 | is free of side effects (without running it) 60 | is powerful**. 61 | This enables me to asynchronously evaluate such expressions 62 | when they appear inside a file, 63 | displaying the evaluation results. 64 | Being able to enter (and edit) such expressions as I'm editing a file, 65 | this sort of "evaluate-as-you-type" functionality is very useful. 66 | I recorded 67 | [a demo](https://asciinema.org/a/pCa75UZYlVXD0uboyLQl5zUbi) 68 | for a friend who asked about this. 69 | 70 | * **Optimize for development speed**. 71 | You can't know 72 | the direction in which you'll take a function or module in the future, 73 | but you can always invest in making your software more malleable. 74 | Make it easier to iterate faster. 75 | Avoid optimizing things for performance 76 | in ways that leave you stuck on local maxima. 77 | 78 | ## Edge: Lessons: Conclusions: What's Next? 79 | 80 | > He savors a hearty brunch; takes his time. 81 | > 82 | > Sitting beside him, feet dangling, roller blades already on, his daughter hums eagerly. 83 | 84 | Who knows where Edge will be in ten years? 85 | Will I still be extending it? 86 | 87 | This section lists some ideas I'd like to explore. 88 | 89 | ### Edge: Lessons: Improve the extension language 90 | 91 | The extension language could use some improvements. 92 | 93 | I'd like to be able to define structures 94 | entirely within the extension language. 95 | This hasn't been too constraining for me 96 | (because I can define structures within the host language 97 | and easily expose them to extensions), 98 | but I think it would enable extensions to grow significantly. 99 | 100 | I would also like to support some form of generics. 101 | Currently, for generic classes like "set" or "vector" 102 | I have to define specific subtypes in the host language 103 | (e.g., `SetString`, `VectorString`, `VectorInt`, etc.). 104 | It's easy to define this in the host language, but far from ideal. 105 | 106 | ### Edge: Lessons: File inspection/manipulation API 107 | 108 | I'd like to provide friendlier ways to express operations 109 | that filter or transform sub-trees of a document, such as: 110 | 111 | * Find all links with the text `xyz` within a bullets list in a section 112 | where the header is `Tags` in this Markdown file. 113 | * Apply 114 | to all those links, transforming their text. 115 | 116 | This API should probably be based on the parsed syntax tree. 117 | 118 | Obviously, I have some APIs to inspect and modify file contents, 119 | but they are line/character oriented, 120 | which makes them too low-level 121 | –expressing intent is difficult. 122 | 123 | ### Edge: Lessons: Zettelkasten improvements 124 | 125 | The functionality to manipulate my Zettelkasten can be refined further. 126 | 127 | For example, I'd like to be able to add more formal annotations to my notes and… 128 | use those annotations as I'm editing files. 129 | Essentially, allow me to define stronger semantics in my notes, 130 | deriving ontologies. 131 | 132 | ### Edge: Lessons: CSV integration 133 | 134 | I'd like to make it easy to use Edge to load a *medium* 135 | (~1e5-rows range) 136 | CSV file and interactively manipulate it. 137 | Edge has CSV support, but it's fairly rudimentary. 138 | 139 | For example, to quickly express things like the following 140 | (probably based on Edge's extension language): 141 | 142 | * "What's the average of the 5th column 143 | across rows where the 2nd column is greater than the 1st column?" 144 | 145 | * "Plot a histogram of the values in the 2nd column, 146 | weighing each entry by the result of multiplying the 4th and 5th columns." 147 | 148 | * "What's the Pearson correlation coefficient between two columns?" 149 | 150 | ### Edge: Lessons: Audio improvements 151 | 152 | I want to find better ways to use audio as an additional information channel. 153 | 154 | Edge has had audio support based on libao for many years. 155 | However, it doesn't work very well: 156 | interruptions or quitting sometimes leaves the audio device in a strange state. 157 | As a result, I end up almost always muting it 158 | (by adding `--mute` to `~/.edge/flags.txt`). 159 | 160 | For example, when you search for a regular expression, 161 | Edge plays a different tune based on the number of results 162 | (simplifying, something like: 163 | "error", "zero matches", "one match", "two matches", "many matches"). 164 | 165 | This would require making the audio support more robust. 166 | Once that's in place, I intend to explore more ideas 167 | about how to use sound. 168 | 169 | -------------------------------------------------------------------------------- /edge/concurrency.md: -------------------------------------------------------------------------------- 1 | # Edge: Lessons: Concurrency 2 | 3 | TL;DR: 4 | My experience with Edge is that threads-based concurrency, 5 | when used carefully, 6 | is worth the additional complexity it unavoidably brings. 7 | Shifting work off to separate threads 8 | that communicate results back to the main thread 9 | helps make the editor significantly more responsive. 10 | 11 | The following techniques helped: 12 | 13 | * Making objects immutable (or, at least, immutable-assignable) 14 | * Using types that explicitly ensure correct access to shared data 15 | * Using a futures API for async work. 16 | 17 | ## Edge: Lessons: Preamble 18 | 19 | This document is part of 20 | [a series of articles](https://github.com/alefore/weblog/blob/master/edge-lessons.md) 21 | articulating lessons I've learned 22 | during the 10 years I've been developing my own text editor. 23 | 24 | ## Necessary 25 | 26 | Why is concurrency necessary? 27 | I expect this will be obvious to most developers; 28 | I don't think I have much new to say here. 29 | 30 | You can only get so far with a single thread/process. 31 | Parallelism lets you offload work from the main thread; 32 | this allows you to do significantly more expensive operations 33 | than when you're constrained to the amount of time between refreshes 34 | (*i.e.,* before the user perceives lag). 35 | 36 | I think I first added a background thread in 37 | [91c1cfe](https://github.com/alefore/edge/commit/91c1cfe99ee0e67cd875806abdd0cf1314b6a9fd), in 2017-04-15, 38 | relatively early in Edge's history. 39 | I avoided this type of concurrency 40 | for what felt (but, looking at the data, wasn't!) a long time. 41 | I feared the unavoidable complexity it would bring. 42 | 43 | ### Limits of non-parallel concurrency 44 | 45 | Depending on your requirements, you may be able to stay single-threaded 46 | and just make all your operations interruptible and resumable. 47 | I still do that in some parts of Edge, for example: 48 | 49 | * The VM uses a `Trampoline` class when evaluating expressions 50 | ([header](https://github.com/alefore/edge/blob/master/src/vm/expression.h)). 51 | After a certain amount of "evaluation steps" 52 | (deliberately defined somewhat vaguely), 53 | it yields back to the caller. 54 | 55 | * The GC implementation is both highly concurrent 56 | (both parallelizing various operations 57 | and deferring work to background threads) 58 | and interruptible: 59 | if a call to `Pool::Collect` outlasts a timeout, the operation is aborted 60 | (but a subsequent call will resume where we had left off). 61 | 62 | This lets you achieve concurrency without parallelism. 63 | But, at some point, as your logic grows, 64 | the complexity of this approach 65 | –explicitly maintaining all the state machinery 66 | required to be able to stop and resume at arbitrary points– 67 | becomes worse than just carefully allowing concurrency in. 68 | You are also limited to a single processor. 69 | 70 | ### Concurrency: Threads vs other approaches 71 | 72 | Of course, you can use processes (*e.g.,* `fork`) rather than threads. 73 | And, of course, you can also use non-parallel concurrency. 74 | When it suffices, you really should. 75 | 76 | My experience with Edge is that threads, 77 | when used carefully, 78 | go further (though they can also bring more headaches). 79 | 80 | While some functions 81 | (such as file-related and many other system calls) 82 | offer async versions, 83 | they tend to be cumbersome and brittle 84 | –*e.g.*, you can't use types 85 | to ensure that you don't accidentally call a blocking version. 86 | Using the regular sync versions in separate threads tends to be easier. 87 | 88 | As an example, 89 | I do this in my file-system wrapper 90 | ([header](https://github.com/alefore/edge/blob/master/src/infrastructure/file_system_driver.h)), 91 | which receives a work queue. 92 | The work queue helps it ensure 93 | that the*results* of all async calls 94 | are always received and processed 95 | by the original thread 96 | (which explicitly handles the work queue). 97 | 98 | ## Constness 99 | 100 | The **benefits of making types immutable 101 | often outweigh the costs**. 102 | 103 | Every type should define `const` semantics 104 | that make it thread-compatible. 105 | When you define a type 106 | you're actually defining two: 107 | the mutable `Type` 108 | and the immutable and thread-compatible `const Type`. 109 | 110 | Unless a class is explicitly marked as thread-safe, 111 | before exposing an instance to multiple threads, 112 | you should drop all non-`const` references. 113 | This is often (depending on the class) enough to avoid data races, 114 | reducing the challenge of writing concurrent code to simply 115 | managing object lifetimes. 116 | 117 | This section talks about how Edge implements `const`ness. 118 | The motivation goes significantly beyond just managing concurrency, 119 | but that's a very important part. 120 | 121 | TODO: Move the following paragraph: 122 | 123 | A pattern that deserves mention is balanced trees of immutable objects. 124 | This is very specific compared to the previous patterns, 125 | but this structure is so versatile that it deserves its own mention. 126 | Mutation operations don't mutate the tree, 127 | they return new trees with the operation applied. 128 | Trees new and old share as many branches as possible. 129 | This allows very efficient implementation of most operations. 130 | Inserting, erasing or retriving an element at an arbitrary location 131 | all have logarithmic runtime complexity. 132 | 133 | ### Immutable Assignable 134 | 135 | **Assignable references to deeply-immutable objects are very versatile**. 136 | These types are classes where all methods are `const` except for two: 137 | the move constructor and the assignment operator. 138 | Objects of such types aren't strictly immutable, but… close. 139 | 140 | Deeply-immutable objects can be too cumbersome: 141 | without assignment support, 142 | customers are very frequently forced to wrap instances 143 | inside `NonNull>` 144 | or `NonNull>` explicitly. 145 | 146 | This is avoided by these mostly-immutable objects, 147 | which behave as assignable pointers to nested deeply-immutable objects. 148 | I've read that this roughly matches the behavior of immutable structs in C#. 149 | 150 | To implement such types, 151 | I usually nest all data fields in a private `Data` struct 152 | and give a single field to the class, 153 | of type `NonNull>`. 154 | 155 | class Line { ... 156 | struct Data { 157 | LazyString contents; 158 | }; 159 | 160 | NonNull> data_; 161 | }; 162 | 163 | This ensures that the state is immutable while 164 | allowing references to be updated, something like this: 165 | 166 | Line line = GetFirstLine(...); 167 | while (line.IsEmpty()) 168 | line = GetNextLine(line, ...); 169 | line = AddSuffix(line, ...); 170 | 171 | Using `NonNull>` 172 | also allows trivial shallow copies of the const values. 173 | 174 | The resulting classes are thread-compatible, though not thread-safe; 175 | assignment needs to be protected. 176 | 177 | #### Examples 178 | 179 | The following are examples of immutable classes in Edge: 180 | 181 | * [`LineSequence`](https://github.com/alefore/edge/blob/master/src/language/text/line_sequence.h) 182 | * [`Line`](https://github.com/alefore/edge/blob/master/src/language/text/line.h) 183 | * [`ConstTree`](https://github.com/alefore/edge/blob/master/src/language/const_tree.h) 184 | 185 | ### Builder Pattern 186 | 187 | I've found a "builder" pattern fairly versatile. 188 | I use **mutable thread-compatible builder instances 189 | to *create* immutable 190 | (and thus thread-safe; and thus widely shared) instances**. 191 | 192 | To avoid dependency cycles, 193 | the output type should not depend on the builder. 194 | The output object should declare constructors private, 195 | which means it should give the builder `friend` access. 196 | 197 | Most builder instances are used to build a single output. 198 | As a performance optimization, 199 | I avoid deep copies in `Build`. 200 | To do this, I make the `Build` method 201 | require `*this` to be an rvalue reference (`&&`) 202 | —so that data can be moved out of the builder— 203 | and implement an explicit `Copy` method 204 | for the few customers that need it, e.g.: 205 | 206 | class LineBuilder { ... 207 | Line Build() &&; 208 | LineBuilder Copy() const; 209 | }; 210 | 211 | Unfortunately, this can be a bit cumbersome 212 | (because of the additional `std::move` calls for some customers). 213 | 214 | ### Examples 215 | 216 | TODO: Link. Add more. 217 | 218 | * [`Line`](https://github.com/alefore/edge/blob/master/src/language/text/line.h) 219 | and 220 | [`LineBuilder`](https://github.com/alefore/edge/blob/master/src/language/text/line_builder.h) 221 | 222 | ## Thread safety 223 | 224 | My **`concurrent::Protected` template has worked well** 225 | for classes that need to be thread-safe (and stay non-const): 226 | [src/concurrent/protected.h](https://github.com/alefore/edge/blob/master/src/concurrent/protected.h) 227 | 228 | Using types effectively can boost thread-safety significantly. 229 | Before I introduced `concurrent::Protected`, 230 | Edge only had a relatively small amount of parallelization. 231 | I was wary of the insidious woes brought upon by multi-threading 232 | and wanted to only allow it in a controlled and careful manner. 233 | 234 | And yet, even though `concurrent::Protected` isn't 100% fool-proof 235 | (one doesn't have to try too hard to escape its protections), 236 | adopting it 237 | —starting to rely on types 238 | to ensure that locks are acquired 239 | before the data they protect can be accessed— 240 | already helped me detect bugs 241 | ([example, where `WorkQueue::NextExecution` was neglecting to lock a 242 | mutex](https://github.com/alefore/edge/commit/69fafea1d557bbb16ca579067dfc3f68a7712c0d)). 243 | 244 | The amount of concurrency in Edge has only grown since. 245 | I'm deferring a lot more work to background threads. 246 | I don't think I'd have been able to get this far 247 | if I had not relied on types to maintain correctness. 248 | 249 | There are other solutions to this problem, such as 250 | [Abseil's lock annotations](https://abseil.io/docs/cpp/guides/synchronization#thread-annotations) 251 | that probably work about as well, perhaps even better. 252 | 253 | ## Edge: Lessons: Futures 254 | 255 | I've had **great success using a Futures API** 256 | to implement asynchronous requirements. 257 | 258 | Futures-based code is not as clean as synchronous code, but close. 259 | It is much cleaner than callback spaghetti. 260 | Code still generally reflects that a function is creating and returning a value, 261 | and its caller is consuming the returned value. 262 | 263 | TODO: Error handling in futures. 264 | 265 | ### Single consumer 266 | 267 | My futures implementation **supports setting only one consumer per future**. 268 | Once available, the **future's value is passed as a value to the consumer** 269 | (rather than, for example, handing out a `const` reference). 270 | This allows me to use futures of moveable non-copyable types. 271 | 272 | For situations where multiple listeners are desirable, 273 | turn a regular future into a `ListenableFuture` 274 | ([implementation](https://github.com/alefore/edge/blob/master/src/futures/listenable_value.h)) 275 | which holds the value and allows setting multiple listeners, 276 | which receive `const` references (rather than the value itself). 277 | 278 | ### WorkQueues 279 | 280 | One pattern I've found useful is to schedule computations in background threads 281 | and use futures in a way that 282 | ensures that the output values 283 | are received by the original thread 284 | (that scheduled the computation). 285 | 286 | This allows objects that aren't yet thread-safe 287 | (for Edge the main case would be the `OpenBuffer` class) 288 | to still delegate work to thread pools. 289 | All they need to do is ensure that 290 | all values they capture in lambdas they pass to thread pools 291 | are thread-safe. 292 | The outputs of those asynchronous computations 293 | will be received in the main thread. 294 | 295 | The customer interface is that the `ThreadPoolWithWorkQueue::Run` method 296 | returns a `futures::Value` that will be notified in the calling thread: 297 | 298 | thread_pool 299 | .Run([...] -> Output { 300 | // This runs in a background thread in the thread-pool. 301 | return ExpensiveComputation(...); 302 | }) 303 | .Transform([this](Output output) { 304 | // Capturing `this` is fine, even if `this` isn't thread-safe: this 305 | // lambda will run in the main thread, thanks to the work queue. 306 | ... 307 | }); 308 | 309 | This works because `ThreadPoolWithWorkQueue` 310 | schedules notification of these futures 311 | through the `WorkQueue`. 312 | All we need to do is ensure that these `WorkQueue` instances 313 | are advanced in the appropriate thread. 314 | 315 | #### Examples 316 | 317 | I use `ThreadPoolWithWorkQueue` in many places. 318 | For example, my `FileSystemInterface` 319 | schedules all blocking operations in a background thread, 320 | but uses a `ThreadPoolWithWorkQueue` to ensure 321 | that their results are received in the main thread. 322 | Customers of `FileSystemInterface` just adopt the futures interface; 323 | they will continue to run in a single thread 324 | (and don't even need to support single-thread reentrancy). 325 | 326 | ### Cancellation 327 | 328 | Being able to **cancel asynchronous computations that have become irrelevant** 329 | can be fairly important. 330 | The pattern I've landed on is the use of a `DeleteNotification` class 331 | that contains a `futures::ListenableValue`. 332 | 333 | A producer that wants to support cancellation 334 | simply receives among its inputs 335 | a `futures::ListenableValue` abort notification. 336 | The producer can either poll on this listenable future 337 | or add a listener callback. 338 | The producer's customer simply creates a `DeleteNotification`, 339 | passes its corresponding future to the producer, 340 | and ensures that the `DeleteNotification` is retained 341 | as long as the future value being produced is still relevant. 342 | 343 | For example, as the user types in a prompt 344 | (e.g., open a file, search for a regexp…) 345 | I kick off background work 346 | (to show a preview of files; 347 | or a count of matches based on the query the user has already entered). 348 | If the user types a second character before this work completes, 349 | I just replace the `DeleteNotification` instance with a newly built instance 350 | (which signals to the background thread that its output has become irrelevant 351 | and it should just abandon whatever partial results it has computed) 352 | and kick off a new operation. 353 | 354 | ### Futures in Const Objects 355 | 356 | You can store `const` views of `ListenableFutures` inside `const` structures. 357 | The producer will eventually give them a value, triggering consumers' execution. 358 | When parts of an object are computed asynchronously, 359 | this allows customers to access each such part as soon as it is ready. 360 | 361 | At first, I found this pattern somewhat… counterintuitive. 362 | It feels strange to tag as `const` 363 | an object whose parts are still being constructed. 364 | 365 | But I think it makes sense. 366 | The contract of the future never changes: 367 | customers can add callbacks that will run 368 | when the value arrives (or immediately). 369 | All callbacks will receive exactly the same value when they run. 370 | 371 | The alternative would be to only return the object once all its parts are ready 372 | (i.e., rather than building an object 373 | containing futures for its asynchronous parts, 374 | build a future of the fully-built object). 375 | But this would unnecessarily delay things. 376 | 377 | #### Examples 378 | 379 | A good example of this pattern 380 | is the `LineMetadataEntry` class 381 | ([source](https://github.com/alefore/edge/blob/master/src/language/text/line.h)). 382 | When loading lines in a file, 383 | Edge tries to compile them 384 | (as expression of Edge's extension language) 385 | and –if they compile and the resulting expressions are free of side-effects– 386 | evaluate them and display the evaluation results. 387 | 388 | For example, 389 | Edge will display "7.60417" 390 | next to a line that contains "365 / (52 - 4)". 391 | 392 | Such evaluation could take a while 393 | (depending on the complexity of the expression), 394 | so Edge feeds these evaluations to a thread pool, 395 | upholding its principle of avoiding blocking the main thread. 396 | 397 | The implementation is very natural: 398 | due to its nested `LineMetadataEntry`, 399 | `Line` contains: 400 | 401 | * A future with the string representation of the evaluation result. 402 | * The initial text that should be displayed 403 | if the future doesn't yet have a result. 404 | 405 | ## Challenges 406 | 407 | I consider the following unsolved challenges when it comes to concurrency. 408 | I think they continue to slow me down 409 | and I would like to find better ways to deal with them. 410 | 411 | * Mangled stacks. 412 | The use of asynchronous techniques such as futures or callback spaghetti 413 | results in very confusing stacks, 414 | which makes debugging difficult. 415 | 416 | -------------------------------------------------------------------------------- /edge/development-cycles.md: -------------------------------------------------------------------------------- 1 | # Edge: Lessons: Development cycles 2 | 3 | TL;DR: Things that seem impossibly difficult today 4 | may become trivial in the future. 5 | My engagement with Edge is extremely bursty and… that's good. 6 | In the short-term, unrelated distractions reduce my focus 7 | (and thus block my progress) on Edge; 8 | but, in the medium-term, they enable transformative ideas to manifest, 9 | which feeds new bursts of development. 10 | 11 | ## Edge: Lessons: Preamble 12 | 13 | This document is part of 14 | [a series of articles](https://github.com/alefore/weblog/blob/master/edge-lessons.md) 15 | articulating lessons I've learned 16 | during the 10 years I've been developing my own text editor. 17 | 18 | ## Bursts & pauses 19 | 20 | ### Bursts 21 | 22 | **My engagement with Edge is extremely bursty**. 23 | Most progress happens in bursts of activity 24 | that happen about once per year 25 | and usually last from two to five months, 26 | during which I commit many changes per day. 27 | After the bursts come long pauses of inactivity. 28 | 29 | #### New Possibilities Open 30 | 31 | > Pastel crumbs on a brown mantle; 32 | > Spring returning to the forest on the hills. 33 | 34 | The factors that sustain these bursts are: 35 | 36 | ┏━━━━━━━━━━━━━━━━┓ 37 | ┃ Transformative ┃ 38 | ┃ ideas ┃ 39 | ┗━┯━━━━━━━━━━━━━━┛ 40 | │ 41 | (+a) ╭─(+b)─╮ 42 | ╭────(+c)─────╮ │ │ │ 43 | ┏━━━━━V━┓ ┏━┷━━V━━━━┷━━━━━━V━┓ 44 | ┃ Focus ┠──(+d)───> Progress ┃ 45 | ┗━━━━━━━┛ ┗━━━━━━━━━━━━━━━━━━┛ 46 | 47 | This starts when "transformative ideas" crystallize 48 | —ideas about new approaches I could try, 49 | all the way from internal implementation details 50 | to very visible changes. 51 | 52 | * Per relationship (a), 53 | when this happens 54 | many features, internal improvements, or optimizations 55 | (for performance or correctness) 56 | that I've been wanting for a long time (sometimes years) 57 | and that previously seemed very hard or even impossible, 58 | suddenly ... fall gracefully into place. 59 | I make a lot of progress. 60 | 61 | * Per relationship (b), 62 | these gains compound: 63 | progress feeds on itself. 64 | Some of the major improvements I've just landed 65 | enable a large set of follow-up effects. 66 | Many adjacent modules become simpler, 67 | faster, 68 | more general, 69 | easier to use, 70 | etc.. 71 | 72 | * Per relationships (c) and (d) respectively, 73 | the excitement from the fast-paced progress is very motivating, 74 | and the increased focus also enables faster progress. 75 | This causes the reinforcing loop that sustains the burst. 76 | 77 | ##### Edge: Lessons: Take Breaks: Bursts: Example GC 78 | 79 | One tangible example 80 | of things "that previously seemed very hard" 81 | falling gracefully into place 82 | are the improvements to Edge's garbage collection (GC) implementation 83 | between 2023-09 and 2023-10. 84 | These changes improved Edge perceptibly, 85 | without making the implementation more complex. 86 | 87 | A burst ending in 2022-12 88 | made GC pauses relatively slow: 89 | fast enough that Edge was still usable, 90 | but slow enough that one could notice when they happened (ugh!). 91 | However, I had the feeling that 92 | Edge's GC implementation was as efficient as I could reasonably expect. 93 | 94 | And then, ten months later, 95 | between 2023-09 and 2023-10, 96 | I optimized the collection 97 | through a series of gradual changes 98 | that made GC pauses imperceptible. 99 | I did this through a combination of: 100 | 101 | 1. Implementing highly-concurrent collection. 102 | Most non-constant-runtime operations now happen in thread-pools 103 | of configurable size. 104 | These are things such as 105 | navigating the set of reachable objects 106 | or deleting unreachable objects. 107 | 108 | 2. Executing work asynchronously. 109 | Not only is work dispatched to multiple threads, 110 | but the main thread now offloads some operations 111 | (*e.g.*, actual deletion of objects) 112 | to a dedicated thread-pool entirely 113 | and no longer waits for their completion. 114 | 115 | 3. Implementing resumable (incremental) collection. 116 | The collection operation detects if it overruns a customer-specified timeout; 117 | when this happens, it just returns directly; 118 | The next collection resumes where we left off. 119 | I had assumed that I would require a very significant rewrite 120 | (since the user can change pointer values 121 | in the middle of interrupted collections). 122 | 123 | 4. Delivering profile-based optimization, 124 | chasing after the functions that consumed significant time 125 | from the main thread. 126 | 127 | Not only did I speed collection up, 128 | I actually managed to simplify the customer interface 129 | ([commit](https://github.com/alefore/edge/commit/42ae36687e96d2ddd2510562368f2965951f9cfd), 130 | [commit](https://github.com/alefore/edge/commit/8eefd3a47f20350e9c18259f9c95811f478b1c25)) 131 | and make usage less error-prone 132 | by removing the burden of calling `Ptr::Protect` from its customers. 133 | 134 | The core implementation 135 | ([gc.h](https://github.com/alefore/edge/blob/master/src/language/gc.h), 136 | [bag.h](https://github.com/alefore/edge/blob/master/src/concurrent/bag.h) and 137 | [gc.cc](https://github.com/alefore/edge/blob/master/src/language/gc.cc)) 138 | didnd't become significantly more complex: 139 | LOC only grew by 3%! 140 | 141 | * Between 2022-12-24 and 2023-08-29 these files didn't change; 142 | they contained 1163 143 | (519 + 214 + 430, respectively) LOC 144 | 145 | * In 2023-10-27, 146 | After all these optimizations, 147 | they grew by a net delta of +35 LOC, 148 | to 1198 149 | (572 + 129 + 497, respectively) LOC. 150 | 151 | In this specific case, LOC is a reasonable proxy 152 | for implementation complexity. 153 | To compute LOC, I excluded unit tests and included header documentation. 154 | 155 | #### Ramp Down 156 | 157 | > Summer afternoon: a swarm of wasps dances around my Spritz. 158 | 159 | Usually after a few months 160 | (though the specific durations vary greatly) 161 | comes a ramp down. 162 | I start pursuing other things in my life. 163 | Edge becomes a secondary endeavor. 164 | 165 | This happens because the supply of transformative ideas is depleted, 166 | causing progress to slow down, 167 | which enables unrelated distractions to reduce my focus on Edge. 168 | 169 | ┏━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━┓ 170 | ┃ Unrelated ┃ ┃ Transformative <─╮ 171 | ┃ distractions ┃ ┃ ideas ┃ │ 172 | ┗━━━━━━━━━━━━┯━┛ ┗━┯━━━━━━━━━━━━━━┛ │ 173 | │ │ │ 174 | │ (+a) ╭─(+b)─╮ (-e) 175 | │ ╭────(+c)─────╮ │ │ │ │ 176 | │ ┏━━━━━V━┓ ┏━┷━━V━━━━┷━━━━━━V━┓ │ 177 | ╰───(-f)────> Focus ┠──(+d)───> Progress ┠──╯ 178 | ┗━━━━━━━┛ ┗━━━━━━━━━━━━━━━━━━┛ 179 | 180 | The order of events is very clear: 181 | 182 | 1. Per relationship (e), 183 | *Progress* depletes the stock of *Transformative ideas* 184 | and their ripple effects. 185 | This dampens the effects (a) and (b). 186 | 187 | 2. *Progress* becomes visibly slower, 188 | dampening relationship (c). 189 | I commit a couple changes a day 190 | but this gradually drops 191 | to a small change or two a week. 192 | I'm still trying to make progress, but … 193 | I've run out of easy things to implement. 194 | everything that remains is way too challenging or impossible 195 | (e.g., everything has been optimized 196 | for performance and correctness and simplicity 197 | as much normal constraints allow). 198 | 199 | 3. *Focus* decreases, 200 | and the effect of relationship (d) disappears. 201 | This allows the tension depicted in relationship (f) to play out: 202 | *Unrelated distractions* decrease my *Focus*. 203 | 204 | Although the tension (f) between *Focus* and *Unrelated distractions* 205 | is present throughout the entire burst, 206 | *Progress* drops first, in step #2. 207 | The shift away from Edge happens afterwards. 208 | *Focus* is necessary but not sufficient. 209 | 210 | ### Pauses 211 | 212 | And then starts a pause. 213 | Other things fully take precedence. 214 | Perhaps I switch to writing a short story, 215 | or 216 | a [Zuri Short](https://github.com/alefore/weblog/blob/master/zurich-shorts.md). 217 | Or learn to play the harmonica. 218 | Or [knit](http://github.com/alefore/knit). 219 | I go on a long trip. 220 | I continue *using* Edge, but I rarely improve it. 221 | 222 | This lasts six months on average, sometimes significantly longer. 223 | Until suddenly I can't resist the itch 224 | to finally fix that annoying behavior 225 | or implement that great idea that found me 226 | and I end up getting sucked back in. 227 | A new cycle begins. 228 | 229 | #### Background Thinking 230 | 231 | > Powdery snow, birches washed white. 232 | > On a twig, a twinkle: a yellow tit chirps. Was it really there? 233 | 234 | What triggers the periods of intense focus? 235 | What enables the fast progress on areas I previously considered out of reach? 236 | 237 | ┏━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━┓ 238 | ┃ Unrelated ┠──(+g)──> Background ┠─(+h⏳)─> Transformative ┃ 239 | ┃ Distractions ┃ ┃ thinking ┃ ┃ ideas ┃ 240 | ┗━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━━┛ 241 | 242 | During the pauses 243 | my brain **keeps making connections and generating and encountering ideas**. 244 | These can be alternative approaches I could try: 245 | new underlying APIs, 246 | different data structures, 247 | entirely new user journeys, 248 | etc.. 249 | 250 | This happens mostly subconsciously. 251 | I'm not working on Edge actively, 252 | but I'm still passively thinking about these problems: 253 | the places where I got stuck 254 | and couldn't figure out a better solution; 255 | the things that annoy me as I use Edge, 256 | even if I don't notice them consciously. 257 | I still read articles about text editors 258 | and find new ideas I had never considered. 259 | 260 | I don't know if this type of medium-term background thinking can be rushed. 261 | Maybe 262 | [journaling](https://github.com/alefore/weblog/blob/master/journaling.md) helps? 263 | Maybe talking to people with different perspectives? 264 | In my experience, 265 | some things help but they only go so far. 266 | This takes time. 267 | 268 | In any case, eventually a new idea, or a group of ideas, 269 | crystallizes and I just have to try things out and ... boom! 270 | The new ideas (often) work, 271 | and they (often) have far reaching implications, 272 | and a new burst begins. 273 | 274 | ### Emotional State 275 | 276 | My emotional state factors strongly into the timing of the cycles. 277 | The timing of bursts is strongly associated 278 | with important events happening in my life: 279 | 280 | * Some bursts started when I was going through difficult personal situations 281 | (*e.g.*, breaking up from a serious relationship) 282 | and wanted some distraction from real life. 283 | This is the case for the long burst from 2019-12 to 2020-05. 284 | 285 | * Many long pauses coincide 286 | with times when I was distracted by real life events. 287 | Perhaps I was doing a lot of traveling, 288 | starting a new relationship, 289 | or distracted with changing apartments. 290 | Being very busy with life events left time for only short-lived bursts during 291 | the pauses from 2015 and 2016; 292 | 2020-05; 293 | and 2021-03. 294 | 295 | The causality goes both ways: 296 | though less frequently, 297 | these cycles have also caused changes in my life. 298 | 299 | * The long burst from 2022-02 to 2022-06 300 | influenced a difficult decision I took at work 301 | to switch away from managing 302 | (which I had been doing for about 5 years) 303 | to a pure engineering role. 304 | The team I was managing grew 305 | from 6 engineers in 2020 306 | to 17 in early 2022. 307 | Most of my time (at work) 308 | became occupied by managerial tasks, 309 | pushing out technical work. 310 | I interpreted the amount of energy I was dedicating to Edge 311 | as a strong sign that I was missing technical work. 312 | Interestingly, the burst stopped shortly after I made the decision 313 | and started searching for someone 314 | I could transfer my management responsibilities to. 315 | 316 | ## Unrelated distractions 317 | 318 | The complete system diagram would be this: 319 | 320 | ┏━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━┓ 321 | ┃ Unrelated ┠──(+g)──> Background ┠─(+h⏳)─> Transformative <─╮ 322 | ┃ distractions <──╮ ┃ thinking ┃ ┃ ideas ┃ │ 323 | ┗━━━━━━━━━━━━┯━┛ │ ┗━━━━━━━━━━━━┛ ┗━┯━━━━━━━━━━━━━━┛ │ 324 | │ │ │ │ 325 | │ ╰──(-i)──╮ (+a) ╭─(+b)─╮ (-e) 326 | │ │ ╭────(+c)─────╮ │ │ │ │ 327 | │ ┏━┷━━━V━┓ ┏━┷━━V━━━━┷━━━━━━V━┓ │ 328 | ╰───(-f)────> Focus ┠──(+d)───> Progress ┠──╯ 329 | ┗━━━━━━━┛ ┗━━━━━━━━━━━━━━━━━━┛ 330 | 331 | This could, however, be reduced to 332 | the main relationships from 333 | *Unrelated distractions* to *Progress*: 334 | 335 | ┏━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━┓ 336 | ┃ Unrelated ┠───(-)─────> Progress ┃ 337 | ┃ distractions ┠──(+⏳)────> ┃ 338 | ┗━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━┛ 339 | 340 | * In the short-term, *Unrelated distractions* hamper *Progress* 341 | because they deplete focus (relationships (f) and (d)). 342 | 343 | * In the long-term, however, 344 | *Unrelated distractions* are necessary for *Progress* 345 | because they enable *Transformative ideas* to manifest 346 | (relationships (g), (h), (a)). 347 | 348 | ## Implications 349 | 350 | What are the main implications? 351 | 352 | ### Acceptance 353 | 354 | > Der Wind nimmt die gelben Blätter des alten Ginkgo. 355 | > 356 | > Frei wie Libellen fliegen sie davon. 357 | 358 | I have to understand and accept when a burst is coming to its conclusion. 359 | **Things that seem impossibly difficult today 360 | may become trivial in the future**. 361 | 362 | I find this very encouraging. 363 | 364 | ### Make it easy to return 365 | 366 | > Stars glow autumn red. 367 | > 368 | > The setting sun retreats from the maple tree. 369 | 370 | Accepting that I'll take breaks means that I should 371 | **make it easy to come back**. 372 | 373 | There's some analogy to 374 | [counter-cyclical fiscal policy](https://ec.europa.eu/eurostat/statistics-explained/index.php?title=Glossary:Counter-cyclical_fiscal_measures#:~:text=Counter%2Dcyclical%20fiscal%20measures%20are,to%20help%20stimulate%20economic%20recovery.). 375 | 376 | To me this is similar to the "two-minute rule" habit formation trick: 377 | leave yourself a lot of crumbs that you can pick up 378 | whenever you happen to have a spare minute or two. 379 | These million small steps tend to add up to meaningful progress. 380 | 381 | #### Document Potential Improvements 382 | 383 | Documenting my ideas for potential improvements 384 | helps me: it makes it easier to find appropriately sized chunks of work, 385 | depending on where I am in the burst-pause cycle. 386 | 387 | I leave many "TODO" notes in the code 388 | listing things that I think I may want to improve some day. 389 | I try too: 390 | 391 | * Classify them by difficulty (trivial, easy, normal) 392 | * Give them hash-tags (to help me find related changes) 393 | 394 | As I'm reading the code working on implementing a specific feature 395 | unrelated ideas come up. 396 | I'm not going to work on them right away; 397 | I'd never accomplish the task I'm working on. 398 | But I'll make sure to document them right away. 399 | 400 | As of 2023-12-21, I have 220 TODO notes in the code. 401 | 16 are marked as trivial, 80 are marked as easy. 402 | 403 | #### Diversity 404 | 405 | I suppose I've been able to keep working on the same project for a decade 406 | because I've been able to work on vastly different sub-projects, 407 | all of which fall under the same umbrella, such as: 408 | 409 | * A memory manager. 410 | * A C++-line VM. 411 | * Arbitrary precision numbers. 412 | * Audio generation 413 | (nothing too fancy, though; 414 | [ref](https://github.com/alefore/edge/blob/master/src/infrastructure/audio.h)). 415 | * A UX for interacting with my Zettelkasten. 416 | * Futures. 417 | * A Naive Bayes implementation 418 | (that I use, for example, to order search results 419 | by their relevance, based on history; 420 | [ref](https://github.com/alefore/edge/blob/master/src/math/naive_bayes.h)) 421 | * Thread pools and work queues. 422 | * A command-line flags module 423 | ([ref](https://github.com/alefore/edge/blob/master/src/infrastructure/command_line.h)). 424 | * Highly concurrent containers (for very specific use cases; 425 | [ref](https://github.com/alefore/edge/blob/master/src/concurrent/bag.h)) 426 | * tty integration 427 | (*e.g.,* parsing the escape sequences produced by an underlying shell with 428 | a pts; 429 | [ref](https://github.com/alefore/edge/blob/master/src/infrastructure/terminal_adapter.cc)). 430 | 431 | ### Incremental Improvement 432 | 433 | > Gli aghi degli abeti in montagna abbracciano calmi i fiocchi di neve. 434 | > Niente ferma una valanga. 435 | 436 | It's easy to underestimate the effect of small changes. 437 | **Changes that, on their own, don't amount to much, 438 | can have very drastic effects as a group**. 439 | I think this is the self-reinforcing loop 440 | of Progress (in one area) enables Progress (in others). 441 | This is a big part of why these bursts happen. 442 | They start as just a few small improvements, 443 | but these small improvements precipitate 444 | and enable many other small improvements and... 445 | eventually they add up to large changes. 446 | 447 | The majority of my commits are small "no-op" changes whose only purpose 448 | is "preparation" to make follow-up changes easier or smaller. 449 | A big change is divided into a million baby steps 450 | and one final simple step that enables the new functionality. 451 | I think this is what enables 452 | things that previously seemed ~impossible 453 | to "suddenly… fall gracefully into place". 454 | 455 | -------------------------------------------------------------------------------- /edge/features.md: -------------------------------------------------------------------------------- 1 | # Edge: Lessons: Features 2 | 3 | ## Edge: Lessons: Preamble 4 | 5 | This document is part of 6 | [a series of articles](https://github.com/alefore/weblog/blob/master/edge-lessons.md) 7 | articulating lessons I've learned 8 | during the 10 years I've been developing my own text editor. 9 | 10 | ## What worked well 11 | 12 | The following are a few *features* that worked better than I anticipated. 13 | I hope this helps other people working on their own text editors. 14 | 15 | ### Linear undo 16 | 17 | **Linear undo**/redo history, 18 | as explained in 19 | [`src/undo_state.cc`](https://github.com/alefore/edge/blob/master/src/undo_state.cc). 20 | If you undo a few transformations and apply a new transformation … 21 | you don't lose the redo history: you can still apply it through undo. 22 | 23 | While the undo history can grow exponentially, this has never mattered in 24 | practice. 25 | 26 | ### Jump in screen 27 | 28 | The ability to **jump to any position in the file that's currently visible** 29 | simply by: 30 | 31 | * Typing `f` (to activate this mode). 32 | * Typing three characters ~matching the text you want to jump to 33 | (with disambiguation when the prefix would match multiple positions). 34 | * Typing return to confirm the selection. 35 | 36 | I'm using this fairly frequently, 37 | comparing with "scrolling" up to the position (or a full regexp search). 38 | 39 | I guess the general principle is to avoid long key presses 40 | (that generate the same key repeatedly); 41 | instead, always replace that with better semantics. 42 | 43 | ### Mutiple cursors 44 | 45 | Native support for **multiple cursors** works well. 46 | 47 | For example, a regexp-search 48 | creates a cursor in every position with a match. 49 | Being able to quickly say 50 | "set the cursors to: a cursor at the beginning of every line 51 | in the current paragraph" 52 | and then just say "add four spaces at each cursor" is powerful. 53 | So is saying "set a cursor on every position (in the current source code file) 54 | where the compiler reported an error". 55 | 56 | ### clang-format 57 | 58 | clang-format integration is a must-have. 59 | 60 | Whenever I save a file of some specific formats 61 | (*e.g.,* C++, Java, Javascript, SQL…), 62 | Edge just pipes it through `clang-format` or similar commands 63 | ([implementation](https://github.com/alefore/edge/blob/master/rc/editor_commands/lib/clang-format.cc)). 64 | 65 | Freeing you from caring about spaces as you're editing 66 | speeds you up significantly. 67 | 68 | Edge has some logic to indent things 69 | roughly predicting what it thinks you want, 70 | but it's very rudimentary 71 | (it predates the integration with `clang-format`). 72 | 73 | ### Preview buffer 74 | 75 | The preview buffer at the bottom of the screen is helpful. 76 | 77 | For example: 78 | 79 | * If the active cursor is on a path to an existing file 80 | (based on some search paths), 81 | Edge gives a preview of its contents 82 | (remembering where the cursor was when that file was last opened). 83 | 84 | * Similarly, Edge lets you preview the output of commands as you type them 85 | (for commands that don't have side-effects). 86 | This means that you can "search as you type" 87 | for commands like like "look", "ls", or "grep". 88 | 89 | * For "destructive" commands (e.g, "git commit…"), 90 | the preview buffer shows you useful data 91 | (depending on the command, 92 | things like a help screen or the output of "git status"). 93 | 94 | ### Compiler buffers 95 | 96 | Native support for "compiler" buffers has worked very well. 97 | 98 | Edge automatically tags certain command buffers as a compiler 99 | (*e.g.*, "if the command is an invocation to `make` or `bazel` or …"). 100 | This causes a few things such as: 101 | 102 | * Parse the contents to detect references to open files. 103 | Edge maintains a database with all these references. 104 | If the compiler references an open file 105 | (which typically means you have errors), 106 | Edge will display an overlay next to the line. 107 | 108 | * Rerun the command whenever a file is saved. 109 | 110 | Saving a source file suffices to kick off compilation; 111 | I continue editing but, as soon as the compiler outputs errors in the file, 112 | I get overlays summarizing the errors. 113 | 114 | ### Git auto-commit 115 | 116 | My Zettelkasten directory contains a `.edge-git-push.txt` file. 117 | Because of this, every time I save a file in my Zettelkasten directory, 118 | Edge automatically commits the change (to the underlying git repository) 119 | and runs `git push`. 120 | 121 | I wouldn't do this for source code repositories: 122 | for those I want to be able to save multiple files 123 | that I can commit as a single logical unit 124 | after significant validations 125 | with a meaningful commit message. 126 | 127 | But for my low-friction Zettelkasten… 128 | this has worked very well, 129 | helping me keep multiple copies in sync. 130 | 131 | ### Good prompts 132 | 133 | I've spent some effort improving the input prompts 134 | (i.e., when Edge has to ask you for a value, 135 | such as the path of the file you want to open 136 | or the value you want to give to a buffer's variable). 137 | Computers are so fast today that there's no reason 138 | not to kick-off processing on every key press. 139 | 140 | What kind of processing? 141 | 142 | * As I'm entering a regular expression to search in the file, 143 | show me how many positions it would match. 144 | 145 | * As mentioned elsewhere, 146 | show me a preview of the output of the side-effects free command I'm typing. 147 | Suppose I want to use `look` to lookup the word "immediately"; 148 | typing `look immed` suffices to narrow it down and show me what I want. 149 | I don't even need to type Enter (and then close the buffer). 150 | 151 | * If I'm typing a path (e.g., to open a file), 152 | show me with colors and additional information: 153 | 154 | * That what I've typed so far matches something 155 | (or show me the first character where it no longer matches something). 156 | 157 | * That what I've typed so far matches exactly 8 files. 158 | If what I've typed so far matches a directory exactly, 159 | show me how many files it contains. 160 | 161 | * That if I pressed Tab right now, this would advance the match 162 | –perhaps to a full match, 163 | perhaps to the point where I have to type more characters to disambiguate. 164 | 165 | * Show me how many "similar" commands I've entered before. 166 | This allows me to quickly search in the history of commands, 167 | simply by entering prefixes and pressing the "up" arrow. 168 | 169 | I've also adjusted some key-bindings to behave optimally 170 | depending on the contents of the prompt. 171 | Specifically, Ctrl+U usually deletes to the beginning of the current line, 172 | but in a path (inside a prompt) it will delete only until the previous slash. 173 | 174 | I've found that, because of this, I'm often running Edge and then, 175 | within Edge, entering the path of the file I want to open 176 | (rather than just using the default shell completion). 177 | 178 | ## Experiments 179 | 180 | This is an attempt to document a few experiments that… 181 | I expected would have worked a bit better than they have. 182 | 183 | These are ideas that held a lot of promise but haven't fully panned out. 184 | It could be that they are bad ideas or it could be that they need more work 185 | before they become useful. 186 | 187 | * Audio integration 188 | 189 | * Dictionary completion 190 | 191 | * Edge supports applying changes to multiple files simultaneously. 192 | For example, I could open 50 files, search for a given identifier 193 | (which puts a cursor in every occurrence), 194 | and then delete the identifier. 195 | While I often do this with multiple cursors within a single file… 196 | I only very rarely edit multiple files at once. 197 | 198 | -------------------------------------------------------------------------------- /edge/intro.md: -------------------------------------------------------------------------------- 1 | # Edge: Lessons: Introduction 2 | 3 | This document is work-in-progress. 4 | As of 2024-04-13, this is still incomplete 5 | and probably contains errors. 6 | I haven't yet had the time to fully review it. 7 | 8 | This document is **an attempt to capture lessons I've learned 9 | after a decade of working on Edge** (my personal text editor). 10 | 11 | Many of the lessons described are fairly technical 12 | but at least one (Bursts & Pauses) is general 13 | —observations about the life-cycle of side-projects. 14 | Among the technical lessons, 15 | many are specific to C++, 16 | but they probably apply, to some extent, 17 | to other languages. 18 | 19 | ## Background 20 | 21 | Edge is a Linux C++ terminal-based text editor. 22 | I started working on 23 | Edge in 2014 24 | –making the 25 | [very first commit](https://github.com/alefore/edge/commit/312ecc2462315e8e0648cbd2680cc0366819df1e) 26 | on 2014-08-09. 27 | I've used Edge ~exclusively since 2015. 28 | 29 | Edge has its own extension language, 30 | which looks like C++ with memory management, 31 | and has logic (such as syntax highlighting) 32 | for editing C++, Markdown, Java, Python 33 | and a few other file types. 34 | 35 | I mainly use it… 36 | 37 | * for programming at work 38 | (though this days I program relatively little at work), 39 | 40 | * to maintain my 41 | [Zettelkasten](https://github.com/alefore/weblog/blob/master/zettelkasten.md), 42 | 43 | * as my planning & productivity management system 44 | (which is integrated with my Zettelkasten, 45 | so this is really repeating the previous point), and… 46 | 47 | * to develop Edge. 48 | 49 | ### Statistics 50 | 51 | As of 2024-04-14, Edge is 67.9k lines of C++ code 52 | (per `wc -l $(find src -name '*.cc' -or -name '*.h' -or -name '*.y'`). 53 | 54 | The following is a plot of commits per day: 55 | 56 | ![Commits per day](images/058.svg) 57 | 58 | ## Caveats 59 | 60 | * I believe there's significant **recency bias** in this distillation. 61 | I'm probably overweighing insights I've reached relatively recently 62 | –towards Edge's tenth anniversary– 63 | and not doing justice 64 | to those from the beginning of the journey. 65 | 66 | * The lessons described here are fairly **subjective**. 67 | These are *not* universally applicable principles. 68 | They rest on various assumptions specific to my context, 69 | which may not apply to other environments or systems. 70 | I'm holding back from prefixing nearly every sentence 71 | with "in my experience" or similar qualifiers, 72 | but they should be assumed. 73 | 74 | -------------------------------------------------------------------------------- /edge/readability.md: -------------------------------------------------------------------------------- 1 | # Edge: Lessons: Readability 2 | 3 | ## Edge: Lessons: Preamble 4 | 5 | This document is part of 6 | [a series of articles](https://github.com/alefore/weblog/blob/master/edge-lessons.md) 7 | articulating lessons I've learned 8 | during the 10 years I've been developing my own text editor. 9 | 10 | ## Make Things Explicit 11 | 12 | **The implementation should reflect your thought process explicitly; 13 | not only its conclusions**. 14 | Removing the thought process makes the software less malleable. 15 | 16 | Early in my career, 17 | I made the mistake of optimizing my implementations for brevity. 18 | If you can say it with fewer words, why more? 19 | 20 | > Je n’ai fait celle-ci plus longue que parce que je n’ai pas eu le loisir de la faire plus courte. 21 | 22 | But simplicity and brevity are different things. 23 | In fact, in software you often reach a point 24 | where extra brevity complicates things. 25 | 26 | It bears saying it explicitly: 27 | just because some expression is shorter (by whatever metric) 28 | doesn't mean it's simpler. 29 | 30 | This has obvious non-controversial implications: 31 | 32 | * Define appropriately named constants; 33 | rather than using magic values directly. 34 | 35 | * Add `const` annotations to relevant class methods. 36 | 37 | * Use appropriate names that convey meaning; eschew abbreviations. 38 | 39 | It also has some less-obvious implications. 40 | 41 | ### Loops 42 | 43 | Most operations on containers (either sets, sequences, maps, etc.) 44 | can be expressed as one of a few canonical operations, 45 | such as finding elements matching a predicate, 46 | transforming elements, 47 | or aggregating elements. 48 | 49 | In those cases, I **avoid writing explicit `for` or `while` loops**. 50 | I prefer standard functions (such as `std::views::transform` and related logic) 51 | for manipulating and aggregating containers. 52 | I'm a big fan of the recent ranges/views APIs. 53 | 54 | #### Edge: Lessons: Make Things Explicit: Loops: Rationale 55 | 56 | Reducing my loops to canonical operations 57 | helps me make the intent more evident: 58 | 59 | * I am just checking if a list has at least an element matching a predicate. 60 | * I am just copying all elements, with some transformation applied. 61 | * I am mutating the container directly. 62 | 63 | Seeing that I'm calling `std::views::filter | std::views::transform` 64 | makes it immediately obvious, 65 | even more obvious than simple range-based loops. 66 | 67 | Consistently avoiding explicit loops has the advantage that 68 | in those cases that can't be neatly reduced to a canonical operation, 69 | **the presence of an explicit `for` or `while` loop alerts you: 70 | there's something unusual in this block**. 71 | 72 | ### Avoid auto 73 | 74 | TODO: Flesh out. 75 | 76 | ## Use lambdas liberally 77 | 78 | I've found that liberal use of lambda expressions can aid readability 79 | in a number of ways: 80 | 81 | * By explicitly capturing a small number of variables 82 | (typically by reference), 83 | it helps you signal that a given scope 84 | (the lambda body) 85 | only has access to a small set of all the available local variables. 86 | 87 | * It helps signal that temporary variables 88 | (which you define within the lambda's scope) 89 | are only defined 90 | for the specific purpose of computing the lambda's output. 91 | This is similar to creating nested scopes 92 | (without using lambda forms). 93 | 94 | * It helps you use various `return` statements 95 | to yield the value that you'll assign 96 | to a given (possibly `const`) variable. 97 | The alternative to this would be to use assignment. 98 | Said differently, this lets you avoid assignment and, 99 | instead, use construction. 100 | One advantage of this is helping you ensure 101 | that you always give the variable a value explicitly 102 | (since your lambda expression has to return a value). 103 | 104 | ### Edge: Lessons: Use std::invoke 105 | 106 | When I declare lambdas that must be run directly, 107 | I use `std::invoke`. 108 | 109 | Rather than: 110 | 111 | […] { … }() 112 | 113 | I write: 114 | 115 | std::invoke([…] { … }) 116 | 117 | This is more wordy, 118 | but makes it more explicit that the lambda is run immediately. 119 | 120 | ## Use std::variant 121 | 122 | I've found `std::variant` very helpful. 123 | I see it as playing a similar role to interfaces, 124 | but allowing you to more easily enumerate the entire set of subtypes. 125 | I use variants with `std::visit` throughout. 126 | 127 | ## Constructors 128 | 129 | I try to keep constructors as simple as possible. 130 | Hard work must be moved to either 131 | customer sites 132 | (for classes that are only instantiated by few customers) 133 | or static factory methods. 134 | 135 | ### Declaration > Initialization 136 | 137 | I prefer to assign default values to class variables in their declaration: 138 | 139 | class Good { … 140 | LazyString value_ = LazyString{"default"} 141 | 142 | public: 143 | Good() = default; 144 | }; 145 | 146 | class Bad { 147 | LazyString value_; 148 | 149 | public: 150 | Bad() : value_(LazyString{"default"}); 151 | }; 152 | 153 | That's preferable mainly because 154 | the variable's declaration is the most natural place to look for its semantics. 155 | There are other smaller benefits 156 | (such as avoiding possible bad declaration order, 157 | or neglecting to assign values when there are multiple non-delegating 158 | constructors). 159 | 160 | This isn't always possible: 161 | sometimes the default value depends on a parameter received by the constructor. 162 | 163 | ### Aggregate initialization 164 | 165 | When instantiating aggregate classes, 166 | I always use aggregate initialization explicitly 167 | (*i.e.,* `MyClass{…}`) 168 | rather than parentheses-based construction 169 | (*i.e.,* `MyClass(…)`) syntax. 170 | I also prefer always fully specifying the type 171 | (rather than relying on type deduction). 172 | 173 | This makes it immediately visible that 174 | the type of the resulting expression is exactly what I've written 175 | and, perhaps more importantly, 176 | that no complex logic is running, 177 | this is mostly just moving data into place 178 | (and possibly creating a few other instances 179 | for variables I'm not explicitly initializing). 180 | 181 | By specifying the type fully, 182 | I also help the compiler produce nicer error messages 183 | when I get the type of one of the sub-expressions wrong. 184 | This can be very helpful for long and complex initialization expressions. 185 | 186 | ## What's next? 187 | 188 | I'd like to explore a few ideas: 189 | 190 | * Replace a few uses of `std::variant` with `std::expected`. 191 | I only learned about `std::expected` relatively recently, 192 | and I think there's a few places where it'd fit slightly better. 193 | 194 | -------------------------------------------------------------------------------- /images/003.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | Gnuplot 10 | Produced by GNUPLOT 5.2 patchlevel 8 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 0 47 | 48 | 49 | 50 | 51 | 5x10-7 52 | 53 | 54 | 55 | 56 | 1x10-6 57 | 58 | 59 | 60 | 61 | 1.5x10-6 62 | 63 | 64 | 65 | 66 | 2x10-6 67 | 68 | 69 | 70 | 71 | 2.5x10-6 72 | 73 | 74 | 75 | 76 | 3x10-6 77 | 78 | 79 | 80 | 81 | 1 82 | 83 | 84 | 85 | 90 | 10 91 | 92 | 93 | 94 | 99 | 100 100 | 101 | 102 | 103 | 108 | 1000 109 | 110 | 111 | 112 | 117 | 10000 118 | 119 | 120 | 121 | 126 | 100000 127 | 128 | 129 | 130 | 135 | 1x106 136 | 137 | 138 | 139 | 141 | 142 | 143 | 144 | 145 | Seconds 146 | 147 | 148 | 149 | 150 | ConstTree size 151 | 152 | 153 | 154 | 155 | ConstTree Append 156 | 157 | 158 | ConstTree Append 159 | 160 | 161 | 162 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /images/005.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alefore/weblog/e159e2d8802676273fdc2d7aade269f1b72735fd/images/005.jpg -------------------------------------------------------------------------------- /images/006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alefore/weblog/e159e2d8802676273fdc2d7aade269f1b72735fd/images/006.png -------------------------------------------------------------------------------- /images/007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alefore/weblog/e159e2d8802676273fdc2d7aade269f1b72735fd/images/007.png -------------------------------------------------------------------------------- /images/009.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | PowerLaw 10 | Produced by GNUPLOT 5.4 patchlevel 4 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 0 47 | 48 | 49 | 50 | 51 | 0.1 52 | 53 | 54 | 55 | 56 | 0.2 57 | 58 | 59 | 60 | 61 | 0.3 62 | 63 | 64 | 65 | 66 | 0.4 67 | 68 | 69 | 70 | 71 | 0.5 72 | 73 | 74 | 75 | 76 | 1 77 | 78 | 79 | 80 | 81 | 2 82 | 83 | 84 | 85 | 86 | 3 87 | 88 | 89 | 90 | 91 | 4 92 | 93 | 94 | 95 | 96 | 5 97 | 98 | 99 | 100 | 101 | 6 102 | 103 | 104 | 105 | 106 | 7 107 | 108 | 109 | 110 | 111 | 8 112 | 113 | 114 | 115 | 116 | 9 117 | 118 | 119 | 120 | 121 | 10 122 | 123 | 124 | 125 | 126 | 11 127 | 128 | 129 | 130 | 131 | 12 132 | 133 | 134 | 135 | 136 | 13 137 | 138 | 139 | 140 | 141 | 14 142 | 143 | 144 | 145 | 146 | 15 147 | 148 | 149 | 150 | 151 | 16 152 | 153 | 154 | 155 | 156 | 17 157 | 158 | 159 | 160 | 161 | 18 162 | 163 | 164 | 165 | 166 | 19 167 | 168 | 169 | 170 | 171 | 20 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | Power Law: y = 2-x 181 | 182 | 183 | Power Law: y = 2-x 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /images/013.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | Links 10 | Produced by GNUPLOT 5.4 patchlevel 4 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 0 47 | 48 | 49 | 50 | 51 | 100 52 | 53 | 54 | 55 | 56 | 200 57 | 58 | 59 | 60 | 61 | 300 62 | 63 | 64 | 65 | 66 | 400 67 | 68 | 69 | 70 | 71 | 500 72 | 73 | 74 | 75 | 76 | 600 77 | 78 | 79 | 80 | 81 | 700 82 | 83 | 84 | 85 | 86 | 800 87 | 88 | 89 | 90 | 91 | 900 92 | 93 | 94 | 95 | 96 | -1 97 | 98 | 99 | 100 | 101 | 0 102 | 103 | 104 | 105 | 106 | 1 107 | 108 | 109 | 110 | 111 | 2 112 | 113 | 114 | 115 | 116 | 3 117 | 118 | 119 | 120 | 121 | 4 122 | 123 | 124 | 125 | 126 | 5 127 | 128 | 129 | 130 | 131 | 6 132 | 133 | 134 | 135 | 136 | 7 137 | 138 | 139 | 140 | 141 | 8 142 | 143 | 144 | 145 | 146 | 9 147 | 148 | 149 | 150 | 151 | 10 152 | 153 | 154 | 155 | 156 | 11 157 | 158 | 159 | 160 | 161 | 12 162 | 163 | 164 | 165 | 166 | 13 167 | 168 | 169 | 170 | 171 | 14 172 | 173 | 174 | 175 | 176 | 15 177 | 178 | 179 | 180 | 181 | 16 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | Notes 191 | 192 | 193 | 194 | 195 | Links 196 | 197 | 198 | 199 | 200 | Links 201 | 202 | 203 | Links 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | -------------------------------------------------------------------------------- /images/014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alefore/weblog/e159e2d8802676273fdc2d7aade269f1b72735fd/images/014.png -------------------------------------------------------------------------------- /imaginar-y-vivir.md: -------------------------------------------------------------------------------- 1 | # Imaginar y vivir 2 | 3 | Un día novembrino,
4 | vencido por una luz tenue,
5 | abandono cobijas.
6 | Tras gotas condensadas en el vidrio
7 | veo al árbol desnudo
8 | aruñar el cielo oscuro.
9 | Se acerca una tormenta. 10 | 11 | Al abrir la ventana,
12 | el sereno me cachetea.
13 | Será día de velas trémulas,
14 | de sumergirse en yerbabuena
15 | en vez de volar con café. 16 | 17 | Junto a la cama,
18 | una callada silla de madera;
19 | en su respaldo,
20 | regalo de alguien que ya no es,
21 | un refugio contra el frio:
22 | mi saco de lana fiel,
23 | viejo amigo conocido. 24 | 25 | Frotándome los brazos
26 | lo miro en la penumbra:
27 | lana gruesa, firme,
28 | como unas vigas de roble
29 | bajo tejas viejas
30 | que aún me recuerdan. 31 | 32 | Al ponérmelo,
33 | en la mañana gris,
34 | reconozco la calma
35 | que anida en sus laberintos.
36 | Su aroma refleja
37 | fogatas y agüepanelas,
38 | magnolios en flor,
39 | manzanas con canela,
40 | librerías
41 | y a quién, décadas atras,
42 | lo tejiera. 43 | 44 | Recibo su suavidad de siempre,
45 | niebla de páramo,
46 | ¡tantos otoños conmigo!
47 | Tantos paseos, hojas secas,
48 | con él a la cintura;
49 | tantos copos de nieve
50 | desvanecidos en sus praderas y abismos. 51 | 52 | Senderos de lana virgen,
53 | tierna al tacto:
54 | olla de barro,
55 | llovizna sobre musgo,
56 | agua de manantial que pregunta, en susurros:
57 | ¿Cuántas cartas escribimos?
58 | ¿A cuántos abrazos sumaste tu caricia? 59 | 60 | Y aunque no hay sorpresa alguna,
61 | la lana teje, al cubrirme,
62 | nuevas profundidades tibias.
63 | En lo familiar nacen
64 | espacios inexplorados,
65 | revelando la distancia insondable
66 | entre imaginar y vivir. 67 | -------------------------------------------------------------------------------- /immutable-avl-trees.md: -------------------------------------------------------------------------------- 1 | # Immutable AVL Trees 2 | 3 | Immutable AVL trees are an undervalued container data structure for holding 4 | lookup tables. 5 | 6 | ## What are Immutable AVL Trees 7 | 8 | Immutable AVL Trees are a balanced binary tree structure where each 9 | non-empty tree holds: 10 | 11 | * An immutable "head" value. 12 | * Two immutable AVL "left" and "right" subtrees. Since this is an AVL tree, 13 | the depth of these subtrees must differ by at most 1. 14 | * As an optimization, to permit quick implementation of insertion operations as 15 | well as position-based indexing, the head also stores the depth of 16 | the tree explicitly. 17 | 18 | I call each non-empty tree a "tree node". 19 | 20 | ### Index-based Access 21 | 22 | An Immutable AVL Tree can enable index-based access, with operations 23 | such as: 24 | 25 | * Return the value at index i (where 0 ≤ i < n, where n is the number of 26 | elements in the tree). 27 | * Insert a value at index i, displacing elements in subsequent positions. 28 | 29 | To support this, nodes should store explicitly a counter of the number of values 30 | they contain (the sum of values in its subtrees, plus 1). This allows them to 31 | quickly know to which of its subtrees they should dispatch a position-based 32 | operation that they can't handle directly (i.e., that doesn't refer to their 33 | head). 34 | 35 | ### Key-based Access 36 | 37 | If an Immutable AVL Tree wants to support key → value lookups based on an 38 | arbitrary key type, each node can store the ID corresponding to its head. 39 | Alternatively, the tree could let the keys be stored implicity in the values, 40 | using a deterministic accessor function to retrieve them dynamically. 41 | 42 | Key-based access can be offered instead of or in addition to Index-based 43 | Access. If no keys are provided, only index-based access will 44 | be supported. 45 | 46 | #### Keys: Full Order 47 | 48 | To support key-based indexing, a full order must be defined on the 49 | keys' type. The tree will hold the elements in the order given by the keys, in 50 | order to be able to quickly establish which subtree holds a given key (whether 51 | it's smaller or greater than the node's key). 52 | 53 | #### Keys: Uniqueness 54 | 55 | The insert operation of an Immutable AVL Tree that supports Key-based 56 | Indexing will typically ensure uniqueness of the keys: that a key will 57 | be associated to at most one value in the tree. 58 | 59 | However, an Immutable AVL Tree could relax this requirement and support 60 | multivalue lookups. 61 | 62 | ##### Support multivalue lookups 63 | 64 | To support multivalue (i.e., key → {value}) lookups, an Immutable AVL Tree needs 65 | only to use as its value type an immutable container of the customer's value 66 | type. 67 | 68 | In this case, special care should be taken to define clean semantics for trees 69 | that also wants to support index-based access. 70 | 71 | ### Examples 72 | 73 | The following are a few example implementations: 74 | 75 | #### Edge 76 | 77 | A fairly succinct example implementation of an Immutable AVL Tree is the 78 | `ConstTree` class distributed as part of the Edge text editor: 79 | 80 | * https://github.com/alefore/edge/blob/master/src/const_tree.h 81 | 82 | Edge uses this implementation both to store the list of lines in a file, as well 83 | as the list of characters in a modified line (as an optimization to reduce 84 | memory, unmodified lines are stored contiguously as a char array). 85 | 86 | #### Micha 87 | 88 | An implementation of Immutable AVL Trees for Java can be found in 89 | Micha Riser's [Java Collection 90 | Library](https://github.com/iquadrat/collection/blob/master/README.md) in the 91 | [PersistentTreeMap 92 | class](https://github.com/iquadrat/collection/blob/master/org.povworld.collection/src/org/povworld/collection/persistent/PersistentTreeMap.java) 93 | 94 | ### Immutable Operations 95 | 96 | The "immutable" characteristic of the tree means that a tree is fundamentally 97 | immutable. This requires, naturally, that the values contained are also deeply 98 | immutable. 99 | 100 | That means that operations such as insertion or deletion just return a copy of a 101 | tree with the modification applied to it. 102 | 103 | #### Example Immutable Operations in Edge 104 | 105 | The following is an example in Edge to insert an element `line` at the position 106 | `line_position.line` into the tree `lines` (`Lines` here is just an alias for 107 | the tree type): 108 | 109 | auto prefix = Lines::Prefix(lines_, line_position.line); 110 | auto suffix = Lines::Suffix(lines_, line_position.line); 111 | lines_ = Lines::Append(Lines::PushBack(prefix, std::move(line)), suffix); 112 | 113 | The point here is that the original tree pointed to by `lines_` is never 114 | directly mutated; instead, we create two new trees and append them, with the new 115 | element in the middle. `Lines::PushBack` doesn't mutate the tree pointed to by 116 | `prefix` but return a new tree that references it internally. 117 | 118 | ### C++: Shared Pointers 119 | 120 | In C++, `std::shared_ptr` (where `TreeNode` is the structure 121 | containing the fields of a non-empty tree) is a great fit for a node in an 122 | Immutable AVL Tree to hold pointers to the subtrees. 123 | 124 | This allows new trees to share most of the memory of their parent trees, 125 | allowing older nodes to be reclaimed automatically as they are abandoned. 126 | 127 | #### Shared Pointers: No cycles 128 | 129 | The immutability of the structure and values in Immutable AVL Trees 130 | makes cycles impossible: a node can only refer to elements that existed before 131 | its construction. This alleviating the main problem of shared pointers: 132 | memory leaks due to unreachable cycles. 133 | 134 | ### Values: Requirements 135 | 136 | Tree operations will need to create new trees that share structure (at least 137 | partially). Because of this, implementors have two choices: 138 | 139 | * If a given value doesn't require too much memory and can be quickly copied, 140 | implementors may allow the tree to copy them around when needed (i.e., when 141 | a new tree node needs to be created holding them). 142 | 143 | * Otherwise, the tree can store the values in the nodes as shared pointers 144 | (`std::shared_ptr` in C++). Values in a derived tree (where some 145 | nodes are different than in an original tree) will be pointers to the same 146 | values from the original tree. 147 | 148 | ## Advantages 149 | 150 | Immutable AVL Trees are good because they are: 151 | 152 | * efficient, 153 | * flexible, 154 | * simple, and 155 | * immutable. 156 | 157 | ### Efficient 158 | 159 | Immutable AVL Trees are fast: they can perform the following operations in 160 | logn runtime complexity (all receiving either a key or an index): 161 | 162 | * Return a value 163 | * Insert a value 164 | * Delete a value 165 | * Append two trees (where keys in one tree are smaller than keys in the other). 166 | 167 | #### AVL Trees: Depth Complexity is Logarithmic 168 | 169 | The depth of an AVL tree grows logarithmically to the number of elements it 170 | contains. 171 | 172 | To see this, let Nᵢ be minimal number of nodes that are 173 | required to form a tree of depth i. 174 | 175 | If a tree has depth i, at least one of its subtrees must have depth i - 1. 176 | Because this is the mimimal number of nodes, the other subtree must have the 177 | smallest possible depth allowed by the AVL constraint, i - 2. This gives: 178 | 179 | Nᵢ = Nᵢ₋₁ + Nᵢ₋₂ + 1 180 | 181 | Applying the same formula recursively (to i - 1), we have: 182 | 183 | Nᵢ₋₁ = Nᵢ₋₂ + Nᵢ₋₃ + 1 184 | 185 | Replacing Nᵢ₋₁ in the first formula, we get: 186 | 187 | Nᵢ = (Nᵢ₋₂ + Nᵢ₋₃ + 1) + Nᵢ₋₂ + 1 188 | = 2Nᵢ₋₂ + Nᵢ₋₃ + 2 189 | 190 | This implies: 191 | 192 | Nᵢ > 2Nᵢ₋₂ 193 | 194 | In other words, going up two levels (from depth i - 2 to i) means we'll need 195 | at least twice as many nodes. 196 | 197 | Applying this recursively yields: 198 | 199 | N₂ᵢ > 2ⁱ 200 | 201 | This implies that the number of nodes required to reach a given depth grows 202 | exponentially; in other words, given N nodes, the maximum depth that you can 203 | reach will grow proportionally to log₂N. 204 | 205 | #### AVL Trees: Rotations 206 | 207 | We call a binary search tree an "almost AVL" tree if its subtrees are AVL 208 | trees and their depth differs by at most 2. 209 | 210 | The rotation operations are at the heart of the AVL trees. They take an almost 211 | AVL tree and, in constant time, produce an equivalent AVL tree. 212 | 213 | If the input tree was already an AVL tree, no rotation is needed: the tree is 214 | returned as is. Otherwise, if the difference in the subtrees' depth was actually 215 | 2, the rotations will produce a tree with depth equal to the depth of the input 216 | minus 1. 217 | 218 | #### AVL Trees: Insertion is Logarithmic 219 | 220 | To insert a new element into an AVL tree, we simply go down the tree selecting 221 | the branch that leaves us closest to the insertion point until we arrive to an 222 | empty tree and replace that empty tree with a new leaf tree containing the new 223 | element. 224 | 225 | For example, suppose we have the following AVL tree (A is an unexpanded 226 | subtree): 227 | 228 | p 229 | / \ 230 | A s 231 | / \ 232 | r t 233 | / 234 | q 235 | 236 | If we wanted to insert an element immediately after `p`, we would use the new 237 | leaf as `q`'s left child. If we wanted to insert it after `q`, it would be its 238 | right child. 239 | 240 | The new tree won't necessarily be an AVL tree, but working our way upwards, 241 | starting at the insertion point and applying rotations as necessary, we can turn 242 | it into an AVL tree by applying at most a logarithmic number of constant-time 243 | rotations. 244 | 245 | #### AVL Trees: Deletion is Logarithmic 246 | 247 | It is also easy to see that deletion is logarithmic. 248 | 249 | Start by recursing down the tree until you find the node `N` corresponding to 250 | the element you want to delete. We will replace `N` with a new tree `N'` where 251 | the difference in their depth will be at most 1, so `N`'s parent will be an 252 | almost AVL tree. We can then apply a rotation, to turn the almost AVL tree into 253 | an equivalent AVL tree; its parent will now be an almost AVL tree, and so we 254 | recurse upwards. The two operations are just traversing the tree downwards and 255 | upwards, applying constant-time operations at each step, so they have 256 | logarithmic runtime. 257 | 258 | To produce `N'` we simply "borrow" the head from one of the subtrees and recurse 259 | downwards into that subtree to delete its head. If at least one subtree is 260 | empty, we can simply use the other subtree. 261 | 262 | #### AVL Trees: Append is Logarithmic 263 | 264 | Two AVL trees can be appended in logarithmic time, as long as all the keys in 265 | one tree are larger than the keys in the other. For index-based AVL trees this 266 | is true by definition of the "append" operation (elements in the second tree 267 | come immediately after those in the first). 268 | 269 | To append two non-empty trees, let `x` be the last element in the first of the 270 | trees and `A` an AVL tree equivalent to the first tree with `x` removed. We can 271 | compute `x` and generate `A` in logarithmic runtime. Our new task is to append 272 | two trees `A` and `B` with `x` in-between, as in: 273 | 274 | x 275 | / \ 276 | A B 277 | 278 | Obviously, we can't just return such a tree, since `A` and `B` may have vastly 279 | different depths. Instead, we construct an equivalent tree that meets the AVL 280 | invariant. 281 | 282 | For simplicity, let's assume that `A` is the deepest of the two trees (the other 283 | case is symmetric). We're going to insert the elements in `B` inside of `A` 284 | (producing a new AVL tree that contains the desired output), and we're going to 285 | do this in logarithmic time. 286 | 287 | We descend in `A` always following the right subtree (the elements that will be 288 | adjacent to `B`) until we find a right subtree that is equal in depth to `B`. 289 | Suppose that `A` is equal to the following tree (where `C`, `E`, `F`, `G`, and 290 | `H` are subtrees that I didn't fully expand) and that `H` has the same depth as 291 | `B`: 292 | 293 | o 294 | / \ 295 | C p 296 | / \ 297 | E q 298 | / \ 299 | F r 300 | / \ 301 | G H 302 | 303 | We now replace `H` with a new AVL tree with head `x`: 304 | 305 | o 306 | / \ 307 | C p 308 | / \ 309 | E q 310 | / \ 311 | F r 312 | / \ 313 | G x 314 | / \ 315 | H B 316 | 317 | At this point, the tree at `x` is an AVL tree (by construction: `H` and `B` have the same depth) but the ancestors (the trees at `r`, `q`, `p`, and `o`) 318 | may not be: the depths of `r`'s subtrees, `G` and the tree at 319 | `x`, may be 2 (this happens when `depth(G) + 1` equals `depth(H)`). 320 | 321 | However, in this case, we simply do a rotation (to turn the tree with head `r` 322 | into an AVL tree), and follow the ancestors upwards, applying rotations (if 323 | necessary) until we reach the head. In the worst case, we'll have effectively 324 | applied a logarithmic number of constant-time rotations. 325 | 326 | #### logn Complexity is Fine 327 | 328 | For most algorithms that we encounter in practice, logn runtime 329 | complexity tends to be perfectly acceptable. 330 | 331 | ##### Why logn Algorithmic Complexity is Fine 332 | 333 | For most values of n that we're likely to encounter in practice, algorithms of 334 | logn complexity will require so few operations that practical considerations 335 | will matter significantly more. That means they're largely undistinguishable 336 | from constant runtime complexity. 337 | 338 | For example, consider a logn operation on a container holding 10 million items, 339 | more than most containers we find in practice. Such an operation will require 340 | only 24 operations! If we constrain ourselves to 50 operations, we'll already 341 | able to handle over 1125 trillion items. 342 | 343 | #### Const Tree: Performance 344 | 345 | ##### Get, Insert, Delete 346 | 347 | The following graph depicts the performance of Get, Insert, and Delete 348 | operations in my ConstTree implementation as the container grows: 349 | 350 | ![002.svg](images/002.svg) 351 | 352 | ##### Append Trees Graph 353 | 354 | The following is the time in seconds of creating a tree of a given size by 355 | appending two trees (of random sizes that add up to the final size), showing 356 | how it scales as the size of the resulting tree grows: 357 | 358 | ![003.svg](images/003.svg) 359 | 360 | ### Flexible 361 | 362 | Immutable AVL Trees are very flexible. Since they're fast, they can 363 | directly replace all of the following structures in many situations: 364 | 365 | * Hash tables, as long as a full order can be defined on the keys. We trade the 366 | amortized constant-time lookup for logarithmic lookup, but we avoid the 367 | worst-case linear time due to collisions (and, conversely, the need to 368 | resize the table). 369 | 370 | * Multi-value hash tables, by using an immutable container as the internal 371 | value. 372 | 373 | * Vectors, based on index-based access. AVL trees have significantly 374 | faster insertion (logarithmic vs linear) and acceptable lookup. Vectors are a 375 | better choice in situations that only require insertion or deletion at the 376 | end, where their constant-time lookup will beat AVL tree's logarithmic lookup. 377 | 378 | * Lists, based on index-based access, with significantly faster 379 | lookup. One advantage of lists, though, is when index-based access is used and 380 | iterators that follow a value across insertions and deletions are useful. In 381 | this case, for lists, we simply take a pointer to the node; this becomes 382 | trickier with Immutable AVL Trees, where the index of existing elements shifts 383 | as insertions or deletions execute. 384 | 385 | ### Simple 386 | 387 | Immutable AVL Trees are very easy to implement. There's a slight bit of 388 | complexity required around the rotations, but it's not too bad. 389 | 390 | I'll offer, again, the example from Edge's [ConstTree 391 | class](https://github.com/alefore/edge/blob/master/src/const_tree.h). It uses 392 | only 184 lines of code, a big part of which is just to implement "optional" 393 | operations such as `Every`, `Prefix`, `Suffix`, and `UpperBound`. I invite you 394 | to read it and see for yourself how simple this really is. 395 | 396 | ### Immutable 397 | 398 | The immutable aspect is a big advantage of Immutable AVL Trees because 399 | of two reasons: 400 | 401 | * Retaining Snapshots 402 | * Immutable state 403 | 404 | #### Snapshotting 405 | 406 | It is possible to trivially retain snapshots of an Immutable AVL tree, in 407 | constant time: one simply needs to retain a pointer to the tree. 408 | 409 | ##### Share Memory 410 | 411 | As the program moves along, applying modifications and retaining the result of 412 | those modifications, the two (or more) trees will share as much of their 413 | internal structure as feasible (while still maintaining their balance, so that 414 | operations on them remain fast). 415 | 416 | ##### Comparison 417 | 418 | It is possible to implement snapshotting for other types of container 419 | structures, but it's likely going to be significantly less efficient and/or more 420 | complex than it is in Immutable AVL Trees. 421 | 422 | ##### Example in Edge 423 | 424 | As an example to illustrate the value of Snapshotting in Immutable 425 | AVL Trees, in Edge I use this to feed work to background threads, 426 | without having to synchronize state (since the threads don't share any mutable 427 | state). This allows the user to continue modifying the buffer while these 428 | operations run asynchronously, on a snapshot. I use this to: 429 | 430 | * Asynchronously update the syntax tree (for syntax highlighting) while the 431 | user may continue to modify the buffer. 432 | 433 | * Asynchronously save the buffer to disk. The user may continue to modify the 434 | buffer concurrently; the save operation will just store the snapshot. 435 | 436 | When background threads are done computing something, they simply schedule a 437 | callback to consume the results, to be executed in the main thread. 438 | 439 | #### Readable Code 440 | 441 | Immutable AVL Trees support the efficient construction of new trees 442 | derived of original trees. This allows customer functions that operate on them 443 | to receive a tree as an input and return a (possibly new) tree as their output. 444 | With other mutable data structures, those functions would instead have to 445 | operate in a structure in-place. 446 | 447 | This is good because it supports and encourages referential 448 | transparency for functions that operate on these types. 449 | 450 | -------------------------------------------------------------------------------- /intent-oriented-interfaces.md: -------------------------------------------------------------------------------- 1 | # Intent-Oriented Interfaces 2 | 3 | ## Introduction 4 | 5 | This is a very early and incomplete draft. 6 | 7 | This article: 8 | 9 | * Introduces terminology to describe system interfaces 10 | as being intent-oriented, 11 | offering a concrete definition for "intent". 12 | 13 | * Describes the advantages of intent-oriented interfaces. 14 | 15 | * Offers a few principles that may help 16 | design interfaces that are more intent-oriented. 17 | 18 | This article is based on many conversations with 19 | Chris Nokleberg, 20 | Ivan Metlushko, and 21 | Tudor-Ioan Salomie. 22 | 23 | ## What is intent 24 | 25 | ### Definition 26 | 27 | A customer adopts a shared infrastructure system 28 | to achieve some goals. 29 | These goals, along with any requirements or expectations, 30 | are what we call "intent". 31 | 32 | ### Interfaces 33 | 34 | These shared infrastructure systems 35 | offer an interface 36 | that the customer uses to communicate with them. 37 | 38 | Among others, these interfaces can be: 39 | 40 | * A configuration language 41 | * A command-line interface 42 | * An application programming interface 43 | 44 | #### Interface Abstractions 45 | 46 | The interfaces expose a set of abstractions 47 | that customers must learn and use 48 | to create artifacts that express or implement the customers' intent. 49 | 50 | Unfortunately, these abstractions 51 | rarely allow the customer to express the intent in the most natural way. 52 | Instead, the customer must learn an arbitrary (form their perspective) language 53 | to which he needs to translate the intent. 54 | 55 | We use *mechanics* to refer to the artifacts where intent is expressed 56 | indirectly, using arbitrary primitives that are arcane to the intent. 57 | Mechanics map customers' intent to primitives offered 58 | by the interface. 59 | 60 | ### Terminology 61 | 62 | Throughout this document we'll take the view 63 | of the owner of a *shared system* 64 | (sometimes just a *system*) 65 | that exposes an *interface*. 66 | *Artifacts* created and maintained by *customers* 67 | use this interface to *configure* (interact with) the system. 68 | 69 | #### Artifacts 70 | 71 | Artifacts come in many forms: 72 | 73 | * Configuration files (adhering to the system's configuration schema) 74 | * Source code (calling into the system's API) 75 | 76 | They can be *very* complex, 77 | often orders of magnitude more complex than the underlying systems. 78 | As Larry Wall 79 | [wrote](https://www.tuhs.org/Usenet/comp.unix.shell/1991-January/002464.html), 80 | "*it is easier to port a shell than a shell script.*" 81 | 82 | ### Intent-oriented interfaces 83 | 84 | We say that interfaces are intent-oriented 85 | to the extent the abstractions or primitives they expose 86 | match the mental model 87 | in which the interfaces' customers reason about their intent. 88 | That is, to the extent that the logical distance 89 | between the mental models of the interfaces 90 | and of the system's customers is small. 91 | 92 | Intent-oriented systems 93 | implement methods of fulfilling intent 94 | based on declarations of intent, 95 | rather than force customers' to specify and maintain this logic. 96 | 97 | We use "a system is intent-oriented" as short-hand for 98 | "a system is used through intent-oriented interfaces". 99 | 100 | #### Subjective 101 | 102 | Not only do different customers pursue different goals, 103 | they also think about their goals in different ways. 104 | The extent to which an interface is intent-oriented 105 | can change as customers come and go. 106 | It can also change as customers' mindsets change. 107 | The extent to which an interface is intent-oriented 108 | can only be measured relative to its current user base. 109 | 110 | ## Why intent-oriented interfaces 111 | 112 | Gaps between customers' mental models and 113 | systems' interfaces 114 | force the use of constructs that are only loosely related to the intent. 115 | When this happens, 116 | customers build artifacts 117 | using primitives that obscure what truly matters to them. 118 | 119 | ### Making the actual goal explicit 120 | 121 | Configuring the system with arbitrary concepts brings false negatives: 122 | customers' actual goals are obscured. 123 | You don't know *why* they configured things a specific way: 124 | what was the fundamental reason for a given choice? 125 | 126 | ### Don't emphasize irrelevant aspects 127 | 128 | Conversely, configuring the system with arbitrary primitives 129 | causes false positives: 130 | irrelevant aspects are over-emphasized. 131 | Since customers had to express intent using arbitrary concepts, 132 | you can't easily know which of these arbitrary concepts are just incidental. 133 | Did the user set up a certain parameter because they really care about it? 134 | Or was this just the mechanism through which they can achieve their goal? 135 | 136 | ### Allow evolution 137 | 138 | Enabling intent to surface explicitly, 139 | rather than being lost in complexity of mechanics, 140 | enables evolution. 141 | This happens because it moves complexity 142 | from layers above the interface 143 | –the artifacts your customers maintain– 144 | directly into your system. 145 | In your system, you can manage it more easily. 146 | This makes it easier … 147 | 148 | * … to roll out new features 149 | that enable the system to better serve its customers. 150 | You can roll out new features you implement 151 | without having to adjust many complex customer artifacts. 152 | Otherwise, you may need to conditionally rewrite customer configurations' 153 | to apply an optimization that better achieves customer's goals. 154 | 155 | * … to understand 156 | the relative importance of different potential improvements. 157 | How much would customers' benefit from a given optimization? 158 | How many have an intent to which the optimization is actually relevant? 159 | 160 | * … to identify and remove features 161 | that your customers' don't actually care about. 162 | When no customer really cares about the feature 163 | (they only depend on it due to mechanics, 164 | because they had no way to express their intent), 165 | you won't be able to retire it 166 | until you've rewritten all customer artifacts 167 | that depend on it. 168 | 169 | ## Designing intent-oriented interfaces 170 | 171 | There's no silver bullet 172 | for how to align interfaces with their customers' intent. 173 | 174 | This document attempts to offer, nevertheless, 175 | a few techniques or principles that may help. 176 | 177 | TODO: Explicit representation of intent. 178 | 179 | ### Customer User Journeys 180 | 181 | Explicitly listing your user journeys can be of great help. 182 | That means spending time answering the questions of: 183 | 184 | * Who are your target customers? 185 | 186 | * What are the most valuable ways your system can be useful to your customers? 187 | What are their goals? 188 | Can you articulate their goals in terms of value dimensions? 189 | 190 | An ongoing process of iteratively refining these journeys, 191 | putting them at the center of your interface design, 192 | can go a long way. 193 | 194 | ### Seek feedback 195 | 196 | Regularly collect feedback from your customers. 197 | Seek to identify and understand common challenges they have with your system. 198 | This may highlight places where there may be mismatches. 199 | 200 | Be aware of the [Einstellung effect](https://en.wikipedia.org/wiki/Einstellung_effect). 201 | This implies that you need to spend time with your customers 202 | to really be able to understand their needs. 203 | In a broader sense, 204 | this is what [déformation professionnelle](https://en.wikipedia.org/wiki/D%C3%A9formation_professionnelle) 205 | implies: 206 | the longer and sharper your focus on shared infrastructure 207 | (or a specific part of a complex layered system), 208 | the more difficult it may be for you 209 | to understand the perspective of your customers 210 | (or of those on other layers). 211 | 212 | ### Refine your abstractions 213 | 214 | Review the abstractions your interfaces offer 215 | and improve them iteratively. 216 | Do they capture the fundamental attributes your customers care about? 217 | Can you introduce more semantically rich primitives that could replace them? 218 | 219 | This is difficult and time-consuming work. 220 | It moves complexity from your customers' artifacts 221 | into your system –the lower layers that implement the interface. 222 | 223 | ## Conclusion 224 | 225 | TODO 226 | 227 | -------------------------------------------------------------------------------- /journaling.md: -------------------------------------------------------------------------------- 1 | # Journaling 2 | 3 | Journaling is, for me, a very useful mechanism to introspect; it has 4 | enabled me to identify ideas that were hidden in plain sight and come to very 5 | useful insights that have accelerated my career path. 6 | 7 | ## Ideas Hidden in Plain Sight 8 | 9 | Many good ideas remain hidden in plain sight for a very long time. 10 | 11 | ## Asking questions 12 | 13 | A useful technique when journaling 14 | is focusing on writing down a tree (or sequence) of questions, 15 | where explicitly stating each question 16 | prompts child subquestions 17 | that tend to be easier to answer 18 | and/or to advance my model of the reality under analysis 19 | in directions that can yield further insights. 20 | 21 | ### Asking Questions: More important than Finding Answers 22 | 23 | Asking the right questions can be more important 24 | than figuring out their exact answers. 25 | 26 | > The mere formulation of a problem 27 | > is far more often essential than its solution, 28 | > which may be merely a matter of mathematical or experimental skill. 29 | > To raise new questions, new possibilities, 30 | > to regard old problems from a new angle, 31 | > requires creative imagination 32 | > and marks real advances in science. 33 | > – Albert Einstein 34 | 35 | #### Asking Questions: Why? 36 | 37 | The reason asking the right questions can be very valuable is that it 38 | helps us direct our concentration towards the most important aspects of a model. 39 | 40 | #### Ask the meta-question 41 | 42 | In order to figure out the answer to a given question, 43 | it often pays focus on the derivative: 44 | to explicitly reframe the question as the meta-question 45 | "how could I figure out the answer to this question?" 46 | 47 | ## Think of Your Future Selves 48 | 49 | Be considerate to your future selves 50 | —the versions of you that will exist in the future. 51 | Do now the things that maximize their happiness. 52 | 53 | In particular, consider the future versions of you that will exist in: 54 | 55 | * 1 hour 56 | * 4 hours 57 | * 12 hours 58 | * 1 day 59 | * 2 days 60 | * 4 days 61 | * 1 week 62 | * 2 weeks 63 | * 1 month 64 | * 1 quarter 65 | * 1 year 66 | * 2 years 67 | * 5 years 68 | * 15 years 69 | 70 | When journaling, for each of these identities, ask the question: 71 | "What can I do now that $IDENTITY will appreciate the most?" 72 | The "now" time-frame depends somewhat on the $IDENTITY. 73 | 74 | ## Format 75 | 76 | I found that using a fountain pen and paper for journaling 77 | works better *for me* than other approaches I've tried. 78 | 79 | The difference isn't huge: 80 | all of these digital formats are almost as good. 81 | So I can't argue very strongly one way or another. 82 | 83 | ### Digital 84 | 85 | I tried various digital approaches for journaling over the years: 86 | 87 | * A Google Docs file. 88 | 89 | * Markdown files in 90 | [my Zettelkasten](https://github.com/alefore/weblog/blob/master/zettelkasten.md) 91 | that I edit through 92 | [my text editor](https://github.com/alefore/edge). 93 | 94 | * A Supernote Tablet with a Lamy digital pen. 95 | 96 | 97 | ### Linux: Generate background for notes 98 | 99 | I prefer to write over paper with a grid of dots. 100 | 101 | I've experimented with various parameters such as: 102 | 103 | * Distance between dots. 104 | 105 | * Dot size. 106 | 107 | * Dot color (weight). 108 | 109 | * Margin. 110 | 111 | I implemented my preferences as a Python + ReportLab script. 112 | The script generates a `background.pdf` PDF file with a single-page, 113 | which I can print as often as I want. 114 | 115 | The top of the page has a small table where I write the date 116 | (YYYY-MM-DD) and page ID (within the date). 117 | 118 | File `~/bin/pdf-notes-background` (mode 755): 119 | 120 | #!/home/alejo/local/bin/python3 121 | from reportlab.lib.pagesizes import A4 122 | from reportlab.pdfgen import canvas 123 | from reportlab.lib.units import mm 124 | from reportlab.lib import colors 125 | 126 | c = canvas.Canvas("background.pdf", pagesize=A4) 127 | 128 | GRAY = colors.HexColor(0xdddddd) 129 | LIGHT_GRAY = colors.HexColor(0xeeeeee) 130 | 131 | dot_distance = 5 # mm between dots 132 | dot_size = 0.1 # Diameter of dots in mm 133 | margin = 14 # Margin around the page in mm 134 | 135 | # Page dimensions in mm (for A4) 136 | page_width_mm, page_height_mm = A4[0] / mm, A4[1] / mm 137 | 138 | # Adjust start positions to ensure dots are centered with equal margins 139 | start_x = margin + ((page_width_mm - 2 * margin) % dot_distance) / 2 140 | start_y = margin + ((page_height_mm - 2 * margin) % dot_distance) / 2 141 | 142 | c.setStrokeColor(GRAY) 143 | c.setFillColor(GRAY) 144 | 145 | x = start_x 146 | while x <= page_width_mm - margin: 147 | y = start_y 148 | while y <= page_height_mm - margin: 149 | c.circle( 150 | x * mm, (page_height_mm - y) * mm, dot_size * mm, fill=1, stroke=1) 151 | y += dot_distance 152 | x += dot_distance 153 | 154 | rect_dot_position = 0 155 | c.setStrokeColor(LIGHT_GRAY) 156 | for rects in map(len, reversed(['YYYY', 'MM', 'DD', 'XXX'])): 157 | c.rect((x - (1 + rect_dot_position + rects) * dot_distance) * mm, 158 | (y - 1 * dot_distance) * mm, (rects * dot_distance) * mm, 159 | dot_distance * mm) 160 | rect_dot_position += rects 161 | 162 | c.save() 163 | 164 | -------------------------------------------------------------------------------- /kitchen.md: -------------------------------------------------------------------------------- 1 | # Clean up the kitchen 2 | 3 | ## Introduction 4 | 5 | This document is a description 6 | of my personal algorithm for cleaning up my kitchen. 7 | 8 | I was inspired by a similar 9 | [post](https://drmaciver.substack.com/p/how-i-clean-my-kitchen-at-the-end) 10 | in the excellent Overthinking Everything blog. 11 | 12 | ## Algorithm 13 | 14 | * 🍽️ Stow away any clean and dry tableware 15 | from the drying rack and dishwasher. 16 | 17 | * 🗑️ Are any garbage cans full? 18 | Empty them (take the bags away, place new bags). 19 | 20 | * 🧹 Declutter: Is there any non-kitchen clutter in the counters? 21 | Return it to its usual place. 22 | 23 | * If, while cooking, you dirty anything 24 | that doesn't go in the dishwasher (e.g., pots), 25 | leave it soaking in the sink. 26 | 27 | * 🛒 After eating, bring into the kitchen 28 | all tableware and food from the dining room. 29 | 30 | * 🥫 Handle food: 31 | 32 | * 🗑️ Not worth keeping? Trash it. 33 | 34 | * 🔥 Needs to cool down? Leave on the stove. 35 | 36 | * 🌡️ At room temperature? Store it. 37 | 38 | * Sort dirty tableware into dishwasher and sink. 39 | If the sink is full, 40 | leave stuff in the counter next to it. 41 | 42 | * Start the dishwasher under any of these conditions: 43 | * Some section is full 44 | (e.g., no more room for glasses). 45 | * Most items of a given kind are in the dishwasher 46 | (e.g., ~all forks are dirty). 47 | 48 | * 🧽 Clean the counter next to the stove. 49 | This is mostly a superficial clean 50 | (no need for a thorough clean every day). 51 | 52 | * 🫧 Wash everything in the sink. 53 | 54 | * Prefer to start with large items (e.g., pots). 55 | 56 | * Work in batches where you first soap many things 57 | before you start to rinse the soap away from them 58 | – you'll finish a batch when the soap begins to dry in some items. 59 | 60 | * Leave things to dry on drying rack or stove counter. 61 | 62 | * 🧽 Clean the counter next to the sink. 63 | 64 | * 🧽 Clean the sink. 65 | 66 | ## Principles 67 | 68 | * Minimize context switches. 69 | Context switches slow you down. 70 | For example, minimize the number of times 71 | you have to: 72 | * Wash and/or dry your hands. 73 | * Open or close drawers or the dishwasher. 74 | 75 | * Prioritize reducing in-flight work. 76 | Think of the kitchen as a system 77 | with many subsystems. 78 | Food and dirty utensils flow through these subsystems 79 | until they are properly put away. 80 | As you reduce the number of items 81 | pending in each subsystem, 82 | you free capacity (e.g., more space in the drying rack), 83 | generally enabling other items to flow more efficiently. 84 | -------------------------------------------------------------------------------- /load-shedding-surprises.md: -------------------------------------------------------------------------------- 1 | # Load Shedding: Surprises 2 | 3 | ## Introduction 4 | 5 | This article lists a few counterintuitive or surprising things 6 | I've learned during fifteen years working on load shedding at Google. 7 | 8 | This is an early draft (as of 2024-05-05). 9 | 10 | ## Retries are difficult 11 | 12 | As a mechanism to handle spurious overload problems, 13 | retries are very difficult to implement correctly; 14 | their apparent simplicity is deceptive. 15 | Engineers consistently get it wrong, 16 | deploying implementations that make overload problems significantly worse. 17 | 18 | For example, engineers often assume that exponential back-off 19 | is a good strategy for clients to dynamically reduce the load they send 20 | to an overloaded server. 21 | Exponential back-off is, in fact, a terrible strategy in many situations. 22 | 23 | TODO: Explain why Exponential back-off is terrible. 24 | 25 | ## Overloaded and mostly idle 26 | 27 | Sufficiently large distributed systems are *simultaneously* 28 | mostly idle and overloaded. 29 | 30 | It is difficult to increase utilization 31 | without causing some sub-components to run out of capacity. 32 | Perfect load balancing does not exist. 33 | Once they become large and complex enough, 34 | even systems with low average utilization will have overloaded sub-components. 35 | 36 | ## Overload errors are undesirable; protections, necessary 37 | 38 | Nobody likes overload errors: 39 | we should do what we can to avoid them 40 | (improve load balancing, dynamically provision systems, etc.). 41 | 42 | None of that changes the fact that **all 43 | subcomponents of large distributed systems 44 | must degrade gracefully when overloaded**. 45 | The desire to eliminate overload errors 46 | should never justify rolling back overload protections. 47 | 48 | The only way to ensure that a system (or a part thereof) 49 | never returns "overloaded!" errors 50 | is to make sure it has access to infinite capacity. 51 | But, in our universe, there's no such thing as infinite capacity! 52 | Once you accept a maximum bound for the capacity you can afford, 53 | you are forced to face the question: 54 | What should the system do in the unlikely situation that demand 55 | (e.g., incoming traffic) exceeds capacity? 56 | The answer should be obvious: Return "overloaded!" errors. 57 | 58 | This is analogous to erroneously believing that "software should not have bugs" 59 | implies "we shouldn't create mechanisms to deal with software bugs." 60 | We should strive to do both: avoid bugs in software, 61 | and implement adequate mechanisms to deal with them, 62 | when they inevitably occur. 63 | 64 | -------------------------------------------------------------------------------- /mentor.md: -------------------------------------------------------------------------------- 1 | # Mentoring 2 | 3 | ## Introduction 4 | 5 | This document is a draft (as of 2024-05-20) 6 | collecting my experiences with mentoring. 7 | 8 | This is still a very early draft. 9 | I'm publishing just to force myself to get it out there 10 | and start polishing it, 11 | but I expect it's still very incomplete. 12 | 13 | This is written specifically for software engineers; 14 | it may not translate well to other professions. 15 | 16 | ## What makes a mentor good? 17 | 18 | The following are good characteristics for a mentor: 19 | 20 | * Slight relative seniority. 21 | Obviously, your mentor will ideally be *more* senior than you 22 | –specifically, they will have tackled 23 | the types of problems you're trying to solve. 24 | But the key word is *slight*: 25 | ideally, they haven't already moved way past your current seniority. 26 | Concretely, they were solving problems of comparable scope 27 | *not so long ago* 28 | that they've already started to forget those experiences 29 | (as they have moved to accumulate experience solving larger problems). 30 | 31 | * Theme fit. 32 | While, in my experience, some topics are very common, 33 | each mentorship may focus on different types of advice. 34 | You want a mentor that has experience 35 | on precisely the types of problems 36 | you are seeking to improve on. 37 | 38 | * Available energy. 39 | This is perhaps obvious, but it bears saying explicitly: 40 | your mentor should have enough energy to dedicate to the mentorship. 41 | The actual time commitment isn't that large 42 | (I suggest roughly 1 hour per 3 weeks), 43 | but the mentor needs to be able to make the time and show up. 44 | 45 | * Distance. 46 | A good mentor is often somewhat removed from the mentee, 47 | typically not in the same team. 48 | If the mentor and mentee share responsibilities, 49 | it may be difficult to discuss specific topics 50 | (for example, there may be conflicts of interest). 51 | This is perhaps less important than the other criteria. 52 | 53 | ## Rationale 54 | 55 | ### Why get a mentor? 56 | 57 | Advancing in your career means expanding the scope of the problems you tackle, 58 | taking larger responsibilities. 59 | Problems are easier to solve 60 | if you've tackled problems of the same type before. 61 | 62 | The problems we're facing are only new variations of larger general problems. 63 | A good mentor, who has solved them before, can help you from their experience. 64 | They can help you direct your attention in directions you may be neglecting, 65 | identify the right questions you should be asking. 66 | Your mentor will *not* solve your problems for you, 67 | but standing on their shoulders tends to significantly accelerate your success. 68 | 69 | ### Why mentor someone? 70 | 71 | There are a few reasons why you should spend some energy mentoring others. 72 | 73 | * You'll be fostering a culture of collaboration and sharing, 74 | helping others. 75 | 76 | * You can have an outsized influence. 77 | A small investment of your energy (roughly an hour per month) 78 | can go a very long way: 79 | it can significantly accelerate the impact of someone else. 80 | You'll be in a very comfortable position 81 | of being able to guide projects or efforts significantly 82 | without taking either significant responsibilities 83 | nor being in charge of the actual execution. 84 | 85 | * You'll learn: 86 | 87 | * Mentorships are a great opportunity to reflect. 88 | You'll be discussing types of problems you've faced recently. 89 | Forcing you to review and make explicit 90 | (so that you can transmit them to your mentee) 91 | the intuitions you built 92 | can help you dig deeper and consolidate knowledge. 93 | 94 | * They'll often suggest interesting ideas or approaches 95 | you hadn't considered. 96 | 97 | * You'll be growing your network. 98 | Mentoring others is an excellent way to create long-term relationships. 99 | 100 | Even if you don't get directly recognized, 101 | you'll be guided by beauty. 102 | Technical excellence is its own reward. 103 | 104 | ## Finding mentors 105 | 106 | ### Topics list 107 | 108 | The first thing you should do to find a good mentor is 109 | write down a list of three to five general questions 110 | that frame the topics you would like to discuss. 111 | This list will help everybody –including yourself– understand 112 | the type of mentorship we're looking for. 113 | 114 | They shouldn't be too specific 115 | ("how do I deal with a very specific situation") 116 | but rather centered on engineering or leadership skills 117 | that you'd like to improve on. 118 | 119 | 120 | #### Examples 121 | 122 | The following are a few example questions: 123 | 124 | * How important is the phrasing in expectations/OKRs and evaluations? 125 | How much effort should we put into it? 126 | How do we get better at it? 127 | 128 | * How do I strategize around positioning a product 129 | that's deeply integrated within Google's infrastructure? 130 | 131 | * How do I build stronger relationships with partner teams, key customers, 132 | and senior leadership? 133 | 134 | * As an engineering manager, how do I build technical credibility 135 | when I've been away from active coding for a very long time? 136 | 137 | * How can I operate if my software has strong dependencies 138 | on components that don't work very well? 139 | 140 | * How do I "sell" (to stakeholders, my management chain, etc.) 141 | the impact of the improvements I make to developer tools? 142 | 143 | * How do I manage mid-size requests for help 144 | from my customers or other infrastructure teams? 145 | How much guidance should I provide? 146 | What's the story around recognition? 147 | 148 | ### Finding a good mentor 149 | 150 | Easy: send an email to your manager or tech-lead and ask them 151 | to help you find someone to mentor you. 152 | Include your list of topics. 153 | 154 | Make it explicit 155 | that you're not asking them to be your mentor 156 | –you're already discussing your career with them. 157 | Instead, you're asking them to help you find a third-party 158 | who may fit your themes. 159 | 160 | ### Mentors for your teammates 161 | 162 | Senior team leaders, 163 | whether in tech-lead or manager roles, 164 | should volunteer explicitly to help others find mentors. 165 | 166 | Every year or two 167 | I take ~ten minutes in our team meeting to: 168 | 169 | 1. Relate how much having a mentor has helped me. 170 | 171 | 2. Explain why everybody should consider having a mentor 172 | 173 | 3. Offer that anyone interested 174 | can simply email me and I'll search for a good match. 175 | 176 | When people take me up on this offer, 177 | I ask them for the list of questions to inform the search. 178 | I forward their questions 179 | –along with context about the applicant– 180 | to other senior engineers in my network, 181 | asking them if they can think of a good match in their organization. 182 | 183 | ## Structuring your mentorship 184 | 185 | My advice for structuring a good mentorship: 186 | 187 | * **1 hour.** 188 | Sometimes people schedule only 30 minutes; 189 | in my experience that's too short. 190 | You can always finish sooner if you run out of topics. 191 | More than 1 hour seems excessive. 192 | 193 | * **Every 3 weeks.** 194 | * More frequent doesn't leave enough time between sessions 195 | to act on your mentor's feedback. 196 | * Less frequent causes you (especially the mentor) 197 | to start losing context from the previous conversation. 198 | 199 | * **Prepare for each session.** 200 | 201 | * **Look back.** 202 | Start each session by explaining how you acted on the advice you received. 203 | What did you try? 204 | What happened? 205 | How did it work? 206 | That conveys to the mentor that you take their feedback seriously. 207 | 208 | * **Look ahead.** 209 | Aim to bring a new question to focus the conversation. 210 | Sometimes this will be an evolution of the previous questions. 211 | If this becomes difficult, 212 | you may agree with your mentor to end the program. 213 | 214 | * **Express gratitude.** 215 | Your mentor is taking energy to help you. 216 | Make sure you thank them for helping you advance faster in your career. 217 | 218 | This may vary drastically depending on the type of mentorship, 219 | the culture in your organization, 220 | or your or your mentor's personal preferences. 221 | 222 | ## Be a good mentor 223 | 224 | The following are a few ideas you may want to consider 225 | if you're mentoring someone: 226 | 227 | * Focus on their **growth**, 228 | not on concrete problems or questions. 229 | The problems or questions being discussed are only tools 230 | to support their growth. 231 | You're only succeeding if they are building the ability to solve 232 | these problems independently. 233 | 234 | * Guide them by asking them questions, not giving them answers. 235 | 236 | * Give them time to think on their own. 237 | See if they come to similar conclusions to your own. 238 | 239 | * Give them **space**. 240 | Your mentee will have a different perspective to yours. 241 | Don't expect them to come to the same conclusions as you do. 242 | They'll know their specific context much better than you can, 243 | which may enable them to make better decisions 244 | (despite your relative seniority). 245 | Make sure you give them enough room to find their own voice 246 | and run their own experiments 247 | (even if you think those experiments are unnecessary). 248 | 249 | * Be **punctual**. 250 | Show up on time to show respect and consideration. 251 | 252 | This list probably falls short. 253 | If you have ideas, please tell me. 254 | 255 | ## Conclusions 256 | 257 | All engineers 258 | should have a mentor 259 | in order to accelerate their careers. 260 | There are also good reasons 261 | for senior engineers to contribute to others through mentoring. 262 | 263 | I hope the information in this document 264 | gives you actionable ideas 265 | to find a good mentor and structure your mentorship 266 | in a way that sets you up for success. 267 | 268 | -------------------------------------------------------------------------------- /recommendations.md: -------------------------------------------------------------------------------- 1 | # Recommendations 2 | 3 | This is a list of things or software programs 4 | that I enjoy using. 5 | 6 | ## Why Share 7 | 8 | I'm sharing this list for two reasons: 9 | 10 | * Maybe it helps my friends or random readers get one or two useful ideas. 11 | 12 | * I've benefited a lot from advice from my friends. 13 | I'm hoping my sharing this opening 14 | may encourage others to reach out to me 15 | with advice or suggestions: 16 | 17 | * You may be encouraged to write down and share a similar list 18 | of your favorite objects and recommendations. 19 | In this case, please send me an email (alefore@gmail.com) 20 | with a link to your list. 21 | I'd be happy to read it. 22 | 23 | * You may notice something missing or have an idea along the lines of 24 | "if you like X, you'll probably like Y". 25 | If you're one of my friends, please tell me next time we meet. 26 | 27 | Goes without saying that this list is highly subjective. 28 | 29 | ## Software 30 | 31 | ### Excalidraw 32 | 33 | I find Excalidraw excellent, 34 | a great example of a good user interface. 35 | 36 | As I was learning to use it, 37 | I often found myself thinking 38 | "oh, it would be great if I could adjust this object in this way," 39 | to just find out that the corresponding feature exists 40 | and is accessible at the most natural place. 41 | 42 | There's also a lesson to be learned at the fact that 43 | the outputs are deliberately drawn imperfectly 44 | (imitating a hand-drawn sketch). 45 | 46 | ### Syncthing 47 | 48 | Syncthing very quickly became a critical part of my workflow, 49 | allowing me to synchronize my Zettelkasten into various devices 50 | much more efficiently than with other tools (including `git`) that I tried. 51 | 52 | If you haven't heard of it, I recommend you check it out. 53 | You may not need it yet, 54 | but knowing about it may save you some time in the future. 55 | 56 | ### Git 57 | 58 | I depend on `git` to maintain a lot of my information. 59 | 60 | I can't say that git is an example of great design, 61 | not when describing its user interface. 62 | It sometimes seems more complex than merited. 63 | 64 | On the other hand, I can't say that it isn't (an example of great design); 65 | it may just be that my needs are relatively straightforward. 66 | 67 | In any case, I love the flexibility and ease 68 | with which it allows me to access and update 69 | various repositories of information 70 | across many devices. 71 | 72 | ## Furniture 73 | 74 | ### Aeron Miller 75 | 76 | My personal chair is an Aeron Miller. 77 | 78 | It doesn't look very pretty, 79 | but I find it very comfortable. 80 | 81 | ### horgenglarus Classic 82 | 83 | I own a set of horgenglarus Classic chairs. 84 | While they are now a bit too old, 85 | I'm very happy with them. 86 | 87 | Originally designed in 1918, 88 | they are still manufactured and sold, 89 | with only minor variations. 90 | These chairs not only 91 | honor Swiss traditions and history 92 | (and from the Sihl valley and Albis range) but also 93 | exemplify great design. 94 | 95 | ## Home 96 | 97 | # Sonos 98 | 99 | # AeroPress 100 | 101 | # Mud Australia 102 | 103 | ### Acaia Lunar 104 | 105 | The Lunar scale is a pleasure to use. 106 | I use it nearly every day (when making coffee) and it just gets the job done. 107 | It's expensive, but it's one of those great examples of good design, 108 | an object that just lets you achieve your goal and gets out of the way. 109 | 110 | ### Philips Hue Light Bulbs 111 | 112 | We decided to upgrade _most_ light bulbs in our home to be smart light bulbs. 113 | Great decision. 114 | 115 | You can trivially reconfigure all your light switches 116 | to accommodate requirements you hadn't (and couldn't have) anticipated. 117 | I think there's something to be said about enabling iteration, 118 | about adopting practices that let you more easily adjust your surroundings 119 | to better fit your constantly evolving desires. 120 | Christopher Alexander would say that you make your buildings come alive. 121 | 122 | We went with the Philips Hue line, 123 | on the recommendations from a friend. 124 | It just works! We haven't had any issues. 125 | I suppose other similar smart-light bulb systems may just as well; 126 | I can't comment on how Philips Hue stands in comparison to their competitors. 127 | 128 | I'm impressed by the fact that the technology has reached the point 129 | where [a lightswitch](https://www.feller.ch/de/sortiment/schalter-und-taster/funktaster) 130 | can communicate wirelessly 131 | with the home control system 132 | without needing any batteries or electric input 133 | —the simple act of pressing it generates enough power. 134 | 135 | We still have some old-fashioned light bulbs in our home. 136 | Some are, unfortunately, not compatible; 137 | for others (e.g., on very visible lamps) 138 | we haven't found beautiful-enough bulbs in the Philips hue line. 139 | 140 | ## Clothing 141 | 142 | ### Patagonia Better Sweater Fleece Jacket 143 | 144 | My Patagonia Better Sweater Fleece Jacket is the best sweater I've ever owned. 145 | It's probably the sweater I've worn the most in my life. 146 | 147 | I've not only worn it in mountain hikes or antarctic expeditions, 148 | but many days in the office or home. 149 | And, after almost a decade, 150 | it's still pretty much in the same condition it was when I just got it. 151 | 152 | I got it as a gift from Google. 153 | The best piece of swag I've gotten. 154 | Many of my colleagues who also got one are as appreciative of it as I am. 155 | 156 | ## Writing 157 | 158 | ### Pilot Vanishing Point 159 | 160 | I write on paper using a Pilot Vanishing Point fountain pen. 161 | I used it almost daily for journaling for a decade, 162 | before I switched to a Pilot Custom 823 in 2024. 163 | 164 | The pen has its fair share of scratches. 165 | They bothered me in the beginning: 166 | "oh no, it's scratched!" 167 | I've now come to appreciate them. 168 | 169 | ### Topre Realforce Keyboard XF01T0 170 | 171 | Since 2021-09, I do all my typing on a Topre Realforce XF01T0 keyboard. 172 | It's a pleassure to use. 173 | 174 | As of 2024-04-06, I haven't owned it for that long; 175 | I don't know for how long it'll hold up. 176 | But, until now, I haven't experienced any issues with it. 177 | It works like a charm. 178 | 179 | ### Gmund hemp paper 180 | 181 | My paper of chice is the 182 | [Gmund 100% hemp paper (Gmund 183 | Hanf)](https://www.gmund.com/shop/en_int/collections/gmund-hanf.html) 184 | at 120 g/m² (in natural white, without colors). 185 | 186 | ## Baby 187 | 188 | ### Judes diapers 189 | 190 | We decided to use reusable diapers for our baby 191 | and went with the [Judes](https://www.judesfamily.com/) diapers. 192 | We've only used them for about five weeks as of this writing, 193 | but we're very happy with these choices. 194 | 195 | These is highly subjective, 196 | but sustainability considerations aside, 197 | dressing our baby with these high-quality diapers 198 | feels much better than using disposable diapers. 199 | The difference is difficult to describe, 200 | but it feels much more respectful of the baby. 201 | 202 | A roll of poo paper lasts us about 3 weeks. 203 | We got the Klassik-Set (including 21 diapers). 204 | 205 | ### Zipster 206 | 207 | We really like the [Zipster pajamas](https://www.zipsterbaby.ch/): 208 | 209 | * The bamboo fabric is great: 210 | it is very soft 211 | and breaths naturally. 212 | 213 | * Having a zipper (rather than many buttons) is very practical 214 | –you'll appreciate this when changing diappers at 4 a.m.. 215 | 216 | If you're ordering them to Switzerland, 217 | you'll have to pay customs on the import (Zollgebühr). 218 | 219 | ### Burp cloths 220 | 221 | We have many burp cloths (Nuschi), 222 | probably from six different manufacturers. 223 | The quality differences are large. 224 | 225 | The ones we like the most are: 226 | 227 | * Lässig. 228 | Quite pretty designs and great quality (e.g., good absortion). 229 | 230 | * Dear April. 231 | Probably the prettiest designs and great quality. 232 | On the minus, they have a special hem which we find somewhat annoying. 233 | 234 | ### Kalumi bodysuits 235 | 236 | Of all the bodies we've tried, 237 | the [Kalumi](https://kalumi.shop/en/collections/grow-free-with-me-body) 238 | are our favorite. 239 | 240 | Because they are made of Merino wool and silk, 241 | they are quite stretchy (easy to put on) 242 | and seem to keep our baby adequately warm. 243 | 244 | -------------------------------------------------------------------------------- /sardegna.md: -------------------------------------------------------------------------------- 1 | # Sardegna 2 | 3 | Los rayos del sol se cuelan
4 | tras la cortina florida.
5 | Un nuevo día comienza.
6 | Sus párpados pronto vuelan:
7 | Marina, sola, despierta
8 | y abandona su cobija. 9 | 10 | Una hermosa buganvília
11 | que abraza con mucho esmero
12 | barandas curvas de acero,
13 | viejo sendero de hormigas,
14 | entrega flores al viento. 15 | 16 | Cansado de hacer con ellas
17 | remolinos de colores,
18 | el viento olvida las flores
19 | entre escalones de piedra
20 | y el sol bebe sus rubores. 21 | 22 | Besa la brisa marina
23 | cada flor y cada musgo,
24 | las hojas de todo arbusto,
25 | que un chivo de pelo hirsuto
26 | con sus labios examina. 27 | 28 | Marina, ya en la alacena,
29 | llena una cuchara vieja
30 | de viscosa miel de abejas
31 | y moja su boca seca,
32 | la miel refresca su lengua. 33 | 34 | Lame el sol de la mañana,
35 | colándose entre las ramas,
36 | al chivo de labios secos,
37 | maraña de pelos negros,
38 | que se refugia del cielo
39 | bajo troncos retorcidos,
40 | a la sombra de un olivo, 41 | 42 | Besa el sol de la mañana
43 | las paredes amarillas
44 | de la casa de Marina,
45 | a las tenaces hormigas
46 | que recorren la baranda,
47 | y al chivo cuando se asoma.
48 | Marina abre la ventana
49 | tras disfrutar del aroma
50 | del café recién molido.
51 | Afuera llora a alaridos
52 | el bebé de los vecinos. 53 | 54 | Marina sale a la playa
55 | y con su palma arrugada
56 | como la piel del olivo
57 | acaricia la baranda
58 | por la intemperie curvada
59 | como los cuernos del chivo. 60 | 61 | 72 | 73 | Junto a un faro abandonado
74 | la anciana de pelo blanco
75 | en la playa se libera
76 | de su blusa y de su falda.
77 | Las deja en la arena clara
78 | y desnuda al mar se entrega.
79 | El sol le besa las canas.
80 | La sal le lame la espalda. 81 | 82 | 88 | 89 | La tarde transcurre lenta.
90 | El viento borra las huellas.
91 | Se avecina una tormenta.
92 | El cielo se parte en truenos.
93 | En mar brava los veleros
94 | se apresuran a sus puertos.
95 | No será noche de estrellas. 96 | 97 | 115 | 116 | 132 | 133 | Marina, ya de regreso,
134 | se sirve un vaso de vino,
135 | un puñado de aceitunas,
136 | y un buen pedazo de queso.
137 | En el comedor la anciana
138 | disfruta su vermentino,
139 | bajo la luz de la luna
140 | que ingresa por la ventana. 141 | 142 | 150 | 151 | En la aldea y los cultivos
152 | hacen eco los ladridos:
153 | los perros de los vecinos
154 | se pasan la noche en vela.
155 | El llanto fuerte del crío
156 | sin invitación se cuela
157 | entre la ventana abierta.
158 | Mas Marina no despierta;
159 | Marina, profunda, sueña. 160 | 161 | 169 | 170 | Besa el manto de la luna
171 | el plumaje de una gruya
172 | que posada sobre rocas
173 | busca peces en las olas. 174 | 175 | Abraza la luna llena
176 | las olas sobre la arena.
177 | Su luz anida en los mares
178 | y en los dos ojos lunares
179 | del chivo en los olivares. 180 | 181 | 184 | 185 | Los dedos de los olivos,
186 | la danza de remolinos
187 | de flores de buganvília,
188 | el vuelo de golondrinas,
189 | las arrugas de Marina,
190 | y los dos cuernos del chivo;
191 | así se escapan los días,
192 | así pasamos la vida:
193 | escribimos, caminamos,
194 | vemos, hablamos y amamos,
195 | cual barcos a la deriva. 196 | 197 | -------------------------------------------------------------------------------- /smoothing.md: -------------------------------------------------------------------------------- 1 | # Exponential smoothing 2 | 3 | Exponential Smoothing is a technique to keep track of the average value of a 4 | signal, weighted towards the most recent values. 5 | 6 | A "half life" parameter controls how biased the computation 7 | is towards recent values. 8 | 9 | ## The problem 10 | 11 | Discrete events occur at specific timestamps. 12 | A smoothing algoritm receives these timestamps 13 | and computes a timeseries 14 | modeling a "rate" at which events are occurring 15 | (*e.g.*, requests per second, commits per week). 16 | 17 | The output should be somewhat "smooth": 18 | a parameter controls how quickly the output signal 19 | reacts to changes in the event occurrence. 20 | 21 | The input may itself be a timeseries 22 | with a rate of events. 23 | It could even be the output of a previous execution of exponential smoothing. 24 | The smoothing algorithm produces a new "smoother" view. 25 | 26 | ### Examples 27 | 28 | The following are common examples: 29 | 30 | * Given timestamps of requests, 31 | compute a timeseries with the rate of requests per second. 32 | 33 | * Given timestamps of commits to a version control repository, 34 | compute a timeseries with the rate of action 35 | (e.g., commits per week). 36 | 37 | * Given a metric corresponding to the number of active requests 38 | executing in a server sampled every 10 milliseconds, 39 | compute a smoothed view with longer-running 40 | lower-resolution averages. 41 | 42 | ## Smoothing algorithms 43 | 44 | A smoothing algorithm which computes the signal value at a time `t` 45 | can be modeled by taking the set of all events `e` 46 | that have occured at a previous time `e.time() <= t` 47 | and adding the "decayed contribution" of these events 48 | (relative to time `t`): 49 | 50 | o(t) := sum(e.contribution(t) for e in EVENTS if e.time() <= t) 51 | 52 | An efficient implementation wouldn't need to keep track of all events. 53 | Typically, we receive a possibly-very-long stream of time-sorted events 54 | and produce an output incrementally. 55 | 56 | ## Running windows 57 | 58 | If an event occurs at a time `t`, 59 | we could model its contribution as 60 | adding a value of 1 to the output 61 | for all times between `t` and `t + 1 second` (and 0 otherwise): 62 | 63 | ![Naive contribution (Single event)](images/054.svg) 64 | 65 | This is *not* exponential smoothing. 66 | Instead, we call this smoothing algorithm "running window". 67 | That's because an equivalent formulation is to say that at any time, 68 | we simply count how many events occurred in the last second. 69 | 70 | For example, if events ocur at times 71 | 0.1, 0.5, 0.8, 1.5, 1.9, 2.6, 4.5 and 4.8, 72 | this would model the output rate as: 73 | 74 | ![Naive contribution](images/053.svg) 75 | 76 | ## Exponential decay 77 | 78 | Exponential smoothing uses a specific formula 79 | that makes the contribution of an event to the output 80 | decay exponentially as time passes. 81 | 82 | This is done based on a "half-life" parameter that controls 83 | how quickly the output reacts to changes in the rate. 84 | 85 | Let `h` be the half-life and `d` the amount of time elapsed after an event. 86 | The contribution of the event, `decay(d)`, can be computed thus: 87 | 88 | scale_constant = ln(2) / h 89 | 90 | decay(d) := pow(2, -d / h) * scale_constant 91 | 92 | ### Why the scale constant? 93 | 94 | The reason for the `scale_constant` factor (of `ln(2) / h`) is 95 | to scale the function, 96 | so that its integral (from d=0 to d = ∞) is exactly 1: 97 | 98 | ∫₀᪲ decay(t) dt = 1 99 | 100 | If we didn't multiply by ln(2), we would be overestimating the rate: 101 | we would be allowing each event to have a contribution 102 | of 1/ln(2) (~1.4427) to the output. 103 | 104 | ### Simple examples 105 | 106 | The following is the contribution of a single event with different half-lifes: 107 | 108 | ![Single event decay](images/055.svg) 109 | 110 | With the events at 111 | 0.1, 0.5, 0.8, 1.5, 1.9, 2.6, 4.5 and 4.8, 112 | the rate (in events *per second*) would be modeled thus 113 | based on different half-life values: 114 | 115 | ![Multiple events smoothed](images/056.svg) 116 | 117 | ### Real examples 118 | 119 | One can produce a set of timestamps of commits to a git repository thus: 120 | 121 | git log --format="%ct" | sort 122 | 123 | Why do we need to sort it? 124 | Merges/rebases make git history not always follow chronologic order. 125 | 126 | We can use this to produce a view of commit rates 127 | (this example corresponds to [Edge](http://github.com/alefore/edge)): 128 | 129 | ![Commits to Edge with different half-lifes](images/057.svg) 130 | 131 | Given a repository, `exponential-smooth` and `tstosvg` 132 | can generate a view thus: 133 | 134 | git log --format="%ct" |\ 135 | sort |\ 136 | exponential-smooth --half_life=30d --output_rate=1d --output_resolution=6h | \ 137 | tstosvg >/tmp/output.svg 138 | 139 | ### Quality disadvantages of running windows 140 | 141 | The rates modeled by Exponential decay tend to be better 142 | than those from running windows because they incorporate more 143 | information about the events. 144 | 145 | Running windows are "binary" in assigning weights to events: 146 | 147 | * All events that happened inside the window have the same weight (1), 148 | regardless of how recent they are 149 | 150 | * All events outside the window have the same weight (0). 151 | 152 | This means that running windows tend to show strange artifacts 153 | when there are bursts. 154 | 155 | TODO: Graph the above. 156 | 157 | ### Efficient computation 158 | 159 | The sum of the decays of many events equals 160 | the decay of the sum of many events. 161 | In other words, if we know the contribution of all events 162 | at a previous time t0, we can compute the contribution at time t 163 | simply by decaying the sum. 164 | 165 | That allows us to implement exponential decay very efficiently, 166 | simply remembering two values: 167 | 168 | * timestamp: The timestamp of the last update. 169 | This can start at a value infinitely in the past 170 | (or, depending on coding preferences, an explicit "absent" value). 171 | 172 | * rate: The output rate at the last update. 173 | This can start at 0 174 | (when no events have ocurred, the rate is modeled at 0). 175 | 176 | As we iterate the stream of events, we update the values cheaply: 177 | 178 | * timestamp: The timestamp can be updated to the new event time. 179 | 180 | * rate: The output rate is computed by decaying its previous value 181 | depending on the duration between the two timestamps 182 | (the last and the new timestamp values). 183 | 184 | The decay factor can be computed relatively quickly as: 185 | 186 | pow(2, -elapsed_duration / half_life_duration) * scale_constant 187 | 188 | #### Python implementation 189 | 190 | The following is my implementation of a simple Python script 191 | that receives a file with timestamps and event counts 192 | and produces a timeseries applying exponential smoothing. 193 | 194 | One can use it to produce a view of commits on a git repository thus: 195 | 196 | git log --format="%ct 1" | sort -n | \ 197 | exponential-smooth --half_life=30d --output_rate=1d --output_resolution=1d 198 | 199 | This produces a timeseries (which `tstosvg` can render). 200 | 201 | File `~/bin/exponential-smooth` (mode 755): 202 | 203 | #!/usr/bin/python3 204 | import math 205 | import argparse 206 | import re 207 | import sys 208 | from datetime import datetime, timedelta, timezone 209 | from typing import Generator, List, Optional, TextIO, Tuple 210 | 211 | 212 | # Input is lines of the form: "1407621609 23". 213 | # The first token is a timestamp (seconds since beginning of Unix epoch). 214 | # The second otken is a number of events occurring at that timestamp. 215 | def Parse(input_file: TextIO) -> Generator[Tuple[datetime, float], None, None]: 216 | for line in input_file: 217 | parts = line.strip().split() 218 | if 1 <= len(parts) <= 2: 219 | timestamp = datetime.fromtimestamp(float(parts[0]), tz=timezone.utc) 220 | value = float(parts[1]) if len(parts) > 1 else 1 221 | yield timestamp, value 222 | else: 223 | raise ValueError( 224 | "Input should contain exactly two tokens: a timestamp and a value.") 225 | 226 | 227 | def ParseDuration(duration_str: str) -> timedelta: 228 | TIME_UNITS = { 229 | 's': 'seconds', 230 | 'm': 'minutes', 231 | 'h': 'hours', 232 | 'd': 'days', 233 | 'ms': 'milliseconds', 234 | 'us': 'microseconds' 235 | } 236 | TIME_UNITS_REGEX = re.compile(rf"^(\d+) *({'|'.join(TIME_UNITS.keys())})$") 237 | match = TIME_UNITS_REGEX.match(duration_str) 238 | if match: 239 | value, suffix = match.groups() 240 | unit = TIME_UNITS[suffix] 241 | return timedelta(**{unit: int(value)}) 242 | raise ValueError("Invalid duration format. Supported units are: " + 243 | ", ".join(TIME_UNITS)) 244 | 245 | 246 | class Smoother: 247 | 248 | def __init__(self, half_life: timedelta, output_rate: timedelta, 249 | output_resolution: timedelta) -> None: 250 | self.half_life: timedelta = half_life 251 | self.output_resolution: timedelta = output_resolution 252 | self.next_output: Optional[datetime] = None 253 | self.current_timestamp: Optional[datetime] = None 254 | self.current_rate: float = 0 255 | self.decay_scale_constant = math.log(2) / (self.half_life / output_rate) 256 | 257 | def Process(self, values: Generator[Tuple[datetime, float], None, 258 | None]) -> None: 259 | for time, value in values: 260 | if self.next_output: 261 | while self.next_output < time: 262 | self.Output() 263 | else: 264 | self.next_output = time 265 | self.Output() 266 | self.DecayToTime(time) 267 | self.current_rate += value * self.decay_scale_constant 268 | 269 | def Output(self) -> None: 270 | assert self.next_output is not None 271 | self.DecayToTime(self.next_output) 272 | print('%s %s' % (self.next_output.timestamp(), self.current_rate)) 273 | self.next_output += self.output_resolution 274 | 275 | def DecayToTime(self, time: datetime) -> None: 276 | if self.current_timestamp is None: 277 | self.current_timestamp = time 278 | assert time >= self.current_timestamp 279 | elapsed = (time - self.current_timestamp) / self.half_life 280 | self.current_timestamp = time 281 | self.current_rate *= pow(2, -elapsed) 282 | 283 | 284 | def main() -> None: 285 | parser = argparse.ArgumentParser( 286 | description='Apply exponential smoothing to a series of timestamps. ' 287 | 'The output is the per-second rate.') 288 | parser.add_argument( 289 | "--half_life", 290 | type=ParseDuration, 291 | default="1s", 292 | help="Duration for a half-life of decay") 293 | parser.add_argument( 294 | "--output_rate", 295 | type=ParseDuration, 296 | default="1s", 297 | help="Rate to compute (e.g., events per second)") 298 | parser.add_argument( 299 | "--output_resolution", 300 | type=ParseDuration, 301 | default="1s", 302 | help="Frequency of lines in the output") 303 | args = parser.parse_args() 304 | Smoother(args.half_life, args.output_rate, 305 | args.output_resolution).Process(Parse(sys.stdin)) 306 | 307 | 308 | if __name__ == "__main__": 309 | main() 310 | 311 | ## Biases 312 | 313 | How exponential smoothing is used can introduce biases in the data. 314 | This is not specific to exponential smoothing but potentially affects 315 | all smoothing algorithms. 316 | 317 | A smoothing implementation may assume 318 | that events always occur at exactly the timestamp when they are registered. 319 | If the rate is computed at exactly those timestamps, 320 | this may overestimate the rate. 321 | Conversely, if the rate is computed exactly *before* the events are registered, 322 | the rate will be underestimated. 323 | 324 | One way to protect against this could be 325 | to assume that the event occurred at exactly the half-way point 326 | between the last registration and the current registration. 327 | 328 | ### Why this may happen 329 | 330 | For example, a server using a `rate_tracker` instance 331 | (of a class implementing exponential smoothing) 332 | to keep track of request rates could do something like this: 333 | 334 | Time now = clock.Now(); 335 | rate_tracker.Advance(now); 336 | // Underestimates the actual rate! 337 | Rate current_rate = rate_tracker.GetCurrentRate(); 338 | request_rate.RegisterEvent(); 339 | 340 | This would underestimate the rate 341 | (always reading it before registering a new event). 342 | Conversely, registering the event *before* reading the rate 343 | biases the data in the opposite direction: 344 | 345 | rate_tracker.Advance(now); 346 | request_rate.RegisterEvent(); 347 | // Overestimates the actual rate! 348 | Rate current_rate = rate_tracker.GetCurrentRate(); 349 | 350 | -------------------------------------------------------------------------------- /synthetic-aging.md: -------------------------------------------------------------------------------- 1 | # Synthetic aging 2 | 3 | Artisans and manufacturers often synthetically age their products. 4 | This article is an attempt to make sense of this trend. 5 | 6 | This is an incomplete draft. 7 | As of 2024-05-10, I still intend to augment and review it. 8 | 9 | ## Examples 10 | 11 | * Ford uses speakers to digitally generate engine noise 12 | in new versions of Mustangs. 13 | 14 | * A violin maker deliberately scratches new violins with pasta. 15 | 16 | * Pieces of clothing are often deliberately worn out. 17 | 18 | * Vinyl records rose again over digital formats. 19 | This isn't exactly synthetic aging, 20 | but as a deliberate preference for an old technology, 21 | is a closely-related phenomena. 22 | 23 | ## Reasons 24 | 25 | Overt use of old objects conveys preferences for: 26 | 27 | * Quality 28 | 29 | * Exclusivity 30 | 31 | * Minimalism 32 | 33 | * Natural 34 | 35 | * Respect for history 36 | 37 | ### Quality 38 | 39 | Old objects that remain in use tend to convey a preference for quality. 40 | These tend to be objects that were created with emphasis on quality, 41 | in contrast to cheaply mass-produced modern objects. 42 | 43 | In many markets, quality of products decays over time. 44 | This reflects that many manufacturers 45 | focus on reducing costs over increasing (or even maintaining) quality, 46 | possibly in reaction to pressure towards short-term profits. 47 | Objects become commodities and consumers may react more strongly 48 | to price reductions –which are directly visible– 49 | than to less tangible quality increases. 50 | More manufacturers seem to be looking for cheaper materials 51 | than for ways to deliver quality improvements. 52 | 53 | The Lindy effect 54 | –the life expectancy of technologies or ideas 55 | is proportional to their current age– 56 | also plays a role. 57 | By synthetically aging objects, 58 | manufacturers may be taking advantages of biases 59 | borne by this effect. 60 | Ironically, we may subconciously perceive these aged objects as more durable 61 | –especially if we perceive them to be in a remarkably good shape 62 | given how old they appear to be. 63 | 64 | ### Exclusivity 65 | 66 | Old objects convey exclusivity. 67 | They are more "authentic", "unique", or "original", 68 | rather than mere copies. 69 | 70 | They tend to be unique hand-crafted objects, 71 | individual pieces laboriously created by artists (or artisans), 72 | rather than mass-produced copies. 73 | 74 | Each vynil record is unique, 75 | rather than an identical digital copy. 76 | The identical copies may be closer to the artist's vision 77 | –free of the noise that makes the vynil unique– 78 | but many still flock to the authentic experience of vynil records. 79 | 80 | A mechanical camera exposes the negative, 81 | chemically altering a piece of film irreversively. 82 | In contrast, the images captured by a digital camera can be erased trivially, 83 | with no incremental cost per photo. 84 | 85 | ### Minimalism 86 | 87 | Old objects tend to reflect a rejection of ephemeral fads 88 | and a focus on timeless, universal good design. 89 | 90 | They were subject to whatever whims fashion catered to when they were created, 91 | but they nevertheless tend to signal 92 | a rejection of arbitrary and superfluous complexity. 93 | That may reflect that time tends to accentuate the excentricites of fashion. 94 | 95 | A design that is old and still produced is likely good; 96 | else it wouldn't have survived. 97 | The Lindy effect also applies here. 98 | 99 | ### Natural 100 | 101 | Someone may prefer old objects to convey a preference for "natural" objects. 102 | 103 | Natural ways (material, sources, manufacturing processes, technologies…) 104 | have always been available, 105 | whereas "artificial" or "synthetic" ways 106 | are the invention of modern times. 107 | 108 | Thus old objects are significantly more likely 109 | to have been manufactured and operate 110 | based on these traditional, natural ways. 111 | 112 | ### Respect for history 113 | 114 | Use of old objects tends to signal an appreciation to our history 115 | –depending on the nature of the object, 116 | to our traditions, ancestors, origins. 117 | The old object serves as a bridge to the past, 118 | signaling connections from the owner to those who forged our history. 119 | 120 | Old designs have had more time to influence society. 121 | Having been used for a century, 122 | horgenglarus chairs have become part of our collective consciousness. 123 | 124 | Old individual objects have had more time to influence individuals. 125 | Perhaps this old coat was worn by our ancestors and reminds us of them. 126 | Even if the coat is synthetically aged (and thus new), 127 | it implicitly suggests these possibilities. 128 | 129 | -------------------------------------------------------------------------------- /xkbcomp.md: -------------------------------------------------------------------------------- 1 | # Linux: Keyboard Configuration 2 | 3 | I configure my keyboard using `xkbcomp`. 4 | 5 | ## Master file 6 | 7 | My master file starts with the `us` keyboard and includes two custom files, 8 | `accents` and `caps`. 9 | 10 | File `~/.xkb/map`: 11 | 12 | xkb_keymap { 13 | xkb_keycodes { include "evdev+aliases(qwerty)" }; 14 | xkb_types { include "complete" }; 15 | xkb_compat { include "complete" }; 16 | xkb_symbols { 17 | include "pc+us+us:2+inet(evdev)" 18 | include "accents(accents)" 19 | include "caps(caps)" 20 | }; 21 | xkb_geometry { include "pc(pc105)" }; 22 | }; 23 | 24 | ## Spanish & German support 25 | 26 | The first custom file sets accents for Spanish and diaeresis for German. 27 | It also sets a few other custom characters, such as `emdash`, `nobreakspace`, 28 | arrow characters (`←↑↓→`), and `×`. 29 | 30 | File `~/.xkb/symbols/accents`: 31 | 32 | partial alphanumeric_keys 33 | xkb_symbols "accents" { 34 | // Map the right Alt key (`RALT`) to act as an ISO_Level3_Shift, 35 | // ISO_Next_Group key, providing access to third-level symbols or 36 | // characters. 37 | key { 38 | type[group1]= "PC_LALT_LEVEL2", 39 | symbols[Group1]= [ ISO_Level3_Shift, ISO_Next_Group ] 40 | }; 41 | 42 | replace key {[ space, space, space, nobreakspace ]}; 43 | replace key {[ minus, underscore, endash, emdash ]}; 44 | replace key {[ period, greater, U2026 ]}; 45 | 46 | // Spanish characters. 47 | replace key {[ 1, exclam, exclamdown ]}; 48 | replace key {[ e, E, eacute, Eacute ]}; 49 | replace key {[ u, U, uacute, Uacute ]}; 50 | replace key {[ i, I, iacute, Iacute ]}; 51 | replace key {[ o, O, oacute, Oacute ]}; 52 | replace key {[ a, A, aacute, Aacute ]}; 53 | replace key {[ n, N, ntilde, Ntilde ]}; 54 | replace key {[ slash, question, questiondown ]}; 55 | 56 | // German characters. To avoid clashes with the definitions for Spanish 57 | // characters, I define the special keys *below* the corresponding normal 58 | // characters (e.g., `ä` in `z`, the key physically below `a`). 59 | replace key {[ d, D, ediaeresis, Ediaeresis ]}; 60 | replace key {[ j, J, udiaeresis, Udiaeresis ]}; 61 | replace key {[ k, K, idiaeresis, Idiaeresis ]}; 62 | replace key {[ l, L, odiaeresis, Odiaeresis ]}; 63 | replace key {[ z, Z, adiaeresis, Adiaeresis ]}; 64 | replace key {[ x, X, ssharp, U00D7 ]}; 65 | 66 | // Arrows. 67 | replace key {[ Left, Left, U2190 ]}; 68 | replace key {[ Up, Up, U2191 ]}; 69 | replace key {[ Down, Down, U2193 ]}; 70 | replace key {[ Right, Right, U2192 ]}; 71 | }; 72 | 73 | ## Caps Lock 74 | 75 | I disable caps lock. 76 | I never found it useful. 77 | 78 | Instead, I map it to Control. 79 | I use Control very frequently 80 | and I find it uncomfortable to press Control 81 | in the default position in most keyboards. 82 | 83 | File `~/.xkb/symbols/caps`: 84 | 85 | hidden partial modifier_keys 86 | xkb_symbols "caps" { 87 | replace key {[ Control_L ]}; 88 | modifier_map Control { }; 89 | }; 90 | 91 | ## Applying this configuration 92 | 93 | ### Executing xkbcomp directly 94 | 95 | I can apply my configurations with this command: 96 | 97 | xkbcomp -I$HOME/.xkb ~/.xkb/map $DISPLAY 98 | 99 | ### Integration with Udev 100 | 101 | My keyboard is connected to a USB switch. 102 | Occasionally, the configuration is dropped, as if I had unplugged the keyboard and plugged it back in. 103 | To apply my settings every time this happens, I wrote the following script: 104 | 105 | File `~/bin/xkb` (mode 755): 106 | 107 | #!/bin/bash 108 | export XAUTHORITY="/run/user/$(id -u alejo)/gdm/Xauthority" 109 | export DISPLAY=":1" 110 | (sleep 2 ; date ; xkbcomp -I$(eval echo ~alejo/.xkb) ~alejo/.xkb/map $DISPLAY ) >/tmp/xkb.log 2>&1 & 111 | 112 | I register it with this file: 113 | 114 | File `/etc/udev/rules.d/99-custom-kb.rules`: 115 | 116 | ACTION=="add", SUBSYSTEM=="input", ENV{ID_INPUT_KEYBOARD}=="1", RUN+="/home/alejo/bin/xkb" 117 | 118 | The reason for the call to `sleep 2` is that my distribution comes with some `/etc/udev/rules.d/99-...` files that override my settings. 119 | I don't want to edit (or move) any of these files; 120 | I fear this would cause ongoing pain 121 | (e.g., having to restore things or manage conflicts whenever my distribution is updated). 122 | 123 | I opted for the ugly solution: 124 | for the first ~2 seconds after the keyboard is re-detected 125 | (which happens a few times per day), my customizations are missing. 126 | 127 | ## Xmodmap 128 | 129 | I had originally used an `xmodmap` file with all my bindings. 130 | This work well in practice up until some point around 2014; 131 | something happened that made it incredibly slow. 132 | So I migrated away from `xmodmap` to `xkbcomp`. 133 | 134 | -------------------------------------------------------------------------------- /zurich-shorts.md: -------------------------------------------------------------------------------- 1 | Pastel crumbs on a brown mantle; 2 | Spring returning to the forest 3 | on the hills. 4 | 5 | ☙ 6 | 7 | Yet another purple flower shed by the magnolia 8 | joins the mantle of yellow petals drying on the sidewalk. 9 | 10 | ☙ 11 | 12 | Paper thin walls. 13 | Neighbor's early alarm crumpled my dreams. 14 | 15 | ☙ 16 | 17 | A red balloon finally escapes 18 | sticky fingers of a birthday boy; 19 | gets tangled in the branch of a magnolia. 20 | 21 | ☙ 22 | 23 | Old lady throws breadcrumbs at ducks. 24 | Gray days. 25 | Wishes her son called. 26 | 27 | ☙ 28 | 29 | The late snow finally melts 30 | but the daffodils can no longer rise 31 | to greet the sun. 32 | 33 | ☙ 34 | 35 | I walk over the bridge, clutching my umbrella tightly. 36 | Plop! 37 | The wind turns it into a useless antenna. 38 | 39 | ☙ 40 | 41 | Summer afternoon: 42 | a swarm of wasps 43 | dances around my Spritz. 44 | 45 | ☙ 46 | 47 | At the park. The son walks backwards, 48 | further and further and further away 49 | from the ball. 50 | 51 | ☙ 52 | 53 | Old people at the flea market 54 | weave an endless quilt 55 | of patched-up half-forgotten stories. 56 | 57 | ☙ 58 | 59 | A furtive drop of ciocolatto nero 60 | leaps from the ice cream cone 61 | and kisses my white linen shirt. 62 | 63 | ☙ 64 | 65 | A beautiful stranger sleeps next to me. 66 | Where am I? 67 | 68 | ☙ 69 | 70 | October. 71 | Brown leaves soften the cobblestones. 72 | 73 | ☙ 74 | 75 | Golden larches spell cold days ahead. 76 | Nature prepares to rest. 77 | 78 | ☙ 79 | 80 | Blowing on yellowing birches, 81 | Autumn waves with a thousand glittering hands. 82 | 83 | ☙ 84 | 85 | Doors flop open. 86 | For an instant, dark and rainy outdoors 87 | kiss the sanctuary inside the tram. 88 | 89 | ☙ 90 | 91 | Train station hall; little sister hums. 92 | Gray walls absorb her joy. 93 | Train teeth screech on steel. 94 | 95 | ☙ 96 | 97 | A sea of green leaves 98 | grows around a window. 99 | Inside, a library. 100 | 101 | ☙ 102 | 103 | Past demonstrations: 104 | leaflets litter the tunnel under the tracks. 105 | Young roller-skater soars past, 106 | her long purple hair waving free, 107 | dreams of a better future. 108 | 109 | ☙ 110 | 111 | After the rain. 112 | A frightened puddle leaps away from a car, 113 | finds refuge in my fluffy white scarf. 114 | 115 | ☙ 116 | 117 | Disruption. 118 | Little bird, 119 | bundle of feathers, bright yellow and gray, 120 | lies on the sidewalk, dead. 121 | 122 | ☙ 123 | 124 | Triste November. 125 | In the willows, shadows caw. 126 | 127 | ☙ 128 | 129 | Cyclist rushing along river 130 | recognizes staring pedestrian 131 | a second too late. 132 | 133 | ☙ 134 | 135 | Someone sorts cutlery in the kitchen. 136 | Fresh waitress, very first day, 137 | takes orders awkwardly. 138 | 139 | ☙ 140 | 141 | Saturday morning. 142 | Wake up to the melody of trams 143 | gliding by your window. 144 | 145 | ☙ 146 | 147 | Velo Promenade. 148 | Packs of dry leaves bark hoarsely 149 | under your tires. 150 | 151 | ☙ 152 | 153 | The concert finishes. 154 | Drifting foreigners return slowly 155 | from their far-away shores 156 | into the November night. 157 | 158 | ☙ 159 | 160 | Ephemeral constellations under starless Autum skies: 161 | fragments of yellow leaves on asphalt. 162 | 163 | ☙ 164 | 165 | Stars glow autumn red. 166 | 167 | The setting sun retreats from the maple tree. 168 | 169 | ☙ 170 | 171 | Urban gladiator conquers metallic bull. 172 | Light tap, hiss. 173 | Elixir bleeds out of ashtray, perfect extraction, smooth. 174 | Side of caramel biscuit. 175 | 176 | ☙ 177 | 178 | Look out the window, wife rushes down street. 179 | 180 | Sip of coffee, tram nears station. 181 | 182 | "You can make it, go, go, Go!" 183 | 184 | ☙ 185 | 186 | Der Wind nimmt die gelben Blätter des alten Ginkgo. 187 | 188 | Frei wie Libellen fliegen sie davon. 189 | 190 | ☙ 191 | 192 | Wait at the bakery. 193 | Somebody's baby studies you, tiny eyes, big cheeks, smiles at you. 194 | Rays of sun break through the fog, melting snow feeds mountain springs. 195 | 196 | ☙ 197 | 198 | Las grandes hojas del abedul: 199 | unas se van, fugaces, con el viento; 200 | otras caen solas, dando vueltas en el aire frio. 201 | 202 | ☙ 203 | 204 | Sunny november afternoon. 205 | Top floor suits negotiate next year's roadmap. 206 | Big black bird flies east outside. 207 | 208 | ☙ 209 | 210 | 12.10 Zürich → Luzern 211 | 212 | "Letzte Fahrt von Joe Brunner nach 40 Jahren im Führerstand. 213 | Alles Gute im Ruhestand!" 214 | 215 | ☙ 216 | 217 | Opera house. 218 | Young girl holding a small nutcracker leans forward on her seat. 219 | 220 | ☙ 221 | 222 | Black branches of naked trees along river trail 223 | admit the brightness of cloudy autumn skies. 224 | 225 | ☙ 226 | 227 | Icy winds lick dark streets; 228 | glass membrane guards luminous flat filled with tropical plants and effervescent laughter. 229 | 230 | ☙ 231 | 232 | Gli aghi degli abeti in montagna abbracciano calmi i fiocchi di neve. 233 | Niente ferma una valanga. 234 | 235 | ☙ 236 | 237 | Leise fällt der erste Schnee auf die alte Kastanie. 238 | 239 | ☙ 240 | 241 | Unbeirrt folgt das klapprige Tram der Strasse durch den Sturm. 242 | Draussen wirbelt der Schnee. 243 | 244 | ☙ 245 | 246 | Te despierta un suspiro. 247 | Cortinas abiertas. 248 | La nieve ha llegado mientras dormían. 249 | 250 | ☙ 251 | 252 | Powdery snow, birches washed white. 253 | On a twig, a twinkle: a yellow tit chirps. 254 | Was it really there? 255 | 256 | ☙ 257 | 258 | Like prickly legs of a dried up bettle, 259 | gnarly twigs in the naked chestnut 260 | reach out to the cloudy sky. 261 | 262 | ☙ 263 | 264 | Silence becomes laughter; 265 | kids sled down the hill, 266 | smearing dirt over snow. 267 | 268 | ☙ 269 | 270 | Alegres gorriones cantan en un arbusto. 271 | Me detengo a escuchar y se callan. 272 | 273 | ☙ 274 | 275 | La lluvia cesa; 276 | ventanas se abren 277 | al ruido de carros 278 | en calles mojadas. 279 | 280 | ☙ 281 | 282 | En el cementerio 283 | llueven ligeros 284 | los graznidos 285 | de los cuervos. 286 | 287 | ☙ 288 | 289 | Blossoms fair chance a final snow. 290 | An old man, unsteady steps, a fall. 291 | His crutches rest on the bench. 292 | 293 | ☙ 294 | 295 | Tarde lluviosa. 296 | En la sala, nubes de hierbabuena. 297 | Alguien timbra. 298 | 299 | ☙ 300 | 301 | A solitary leaf lands on thick rug. 302 | Living-room trees know not of storms. 303 | 304 | ☙ 305 | 306 | Brutalism, dark shadows. 307 | Raindrops fade on bare concrete. 308 | Gray jungle breath. 309 | 310 | ☙ 311 | 312 | He savors a hearty brunch; 313 | takes his time. 314 | 315 | Sitting beside him, 316 | feet dangling, 317 | roller blades already on, 318 | his daughter hums eagerly. 319 | 320 | ☙ 321 | 322 | She mumbles seven, ten words, 323 | incomprehensible, yet joyful. 324 | 325 | "I never remember," 326 | she says come morn, 327 | "my dreams." 328 | 329 | ☙ 330 | 331 | After the rain, 332 | blackbirds land heavy 333 | on wet twigs above. 334 | 335 | ☙ 336 | 337 | You discover, 338 | on streets you walk daily, 339 | a beautiful old house. 340 | 341 | ☙ 342 | 343 | Una señora se detiene y admira relojes en vitrina; 344 | un joven, bicicletas. 345 | 346 | ☙ 347 | 348 | Einer der zwei Vögel 349 | trägt einen Zweig im Schnabel. 350 | 351 | ☙ 352 | 353 | El sendero cruza 354 | a un viejo con bastón 355 | y un niño estrenando bici. 356 | 357 | Miradas fugaces. 358 | 359 | ☙ 360 | 361 | La temperatura 362 | del agua 363 | que traza 364 | senderos 365 | por tu espalda 366 | cambia. 367 | 368 | ☙ 369 | 370 | She stops lifting little brother 371 | toward the treehouse. 372 | 373 | He reaches it, laughing, shouting her name– 374 | but she's gone. 375 | 376 | ☙ 377 | 378 | El bebé cae dormido; 379 | finalmente podrás sentarte a almorzar. 380 | 381 | Al poner la mesa, 382 | se cae un tenedor al piso. 383 | 384 | ☙ 385 | 386 | Las ventanas de la oficina dan directo a la estaciónext de tren. 387 | 388 | La reunión se alarga. 389 | 390 | Los trenes no dejan de pasar. 391 | 392 | ☙ 393 | 394 | Como todas las noches, 395 | el bebé pone el grito en el cielo. 396 | 397 | Muerto de sueño 398 | no se quiere dormir. 399 | 400 | ☙ 401 | 402 | Cinco adultos con seis niños 403 | y una docena de maletas 404 | te preguntan: 405 | –does this train go to the airport? 406 | 407 | ☙ 408 | 409 | En la noche el papá duerme al bebé. 410 | 411 | En la mañana el bebé despierta al papá. 412 | 413 | ☙ 414 | 415 | Bus fills up slowly. 416 | Standing outside, 417 | driver looks at watch 418 | and gives cigarette 419 | urgent final suck. 420 | 421 | ☙ 422 | 423 | Train station, grocery shop. 424 | 425 | You wait, no time to spare, 426 | for your turn to pay. 427 | Must catch next train! 428 | 429 | Lady before you chats amiably 430 | with cashier. 431 | 432 | ☙ 433 | 434 | Escribes la postal lo más rápido posible. 435 | 436 | El buzón será vaciado en cinco minutos. 437 | 438 | ☙ 439 | 440 | Reconoces en el tranvía 441 | al mesero que te atendió 442 | la semana pasada. 443 | 444 | Él a ti no. 445 | 446 | ☙ 447 | 448 | Wiedikon. 449 | 450 | Cercas oxidadas 451 | rodean parches de pasto 452 | salpicados de basura. 453 | 454 | En primavera, 455 | se asoman margaritas. 456 | 457 | ☙ 458 | 459 | In den Bäumen der Musikschule 460 | singen die Spatzen laut. 461 | 462 | ☙ 463 | 464 | Minuto de silencio por la difunta. 465 | 466 | Eco de voces secas de cuervos. 467 | 468 | ☙ 469 | 470 | Dos brillantes esmeraldas. 471 | 472 | Una mancha negra se esconde bajo un carro. 473 | 474 | ☙ 475 | 476 | Ancianos se besan con pasión 477 | en un callejón estrecho. 478 | 479 | ☙ 480 | 481 | Tranvía. 482 | 483 | Un niñito apunta una ramita hacia ti. 484 | 485 | –¡Abracadabra! –dice. 486 | 487 | ☙ 488 | 489 | "Scusi," the barista asks, mid-pour, "Italian?" 490 | 491 | "No." 492 | 493 | He studies you. "You should be!" 494 | 495 | ☙ 496 | 497 | Stau. 498 | 499 | Der Fussgänger 500 | überholt das Auto 501 | mit der lauten Musick 502 | dreimal. 503 | 504 | ☙ 505 | 506 | Triunfos. 507 | 508 | Echas un puñado de granos 509 | al molino de café. 510 | La balanza anuncia: 11.0 gr. 511 | 512 | ☙ 513 | 514 | Parque. 515 | 516 | Hombre lleva en pecho bebé 517 | que aún nada entiende 518 | y le enseña los árboles. 519 | 520 | ☙ 521 | 522 | Reuiones interesantes– 523 | tu opinión cambia radicalmente 524 | al escuchar los argumentos 525 | de tu colega. 526 | 527 | ☙ 528 | 529 | Preparas una sorpresa emocionante 530 | para tu pareja. 531 | 532 | ¡Qué difícil es no decir nada! 533 | 534 | ☙ 535 | 536 | Car slows down. 537 | 538 | Just before couple on crosswalk reach sidewalk, 539 | impatient driver slams honk rudely 540 | and drives past them dangerously. 541 | 542 | To slam brakes on traffic 543 | twenty meters ahead. 544 | 545 | ☙ 546 | 547 | Resignación. 548 | 549 | 18 años en la empresa y la única opción es renunciar. 550 | 551 | Los juegos de poder 552 | e intrigas corpororativas 553 | se volvieron insoportables. 554 | 555 | --------------------------------------------------------------------------------