Posts in Julia (20 found)
annie's blog 2 days ago

I have no idea who celebrities are anymore

Julia Roberts? She was in that one movie with that guy, and the other one with the other guy, and like 100 more. Whatever. But she’s old news. Like all the other celebrity names I actually recognize, which isn’t a lot, but is some. Just a minute ago a headline floated by: Person A is doing Thing with Person B, what will Person C think? I have no idea: Who the people are, their relationship or lack thereof, their various claims to fame. I do not possess any crumbs of context helping me interpret the situation or nod knowingly about what C’s thoughts will be. I Got Nothing. Which is fine. Preferable, even. I’ve never been a very good fan, it’s just not my thing. But cultural knowledge always seeps in. You just know some stuff like who’s famous and why, and you even have some sort of opinion about them. Until you don’t. I have reached the don’t point. It’s peaceful here.

0 views
Manuel Moreale 3 weeks ago

Edoardo Baldi

This week on the People and Blogs series we have an interview with Edoardo Baldi, whose blog can be found at edoardob.blog . Tired of RSS? Read this in your browser or sign up for the newsletter . People and Blogs is supported by the "One a Month" club members. If you enjoy P&B, consider becoming one for as little as 1 dollar a month. Hello! I’m Edoardo, in my thirties, born near Milan (Italy) and raised in the Alps of the same region, to escape the boredom of too flat a horizon. I studied physics, first in Milan, then abroad in Switzerland, where I spent a little over four years on a PhD that convinced me academic research wasn’t for me – or so I thought, since I didn’t stray too far. In the following years I became a “research software engineer”, meaning a software developer who works closely with research. It took me a while to realize that, despite the many benefits, that work had become a routine I was taking too much for granted. Or better: I had lost sight of why I was staying there; why I kept choosing that configuration for my life. Now I’m trying to figure out if teaching the two subjects I’m most passionate about – math and physics – is what I want to do in the next chapter of my career. I can never get enough of hiking in the mountains, especially over multiple days – as long as my body agrees. And sharing an experience with other people who love the same thing is my ideal vacation. Books, writing – I don’t know how many experiments with novels and short stories I’ve done over the years – and puzzles of all kinds (including programming challenges, even though I’m a particularly slow coder) are some of the activities that can easily fill my free time. Having always loved tinkering with computers, I think I started writing random things online quite early. If I remember correctly, it was on LiveJournal or MySpace, prehistoric stuff now. I discovered WordPress during high school, following a guy from my same school who wrote ironic essays on philosophy topics. I tried to emulate that model, but I didn’t get very far as it wasn’t my thing. Years later, with some friends fond of cinema, again on WordPress, I started a collective blog where we wrote our opinions on the movies we watched, often together. The name of the blog – Sweet Sue and Her Society Syncopators – was a tribute to a classic 50s American comedy. (I’ll let you work that one out.) During my PhD, I collaborated on and managed the university cinema club’s blog. At the time, however, I also started publishing my very personal ideas on books and movies on another blog, whose name or domain I honestly don’t even remember now. I think I tried to recover something from that blog via the Wayback Machine, with no success. Fast-forward several years, I realized why none of those blogs had survived: I was writing on commission – I loved the perk of press screenings, but writing something afterwards was non-negotiable. Or I was performing for some imagined audience by covering whatever was trending, not what I actually cared about. I could say that my personal blog was born when I decided that my online space would be only a public personal journal: the only rule was to write about what interested me the most, in the way that felt most natural. This is still the reason behind my current blog. How long is it going to survive? I don’t know. It did well, so far, with ups and downs. Beyond my hiking recaps, almost everything I write starts from curiosity – a science-based question (“if I ate an apple a day for a year, how many kg of peel could I accumulate?”), something I want to understand well enough to explain, a brain teaser that sometimes keeps me awake. Since it’s often something I don’t know, a research phase almost always follows – and I admit that, sometimes, it derails my intention to write. I keep a dedicated note for each idea, where I track its evolution. When I feel like I’ve reached a conclusion of sorts, I then sketch out a structure and use it as a guide for the first draft. Curiously, all my notes are in English, but the first draft of anything I write is always in Italian. Then I translate into English, and very often rewrite some parts that don’t flow very well in the other language. And yes, I often use Claude for a final proofread: I’ve given it strict instructions on what it can and can’t touch, and how. The content is always mine, and I’m careful to keep it that way: I don’t want to end up with a voice I no longer recognize as my own. As for the tools, my personal notes live in an Obsidian vault – because they must be plain text files – and I write all my drafts almost exclusively in iA Writer. It’s been my first choice for many writing projects, at least in their early stages. One feature I particularly love is its support for authorship , without violating the plain text pact. When I sit down to write the first draft, I have only one need: to be alone in a fairly quiet environment. Honestly, I’ve never tried writing in a public place, like a café – and the few times I did write on a train, it was surely due to a deadline I couldn’t avoid. As far as I’m concerned, it’s more the act of moving through space that stimulates what I might call creative thinking – which I take to mean authentic rather than original , as in “totally new”. And I’m also convinced that the environment influences my creativity, but I couldn’t say how or why. Often I’ve only realized much later that I had visited an environment from which I returned with ideas I considered creative – whether these didn’t go very far is another, unresolved story. I think I’ve tried dozens of frameworks to create a blog, starting with the large family of static-site generators. After several attempts, intrigued by some input from Manu, I gave Kirby a chance and discovered that it met all my needs. One above all: my blog’s content must be in plain text, as I don’t want to deal with any kind of problem taking it with me, wherever it might be in the future. So, for the moment: Kirby CMS, hosted on a fairly basic server managed by Hetzner. The domain is registered on Porkbun, and the DNS is managed by Cloudflare. I’ve also written a dozen custom plugins to tweak many aspects of my website because, for me, tinkering with the mechanics of a personal blog is part of the joy of having one. I just can’t resist – and I keep telling myself “tinker less, write more”. I would probably study web design and web technologies properly from the start – I mostly stumbled into this stuff through my day job. I say this to avoid having to settle for some preconfigured service that isn’t right for me. I would love to have a domain like , but the problem isn’t availability so much as the popularity of my name. And, honestly, I’m not ready to pay $200 a year for a personal website. The maintenance costs for my blog are quite low: 4€ and something a month for the server, plus the annual cost of the domain – about 20€. Kirby CMS requires a one-time license (100€, renewed every four years), and this is the only expense I periodically re-evaluate: the moment it no longer aligns with my needs, I will have no problem planning a migration elsewhere. In fact, I’ve already done it several times as a stress test , but for now I don’t feel the need to. My website generates no revenue, nor have I ever tried to make it do so. Personally, I have nothing against monetising a personal website, provided it’s done honestly. If I were to do it, I probably wouldn’t rely on platforms like Substack – only because I like building things myself. Even today I financially support some blogs because I believe in the work of the people behind them – or to give a friend a small nudge to keep going. A good part of the blogs I follow, or like to return to from time to time, I discovered thanks to “People & Blogs” – or through “Ye Olde Blogroll” . I think it’s unlikely that anyone reading this page doesn’t know either of them; but if that’s the case, I invite you to take a look, exploring even the older, less obvious stuff. I want to mention a friend’s project, halfway between a personal blog and a photography portfolio, that I had the pleasure of contributing to . I’m very fond of it: partly for my friendship with the author, and partly because it circles a theme that has quietly followed me for years: the sense of belonging to a place, or to multiple places; the idea, the concept, the experience of what we call home . The project is “Stay Stay Stay” by Elettra Pistoni: if you’re not into reading about this topic, her pictures are well worth a look. I also think she would more than gladly welcome the opportunity for this interview, but I’ll leave the decision to those in charge. I’ve lost count of how many newsletters or feeds I’ve subscribed to over the years, and it doesn’t really matter. I’ve reached the point where the list of online content I follow consistently has no more than ten items. Among these, two blogs and a newsletter (in Italian) that I return to quite regularly, even to reread older things: I’ll take this as a cue to share a bit of what’s going through my head – two thoughts and a side project that will maybe see the light someday. Finally, a heartfelt thanks to Manu for offering me the opportunity to share a bit of myself with this community! Now that you're done reading the interview, go check the blog and subscribe to the RSS feed . If you're looking for more content, go read one of the previous 146 interviews . People and Blogs is possible because kind people support it. “Useful Fictions” by Cate Hall Julia Evans ’s blog, a trove for tech enthusiasts The newsletter “It’s Friday I’m (not) in love” , partly inspired by “Modern Love”, the New York Times’ well-known column. Whenever I feel like telling someone “I don’t have time”, I stop and remind myself that it’s almost never true. In fact, never. It’s just my fear of making a commitment, or a lack of courage to admit what I really care about. I try never to hide behind this excuse with the people I really care about, because they don’t deserve it. I’ve also written a short post about it . This could be one of my guiding tenets , because I haven’t been able to refute it yet: “Actions, not words, reveal our real values” . It’s not mine , and I often struggle to accept it myself. But I’m convinced that if we actually lived by it, we would have far more genuine and satisfying relationships with other people – in whatever sense you want to take that. Being a hiker obsessed with traveling light, I started working on an app (web only to begin with) that lets me keep track of my gear and which items I decide to bring on each trip. Dozens of these tools already exist, but this is my vision of what I’d want such an app to do. I called it “Baseweight”, and I hope to have an alpha version out in the near future. If someone is curious, the app’s future home will be at baseweight.my . And if you’d like to share your thoughts on it, don’t hesitate to reach out ! Opinions and suggestions are especially welcome at this early stage.

1 views
Michael Lynch 1 months ago

Refactoring English: Month 18

Hi, I’m Michael. I’m a software developer and founder of small, indie tech businesses. I’m currently working on a book called Refactoring English: Effective Writing for Software Developers . Every month, I publish a retrospective like this one to share how things are going with my book and my professional life overall. At the start of each month, I declare what I’d like to accomplish. Here’s how I did against those goals: This has felt like it was a week away for six weeks, so I’m glad to finally have all the chapters done. This seemed like it should basically be a 2-3-day project, but I realized it’s more difficult than it seemed, especially due to the great blockade . Eep, I continue to neglect marketing, and the numbers are suffering for it. I was desperate to get the last few chapters of the book done, so I focused only on that rather than investing in any marketing. I’ve continued pursuing security bug bounties, but I’ve reduced my time on them. I’m not quite doing the 70/30 split I planned, but maybe like 60/40. The main vendor I’ve been working with paid me another $7k (bringing me to $17k total) for reports, but they’ve slowed down on processing reports, so I’ve mostly stopped searching for new bugs in their code. I submitted bugs to a few other programs to check if any are processing bug reports quickly, but none of them are: I’ve completed all the chapters of the book, which is a relief, but I don’t consider it officially “done.” I wrote the book over the past year and a half, usually focusing on a single chapter at a time. I haven’t ever read my own book cover-to-cover to make sure it’s all consistent. I want to do at least a few complete readthroughs before I call it done. I originally planned to continuously edit the book based on reader feedback. That way, when I got to the last chapter, the book would be pretty much done because the rest of the book would have had so many revisions based on comments from readers. In reality, I integrated reader feedback far less than I expected. I found it hard to split my focus between revising past chapters and writing new ones. If I spent a week revising old chapters, it didn’t feel like forward progress. When I added a new chapter, it meant that my public progress meter got a little fuller, which was motivating. Progress meter from book website The other reason I didn’t continuously revise is that I didn’t reach out to readers as much as I planned. Part of that is that I constantly felt behind on the book, so there was always a sense of, “I want to get this chapter out, and then I’ll invest more into reader outreach.” But even when I reached out to readers, it rarely impacted the book. The most common responses from readers were, “I like the book” or, “I haven’t started it yet.” When I did get detailed feedback, I wasn’t always sure how to integrate it. In some cases, I agreed with the feedback, so it was an easy decision. Usually, though, the reader would suggest adding something that I didn’t think was necessary. And that’s not to say the reader was wrong, but I’d want to see a pattern in reader feedback before I go against my intuition, and I wasn’t getting enough feedback to see a pattern. Now that I’ve completed all the chapters, I feel like I have more space to reach out to readers. I like the idea of Help this Book , a web app that allows readers to give feedback directly in your ebook, but I didn’t want to store all of my feedback with a third party and pay monthly rent. I saw that Julia Evans made her own reader feedback tool , customized to her products, and I thought that was neat, so I’m working on that. I’m working on a web app to make it easier for readers to give me feedback about my book. Overall, I’ve found that AI makes me more productive when programming. There are certain tasks like resolving git merge conflicts, debugging unfamiliar code, or making simple tools where AI is a clear win. I used to think AI was great at helping me start projects, but now I’m not so sure. I keep hitting what I call “the great blockade.” Six months ago, I’d give the AI agent a high-level overview of what I wanted and tell it to implement a basic v1 implementation. I knew the agent’s output would be messy, but it was just a prototype, so I could keep giving it feedback until it matched my programming sensibilities. It turns out that it’s harder than I expected to clean up a bad prototype. Once the prototype is bad enough, I have a hard time untangling what the code is even trying to do. AI seems to have a weird bias to justify whatever code is already present. If I tell the AI that a component seems confusing because it’s iterating over the same data three times, it just keeps insisting we have to iterate over the data three times because of X, Y, and Z. But it never questions whether X, Y, and Z are artificial constraints. This is the blockade. I get stuck trying to move beyond a giant wall of confusing code that AI constructed. If I don’t fix the core logic, the problem keeps getting worse. The code smells grow like fungus and spread throughout the codebase. I’m building on top of a weak foundation, and the AI just keeps duplicating bad patterns that already exist. Okay, easy fix: have the AI agent create the prototype in smaller pieces. Keep the AI on a tighter leash so it can’t go so far into the weeds. Instead of having the AI create the whole prototype, have it start with a welcome page. Once that’s reviewed and merged, add one simple feature, and so on. That works fine until I get to a complex chunk, like authentication. AI creates a pull request that’s 2-5k LOC of confusing code, and that becomes a huge wall. I can’t think of a way to break down the feature any further, so I’m stuck with this massive PR, another great blockade. Not only does a 4k LOC change take 20x as long to review as a 400 LOC change, but it also requires larger review windows. If I have a 20-minute block available, I can tackle the 400 LOC change, but if I have a 4k LOC change, I need 20 minutes just to build up context. To make meaningful progress on a 4k LOC change without wasting most of it on context friction, I need a 90-minute window, which is hard to come by especially for weekend projects. Here’s an example. For Little Moments , I’m doing authentication with magic login emails . And for several weeks, I couldn’t think of a way to break that feature down without introducing dead code or broken features. I can’t implement half a login flow. After several weeks of chipping away at a giant PR little by little, I realized I actually could implement half a login. PicoShare , another app I maintain, has a simple authentication flow. The app assumes a single authorized user, so authentication is just a passphrase, not even a username/password pair. Instead of a huge switch from no authentication to email-based authentication, I could go from no authentication to passphrase authentication. So, I got passphrase authentication working , but moving from passphrase to magic email logins was still a pretty massive PR that would take me weeks to review. After hacking on it over several days, I realized I could break it down further. Instead of actually sending emails with a login link, I could just immediately redirect the user to the link I would have sent them. That was still a 1.7k LOC PR , but it was more manageable than sending actual emails. And it reduced the actually sending emails part to a mere 1k LOC. The thing that makes me wonder if AI is a net positive on this type of work is that I know I would have spotted these opportunities to break down the problem had I not been using AI. I would never create a 4k LOC PR and then say, “Hmm, this is pretty big.” As the PR grows larger, it becomes more painful to work with, so I naturally see opportunities to break the change into smaller pieces. AI disrupts that natural feedback loop. With AI, there’s no pain in creating a 4k LOC PR because it happens in two minutes while I check my email. And I can easily give notes to improve the 4k LOC PR and feel like I’m making progress, but the big change makes it hard for me to identify what pieces can lift out into their own smaller changes. Now that I recognize how easy it is to generate huge, unmanageable PRs for complex changes, I can change the way I use AI to invest more upfront into breaking features down into tinier changes. I’ve completed all 22 chapters of my book. I thought AI made prototyping faster, but now I’m not so sure. Result : I’ve completed all chapters. Result : The tool is only about 40% complete. KeePassXC - I submitted an RCE to Zero Day Initiative on May 18th, but I haven’t heard any response. For KeePassXC users, this isn’t a zero-click attack or something that could compromise your database by just visiting a malicious website, so don’t get too worried. Cloudflare - I submitted a DoS / logic bypass via HackerOne on May 22nd. No response. Proton - I submitted one low-severity issue. They asked for a video proof of concept, so I made one on May 29th, and they said to wait to hear back. Published the last chapters of my book. Created a partial prototype of a book feedback app. Partially implemented authentication for Little Moments. Cut two new releases of PicoShare. Using AI eliminates the natural feedback cycle that motivates me to build software in smaller chunks. I think the solution is to work harder earlier in the lifecycle of complex features to break things down into smaller chunks and be more strict in checking the AI’s output. Invest at least five hours into improving the Refactoring English website. Attract 30k unique readers to the Refactoring English website. Complete my reader feedback tool.

