HNHacker News
TopNewBestAskShowJobs

YorickPeterse

2,777 karma · joined March 29, 2011

Author of the Inko programming language, formerly a staff developer at GitLab.

Website: https://yorickpeterse.com

submissionscomments
YorickPeterse··on Ghostty 1.0
This sounds like an issue with your environment. For example, on my Fedora 41 setup Ghostty starts up pretty much instantly regardless of the value of this option.
YorickPeterse··on Are Immutable Linux Distros right for you?
My `~/.var/app` directory is 14 GiB in size, 12 GiB of which is used by Signal (which is mostly photos and videos) and 1.8 GiB by Firefox. All other programs only take up a few MiB of space.

In terms of installation size it's not a problem either, as one can verify using `flatpak list --columns=size,name`:

    1.3 MB    Flatseal
    2.9 MB    Extension Manager
    47.7 MB   Celluloid
    604.6 MB  Freedesktop Platform
    680.0 MB  Freedesktop Platform
    533.8 MB  Mesa
    533.8 MB  Mesa (Extra)
    469.8 MB  Mesa
    469.8 MB  Mesa (Extra)
    46.9 MB   Intel VAAPI driver
    50.9 MB   Intel VAAPI driver
    20.3 MB   FFmpeg extension with extra codecs
    790.0 kB  openh264
    763.9 kB  openh264
    243.7 MB  GNU Image Manipulation Program
    7.7 MB    HEIC
    17.6 MB   Characters
    14.2 MB   Connections
    25.4 MB   Image Viewer
    946.7 kB  HEIC
    25.5 MB   Sushi
    39.8 MB   Papers
    941.3 MB  GNOME Application Platform version 46
    1.0 GB    GNOME Application Platform version 47
    794.1 kB  Fonts
    137.7 MB  gThumb Image Viewer
    1.1 MB    adw-gtk3 Gtk Theme
    269.6 MB  Firefox
    482.3 MB  Signal Desktop
The duplicate entries is because certain Flatpaks may require different versions of e.g. the Freedesktop platform (that being possible is one of its big selling points).

In short, storage isn't a problem at all for any computer produced in the last 20 years.

YorickPeterse··on Building a better and scalable system for data migrations
Both you and sgarland are missing the point here: the example I gave is literally just that: an example. There could be many more complex cases, some relying on other business logic provided by your application, some more isolated. Some of those cases could be handled using some random PG extension, but for many that won't be sufficient.