0 views
matklad 1 months ago

CSS: Unavoidable Bad Parts

An ersatz CSS tutorial for people who need to style a web page, but aren’t web developers. I am a wrong person to write this kind of thing, as I have neither the time, nor experience. I’d much rather read a book about this. Alas, I had to learn all this stuff from trawling MDN, so perhaps it is valuable to document what I have so far. CSS, HTML and Web APIs are truly vast, and it takes a career to become a professional. The good news is that modern web has a reasonably-sized, learnable subset which is enough for simple tasks like a programming blog or a simple GUI. I haven’t seen a resource that teaches just this subset, but it’s not too hard to figure this out. The bad news is that there’s also a nasty set of gotchas, which will mess up your page, which you won’t suspect to exist, and which will need days of debugging to figure out. Still, it’s not that bad. I am quite happy with the styling on this site, and it’s only about 200 of readable CSS . Good: HTML5 semantic tag names It’s worth looking through MDN Elements Reference . There aren’t that many elements, and things like , , , make it much easier to structure your page. Less obvious: Bad: Wrappers If you “View Source” on any “real” website, you’ll notice that everything has layers and layers of wrapper elements, so you might be tricked into thinking that wrappers are how you solve layout problems. I can’t really agree or disagree here, as I never wrote “production” CSS, but, in my experience, it’s much easier to understand if you do the opposite — restrict yourself to using only markup-meaningful semantic tags, and then figure out CSS which works with the markup you have. Bad: Layout This one is not an exclusively Web problem, layout is a struggle in every GUI framework I know. Imagine a fixed sized raster image, and a paragraph of text describing it. There are many ways to arrange these two elements on the screen’s rectangle. Generally, for every given width and height, you can do a decent job, as long as the total area is enough. A typical GUI is a hierarchy of such boxes, with a lot of “layout freedom”. The problem though is that layout of each box affects the layouts of all other boxes, as you generally want all boxes to meet exactly, without gaps and overlaps. An important negative realization is that the layout algorithm doesn’t exist. There isn’t a fully general solution to positioning and sizing GUI boxes. Rather, different systems use different sets of heuristics to do the job, from simple RectCut , to fully general constraint solvers , with everything in between . It is hard to get the mental model of how layout works, in general . So, don’t think “how can I do my layout in a given system”, think instead “what possible layouts are allowed by the system”. Bad: Browser defaults Let’s start with a bare (but still semantic) HTML markup of a blog article, without any CSS. If you open it in a browser, it will show something . The content isn’t unstyled — the text is of a certain color, font and size. Headers are bigger than the main text, links are underlined, etc. These are the default styles of your browser. They are helpful! The problem is that these styles differ between the browsers. So, even when you add your own CSS, and the end result looks fine in your browser, I might see something different, because you might rely on a browser default, without knowing it. The last bit is the killer here — the problem is in something you didn’t write. The general solution here is a CSS reset , or normalization — starting your CSS with an explicit set of rules, overriding defaults. Not because defaults are inherently bad, because they are inconsistent. I don’t know which set of rules you need to override in practice, it’s a good idea to compare several existing CSS resets. This touches on the big question: should you style your web page? There are two competing views of the Web platform — some people treat it as a flexible, adaptive, primarily visual medium for expressing design, others would prefer if the Web focused on delivering the content, allowing each user to customize the presentation. My personal answer here is pragmatic — by default, an unstyled page is poorly usable and looks bad. I would have preferred the world where CSS-less pages were readable as is, but, in this world, I think it is helpful to style the content. At the same time, it’s a good idea to allow advanced users to bring their own CSS. Make sure that your HTML markup is reasonable, that you don’t overfit your HTML to CSS (vice-versa is fine), and that your page functions in reader mode. Good: Classless CSS You can’t reset styles to true neutral nothing: if you make the text invisible (white or transparent), it is still a style. So you might as well embrace it: after reset, style common HTML elements directly. For example, to set your favorite font for all code snippets: If you use , , , tags you can set the overall page layout without writing any CSS selectors. This of course requires making assumptions, in CSS, about the structure of your HTML, but, like, this is your HTML and your CSS, you can do whatever, and, if you don’t like the result, you can always change it! Bad: CSS selectors In programming, we collectively came around to distrust inheritance and prefer composition. Default CSS is like supercharged inheritance, each design element on your web page is affected by multiple rules, and you can always “monkey patch” existing elements by appending to your CSS. There’s an unfortunate gap between CSS affordances, and what you actually want to do. The two reasonable approaches are: Conclude that CSS selectors add abstraction capability along the wrong axis, and stick to classless CSS and inline styles, using something like Tailwind to make writing inlines prettier, and something like JSX (or any other templating engine supporting composition) to avoid repetition in HTML. Use CSS nesting to avoid writing “far reaching” selectors and style component-per-component: Bad: box-sizing UIs are recursive rectangles, layout is the process of figuring out where each rectangles goes, and it is determined by the sizes of rectangles themselves. So, understanding what is the size is quite fundamental. Sadly, by default the definition of size in HTML is very unintuitive: element’s width and height do not include element’s border and padding, which leads to surprising results: everything looks perfect at first, but increasing padding somewhere shifts the entire layout unexpectedly. For this reason, deserves to be the first line in your CSS reset. It makes elements encapsulated, such that adding borders is a local-only change. Chaotic Good: margin collapsing Suppose you want to have a gap around an element. You would think that you need to set the padding property. But that would be wrong — if you have two such elements next to each other, the gap between them would be . The paddings would add, creating a visual gap larger than intended. You want something more akin to social distancing, where if one person is more introverted, this person’s bigger radius of exclusion is what defines the distance. And that’s how the property works. Two neighboring margins are combined using rather than . Margin collapsing is very useful, but it can surprise you. E.g. I think child margin can stick beyond parent’s? To be honest, I don’t have a good intuitive understanding of margins, but I know enough to at least identify when it is the problem. Margins are also one of the indirect inspirations for this post. In Moving away from Tailwind, and learning to structure my CSS Julia Evans writes that you generally don’t want to set margin on an element, and should rather let the parent control the inter-element margin of the children, using the so-called owl selector: That is, add margin to all ’s children exempting the first one. I didn’t know that! And, given all the pain that margin gave me so far, I actually get why you want to do this, and why this is a good idea. But it bugs me that you can’t learn that without becoming “professional” web developer, or reverse-engineering someone else’s CSS framework. Bad: Default (flow) layout Layout in general is tricky, because there’s no universal “layout algorithm”, just a bunch of special cases. But what does HTML actually do? The default layout algorithm I think goes back to the origin of HTML as a language for documents, and overfits a use-case of producing papers — mostly text content with some illustrations, where the text can flow around the pictures. That’s actually what you want for the main body of text of your blog, but, as soon as you want to actually control the spatial arrangement of the elements on your page, you want something different, for example… Good: flexbox This is really what separates modern web-development from the olden days, where you’d need a CSS PhD or a full-blown opaque CSS framework to be able to say “this goes to the left, and this goes to the right”. This layout allows you to arrange a series of elements either vertically or horizontally, adapting to the available space. It is rather complex and I can’t use flexbox without referencing MDN all the time, but usually I am able to get things done in the end. Bad: responsive design Modern CSS allows querying screen size, and implementing conditional logic based on that — a design that “responds” to user-agent constraints. This probably what you should use for “real” CSS, but note that HTML is inherently responsive. Unlike PostScript (PDF), it will automatically reflow the paragraphs when you change window size. So, it’s a good idea to avoid writing explicit responsive rules, and just rely on layout to do the reasonable thing. For example, this blog looks OK on mobile, tablet and desktop without any explicit queries. Unconditionally setting on the main column of text is all that it takes. Lawful Evil: pixels does what you want, but not what it says. It’s not a size of one physical pixel on your screen. Rather, it’s a measure of visual angle . That is, should look perceptually the same on any screen, and it is converted to different number of physical pixels, depending on the screen size, its pixel density, and the typical viewing distance. So you can just size everything in pixels, without thinking about different displays’ pixel densities. It gets weirder. CSS allows “real” units like centimeters or inches, but they are also angles, because everything is defined in terms of pixels. Doubleplusungood: font-size Flexbox is a good way to layout UI-elements. Flow layout works ok for laying out paragraphs of text. But what happens on the level of individual lines and glyphs is, in my opinion, a train wreck and a noob trap. Let’s start with the basics: if you write then is the size of what? Sadly, the answer is “nothing in particular” — this is a size of a virtual box around the glyph, but the box isn’t tight, and the size of the glyph varies, depending on the font. Luckily, property can fix it, and make consistent across fonts. See these two posts for details: Though, at the moment seems to be very niche, so, while personally I’d put right next to , few pages do that. The next issue with is a thorny question of defaults. The good news is that it’s one of the properties that is fairly consistent across browsers, with being the overwhelming default. The bad news is that, depending on the font, can be on the smaller size. Not completely illegible, but very close to the lower bound. What’s worse, some default fonts are particularly small. For example, on Apple, looks much smaller than , and is almost uncomfortable to read at 16px. Can you just set or whatever works best for your chosen font? I think the answer is yes, but there are some caveats to keep in mind. Refer to Accessibility: px or rem? for details. The issue is that modern browsers support two ways of making text on a page bigger: Setting in your CSS disables that second approach. Taking everything together: don’t assume that text on your page will be readable by default, check different configurations. Set to reduce the number of degrees of freedom and to pin down the meaning of . If the result looks fine with your chosen (or your user’s default) font and default font-size of , then you are done. Otherwise, set to a bigger number. Afterwards, check that the page is readable in reader mode as well. Bad: Despite the name, doesn’t set the height of a line. It is a height of a run of glyphs, set in the same font . The two coincide when all the text is in the same font. But if you have, e.g., some words set in font, you are in for a surprise. While fixes the size of a glyph inside the box, it still leaves its relative position unspecified. So, when two runs of text in different fonts are aligned vertically to share the baseline, their line-height line-boxes get shifted relative to each other: one sticks below, one sticks above. The line height overall becomes larger that what you’d expect, as it is configured as a union. See Deep dive CSS: font metrics, line-height and vertical-align for a thorough explanation of this effect. Bad: vertical rhythm If you google long enough this cluster of problems, sooner or later you’ll come across the idea of vertical rhythm, that you should make sure that lines are in the same relative position across different paragraphs, even if you have headings, images, and what not. As if there’s invisible lined paper behind your web-page. As far as I can tell, this is pure voodoo and is not useful. If you do two-column layout, then you want lines on opposite sides to align, but it makes no sense to jump through hoops for a single-column layout (hat tip to @chrismorgan ). Bad: The genius of the flow layout is its dynamism. It takes a moment of reflection to appreciate the technical marvel of text breaking itself neatly into lines as the window is resized to be narrower. Getting that to work for the first time ever in the world of durably printed text must have felt incredible. But the magic has its limits — you can only break the line at the whitespace, or at the hyphenation points. And some long spans, like or URLs, might be unbreakable. This leads to overflow annoyance on mobile devices, something you notice only after you publish your work. There’s no one trick to fix it, but some tips are available here: Against Horizontal Scroll for details. And … that’s all I remember so far? I reiterate my request for someone to write a short 100-page book explaining just enough of HTML&CSS to make a simple blog without getting collapsed by the margins! for any kind of list, like site’s sections in . for table-of-contents (check the source of MDN). / for list of pairs. Conclude that CSS selectors add abstraction capability along the wrong axis, and stick to classless CSS and inline styles, using something like Tailwind to make writing inlines prettier, and something like JSX (or any other templating engine supporting composition) to avoid repetition in HTML. Use CSS nesting to avoid writing “far reaching” selectors and style component-per-component: font-size-adjust Is Useful Font size is useless; let’s fix it Zoom, which has a dedicated UI element, shortcuts/gestures, per-page persistence/overrides and a global default. Changing default font-size, a global setting buried deeply in the configuration page.

0 views
Dangling Pointers 3 months ago

FlexGuard: Fast Mutual Exclusion Independent of Subscription

FlexGuard: Fast Mutual Exclusion Independent of Subscription Victor Laforet, Sanidhya Kashyap, Călin Iorgulescu, Julia Lawall, and Jean-Pierre Lozi SOSP'25 This paper presents an interesting use of eBPF to effectively add an OS feature: coordination between user space locking code and the kernel thread scheduler to improve locking performance. The paper describes most lock implementations as spin-then-park locks (e.g., busy wait in user space for some time, then give up and call the OS to block the waiting thread). A big problem with busy waiting is the performance cliff under oversubscription . Oversubscription occurs when there are more active threads than cores. In this case, busy waiting can be harmful, because it wastes CPU cycles when there is other useful work to do. The worst case occurs when a thread acquires a lock and then is preempted by the OS scheduler while many other threads are busy waiting. If the OS thread scheduler were smart, it would preempt one of the busy waiters and let the lock holder keep running. But alas, that level of coordination isn’t available … until now. In the good old days, researchers would have modified Linux scheduling code and tested their modified kernel. The modern (easier) way to achieve this is to use eBPF. The authors wrote an eBPF program that runs (in kernel space) each time a context switch occurs. This program is called the Preemption Monitor . The Preemption Monitor works in conjunction with a custom user space lock implementation. The net result is that the Preemption Monitor can reliably detect when the OS scheduler preempts a thread that is holding a lock. When this occurs the eBPF program writes information to a variable that user space code can read. The locking algorithm is as follows: First, try to acquire the lock with a simple atomic compare-and-swap. If that fails, then busy wait. Similar to Hapax locks , this busy waiting avoids contention on one cache line by forcing all threads to agree on the order they will acquire the lock and letting each thread spin on per-thread variables. During busy waiting, the variable written by the Preemption Monitor is checked. If this variable indicates that there currently exists a thread which has acquired a lock and has been preempted by the OS, then threads stop busy waiting and instead call the OS to block until the lock is released (using the same system call that a futex would use). Fig. 2 has performance results. The x-axis shows thread count (which varies over time). The green line is FlexGuard. The idea is that it gives great performance when there is no oversubscription (i.e., fewer than 150 threads) and offers performance similar to a purely blocking lock (the dark blue line) when there is oversubscription. Source: https://dl.acm.org/doi/10.1145/3731569.3764852 Dangling Pointers This problem seems ripe for overengineering. In some sick world, the compiler, OS, and hardware could all coordinate to support a “true critical section”. All pages accessed inside this critical section would be pinned into main memory (or even closer to the CPU), and the OS would try extremely hard not to preempt threads inside of the critical section. This would require some upper bound on the critical section working set and running time. Subscribe now First, try to acquire the lock with a simple atomic compare-and-swap. If that fails, then busy wait. Similar to Hapax locks , this busy waiting avoids contention on one cache line by forcing all threads to agree on the order they will acquire the lock and letting each thread spin on per-thread variables. During busy waiting, the variable written by the Preemption Monitor is checked. If this variable indicates that there currently exists a thread which has acquired a lock and has been preempted by the OS, then threads stop busy waiting and instead call the OS to block until the lock is released (using the same system call that a futex would use).

0 views
Justin Duke 5 months ago

Ocean's Twelve

This might not be the perfect time or whatever to talk about it but I've been doing my homework and I'd really like to play a more central role this time around. I consider this film's prequel as close as you can get to a perfect film. Ocean's Eleven is a movie that knew exactly what its goal was—to be as relentlessly and easily entertaining and pleasurable as possible—and succeeds in doing so more than any other movie with similar ambitions. The sequel to such an endeavor has an inherently impossible task ahead of it. I held off on watching this for a long time. Partially because it seemed unnecessary; why would I watch a sequel when I could just watch the original again? And partially because it is poorly reviewed, in the same way many of Soderbergh's works are. The phrase self-satisfied and bizarrely sloppy comes up a lot in reviews of his early-aughts output. I think it's fair to be upset. The heist in this movie is, to a certain extent, on us , the viewer, for sitting down and thinking that we were getting treated to a heist movie when instead what Soderbergh wants to give us is two hours' time in the companionship of people who are effortlessly beautiful and charming. This film is filled with metatext: the Julia Roberts bit, Clooney's age, Matt Damon trying to become a leader. You can accuse some of the smaller bits as rehash, and I agree with the central complaint that a plot twist which invalidates everything we've seen for the preceding hour is unsatisfying. At the end of the day, I didn't care that much because I enjoyed watching my buddies having fun. It is a lesser film; it still succeeds in its goals, with grace and panache. One more thing: this movie hints at creating a slightly larger mythos, in the same way John Wick eventually created an extended universe unto itself. While part of me would have loved to see six more of these films, I think the key to their enduring charm is that they are a snapshot in wide frame. The warmest and happiest scenes involve as many people as possible, whereas the formula of John Wick really only requires a single protagonist and an endless barrage of faceless, unnamed fodder.

0 views
Krebs on Security 6 months ago

Who Benefited from the Aisuru and Kimwolf Botnets?

Our first story of 2026 revealed how a destructive new botnet called Kimwolf has infected more than two million devices by mass-compromising a vast number of unofficial Android TV streaming boxes . Today, we’ll dig through digital clues left behind by the hackers, network operators and services that appear to have benefitted from Kimwolf’s spread. On Dec. 17, 2025, the Chinese security firm XLab published a deep dive on Kimwolf , which forces infected devices to participate in distributed denial-of-service (DDoS) attacks and to relay abusive and malicious Internet traffic for so-called “residential proxy” services. The software that turns one’s device into a residential proxy is often quietly bundled with mobile apps and games. Kimwolf specifically targeted residential proxy software that is factory installed on more than a thousand different models of unsanctioned Android TV streaming devices. Very quickly, the residential proxy’s Internet address starts funneling traffic that is linked to ad fraud, account takeover attempts and mass content scraping. The XLab report explained its researchers found “definitive evidence” that the same cybercriminal actors and infrastructure were used to deploy both Kimwolf and the Aisuru botnet — an earlier version of Kimwolf that also enslaved devices for use in DDoS attacks and proxy services. XLab said it suspected since October that Kimwolf and Aisuru had the same author(s) and operators, based in part on shared code changes over time. But it said those suspicions were confirmed on December 8 when it witnessed both botnet strains being distributed by the same Internet address at 93.95.112[.]59 . Image: XLab. Public records show the Internet address range flagged by XLab is assigned to Lehi, Utah-based Resi Rack LLC . Resi Rack’s website bills the company as a “Premium Game Server Hosting Provider.” Meanwhile, Resi Rack’s ads on the Internet moneymaking forum BlackHatWorld  refer to it as a “Premium Residential Proxy Hosting and Proxy Software Solutions Company.” Resi Rack co-founder Cassidy Hales told KrebsOnSecurity his company received a notification on December 10 about Kimwolf using their network “that detailed what was being done by one of our customers leasing our servers.” “When we received this email we took care of this issue immediately,” Hales wrote in response to an email requesting comment. “This is something we are very disappointed is now associated with our name and this was not the intention of our company whatsoever.” The Resi Rack Internet address cited by XLab on December 8 came onto KrebsOnSecurity’s radar more than two weeks before that. Benjamin Brundage is founder of Synthient , a startup that tracks proxy services. In late October 2025, Brundage shared that the people selling various proxy services which benefitted from the Aisuru and Kimwolf botnets were doing so at a new Discord server called resi[.]to . On November 24, 2025, a member of the resi-dot-to Discord channel shares an IP address responsible for proxying traffic over Android TV streaming boxes infected by the Kimwolf botnet. When KrebsOnSecurity joined the resi[.]to Discord channel in late October as a silent lurker, the server had fewer than 150 members, including “ Shox ” — the nickname used by Resi Rack’s co-founder Mr. Hales — and his business partner “ Linus ,” who did not respond to requests for comment. Other members of the resi[.]to Discord channel would periodically post new IP addresses that were responsible for proxying traffic over the Kimwolf botnet. As the screenshot from resi[.]to above shows, that Resi Rack Internet address flagged by XLab was used by Kimwolf to direct proxy traffic as far back as November 24, if not earlier. All told, Synthient said it tracked at least seven static Resi Rack IP addresses connected to Kimwolf proxy infrastructure between October and December 2025. Neither of Resi Rack’s co-owners responded to follow-up questions. Both have been active in selling proxy services via Discord for nearly two years. According to a review of Discord messages indexed by the cyber intelligence firm Flashpoint , Shox and Linus spent much of 2024 selling static “ISP proxies” by routing various Internet address blocks at major U.S. Internet service providers. In February 2025, AT&T announced that effective July 31, 2025, it would no longer originate routes for network blocks that are not owned and managed by AT&T (other major ISPs have since made similar moves). Less than a month later, Shox and Linus told customers they would soon cease offering static ISP proxies as a result of these policy changes. Shox and Linux, talking about their decision to stop selling ISP proxies. The stated owner of the resi[.]to Discord server went by the abbreviated username “D.” That initial appears to be short for the hacker handle “ Dort ,” a name that was invoked frequently throughout these Discord chats. Dort’s profile on resi dot to. This “Dort” nickname came up in KrebsOnSecurity’s recent conversations with “ Forky ,” a Brazilian man who acknowledged being involved in the marketing of the Aisuru botnet at its inception in late 2024. But Forky vehemently denied having anything to do with a series of massive and record-smashing DDoS attacks in the latter half of 2025 that were blamed on Aisuru, saying the botnet by that point had been taken over by rivals. Forky asserts that Dort is a resident of Canada and one of at least two individuals currently in control of the Aisuru/Kimwolf botnet. The other individual Forky named as an Aisuru/Kimwolf botmaster goes by the nickname “ Snow .” On January 2 — just hours after our story on Kimwolf was published — the historical chat records on resi[.]to were erased without warning and replaced by a profanity-laced message for Synthient’s founder. Minutes after that, the entire server disappeared. Later that same day, several of the more active members of the now-defunct resi[.]to Discord server moved to a Telegram channel where they posted Brundage’s personal information, and generally complained about being unable to find reliable “bulletproof” hosting for their botnet. Hilariously, a user by the name “Richard Remington” briefly appeared in the group’s Telegram server to post a crude “Happy New Year” sketch that claims Dort and Snow are now in control of 3.5 million devices infected by Aisuru and/or Kimwolf. Richard Remington’s Telegram account has since been deleted, but it previously stated its owner operates a website that caters to DDoS-for-hire or “stresser” services seeking to test their firepower. Reports from both Synthient and XLab found that Kimwolf was used to deploy programs that turned infected systems into Internet traffic relays for multiple residential proxy services. Among those was a component that installed a software development kit (SDK) called ByteConnect, which is distributed by a provider known as Plainproxies . ByteConnect says it specializes in “monetizing apps ethically and free,” while Plainproxies advertises the ability to provide content scraping companies with “unlimited” proxy pools. However, Synthient said that upon connecting to ByteConnect’s SDK they instead observed a mass influx of credential-stuffing attacks targeting email servers and popular online websites. A search on LinkedIn finds the CEO of Plainproxies is Friedrich Kraft , whose resume says he is co-founder of ByteConnect Ltd. Public Internet routing records show Mr. Kraft also operates a hosting firm in Germany called 3XK Tech GmbH . Mr. Kraft did not respond to repeated requests for an interview. In July 2025, Cloudflare reported that 3XK Tech (a.k.a. Drei-K-Tech) had become the Internet’s largest source of application-layer DDoS attacks . In November 2025, the security firm GreyNoise Intelligence found that Internet addresses on 3XK Tech were responsible for roughly three-quarters of the Internet scanning being done at the time for a newly discovered and critical vulnerability in security products made by Palo Alto Networks. Source: Cloudflare’s Q2 2025 DDoS threat report. LinkedIn has a profile for another Plainproxies employee, Julia Levi , who is listed as co-founder of ByteConnect. Ms. Levi did not respond to requests for comment. Her resume says she previously worked for two major proxy providers: Netnut Proxy Network, and Bright Data. Synthient likewise said Plainproxies ignored their outreach, noting that the Byteconnect SDK continues to remain active on devices compromised by Kimwolf. A post from the LinkedIn page of Plainproxies Chief Revenue Officer Julia Levi, explaining how the residential proxy business works. Synthient’s January 2 report said another proxy provider heavily involved in the sale of Kimwolf proxies was Maskify , which currently advertises on multiple cybercrime forums that it has more than six million residential Internet addresses for rent. Maskify prices its service at a rate of 30 cents per gigabyte of data relayed through their proxies. According to Synthient, that price range is insanely low and is far cheaper than any other proxy provider in business today. “Synthient’s Research Team received screenshots from other proxy providers showing key Kimwolf actors attempting to offload proxy bandwidth in exchange for upfront cash,” the Synthient report noted. “This approach likely helped fuel early development, with associated members spending earnings on infrastructure and outsourced development tasks. Please note that resellers know precisely what they are selling; proxies at these prices are not ethically sourced.” Maskify did not respond to requests for comment. The Maskify website. Image: Synthient. Hours after our first Kimwolf story was published last week, the resi[.]to Discord server vanished, Synthient’s website was hit with a DDoS attack, and the Kimwolf botmasters took to doxing Brundage via their botnet. The harassing messages appeared as text records uploaded to the Ethereum Name Service (ENS), a distributed system for supporting smart contracts deployed on the Ethereum blockchain. As documented by XLab, in mid-December the Kimwolf operators upgraded their infrastructure and began using ENS to better withstand the near-constant takedown efforts targeting the botnet’s control servers. An ENS record used by the Kimwolf operators taunts security firms trying to take down the botnet’s control servers. Image: XLab. By telling infected systems to seek out the Kimwolf control servers via ENS, even if the servers that the botmasters use to control the botnet are taken down the attacker only needs to update the ENS text record to reflect the new Internet address of the control server, and the infected devices will immediately know where to look for further instructions. “This channel itself relies on the decentralized nature of blockchain, unregulated by Ethereum or other blockchain operators, and cannot be blocked,” XLab wrote. The text records included in Kimwolf’s ENS instructions can also feature short messages, such as those that carried Brundage’s personal information. Other ENS text records associated with Kimwolf offered some sage advice: “If flagged, we encourage the TV box to be destroyed.” An ENS record tied to the Kimwolf botnet advises, “If flagged, we encourage the TV box to be destroyed.” Both Synthient and XLabs say Kimwolf targets a vast number of Android TV streaming box models, all of which have zero security protections, and many of which ship with proxy malware built in. Generally speaking, if you can send a data packet to one of these devices you can also seize administrative control over it. If you own a TV box that matches one of these model names and/or numbers , please just rip it out of your network. If you encounter one of these devices on the network of a family member or friend, send them a link to this story (or to our January 2 story on Kimwolf ) and explain that it’s not worth the potential hassle and harm created by keeping them plugged in.