In contrast, a migration written in an actual programming language can handle all such cases. Depending on the amount of abstractions applied, it can also look close enough to a declarative language (in other words, it doesn't have to be verbose).

So yes, the limitation I'm implying very much is a limitation of SQL (or really any declarative query language for that matter). It has nothing to do with the data model as it applies equally well to using e.g. MongoDB, Redis, or really anything else that stores a pile of data you may want to transform in complex ways.

YorickPeterse··on Building a better and scalable system for data migrations
> Disagree. SQL is a declarative language that is both clear and universal. Feel free to generate the migrations however you’d like, but I want to see the SQL.

This works until you need something beyond the trivial "UPDATE table SET column1 = column2", such as "Update the values in column X with the contents of file Y that exists in Amazon S3", or really anything else you can't express in SQL.

> Another bonus (and I’m sure I’ll be told I’m wrong) is that you don’t need to write tests for the migration, because it’s declarative.

This indeed is wrong, and essentially comes down to "It looks simple so it's correct". A SQL query "DELETE FROM users" might be correct, but if you meant for it to be "DELETE FROM users WHERE id IN (...)" it's going to cause problems.

In other words, at least for data migrations you absolutely have to write tests or you will run into problems.

YorickPeterse··on Building a better and scalable system for data migrations
If you enjoy maintaining 5 different formats of the same data, then by all means use a document storage DB. But at some point you'll have to migrate the data, at which point you'll face the same issues (or possibly worse due to the lack of support for schema migrations) as relational databases.
YorickPeterse··on Building a better and scalable system for data migrations
Good point, I forgot about tunability. At GitLab we did have a mechanism for this for background migrations, IIRC it looked at the vacuuming statistics of PostgreSQL and would back off if it deemed there was too much load.
YorickPeterse··on Building a better and scalable system for data migrations
I hadn't heard of Liquibase until now. Taking a brief look, it doesn't seem like it would be suitable though. For large scale migrations you really need to be able to express arbitrary logic ("migrate A to B but only if file X on the file system contains Y"), which you can't do with something like SQL or XML.
YorickPeterse··on Hezbollah hand-held radios detonate across Lebanon, sources say
You can call Israel out on its bullshit while also doing the same with Hezbollah and Hamas, it's not mutually exclusive.
YorickPeterse··on Asynchronous IO: the next billion-dollar mistake?
Is this actually proven though? I've seen other people argue that threads are already as fast as they can be, yet nobody is able to actually substantiate that claim.
YorickPeterse··on Asynchronous IO: the next billion-dollar mistake?
That particular paragraph can be phrased better so I'll adjust that. What I meant to say is that you can't handle it asynchronously like you can with sockets, i.e. polling it for readiness. This is because for file IO, reads and writes are always reported as being available, making epoll/kqueue/etc effectively useless.
YorickPeterse··on Asynchronous IO: the next billion-dollar mistake?
I'm not sure how any of this is relevant to what you're quoting? I'm specifically arguing that if creating and switching between threads was as efficient as say Goroutines, you wouldn't need epoll/kqueue/async await/etc, because you'd be able to just spawn more OS threads. In such a setup, your unit of concurrency is an OS thread, not a green thread coupled with some sort of IO scheduler.
YorickPeterse··on How does it feel to test a compiler?
For parsing this might be the case, but writing tests becomes a lot more tricky the further down the compilation pipeline you go, as the amount of boilerplate code needed to set things up increases.
YorickPeterse··on Alacritty – A fast, cross-platform, OpenGL terminal emulator
It's more the latter: Ghostty doesn't introduce any groundbreaking features, instead it's a bunch of incremental improvements/nice-to-have's.
YorickPeterse··on I can't stand using VSCode so I wrote my own
There are DAP extensions for both Vim (e.g. https://github.com/puremourning/vimspector) and NeoVim (https://github.com/mfussenegger/nvim-dap). I can't speak as to the experience in detail (I think I briefly played with nvim-dap a year or two ago), but I suspect that for most it will be good enough.
YorickPeterse··on Show HN: Dropflow, a CSS layout engine for node or <canvas>
How suitable would this be for a text editor of sorts, and what latency would one expect? Using the demo it's difficult to gauge the latency by just eyeballing it.
YorickPeterse··on ASML dethrones Applied Materials, becomes largest fab tool maker
This seems like a very one-sided opinion/experience. While I've certainly had my own share of odd experiences and heard similarly odd stories, this doesn't really strike me as specific to The Netherlands as a whole.
YorickPeterse··on What it was like working for Gitlab
Respectfully, I didn't. I specifically wrote the following:

> I didn't get along well with this manager, The resulting conflict lead to a "performance enablement plan", a procedure meant to get things back on track before the need for a "performance improvement plan" (PIP). A PIP is meant to be used as a last attempt at improving the relationship between an employee, their work, and their employer.

Not getting along with someone doesn't imply or mean that a person is difficult to manage. Instead, it means there's simply friction between two people.

In this specific case, the main source of friction was that my messed up working hours resulted in me performing tasks later than expected (though still within any deadlines), though I recall those time frames not being well specified to begin with (i.e. it was more of an implicit assumption that X would be done by hour Y).

I believe I was also a little late for a meeting because I'd overslept. That's not good, but it certainly isn't a case of "Wow this person is so difficult", instead more of a case of "This person needs to get his schedule back together".

Either way, you seem to be interpreting the story in a way different from what's written down. I doubt I can change that, so I'm going to leave it at this comment.

YorickPeterse··on What it was like working for Gitlab
> how can you assure that? it seems like a classic self-fulfilling prophecy of arrogance.

Because colleagues have told me so, and the annual employee reviews were positive as well. In fact, outside the PEP the only actionable/to-improve feedback I got was essentially "Sometimes you can be a little harsh/blunt", which is vastly different from "this person is difficult to work with".

YorickPeterse··on What it was like working for Gitlab
In Rails, you can write something like this:

    Project.limit(10).each do |project|
      project.members.count
    end
Here `project.members` returns some sort of query object that produces a query along the lines of this:

    [...] FROM members WHERE members.project_id = X [...]
In other words, you can just query associated data on a per-project basis, without needing to pass any additional arguments.

The problem this results in is that in the above code you'd run a COUNT query for _each_ project. Instead what you'd want is a single COUNT that groups data per project, such that you can then pass that data along with the `each` call. This setup would only require 2 queries, instead of 20.

To eager load data in Rails you have to explicitly opt-in, resulting in something like this:

    Project.includes(:members).limit(10).each do |project|
      project.members.count
    end
The problem here is that an opt-in mechanism is too easy to forget (as is evident by how common these N+1 query problems are), and even if you include it there are certain cases where you still end up running extra queries for each row.

The solution here is to separate querying from the row instance types, e.g. a "Project" type can't query data itself and instead requires it to be passed in. This makes it much more difficult to create N+1 query problems.

YorickPeterse··on What it was like working for Gitlab
Every time we'd look into this, we'd look into the challenges we'd have to face and see if they changed since last time. One of the problems GitLab faces is that it JOINs all over the place. In addition, while most data can be shared on a group/namespace basis, GitLab also does a bunch of cross-group queries in frequently accessed pages.

To put it differently, try to think of an application that does all the things you _don't_ want to be doing if you're going to (or want to) shard. Then imagine that that's basically GitLab, coupled with the load patterns simply not justifying it.

YorickPeterse··on What it was like working for Gitlab
I can assure you that me being a pain to manage wasn't the problem, nor was it ever brought up. In fact, the only negative/improve-upon-this-thing kind of feedback I got once or twice was to adjust my communication style to be less blunt/harsh, something I agreed with and did end up improving upon.

I can also guarantee you that the work I did very much did good work instead of "cause problems". Feel free to ask anybody that worked at GitLab during the same time (or is still there) and see for yourself :)

YorickPeterse··on What it was like working for Gitlab
I think the live stream was both a great and terrible idea. It was great because it showed the world we weren't just sitting on our hands. It was terrible because trying to do your work while 5000-something people are watching (we were in the top 10 live streams for most of the day) is incredibly stressful. Looking back, I'm not sure we had much of a choice though, so it's not something I feel particularly negative about.
YorickPeterse··on RubyWM – an X11 window manager in pure Ruby
Or maybe it's just a case of a job queue used for indexing new repositories being full. Not every instance of "X doesn't work" is the result of malicious intent.
YorickPeterse··on Community Contribution at GitLab
> An unexpected benefit of community was shown in its role in avoiding a PR disaster. In January 2017, a database administrator (referred to as "team-member-1")

Small nitpick/pedantics: the person in question wasn't a database administrator, or a junior developer as some sites reported at the time. They were a senior developer that happened to be part of a (too) small database focused team.

YorickPeterse··on Inko Programming Language
I have mixed feelings on Rust's syntax, especially around generics, lifetimes, and the `modifier -> keyword` syntax (i.e. `async fn` or `pub fn`). For Inko, I wanted something that's easy to parse by hand, and no context specific parsing (e.g. `QUOTE -> something` being the start of a lifetime in one place, but a char literal in another place).

Another motivator for that is that years ago I worked on Rubinius for a while (an implementation of Ruby), and helped out with a parser for Ruby (https://github.com/whitequark/parser). The Ruby developers really liked changing their already impossible syntax in even more impossible ways on a regular basis, making it a real challenge to provide syntax related tools that support multiple Ruby versions. I wanted to avoid making the same mistake with Inko, hence I'm actively trying to keep the syntax as simple as is reasonable.

As for the specific examples:

`fn async` means your parser only needs to look for `A | B | fn` in a certain scope, instead of `A | B | fn | async fn`. This cuts down the amount of repetition in the parser. An example is found at https://github.com/inko-lang/inko/blob/8f5ad1e56756fe00325a3..., which parses the body of a class definition.

Skipping parentheses is directly lifted from Ruby, because I really like it. Older versions took this further by also letting you write `function arg1 arg2`, but I got rid of that to make parsing easier. It's especially nice so you can do things like `if foo.bar.baz? { ... }` instead of `if foo().bar().baz?()`, though I suspect opinions will differ on this :)

Until recently we did in fact use `::` as a namespace separator, but I changed that to `.` to keep things consistent with the call syntax, and because it removes the need for remembering "Oh for namespaces I need to use ::, but for calls .".

`[T]` for generics is because most editors automatically insert a closing `]` if you type `[`, but not when you type `<`. If they do, then trying to write `10<20` is annoying because you'd end up with `10<>20`. I also just like the way it looks more. The usual ambiguity issues surrounding `<>` (e.g. what leads to `foo::<T>()` in Rust) doesn't apply to Inko, because we don't allow generics in expressions (i.e. `Array[Int].with_capacity(42)` isn't valid syntax) in the first place.

YorickPeterse··on Inko Programming Language
Ah gotcha. Yes, the actual layout of arrays is:

    header
    size
    capacity
    data ptr
Where `data ptr` is a raw pointer to data that is realloc'd as the array grows.
YorickPeterse··on Inko Programming Language
There are no pre-built binaries. If you're using Arch Linux there's an AUR package (see https://docs.inko-lang.org/manual/main/getting-started/ivm/#...), but for other platforms you'll need to use `cargo install`. I'm hopeful that over time the list of package managers including ivm will grow, making this process easier.
YorickPeterse··on Inko Programming Language
Arrays store just the pointers to their values, storing them inline isn't supported, like so:

    ptr to array  --> array
                      header
                      size
                      capacity
                      [
                        val1 ptr --> val1
                        val2 ptr --> val2
                      ]
At some point I'd like to support stack allocated values as an optimization, including the ability to allocate values directly into arrays, but that's not a priority at this point.
YorickPeterse··on Inko Programming Language
The cost of reference counting is only present when creating and destroying aliases, moving moved values around incurs no cost. That alone significantly reduces the overhead.

In addition, for objects other than String, Channel and processes, the count is just a regular increment (i.e. `count += 1`), not an atomic increment.

It's true that this can have an impact on caches, as the count increment is a write to the object, and so over time I hope to add optimizations to remove redundant increments whenever possible.

As for the borrow checker: Inko doesn't have one, at least not like Rust (i.e it's perfectly fine to both mutably and immutably borrow a value at the same time), so the cost of that isn't present.

YorickPeterse··on Inko Programming Language
You're right that "expect" combined with the error message here could be better. Specifically, I've been wanting a better name for the method, but haven't really done anything about it yet. The name itself is taken from Rust.

"unwrap_or" could perhaps be called "unwrap_or_use_default", but that's a tad too much to type. What it basically does is this:

    match the_value {
      case Some(val) -> val
      case _ -> 0
    }
i.e if you have a Some, you "unwrap" its value and return it, otherwise you return a default value.
← PreviousPage 3 of 14Next →