0 views
Chris Coyier 9 months ago

Media Diet

📺 Wondla — 10/10 kids show. I was way into it. Post-apoc situation with underground bunkers (apparently Apple loves that theme) where when the protagonist girl busts out of it, the world is quite different. The premise and payoff in Season 1 was better than the commentary vibe of Season 2, but I liked it all. Apparently there is one more season coming . 🎥 Downton Abbey: The Grand Finale — The darkest of the three movies? Weird. I love spending time in this world though so I was happy to be there. But honestly I was coming off a couple of day beers when I saw it in the theater and it put me in a weird mood and I should probably watch it again normally. How to proper movie critics review movies without their random current moods affecting the review?! 📕 Annie Bot —  Sierra Greer is like, what if we turned AI into sex bots? Which honestly feels about 7 minutes away at this point. I’m only like half through it and it’s kinda sexy in that 50-shades kinda way where there is obviously some dark shit coming. 📔 Impossible People — Binge-able graphic novel by Julia Wertz about a redemption arc out of addiction. I’m an absolute sucker for addiction stories. This is very vulnerable and endearing. Like I could imagine having a very complicated friendship with Julia. It doesn’t go down to the absolute bottom of the well like in books like A Million Little Pieces or The Book of Drugs , so I’d say it’s a bit safer for you if you find stuff like that too gut wrenching.

0 views

how i use my terminal

this is a whole blog post because it is "outside the overton window"; it usually takes at least a video before people even understand the thing i am trying to describe. so, here's the video: the steps here that tend to surprise people are 0:11 , 0:21 , and 0:41 . when i say "surprise" i don't just mean that people are surprised that i've set this up, but they are surprised this is possible at all. here's what happens in that video: i got annoyed at VSCode a while back for being laggy, especially when the vim plugin was running, and at having lots of keybind conflicts between the editor, vim plugin, terminal, and window management. i tried zed but at the time it was quite immature (and still had the problem of lots of keybind conflicts). i switched to using nvim in the terminal, but quickly got annoyed at how much time i spent copy-pasting filenames into the editor; in particular i would often copy-paste files with columns from ripgrep, get a syntax error, and then have to edit them before actually opening the file. this was quite annoying. what i wanted was an equivalent of ctrl-click in vscode, where i could take an arbitrary file path and have it open as smoothly as i could navigate to it. so, i started using tmux and built it myself. people sometimes ask me why i use tmux. this is why! this is the whole reason! (well, this and session persistence.) terminals are stupidly powerful and most of them expose almost none of it to you as the user. i like tmux, despite its age, bugs, and antiquated syntax, because it's very extensible in this way. this is done purely with tmux config: and this is the contents of : i will not go through the whole regex, but uh. there you go. i spent more time on this than i probably should have. this is actually a trick; there are many steps here. this part is not so bad. tmux again. i also have a version that always opens an editor in the current pane, instead of launching in the default application. for example i use by default to view json files, but to edit them. here is the trick. i have created a shell script (actually a perl script) that is the default application for all text files. setting up that many file associations by hand is a pain. i will write a separate blog post about the scripts that install my dotfiles onto a system. i don't use Nix partly because all my friends who use Nix have even weirder bugs than they already had, and partly because i don't like the philosophy of not being able to install things at runtime. i want to install things at runtime and track that i did so. that's a separate post too. the relevant part is this: this bounces back to tmux. in particular, this is being very dumb and assuming that tmux is running on the machine where the file is, which happens to be the case here. this is not too bad to ensure - i just use a separate terminal emulator tab for each instance of tmux i care about; for example i will often have open one Windows Terminal tab for WSL on my local laptop, one for my desktop, and one for a remote work machine via a VPN. there's actually even more going on here—for example i am translating the syntax to something vim understands, and overriding so that it doesn't error out on the —but for the most part it's straightforward and not that interesting. this is a perl script that scripts tmux to send keys to a running instance of nvim (actually the same perl script as before, so that both of these can be bound to the same keybind regardless of whether nvim is already open or not): well. well. now that you mention it. the last thing keeping me on tmux was session persistence and Ansuz has just released a standalone tool that does persistence and nothing else . so. i plan to switch to kitty in the near future, which lets me keep all these scripts and does not require shoving a whole second terminal emulator inside my terminal emulator, which hopefully will reduce the number of weird mysterious bugs i encounter on a regular basis. the reason i picked kitty over wezterm is that ssh integration works by integrating with the shell, not by launching a server process, so it doesn't need to be installed on the remote. this mattered less for tmux because tmux is everywhere, but hardly anywhere has wezterm installed by default. honestly, yeah. i spend quite a lot less time fighting my editor these days. that said, i cannot in good conscience recommend this to anyone else. all my scripts are fragile and will probably break if you look at them wrong, which is not ideal if you haven't written them yourself and don't know where to start debugging them. if you do want something similar without writing your own tools, i can recommend: hopefully this was interesting! i am always curious what tools people use and how - feel free to email me about your own setup :) 0:00 I start with Windows Terminal open on my laptop. 0:02 I hit ctrl + shift + 5 , which opens a new terminal tab which 's to my home desktop and immediately launches tmux. 0:03 tmux launches my default shell, . zsh shows a prompt, while loading the full config asynchronously 0:08 i use to fuzzy find a recent directory 0:09 i start typing a ripgrep command. zsh autofills the command since i've typed it before and i accept it with ctrl + f . 0:11 i hit ctrl + k f , which tells tmux to search all output in the scrollback for filenames. the filenames are highlighted in blue. 0:12 i hold n to navigate through the files. there are a lot of them, so it takes me a bit to find the one i'm looking for. 0:21 i press o to open the selected file in my default application ( ). tmux launches it in a new pane. note that this is still running on the remote server ; it is opening a remote file in a remote tmux pane. i do not need to have this codebase cloned locally on my laptop. 0:26 i try to navigate to several references using rust-analyzer, which fails because RA doesn't understand the macros in this file. at 0:32 i finally find one which works and navigate to it. 0:38 i hit ctrl + k h , which tells tmux to switch focus back to the left pane. 0:39 i hit n again. the pane is still in "copy-mode", so all the files from before are still the focus of the search. they are highlighted again and tmux selects the next file in search order. 0:41 i hit o , which opens a different file than before, but in the same instance of . 0:43 i hit b , which shows my open file buffers. in particular, this shows that the earlier file is still open. i switch back and forth between the two files a couple times before ending the stream. i don't need a fancy terminal locally; something with nice fonts is enough. all the fancy things are done through tmux, which is good because it means they work on Windows too without needing to install a separate terminal. the editor thing works even if the editor doesn't support remote scripting. nvim does support RPC, but this setup also worked back when i used and . i could have written this such that the fancy terminal emulator scripts were in my editor, not in tmux (e.g. in nvim). but again this locks me into the editor; and the built-in terminals in editors are usually not very good. it's much easier to debug when something goes wrong (vscode's debugging tools are mostly for plugin extension authors and running them is non-trivial). with vim plugins i can just add statements to the lua source and see what's happening. all my keybinds make sense to me! my editor is less laggy. my terminal is much easier to script through tmux than through writing a VSCode plugin, which usually involves setting up a whole typescript toolchain and context-switching into a new project fish + zoxide + fzf . that gets you steps 4, 5, and kinda sorta-ish 6. "builtin functionality in your editor" - fuzzy find, full text search, tabs and windows, and "open recent file" are all commonly supported. qf , which gets you the "select files in terminal output" part of 6, kinda. you have to remember to pipe your output to it though, so it doesn't work after the fact and it doesn't work if your tool is interactive. note that it hard-codes a vi-like CLI ( ), so you may need to fork it or still add a script that takes the place of $EDITOR. see julia evans' most recent post for more info. e , which gets you the "translate into something your editor recognizes" part of 8, kinda. i had never heard of this tool until i wrote my own with literally the exactly the same name that did literally exactly the same thing, forgot to put it in PATH, and got a suggestion from asking if i wanted to install it, lol. or or , all of which get you 12, kinda. the problem with this is that they don't all support , and it means you have to modify this whenever you switch editors. admittedly most people don't switch editors that often, lol. terminals are a lot more powerful than people think! by using terminals that let you script them, you can do quite a lot of things. you can kinda sorta replicate most of these features without scripting your terminal, as long as you don't mind tying yourself to an editor. doing this requires quite a lot of work, because no one who builds these tools thought of these features ahead of time.

0 views

theory building without a mentor

NOTE: if you are just here for the how-to guide, click here to skip the philosophizing. Peter Naur wrote a famous article in 1985 called Programming as Theory Building . it has some excellent ideas, such as: programming must be the programmers’ building up knowledge of a certain kind, knowledge taken to be basically the programmers’ immediate possession, any documentation being an auxiliary product. solutions suggested by group B [who did not possess a theory of the program] […] effectively destroyed its power and simplicity. The members of group A [who did possess a theory] were able to spot these cases instantly and could propose simple and effective solutions, framed entirely within the existing structure. the program text and its documentation proved insufficient as a carrier of the most important design ideas i think this article is excellent, and highly recommend reading it in full. however, i want to discuss one particular idea Naur mentions: For a new programmer to come to possess an existing theory of a program it is insufficient that he or she has the opportunity to become familiar with the program text and other documentation. What is required is that the new programmer has the opportunity to work in close contact with the programmers who already possess the theory [...] program revival, that is reestablishing the theory of a program merely from the documentation, is strictly impossible. i do not think it is true that it is impossible to recover a theory of the program merely from the code and docs. my day job, and indeed one of my most prized skills when i interview for jobs, is creating a theory of programs from their text and documentation alone. this blog post is about how i do that, and how you can too. Naur also says in the article: “in a certain sense there can be no question of theory modification, only program modification” i think this is wrong: theory modification is exactly what Ward Cunningham describes as "consolidation" in his 1992 article on Technical Debt . i highly recommend the original article, but the basic idea is that over time, your understanding of how the program should behave changes, and you modify and refactor your program to match that idea. this happens in all programs, but the modification is easier in programs with little technical risk . furthermore, this theory modification often happens unintentionally over time as people are added and removed from teams. as ceejbot puts it : This is Conway’s Law over time. Teams are immutable: adding or removing a person to a team produces a different team. After enough change, the team is different enough that it no longer recognizes itself in the software system it produces. The result is people being vaguely unhappy about software that might be working perfectly well. i bring this up to note that you will never recover the same theory as the original programmers (at least, not without talking to them directly). the most you can do is to recover one similar enough that it does not require large changes to the program. in other words, you are creating a new theory of the program, and may end up having to adapt the program to your new theory. this is useful both when fixing bugs and when adding new features; i will focus on new features because i want to emphasize that these skills are useful any time you modify a program. for a focus on debugging, see Julia Evans' Pocket Guide to Debugging . this post is about creating theories at the "micro" level, for small portions of the program. i hope to make a post about the "macro" level in the future, since that's what really lets you start making design decisions about a program. i recently made a PR to neovim , having never worked on neovim before; i'll use that as an example going forward. i highly recommend following along with a piece of code you want to learn more about. if you don't have one in mind, i have hidden all the examples behind a drop-down menu, so you can try to apply the ideas on your own before seeing how i use them. the investigation i did in this blog post was based off neovim commit 57d99a5 . Click here to open all notes. to start off, you need an idea of what change you want to make to the program. almost always, programs are too large for you to get an idea of the whole program at once. instead, you need to focus on theory-building for the parts you care about, and only understand the rest of the program to the extent that the parts you care about interact with it. in my neovim PR, i cared about the command, which opens a file if it isn't loaded, or switches to the relevant buffer if it is. specifically i wanted to extend the "switch to the relevant buffer" part to also respect , so that i could pass it a line number. there are several ways to get started here. the simplest is just finding the relevant part of the code or docs—if you can provoke an error that's related to the part of the code you're changing, you can search for that error directly. often, knowing how execution reaches that state is very helpful, which you can do by getting a backtrace. you can get backtraces for output from arbitrary programs with liberal use of rr , but if you're debugging rustc specifically, there's actually a built-in flag for this, so you can just use . for , this didn't work: it was documented on neovim's site , but i didn't know a -specific error to search for. if this doesn't print an error message, or if it's not possible to get a recording of the program, things are harder. you want to look for something you already know the name of; search for literal strings with that name, or substrings that might form part of a template. for i searched for the literal string , since something needs to parse commands and it's not super common for it to be on its own in a string. that pulled up the following hits: looked promising, so i read the code around there. sometimes triggering the condition is hard, so instead i read the source code to reverse-engineer the stack trace. seeing all possible call sites of a function is instructive in itself, and you can usually narrow it down to only a few callers by skimming what the callers are doing. i highly recommend using an LSP for this part since the advantage comes from seeing all possible callers, not just most, and regex is less reliable than proper name resolution. it turned out that none of the code i found in my search was for itself, but i did find it was in a function named . had only one caller, . that was called by . the doc-comment on mentions that it parses the string, but i am not used to having documentation so i went up one level too far to . at that point, looking at the call site of , i realized i had gone too far because it was passing in the whole string of the Ex command line. i found a more relevant part of the code by looking at the uses of in : i got lucky - this was not actually the code i cared about, but the bit i did care about had a similar name, so i found it by searching for : from there i went to the definition of (in ) and found in that file: and from there found that the function i cared about was called . if i had been a little more careful, i could have found sooner with (this time without filtering out hidden files or limiting to the source directory). but this way worked fine as well. do mini experiments: if you see an error emitted in nearby code, try to trigger it so that you verify you're looking in the right place. when debugging, i often use process of elimination to narrow down callers: if an error would have been emitted if a certain code path was taken, or if there would have been more or less logging, i can be sure that code i am looking at was not run. the simplest experiment is just ; it's easy to notice and doesn't change the state of the program, and it can't fail. other experiments could include "adding custom logging" or "change the behavior of the function", which let you perform multiple experiments at once and understand how the function impacts its callers. for more complicated code, i like to use a debugger, which lets you see much more of the state at once. if possible, in-editor debuggers are really nice—vscode, and since recently, zed , have one built-in; for nvim i use nvim-dap-ui . you can also just use a debugger in a terminal. some experiments i like to try: for , i was quite confident i had found the right code, so i didn't bother with any experiments. there are other cases where it's more useful; i made an earlier PR to tmux where there were many different places search happened, so verifying i was looking at the right one was very helpful. specifically i added to the function i thought was the right place, since debug logging in tmux is non-trivial to access. i rarely use a debugger for adding new code; mostly i use it for debugging existing code. programs complicated enough that i need a debugger just to understand control flow usually have a client/server model that also makes them harder to debug, so i don't bother and just read the source code. reading source code is also useful for finding examples of how to use an API. often it handles edge cases you wouldn't know about by skimming, and uses helper functions that make your life simpler. your goal is to make your change as similar to the existing codebase as possible, both to reduce the risk of bugs and to increase the chance the maintainer likes your change. when i write new code, i will usually copy a small snippet from elsewhere in the codebase and modify it to my needs. i try to copy at most 10-15 lines; more than that indicates that i should try to reuse or create a higher-level API. once in , i skimmed the code and found a snippet looked like it was handling existing files: the bug here is not any code that is present; instead it's code that's missing. i had to figure out where was stored and how to process it. so, i repeated a similar process for . this time i had something more to start with - i knew the command structure was named and had type . looking at the definition of showed me what i wanted: looking for , i found (with a helpful comment saying it was responsible for ) which called , and in turn . looking at the callers of i found , which handles . has exactly the behavior i wanted for , so i copied its behavior: out of caution, i also looked at the other places in the function that handled , and it's a good thing i did, because i found this wild snippet above: i refactored this into a helper function and then called it from both the original command and my new code in . this works in much the same way. try to find existing tests by using the same techniques as finding the code you care about . read them; write them using existing examples. tests are also code, after all. test suites usually have better documentation than the code itself, since adding new tests is much more common than modifying any particular section of code; see if you can find the docs. i look for files, and if i don't find them i fall back to skimming the readme. sometimes there are is also a in the folder where the tests are located, although these tend to be somewhat out of date. i care a lot about iteration times, so i try and find how to run individual tests. that info is usually in the README, or sometime you can figure it out from the test command's output. run your tests! ideally, create and run your tests before modifying the code so that you can see that they start to pass after your change. tests are extra important when you don't already understand the code, because they help you verify that your new theory is correct. run existing tests as well; run those before you make changes so you know which failures are spurious (a surprisingly high number of codebases have flaky or environment-dependent tests). i started by looking for existing tests for : fortunately this had results right away and i was able to start adding my new test. had a pointer to which documented and . neovim has very good internal tooling and when my call failed it gave me a very helpful pointer to . hopefully this was helpful! i am told by my friends that i am unusually good at this skill, so i am interested whether this post was effective at teaching it. if you have any questions, or if you just want to get in contact, feel free to reach out via email . breaking at a function to make sure it is executed printing local variables setting hardware watchpoints on memory to see where something is modified (this especially shines in combination with a time-travel debugger ) programming is theory building . recovering a theory from code and docs alone is hard, but possible. most programs are too large for you to understand them all at once. decide on your goal and learn just enough to accomplish it. reading source code is surprisingly rewarding. match the existing code as closely as you can until you are sure you have a working theory.

0 views
DYNOMIGHT 1 years ago

DumPy: NumPy except it’s OK if you’re dum

What I want from an array language is: I say NumPy misses on three of these. So I’d like to propose a “fix” that—I claim—eliminates 90% of unnecessary thinking, with no loss of power. It would also fix all the things based on NumPy, for example every machine learning library. I know that sounds grandiose. Quite possibly you’re thinking that good-old dynomight has finally lost it. So I warn you now: My solution is utterly non-clever. If anything is clever here, it’s my single-minded rejection of cleverness. To motivate the fix, let me give my story for how NumPy went wrong. It started as a nice little library for array operations and linear algebra. When everything has two or fewer dimensions, it’s great. But at some point, someone showed up with some higher-dimensional arrays. If loops were fast in Python, NumPy would have said, “Hello person with ≥3 dimensions, please call my ≤2 dimensional functions in a loop so I can stay nice and simple, xox, NumPy.” But since loops are slow, NumPy instead took all the complexity that would usually be addressed with loops and pushed it down into individual functions. I think this was a disaster, because every time you see some function call like , you have to think: Different functions have different rules. Sometimes they’re bewildering. This means constantly thinking and constantly moving dimensions around to appease the whims of particular functions. It’s the functions that should be appeasing your whims! Even simple-looking things like or do quite different things depending on the starting shapes. And those starting shapes are often themselves the output of previous functions, so the complexity spirals. Worst of all, if you write a new ≤2 dimensional function, then high-dimensional arrays are your problem. You need to decide what rules to obey, and then you need to re-write your function in a much more complex way to— Voice from the back : Python sucks! If you used a real language, loops would be fast! This problem is stupid! That was a strong argument, ten years ago. But now everything is GPU, and GPUs hate loops. Today, array packages are cheerful interfaces that look like Python (or whatever) but are actually embedded languages that secretly compile everything into special GPU instructions that run on whole arrays in parallel. With big arrays, you need GPUs. So I think the speed of the host language doesn’t matter so much anymore. Python’s slowness may have paradoxically turned out to be an advantage , since it forced everything to be designed to work without loops even before GPUs took over. Still, thinking is bad, and NumPy makes me think, so I don’t like NumPy . Here’s my extremely non-clever idea: Let’s just admit that loops were better. In high dimensions, no one has yet come up with a notation that beats loops and indices. So, let’s do this: That’s basically the whole idea. If you take those three bullet-points, you could probably re-derive everything I do below. I told you this wasn’t clever. Suppose that and are 2D arrays, and is a 4D array. And suppose you want to find a 2D array such that . If you could write loops, this would be easy: That’s not pretty. It’s not short or fast. But it is easy! Meanwhile, how do you do this efficiently in NumPy? Like this: If you’re not a NumPy otaku, that may look like outsider art. Rest assured, it looks like that to me too, and I just wrote it. Why is it so confusing? At a high level, it’s because and and multiplication ( ) have complicated rules and weren’t designed to work together to solve this particular problem nicely. That would be impossible, because there are an infinite number of problems. So you need to mash the arrays around a lot to make those functions happy. Without further ado, here’s how you solve this problem with DumPy (ostensibly D ynomight N umPy ): Yes! If you prefer, you can also use this equivalent syntax: Those are both fully vectorized. No loops are executed behind the scenes. They’ll run on a GPU if you have one. While it looks magical, the way this actually works is fairly simple: If you index a DumPy array with a string (or a object), it creates a special “mapped” array that pretends to have fewer dimensions. When a DumPy function is called (e.g. or (called with )), it checks if any of the arguments have mapped dimensions. If so, it automatically vectorizes the computation, matching up mapped dimensions that share labels. When you assign an array with mapped dimensions to a , it “unmaps” them into the positions you specify. No evil meta-programming abstract syntax tree macro bytecode interception is needed. When you run this code: This is what happens behind the scenes: It might seem like I’ve skipped the hard part. How does know how to vectorize over any combination of input dimensions? Don’t I need to do that for every single function that DumPy includes? Isn’t that hard? It is hard, but did it already. This takes a function defined using ( JAX ’s version of) NumPy and vectorizes it over any set of input dimensions. DumPy relies on this to do all the actual vectorization. (If you prefer your janky and broken, I heartily recommend PyTorch’s .) But hold on. If already exists, then why do we need DumPy? Here’s why: That’s how you solve the same problem with . (And basically what DumPy does behind the scenes.) I think is one of the best parts of the NumPy ecosystem. The above code seems genuinely better than the base NumPy version. But it still involves a lot of thinking! Why put in the inner and in the outer one? Why are all the axes even though you need to vectorize over the second dimension of ? There are answers, but they require thinking. Loops and indices are better. OK, I did do one thing that’s a little clever. Say you want to create a Hilbert matrix with . In base NumPy you’d have to do this: In DumPy, you can just write: Yes! That works! It works because a acts both like a string and like an array mapped along that string. So the above code is roughly equivalent to: In reality, the choose random strings. (The class maintains a stack of active ranges to prevent collisions.) So in more detail, the above code becomes something like this: To test if DumPy is actually better in practice, I took six problems of increasing complexity and implemented each of them using loops, NumPy, JAX (with ), and DumPy. Note that in these examples, I always assume the input arrays are in the class of the system being used. If you try running them, you’ll need to add some conversions with / / . (Pretending doesn’t exist.) The goal is to create with The goal of this problem is, given a list of vectors and a list of Gaussians parameters, and arrays mapping each vector to a list of parameters, evaluate each corresponding vector/parameter combination. Formally, given 2D , , , and and 3D , the goal is to create with See also the discussion in the previous post . I gave each implementation a subjective “goodness” score on a 1-10 scale. I always gave the best implementation for each problem 10 points, and then took off points from the others based on how much thinking they required. According to this dubious methodology and these made-up numbers, DumPy is 96.93877% as good as loops! Knowledge is power! But seriously, while subjective, I don’t think my scores should be too controversial. The most debatable one is probably JAX’s attention score. The only thing DumPy adds to NumPy is some nice notation for indices. That’s it. What I think makes DumPy good is it also removes a lot of stuff. Roughly speaking, I’ve tried to remove anything that is confusing and exists because NumPy doesn’t have loops. I’m not sure that I’ve drawn the line in exactly the right place, but I do feel confident that I’m on the right track with removing stuff. In NumPy, works if and are both scalar. Or if is and is . But not if is and is . Huh? In truth, the broadcasting rules aren’t that complicated for scalar operations like multiplication. But still, I don’t like it, because every time you see , you have to worry about what shapes those have and what the computation might be doing. So, I removed it. In DumPy you can only do if one of or is scalar or and have exactly the same shape. That’s it, anything else raises an error. Instead, use indices, so it’s clear what you’re doing. Instead of this: write this: Indexing in NumPy is absurdly complicated . When you write that could do many different things depending on what all the shapes are. I considered going cold-turkey and only allowing scalar indices in DumPy. That wouldn’t have been so bad, since you can still do advanced stuff using loops. But it’s quite annoying to not be able to write when and are just simple 1D arrays. So I’ve tentatively decided to be more pragmatic. In DumPy, you can index with integers, or slices, or (possibly mapped) s. But only one index can be non-scalar . I settled on this because it’s the most general syntax that doesn’t require thinking. Let me show you what I mean. If you see this: It’s “obvious” what the output shape will be. (First the shape of , then the shape of , then the shape of ). Simple enough. But as soon as you have two multidimensional array inputs like this: Suddenly all hell breaks loose. You need to think about broadcasting between and , orthogonal vs. pointwise indices, slices behaving differently than arrays, and quirks for where the output dimensions go. So DumPy forbids this. Instead, you need to write one of these: They all do exactly what they look like they do. Oh, and one more thing! In DumPy, you must index all dimensions . In NumPy, if has three dimensions, then is equivalent to . This is sometimes nice, but it means that every time you see , you have to worry about how many dimensions has. In DumPy, every time you index an array or assign to a , it checks that all indices have been included. So when you see option (4) above, you know that: Always, always, always . No cases, no thinking. Again, many NumPy functions have complex conventions for vectorization. sort of says, “If the inputs have ≤2 dimensions, do the obvious thing. Otherwise, do some extremely confusing broadcasting stuff.” DumPy removes the confusing broadcasting stuff. When you see , you know that and have no more than two dimensions, so nothing tricky is happening. Similarly, in NumPy, is equivalent to . When both inputs have ≤2 or fewer dimensions, this does the “obvious thing”. (Either an inner-product or some kind of matrix/vector multiplication.) Otherwise, it broadcasts or vectorizes or something? I can never remember. In DumPy you don’t have that problem, because it restricts to arrays with one or two dimensions only. If you need more dimensions, no problem: Use indices. It might seem annoying to remove features, but I’m telling you: Just try it . If you program this way, a wonderful feeling of calmness comes over you, as class after class of possible errors disappear. Put another way, why remove all the fancy stuff, instead of leaving it optional? Because optional implies thinking! I want to program in a simple way. I don’t want to worry that I’m accidentally triggering some confusing broadcasting insanity, because that would be a mistake. I want the computer to help me catch mistakes, not silently do something weird that I didn’t intend. In principle, it would be OK if there was a method that preserves all the confusing batching stuff. If you really want that, you can make it yourself: You can use that same wrapper to convert any JAX NumPy function to work with DumPy. Think about math: In two or fewer dimensions, coordinate-free linear algebra notation is wonderful. But for higher dimensional tensors , there are just too many cases, so most physicists just use coordinates. So this solution seems pretty obvious to me. Honestly, I’m a little confused why it isn’t already standard. Am I missing something? When I complain about NumPy, many people often suggest looking into APL -type languages, like A, J, K, or Q. (All single-letter languages are APL-like, except C, D, F, R, T, X, and many others. Convenient, right?) The obvious disadvantages of these are that: None of those bother me. If the languages are better, we should learn to use them and make them do autodiff on GPUs. But I’m not convinced they are better. When you actually learn these languages, what you figure out is that the symbol gibberish basically amounts to doing the same kind of dimension mashing that we saw earlier in NumPy: The reason is that, just like NumPy and , these languages choose align dimensions by position , rather than by name. If I have to mash dimensions, I want to use the best tool. But I’d prefer not to mash dimensions at all. People also often suggest “NumPy with named dimensions” as in xarray . (PyTorch also has a half-hearted implementation .) Of course, DumPy also uses named dimensions, but there’s a critical difference. In xarray, they’re part of the arrays themselves, while in DumPy, they live outside the arrays. In some cases, permanent named dimensions are very nice. But for linear algebra, they’re confusing. For example, suppose is 2-D with named dimensions and . Now, what dimensions should have? ( twice?) Or say you take a singular value decomposition like . What name should the inner dimensions have? Does the user have to specify it? I haven’t seen a nice solution. xarray doesn’t focus on linear algebra, so it’s not much of an issue there. A theoretical “DumPy with permanent names” might be very nice, but I’m not sure how it should work. This is worth thinking about more. I like Julia ! Loops are fast in Julia! But again, I don’t think fast loops matter that much, because I want to move all the loops to the GPU. So even if I was using Julia, I think I’d want to use a DumPy-type solution. I think Julia might well be a better host language than Python, but it wouldn’t be because of fast loops, but because it offers much more powerful meta-programming capabilities. I built DumPy on top of JAX just because JAX is very mature and good at calling the GPU, but I’d love to see the same idea used in Julia (“Dulia”?) or other languages. OK, I promised a link to my prototype, so here it is: It’s just a single file with around 700 lines. I’m leaving it as a single file because I want to stress that this is just something I hacked together in the service of this rant . I wanted to show that I’m not totally out of my mind, and that doing all this is actually pretty easy. I stress that I don’t really intend to update or improve this. (Unless someone gives me a lot of money?) So please do not attempt to use it for “real work”, and do not make fun of my code. PS. DumPy works out of the box with both and . For gradients, you need to either cast the output to a JAX scalar or use the wrapper. PPS. If you like this, you may also like einx or torchdim . Update : Due to many requests, I have turned this into a “real” package, available on PyPi as . You can install it by typing: Or, if you use uv (you should) you can play around with DumPy by just typing this one-liner in your terminal: For example: Don’t make me think. Run fast on GPUs. Really, do not make me think. OK, what shapes do all those arrays have? And what does do when it sees those shapes? Bring back the syntax of loops and indices. But don’t actually execute the loops. Just take the syntax and secretly compile it into vectorized operations. Also, let’s get rid of all the insanity that’s been added to NumPy because loops were slow. If you index a DumPy array with a string (or a object), it creates a special “mapped” array that pretends to have fewer dimensions. When a DumPy function is called (e.g. or (called with )), it checks if any of the arguments have mapped dimensions. If so, it automatically vectorizes the computation, matching up mapped dimensions that share labels. When you assign an array with mapped dimensions to a , it “unmaps” them into the positions you specify. has 4 dimensions has 2 dimensions has 1 dimension has 4 dimensions They’re unfamiliar. The code looks like gibberish. They don’t usually provide autodiff or GPU execution.

0 views
fnands 1 years ago

A quick first look at GPU programming in Mojo

The day has finally arrived. Well actually, the day arrived in February, but who’s counting. The Mojo language has finally publicly released the ability to do GPU programming - if you have a reasonably modern NVIDIA GPU. Luckily for me, I have an RTX 3090, and although it isn’t officially supported , it is basically an A10, which is. Looking at some of the comments on the nightly releases, it does seem that AMD support is on the way as well. The Modular team publicly released the ability to do GPU programming in Mojo in release 25.1, with further support and documentation in release 25.2. Fun fact: release 25.2 also saw my first (tiny) contribution to the Mojo standard library. This is a really important step for Mojo, a language that bills itself as a language designed to solve a variety of AI development challenges, which in this day and age basically means programming an increasingly heterogeneous stack of hardware. Today this mostly means GPUs, but there is an explosion of new accelerators like the ones from Cerebras, Groq and SambaNova, not to mention the not-so-new TPU from Google. As DeepSeek showed the world recently: if you’re willing to put the work in, there is a lot more to be squeezed out of current-gen hardware than most people thought. Now, I don’t think every ML engineer or researcher should be looking for every possible way to get more out of their compute, but there are definitely some wins to be had. As an example, I’m really fascinated by the work of Tri Dao and his collaborators, who work on deeply hardware aware improvements in machine learning, e.g.  FlashAttention , which is mathematically equivalent to the attention mechanism that powers all transformer models, but with hardware aware optimizations that take into account the cost of memory access in GPUs. This does make me wonder what other optimizations are out there to be discovered. This however is not easy, as the authors note in the “Limitations and Future Directions” section of the FlashAttention paper: Our current approach to building IO-aware implementations of attention requires writing a new CUDA kernel for each new attention implementation. This requires writing the attention algorithm in a considerably lower-level language than PyTorch, and requires significant engineering effort. Implementations may also not be transferrable across GPU architectures. These limitations suggest the need for a method that supports writing attention algorithms in a high-level language (e.g., PyTorch), and compiling to IO-aware implementations in CUDA What makes GPU programming in Mojo interesting is that you don’t need the CUDA toolkit to do so, and compiles down to PTX which you can think of as NVIDIA’s version of assembly. If Mojo (and Max in general) can make it easier to write GPU kernels in a more user-friendly language, it could be a game changer. If you want to get started, there is a guide for getting started with GPU programming in Mojo from Modular (the company behind Mojo), which I strongly recommend. I learn by doing, so I wanted to try to implement something with relatively simple using the GPU. The example idea I chose is to transform an RGB image to grayscale, which is an embarrassingly parallel problem without a lot of complexity. I was halfway through writing this post before I realized that there was already an example of how to do grayscale conversion in the Mojo repo, but oh well. I basically just start with what’s in the documentation, but I added another example that I did do myself. To start, let’s read in an image using mimage , an image processing library I am working on. The image is represented here as a rank three tensor with the dimensions being width, height and channels, and the data type is an unsigned 8-bit integer. In this case we have four channels: red, green, blue and alpha (transparency), the latter being 255 for all pixels. So what we want to do here is to sum together the RGB values for each pixel, using the weights , and for red, green and blue respectively. If you want to know why we are using these weights, read this article . Now that we have that, let’s define a simple version of the transform we want on CPU. So hopefully that worked! Let’s see if it’s correct. I haven’t implemented image saving in mimage yet, so let’s use the good old Python PIL library to save the image. Now that we have a working CPU implementation, let’s try to implement the same function on the GPU. But first, let’s check if Mojo can actually find my GPU: Now that we know that Mojo can find our GPU, let’s define the function that will do the actual conversion. This kernel reads a pixel from the input tensor, converts it to grayscale and writes the result to the output tensor. It is parallelized across the output tensor, which means that each thread is responsible for one pixel in the output tensor. As you can see, it takes in as parameters the layout specifications of the input and output tensors, the width and height of the image, and the input and output tensors themselves. Now, the first slightly awkward thing I had to do was convert the image from a , which is what is returned by , to a , which is the new tensor type that is compatible with GPU programming. I am assuming that will be deprecated in the future. With this new tensor type you can explicitly set which device the tensor should be allocated on. In this case I will allocate it to the CPU, i.e. the host device, and then copy over the data from the old tensor to the new one. Next, we have to move the tensor to the GPU. Now that was easy enough. The next step is to allocate the output grayscale tensor. As we don’t need to copy over the data from the old tensor, we can just allocate it on the GPU immediately. Next, we get the layout tensors for the input and output tensors. The documentation on LayoutTensor is a bit sparse, but it seems to be there to make it easy to reason about memory layouts. There seems to be two ways to use GPU functions in Mojo. The first is to use the function, which is what I do here. This compiles the gpu kernel into a function which can be called as normal. While this function is being executed on the GPU, the host device will wait until it is finished before moving on. Later in this post I will show the other option which allows the host device to do other things while waiting for the GPU. And that’s it! Let’s call the GPU function. Here I will device the image up into blocks of 32x32 pixels, and then call the function. I have to admit, I have no clue what the best practices are for choosing the block size, so if you know a good rule of thumb, please let me know. I wonder if there is a way to tune these parameters at compile time? Once that is run, we move the grayscale tensor back to the CPU and compare the results. and there we have it! We have successfully converted an image to grayscale using the GPU. Another example I wanted to try is downsampling an image. This is a bit more complex than the grayscale conversion, because we need to handle the different dimensions of the input and output tensors. First let’s define some test images to make sure the function is doing what we expect. If this works we should have a downsampled 8x8 image with the same values as the original image. Let’s start with a CPU implementation: So it works! This does make some assumptions about the input image, like that it is a multiple of the factor. But good enough for a blog post. Now let’s try to do the same on the GPU. We again define our output tensor on the GPU, get the layout tensor and move the data from the host device to the GPU. This time we will try the other way of using GPU functions: enqueing the function(s) to be executed on the GPU. This means the host device will not wait for the GPU to finish the function, but can do other things while the GPU is running. When we call the host device will wait for the GPU to finish all enqueued functions. This allows for some interesting things, like running the GPU function in parallel with some other code on the host device. This is can also be a little bit dangerous if you try to access the GPU memory from the host device while the GPU is still running. Let’s try it out: Again, it works! Let’s try it on our original image, and downsample it by a factor of 2 and 4. Let’s also do a CPU version for comparison, and define the output tensors on the GPU. Now we can call the GPU function. Notice how we can enqueue a second function while the first one is still running. As it does not depend on the first function to finish, it can potentially start running before the first function has finished. Now let’s verify the results: Great! We can save these and see what they look like: And as we can see, the images get progressively more blurry the more we downsample. This was my first quick look at GPU programming in Mojo. I feel the hardest thing is conceptually understanding how to properly divide the work between threads, and how to assign the correct numbers of threads, blocks and warps (which I didn’t even get into here). I guess the next move is to look up some guide on how to efficiently program GPUs, and to maybe try some more substantial examples. The documentation on GPU programming in Mojo is still a bit sparse, and there aren’t many examples out there in the wild to learn from, but I am sure that will change soon. The Moduar team did say they are releasing it unpolished so that they can gather some community feedback early. For someone who uses GPUs a lot in my day job, I never really interact with the GPUs at a low level; it’s always through PyTorch or JAX or some other layer of abstraction from Python. It’s quite fun to have such low level access to the hardware in a language that doesn’t feel that dissimilar from Python. I think this is really where I am starting to see the vision behind Mojo more clearly. I think the shallow take is that Mojo is a faster Python, or basically some ungodly hybrid between Python and Rust, but the more I play with it the more I feel it’s a language designed to make programming heterogenous hardware easier. I don’t think it will be the only language like this we’ll see, and I am curious to see if other languages based on MLIR will pop up soon, or if some existing languages will adapt. Maybe basing Julia 2.0 off MLIR instead of LLVM is a good next move for the language. You only need to look at the schematic off Apple silicon chips these days to see which way the wind is blowing: a significant fraction of the chip is dedicated to GPU cores. I think the days where having a GPU attached to your computer was only for specialists is going out the window, and we might pretty soon be able to safely assume that every modern computer will have at least a decent amount of GPU cores available for general purpose tasks, and not just graphics. Still, I doubt most programmers will ever have to worry about actually directly programming GPUs, but I am interested to see how libraries take advantage of this fact.

0 views
A Room of My Own 1 years ago

Life Without Envy

Jealousy is always a mask for fear: fear that we aren’t able to get what we want; frustration that somebody else seems to be getting what is rightfully ours even if we are too frightened to reach for it. At its root, jealousy is a stingy emotion. It doesn’t allow for the abundance and multiplicity of the universe. Jealousy tells us there is room for only one—one poet, one painter, one whatever you dream of being. Julia Cameron, The Artist's Way I read Life Without Envy: Ego Management for Creative People  by Camille DeAngelis a long time ago, but I never really processed its highlights. Usually, I read on my Kindle and manage my highlights through Readwise. I always have these ambitious plans to revisit my highlights and reflect on them, and while I do that occasionally, most of the time, they just sit there, waiting for the "right" moment. This particular book comes back to me often—not because it was especially brilliant or life-changing. I wouldn’t call it a great book, but it resonated in many ways. It put into words something I’d been feeling for a long time: the belief that my creative work isn’t worth anything until it’s publicly validated by someone else. Or as the author puts it:  I just need to prove myself as soon as possible, and then I’ll be someone important. I’ve struggled with this, especially when it comes to blogging. I wrote about my hesitation before —how much time I spent questioning whether what I wrote had any value or whether people I know would read my posts and judge me. Whether I sound ridiculous. That fear held me back for a long time. What helped me, at least partially, was stepping outside of my comfort zone in other ways. I joined a critique group for fiction writing, where we read our work out loud and received feedback from others. It was terrifying at first—sharing something so personal and opening myself up to critique—but it taught me to detach a little from the fear of judgment. I learned to listen, take what was useful, and leave the rest. Eventually, I moved further outside my comfort zone by sending my manuscript for assessment. When the feedback came back, it was also incredibly helpful. That experience reinforced the importance of putting my work out there. Below, I’ve shared the highlights I took from this book (in italics), along with my own commentary. Life Without Envy: Ego Management for Creative People  by Camille DeAngelis How many times did the hunger for approval win out over curiosity and imagination? We are made to feel that we must always be striving for more. A bigger house, more money, more success, because if you feel complete just as you are, then you’re no longer a cog in the system. So you see, you’re not special. And that is the one great and profound benediction underwriting your entire existence. The idea here is pretty simple: you’re not special—and that’s actually a good thing. Not being inherently "special" frees you from the pressure of living up to extraordinary expectations or comparisons. Instead, it emphasizes that the lack of special status allows you to simply be —to exist, learn, grow, and find meaning without the weight of proving uniqueness. When we let go of trying to be "special," you realize something beautiful: we’re all in this together. Life isn’t about being the best or standing out—it’s about living, growing, and connecting with others. The real gift of life is knowing your worth doesn’t depend on being unique or exceptional. You’re enough just as you are, and that’s more than okay—it’s freeing. When we talk about wanting to be “great,” we implicitly set ourselves above others. We see ourselves as chosen where others are not. We oftent think: I just need to prove myself as soon as possible, and then I’ll be someone important. The frantic desire to produce an amazing work of art as soon as possible so that everyone will hail your genius before any of your contemporaries can edge you out. If I can’t be the best, then I don’t deserve to be here. I’m not a “real” artist until I make my work public. Even commercially successful artists sometimes work under a scarcity mentality: there are only so many artists who can be taken seriously, and I am not one of them. Here’s the saddest part: if all along we’ve been creating from a place of lack, what might we be capable of if we drew from a full well? As the entrepreneur and business coach Marie Forleo says: “Envy is often a clue that there’s something latent in you that needs to be expressed.” If you keep wanting what someone else has, you can’t grow into everything you could be. When you hinge your perception of success or failure on how your work is received, you create your own misery. You may think, “But I don’t think I’m entitled. I know I have to earn it.” Yet if you look back over the past ten or twenty or thirty years, at the various ways in which you may have waited for your life to happen to you, you begin to see that this passivity has been an expectancy: entitlement in a softer guise. So, what can you do? DeAngelis concludes that we don’t a castle or a faraway destination. Step outside and soak in the natural world—whether it’s a quiet park, a beach, or even your backyard. Shake up your daily routine with something new, no matter how small. And most importantly, set the intention that something extraordinary can happen. Bliss is right here, waiting for us to notice it. Related Post: Recognizing the Scarcity Mentality I just need to prove myself as soon as possible, and then I’ll be someone important. The frantic desire to produce an amazing work of art as soon as possible so that everyone will hail your genius before any of your contemporaries can edge you out. If I can’t be the best, then I don’t deserve to be here. I’m not a “real” artist until I make my work public.

0 views
Julia Evans 1 years ago

Importing a frontend Javascript library without a build system

I like writing Javascript without a build system and for the millionth time yesterday I ran into a problem where I needed to figure out how to import a Javascript library in my code without using a build system, and it took FOREVER to figure out how to import it because the library’s setup instructions assume that you’re using a build system. Luckily at this point I’ve mostly learned how to navigate this situation and either successfully use the library or decide it’s too difficult and switch to a different library, so here’s the guide I wish I had to importing Javascript libraries years ago. I’m only going to talk about using Javacript libraries on the frontend, and only about how to use them in a no-build-system setup. In this post I’m going to talk about: There are 3 basic types of Javascript files a library can provide: I’m not sure if there’s a better name for the “classic” type but I’m just going to call it “classic”. Also there’s a type called “AMD” but I’m not sure how relevant it is in 2024. Now that we know the 3 types of files, let’s talk about how to figure out which of these the library actually provides! Every Javascript library has a build which it uploads to NPM. You might be thinking (like I did originally) – Julia! The whole POINT is that we’re not using Node to build our library! Why are we talking about NPM? But if you’re using a link from a CDN like https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js , you’re still using the NPM build! All the files on the CDNs originally come from NPM. Because of this, I sometimes like to the library even if I’m not planning to use Node to build my library at all – I’ll just create a new temp folder, there, and then delete it when I’m done. I like being able to poke around in the files in the NPM build on my filesystem, because then I can be 100% sure that I’m seeing everything that the library is making available in its build and that the CDN isn’t hiding something from me. So let’s a few libraries and try to figure out what types of Javascript files they provide in their builds! First let’s look inside Chart.js , a plotting library. This library seems to have 3 basic options: option 1: . The suffix tells me that this is a CommonJS file , for using in Node. This means it’s impossible to use it directly in the browser without some kind of build step. option 2: . The suffix by itself doesn’t tell us what kind of file it is, but if I open it up, I see which is an immediate sign that this is an ES module – the syntax is ES module syntax. option 3: . “UMD” stands for “Universal Module Definition”, which I think means that you can use this file either with a basic , CommonJS, or some third thing called AMD that I don’t understand. When I was using Chart.js I picked Option 3. I just needed to add this to my code: and then I could use the library with the global environment variable. Couldn’t be easier. I just copied into my Git repository so that I didn’t have to worry about using NPM or the CDNs going down or anything. A lot of libraries will put their build in the directory, but not always! The build files’ location is specified in the library’s . For example here’s an excerpt from Chart.js’s . I think this is saying that if you want to use an ES Module ( ) you should use , but the jsDelivr and unpkg CDNs should use . I guess is for Node. ’s also says , which according to this documentation tells Node to treat files as ES modules by default. I think it doesn’t tell us specifically which files are ES modules and which ones aren’t but it does tell us that something in there is an ES module. is a library for logging into Bluesky with OAuth in the browser. Let’s see what kinds of Javascript files it provides in its build! It seems like the only plausible root file in here is , which looks something like this: This syntax means it’s an ES module . That means we can use it in the browser without a build step! Let’s see how to do that. Using an ES module isn’t an easy as just adding a . Instead, if the ES module has dependencies (like does) the steps are: The reason we need an import map instead of just doing something like is that internally the module has more import statements like , and we need to tell the browser where to get the code for and all of its other dependencies. Here’s what the importmap I used looks like for : Getting these import maps to work is pretty fiddly, I feel like there must be a tool to generate them automatically but I haven’t found one yet. It’s definitely possible to write a script that automatically generates the importmaps using esbuild’s metafile but I haven’t done that and maybe there’s a better way. I decided to set up importmaps yesterday to get github.com/jvns/bsky-oauth-example to work, so there’s some example code in that repo. Also someone pointed me to Simon Willison’s download-esm , which will download an ES module and rewrite the imports to point to the JS files directly so that you don’t need importmaps. I haven’t tried it yet but it seems like a great idea. I did run into some problems with using importmaps in the browser though – it needed to download dozens of Javascript files to load my site, and my webserver in development couldn’t keep up for some reason. I kept seeing files fail to load randomly and then had to reload the page and hope that they would succeed this time. It wasn’t an issue anymore when I deployed my site to production, so I guess it was a problem with my local dev environment. Also one slightly annoying thing about ES modules in general is that you need to be running a webserver to use them, I’m sure this is for a good reason but it’s easier when you can just open your file without starting a webserver. Because of the “too many files” thing I think actually using ES modules with importmaps in this way isn’t actually that appealing to me, but it’s good to know it’s possible. If the ES module doesn’t have dependencies then it’s even easier – you don’t need the importmaps! You can just: If you don’t want to use importmaps, you can also use a build system like esbuild . I talked about how to do that in Some notes on using esbuild , but this blog post is about ways to avoid build systems completely so I’m not going to talk about that option here. I do still like esbuild though and I think it’s a good option in this case. CanIUse says that importmaps are in “Baseline 2023: newly available across major browsers” so my sense is that in 2024 that’s still maybe a little bit too new? I think I would use importmaps for some fun experimental code that I only wanted like myself and 12 people to use, but if I wanted my code to be more widely usable I’d use instead. Let’s look at one final example library! This is a different Bluesky auth library than . Again, it seems like only real candidate file here is . But this is a different situation from the previous example library! Let’s take a look at : There’s a bunch of stuff like this in : This syntax is CommonJS syntax, which means that we can’t use this file in the browser at all, we need to use some kind of build step, and ESBuild won’t work either. Also in this library’s it says which is another way to tell it’s CommonJS. Originally I thought it was impossible to use CommonJS modules without learning a build system, but then someone Bluesky told me about esm.sh ! It’s a CDN that will translate anything into an ES Module. skypack.dev does something similar, I’m not sure what the difference is but one person mentioned that if one doesn’t work sometimes they’ll try the other one. For using it seems pretty simple, I just need to put this in my HTML: and then put this in . It seems to Just Work, which is cool! Of course this is still sort of using a build system – it’s just that esm.sh is running the build instead of me. My main concerns with this approach are: I also learned that you can also use to convert a CommonJS module into an ES module, though there are some limitations – the syntax doesn’t work. Here’s a github issue about that . I think the approach is probably more appealing to me than the approach because it’s a tool that I already have on my computer so I trust it more. I haven’t experimented with this much yet though. Here’s a summary of the three types of JS files you might encounter, options for how to use them, and how to identify them. Unhelpfully a or file extension could be any of these 3 options, so if the file is you need to do more detective work to figure out what you’re dealing with. The main difference between CommonJS modules and ES modules from my perspective is that ES modules are actually a standard. This makes me feel a lot more confident using them, because browsers commit to backwards compatibility for web standards forever – if I write some code using ES modules today, I can feel sure that it’ll still work the same way in 15 years. It also makes me feel better about using tooling like because even if the esbuild project dies, because it’s implementing a standard it feels likely that there will be another similar tool in the future that I can replace it with. A lot of the time when I talk about this stuff I get responses like “I hate javascript!!! it’s the worst!!!”. But my experience is that there are a lot of great tools for Javascript (I just learned about https://esm.sh yesterday which seems great! I love esbuild!), and that if I take the time to learn how things works I can take advantage of some of those tools and make my life a lot easier. So the goal of this post is definitely not to complain about Javascript, it’s to understand the landscape so I can use the tooling in a way that feels good to me. Here are some questions I still have, I’ll add the answers into the post if I learn the answer. Here’s a list of every tool we talked about in this post: Writing this post has made me think that even though I usually don’t want to have a build that I run every time I update the project, I might be willing to have a build step (using or something) that I run only once when setting up the project and never run again except maybe if I’m updating my dependency versions. Thanks to Marco Rogers who taught me a lot of the things in this post. I’ve probably made some mistakes in this post and I’d love to know what they are – let me know on Bluesky or Mastodon!

0 views
Julia Evans 1 years ago

New microblog with TILs

I added a new section to this site a couple weeks ago called TIL (“today I learned”). One kind of thing I like to post on Mastodon/Bluesky is “hey, here’s a cool thing”, like the great SQLite repl litecli , or the fact that cross compiling in Go Just Works and it’s amazing, or cryptographic right answers , or this great diff tool . Usually I don’t want to write a whole blog post about those things because I really don’t have much more to say than “hey this is useful!” It started to bother me that I didn’t have anywhere to put those things: for example recently I wanted to use diffdiff and I just could not remember what it was called. So I quickly made a new folder called /til/ , added some custom styling (I wanted to style the posts to look a little bit like a tweet), made a little Rake task to help me create new posts quickly ( ), and set up a separate RSS Feed for it. I think this new section of the blog might be more for myself than anything, now when I forget the link to Cryptographic Right Answers I can hopefully look it up on the TIL page. (you might think “julia, why not use bookmarks??” but I have been failing to use bookmarks for my whole life and I don’t see that changing ever, putting things in public is for whatever reason much easier for me) So far it’s been working, often I can actually just make a quick post in 2 minutes which was the goal. My page is inspired by Simon Willison’s great TIL blog , though my TIL posts are a lot shorter. This came about because I spent a lot of time on Twitter, so I’ve been thinking about what I want to do about all of my tweets. I keep reading the advice to “POSSE” (“post on your own site, syndicate elsewhere”), and while I find the idea appealing in principle, for me part of the appeal of social media is that it’s a little bit ephemeral. I can post polls or questions or observations or jokes and then they can just kind of fade away as they become less relevant. I find it a lot easier to identify specific categories of things that I actually want to have on a Real Website That I Own: and then let everything else be kind of ephemeral. I really believe in the advice to make email lists though – the first two (blog posts & comics) both have email lists and RSS feeds that people can subscribe to if they want. I might add a quick summary of any TIL posts from that week to the “blog posts from this week” mailing list.

0 views
blog.philz.dev 1 years ago

Safari Top, Part 2

I posted recently about getting the top memory-using tabs from Safari. This is the sort of pickle you get into if you're using a laptop with only 8GB of RAM. There are two problems: (1) how to map tabs to process ids and (2) how to get the memory usage of the underlying processes. Once you enable AppleScript works well enough to get the mapping of tabs to process ids, but, crucially for the second problem, was underreporting memory usage. For example, Claude is reportedly using 1GB of memory, but is reporting just 1MB. This led me down a rabbit hole of finding the command, and seeing memory usage more in the 1GB ballpark. I learned about from Julia Evans' blog post and went on a little bit of a detour to try to replicate it. It turns out that to get a "mach port" you need several "Entitlements" like and . So, you make the Rust work, figure out how to and, voila, it still doesn't work . Safari is protected by System Integrity Protection and doesn't allow you to open a mach port to it. So, back at square two, we find out about , find the header files in , and use Python's package. The field seems to match with Activity Monitor says. (The documentation is sparse, and I haven't delved deeper.) The reason to use Python rather than compiling a binary is to avoid a compile or installation step. So, here's the result: Here's the Python code : This time, I converted the AppleScript into "Javascript for Automation" (JXA), and learned that the Script Editor app has an "Open Dictionary" feature which lets you browse what's possible. If you find out how Activity Monitor actually gets the pids of the tabs, let me know!

0 views
Lambda Land 2 years ago

My Top Emacs Packages

If you ask anyone what the best Emacs packages are, you’ll almost definitely hear Magit (the only Git porcelain worth using) and Org Mode (a way to organize anything and everything in plain text) listed as #1 and #2. And they’re right! I use those packages extensively every day. Besides those two powerhouses, there are a handful of packages that make using Emacs a delight. If I had to ever use something else, I would miss these packages most: Jump around your screen crazy fast. Teleport to any character with ~5 key strokes. See https://karthinks.com/software/avy-can-do-anything/ for more reasons why it’s awesome. I almost exclusively rely on and have it bound to . Kind of like a super-charged right-click for Emacs. Works beautifully in dired, when selecting files in the minibuffer. There’s an easy way to make it play well with Avy which is just the best. Eat is a terminal emulator that’s faster almost all the other terminal emulators for Emacs. The only emulator it’s not faster than is Vterm, which is pretty dang speedy. Eat has been more than fast enough for all my needs however. Additionally, it can make a terminal emulator in a particular region , so if you use Eshell, you can get a little terminal emulator for every command you run. Normally, if you run, say, , you see the ugly terminal escape characters printed as text. With Eat, however, those terminal escape characters get interpreted correctly. Interactive programs (e.g. the Julia and Elixir REPLs) work flawlessly with it. Best spellchecking ever. It can spellcheck based off of the fontlock face; I keep this on when I program to get on-the-fly spellchecking of code comments and strings. I keep bound to à la flyspell because it is so darn helpful. Supports checking documents with mixed languages. This is one of the packages I miss most when I’m editing text outside of Emacs. The best way to add citations in Emacs, hands-down. Reads bibtex, inserts in org-mode, LaTeX, whatever. These next packages are all by Daniel Mendler . These packages improve selecting commands, buffers, files, etc. from the and interfaces. These make Emacs insanely ergonomic and excellent. These replace packages like Helm , Ivy/Counsel/Swiper , and Company . In comparison to these packages, Vertico + Consult + Corfu are lighter-weight, faster, less buggy (in my experience; I’ve tried them all!), and work better with other Emacs packages because they follow the default built-in APIs. Lighter-weight, less buggy vertical completing-read interface. Replaces Ivy. Incredibly flexible. Works out-of-the-box with everything that has a interface, so you don’t need special packages to make it play nice. Recommend adding Marginalia as well by the same author to add extra infos. Better than counsel. The live preview is amazing; I use instead of , instead of Swiper. is :fire: for searching large projects with instant previewable results. Pairs well with Embark to save results to a buffer. Lightweight pop-up library. Pairs well with Cape by the same author. See also Orderless which enhances everything from to to the Corfu popup. Vertico + Consult + Orderless + Marginalia + Corfu + Cape + Embark is sometimes called the “minad stack”. Embark and Orderless are both developed by Omar Camarena (oantolin) who frequently collaborates with Daniel Mendler. When I asked Omar on Reddit about the name, Omar replied that “minad stack” is fine; another name they’ve tried for the stack is “iceberg”, which I think is a good name too. It’s the new hotness—that said, it’s gotten really really stable over the past two years. If you like these packages, consider sponsoring their maintainers! These are some of my favorite open-source projects and I try to support them when I can. If you like these packages, you might like my Emacs Bedrock starter kit which, unlike many other starter kits, is meant to be a no-nonsense no-fluff no-abstraction bare-bones start for you to fork and tinker with to your liking. The stock configuration only installs one package ( which-key , which is amazing) but includes some extra example configuration. The extras/base.el file includes sample starter configuration for most of the above packages. (I should add to it, come to think of it…) Avy Jump around your screen crazy fast. Teleport to any character with ~5 key strokes. See https://karthinks.com/software/avy-can-do-anything/ for more reasons why it’s awesome. I almost exclusively rely on and have it bound to . Embark Kind of like a super-charged right-click for Emacs. Works beautifully in dired, when selecting files in the minibuffer. There’s an easy way to make it play well with Avy which is just the best. Eat Eat is a terminal emulator that’s faster almost all the other terminal emulators for Emacs. The only emulator it’s not faster than is Vterm, which is pretty dang speedy. Eat has been more than fast enough for all my needs however. Additionally, it can make a terminal emulator in a particular region , so if you use Eshell, you can get a little terminal emulator for every command you run. Normally, if you run, say, , you see the ugly terminal escape characters printed as text. With Eat, however, those terminal escape characters get interpreted correctly. Interactive programs (e.g. the Julia and Elixir REPLs) work flawlessly with it. Jinx Best spellchecking ever. It can spellcheck based off of the fontlock face; I keep this on when I program to get on-the-fly spellchecking of code comments and strings. I keep bound to à la flyspell because it is so darn helpful. Supports checking documents with mixed languages. This is one of the packages I miss most when I’m editing text outside of Emacs. Citar The best way to add citations in Emacs, hands-down. Reads bibtex, inserts in org-mode, LaTeX, whatever. Vertico Lighter-weight, less buggy vertical completing-read interface. Replaces Ivy. Incredibly flexible. Works out-of-the-box with everything that has a interface, so you don’t need special packages to make it play nice. Recommend adding Marginalia as well by the same author to add extra infos. Consult Better than counsel. The live preview is amazing; I use instead of , instead of Swiper. is :fire: for searching large projects with instant previewable results. Pairs well with Embark to save results to a buffer. Corfu Lightweight pop-up library. Pairs well with Cape by the same author. Eat is not the fastest terminal emulator, Vterm is. Thanks to a Redditor who pointed this out.

0 views
blog.philz.dev 2 years ago

A Bibliography of Sorts and Some Quotes

These were impactful to me, one way or another. Did you just tell me to... is a classic from @jrecursive. Migrations are a fact of software engineering life, and this, by Manu Cornet , is on point. Julia Evans's comics and zines are a national treasure. I learned some options to ! I learned about CSS! I've shared the post on SQL queries don't start with SELECT many times! Google published The Standard of Code Review which includes the following: In general, reviewers should favor approving a CL once it is in a state where it definitely improves the overall code health of the system being worked on, even if the CL isn’t perfect. I learned a lot from the ggplot2 book . I've followed Jeff Heer's work including Vega Lite, and you could do much worse than reading some of it. Matt Eccleston wrote a blog post on code-centric versus product-goal-centric teams . My friend Dan wrote Effective Typescript . Sometimes, coming up with the right approach to testing a problem is the key to solving the problem. Don't take just my word for it; here's FoundationDB Testing and debugging distributed systems is at least as hard as building them. Unexpected process and network failures, message reorderings, and other sources of non determinism can expose subtle bugs and implicit assumptions that break in reality, which are extremely difficult to reproduce or debug. The consequences of such subtle bugs are especially severe for database systems, which purport to offer perfect fidelity to an unambiguous contract. Moreover, the stateful nature of a database system means that any such bug can result in subtle data corruption that may not be discovered for months. FDB took a radical approach— before building the database itself, we built a deterministic database simulation framework that can simulate a network of interacting processes and a variety of disk, process, network, and request-level failures and recoveries, all within a single physical process. This rigorous testing in simulation makes FDB extremely stable, and allows its developers to introduce new features and releases in a rapid cadence. When I approach a new code base, I look first at what happens on disk (typically, the database schemas) and what happens across the wire (the RPC definitions like protobuf or thrift files, OpenAPI/swagger/typescript schemas, clicking around in the network tab in Chrome). This quote, from The Mythical Man-Month (Brooks) strikes a chord: Show me your flowcharts and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won’t usually need your flowcharts; they’ll be obvious. Tracing. I had the privilege of using Dapper while at Google. X-Trace is similar. The ad-hoc version of this is to add a random identifier to log statements (especially "canonical log lines") and pass those identifiers along across RPC boundaries (e.g., via an HTTP header). Add canonical log lines (thanks, Stripe and Brandur Leach, for the write up) to your system. If you can make it queryable with SQL, you have a lovely analytics system! Nelson Elhage's "What does a cache do?" is a great discussion of why read replicas and caches are different. E-mail me if you have pointers to these! A history of UI library APIs. How did we get from Apple II graphics to React? These libraries seem so dynamic; I'd love to read a history! A picture of the Cauldron visualization for a CDH build. (This is very specific!)

0 views
NorikiTech 2 years ago

Writing by hand for side projects

In the past few months I made good progress on my side projects ( DDPub , Typingvania ) compared to the previous period of drought. What changed? I started writing about them by hand. (Every journal is 140 A4 pages and I’m halfway through my third.) I’ve been following a practice called “ morning pages ” created by Julia Cameron. I learned about it by reading her book “ The Artist’s Way .” Previously I tried a similar practice called “freewriting” where you would ask a question and write for a predetermined short amount of time, but it didn’t stick. I’ve been doing the pages for about six months now. It is as simple as is explained on the page above—you sit down, preferably every day, and write down a certain amount straight from your thoughts. For me the right amount is two pages of A4 which takes me about an hour to write. It’s quite a lot and I skip some days, but it works so I keep at it. I use some space to write about my personal life, but over time I also started writing about the projects I’m working on. Usually it takes the form of a narrative around the next steps: Here I am in project right now: … The next two things I want to work on are this and this. How would I implement this approach? I would need to extend this object with such and such fields and add methods that would do this and this. When I transform this data, I’ll store it in this type of structure… I wonder if I could do this instead and rewrite that bit? The same internal monologue would happen if I were sitting in front of the code editor, but it’s hard to fully follow the thread of thought when you’re there trying to write some code at the same time. Writing by hand forces you to complete the thought to the end. There is some recent research showing higher brain activation when writing by hand. I don’t know if it’s that or not, but I often have really neat ideas when writing that I’m sure I would have missed otherwise—and I write them down immediately because I’m writing! Solutions to some architectural or UI problems simply presented themselves to me while I was writing. Clearly I thought about them previously, but I was also receptive during the writing sessions. Another benefit of the morning pages (that I usually do during lunch or even in the evening) is that divorcing the planning from the execution really helps when you are mentally fatigued. I work full time and do my side projects in the evening, and after a full day of work there is sometimes nothing but static in my head. It’s hard to stop working only to sit down and start working again. If I have written my pages, I already have a specific plan of the next steps, what exactly I want to do and how to implement it. It’s easier to sit down and start working on the side project when I’ve already done the hard part (came up with the implementation) and I only need to execute. The progress I make informs my next writing session, and the cycle repeats. The pages may take quite a lot of time, but as I said, they work (for me). I started to work and make progress on ideas that were sitting in my notes for five years or more. If I don’t write the pages, I’m likely to spend the same amount of time just starting at the editor trying to collect my thoughts and understand what to do next, that is if I sit down to work on the project at all. The pages help with that too. I reacknowledge to myself why I’m doing what I’m doing and what result I’m expecting. I’m prone to be distracted and anxious, and the pages give me more focus than I feel otherwise. The practice is simple so if you have similar problems as me, I encourage you to try it for a week and see if it makes any difference.

0 views
blog.philz.dev 2 years ago

Creating a monorepo out of a multirepo

Inspired by Julia Evans' posts on git , I'm jotting down an obscure trick to combine repos. Sometimes you have multiple repos, and you want to create a monorepo out of them. Perhaps you have a distribution of many components, and it's convenient to across all of them together. The following annotated snippet creates two repos and joins them together. The key insight is that a commit in git is made up of (roughly) a message, pointers to parent commits, and a "tree," the latter of which can be made synthetically by using plumbing commands. This approach is probably overkill for a one time merging of two repos. In that case, create a commit in your second repo that moves everything to a subdirectory, add the second repo as a remote, and merge in that second repo using the . Rendered another way (with from and questionable abuse of ): If you're doing this for real, note that the above will fail spectacularly if you have spaces (and their ilk) in your names. Use or something. Create two repos, a-repo and b-repo, and initialize them with a file and some commits. Initialize the monorepo Add both subrepos as remotes and fetch them. Synthesize a tree listing and create it. Synthesize a commit with all the parents Update our working copy You can see how the tree preserves the subrepo histories Now let's let the A repo change Now we have to redo the merge. We use the same trick, sorta. Since we have that nice "README.md" in the mono repo tree, we want to preserve that. But, when we pull out , we have those and trees, and we want to filter those out. So, here we're abusing to filter them out. The and expression is producing . And, sure enough, voila!

0 views