HNHacker News
TopNewBestAskShowJobs

_jackdk_

494 karma · joined May 17, 2023

submissionscomments
_jackdk_··on How Copyover MUD Servers Worked
All of this sounds pretty reasonable, but my goal was to document the simplest way I saw it work back in the day.

You can keep the process alive if copyover fails by writing to a temporary file and designing the server so that you get a "rescue console" if it fails to come up. Then you could inspect the dumped file, see what's going wrong, and exec() into another binary to try again. You could even use sqlite as your state file, which would let you interactively explore and edit it during debugging.

A custom allocator sounds like an extremely interesting approach. I'm still not sure whether I'd want to rely on struct layouts not changing, but if you use it diligently you at least make it easier to find everything you've allocated.

_jackdk_··on How Copyover MUD Servers Worked
Very cool - I thought it would be a forgotten technique by now. Which MUD, and have you written up the details anywhere? I'm very interested to know what a modern version of this looks like, particularly with the systemd integration.
_jackdk_··on How Copyover MUD Servers Worked
Yes, it's about transferring the contents of memory. If you just call exec(), you get a new process with the file descriptors (connections) open, but you have no idea what the game state (e.g., which connection corresponds to which player) before the exec() call. The fork() is a way of keeping that memory around, and the pipe lets the forked copy send its state to the new code that's restarting.
_jackdk_··on Haskell: A Great Procedural Language
I'm glad you like them!
_jackdk_··on Haskell: A Great Procedural Language
> Haskell is apparently quite strong for general backend web dev

Yep. Mostly because of the https://www.servant.dev/ framework (but see also IHP, Yesod, and other frameworks). Servant lets you declare your HTTP API at the type level, and then it will infer the correct types for your endpoint handlers. You can also extract OpenAPI specs from it, generate clients for Haskell or other languages, etc.

My current employer, Bellroy, uses Haskell for pretty much all new code and Servant for all new HTTP APIs. https://exploring-better-ways.bellroy.com/our-technology-sta... is an older post discussing the shift to Haskell. We've found Haskell code to be much more compact than the equivalent Ruby, and significantly more robust.

_jackdk_··on Overloading the lambda abstraction in Haskell (2022)
Fair. I think I mentally swapped adjectives while composing my response.
_jackdk_··on Overloading the lambda abstraction in Haskell (2022)
The linear functions/SMC work is really cool, but I am surprised to see the author calling it more mature than the compiling to CCCs work. The linear-smc library hasn't had an upload in a while, and it's currently missing any haddocks beyond the extracted type signatures. It's also a shame that it had to build its own typeclass for monoidal categories instead of using the one in https://hackage.haskell.org/package/categories .

Meanwhile KittyHawk did actually use the compile-to-CCCs work to compile Haskell to C in https://github.com/sellout/compiling-anything-to-categories and someone from there talks about it at https://www.youtube.com/watch?v=VUBj8NW7uMA

I think the linear-smc work is more exciting and I hope it matures with a bit more elbow grease. There was an ICFP talk that accompanied the linked paper ( https://www.youtube.com/watch?v=90OJz0QE4qE ) and monoidal categories can model lots of "boxes and wires" things. Linear functions potentially give you a much more ergonomic DSL for building up those boxes-and-wires models, and in a way that lets you write abstractions that work over any model.

_jackdk_··on Overloading the lambda abstraction in Haskell (2022)
The trick you describe is possible in Haskell also. https://hackage.haskell.org/package/simple-reflect does it by providing a `Num` instance for a particular type.

I'm surprised that you say an imperative language would give you an _easier_ embedding than using Haskell. I would have said the reverse: Haskell makes it impossible to confuse an imperative command used during AST assembly with a representation of an imperative command. That's one of the payoffs of using monads.

_jackdk_··on A dictionary of single-letter variable names
(Author here.)

I think the amount of polymorphic code Haskell enables means that single-letter variables are more common than in other languages (where they're usually confined to narrow scopes), and there's a lot less to say about them. Another HN commenter on this post mentioned IEnumerable<T>, which is very similar: you know nothing about T, but you need to call it _something_, and there's a common understanding in the community that you call such type variables <T>.

Haskell's typeclasses then mean you've got a lot of different concepts that could show up in a type signature, but they're still polymorphic and there's not much you _can_ say beyond "this is Foldable", or "this is a Monad". So an even broader convention has emerged.

_jackdk_··on A dictionary of single-letter variable names
Good catch, I'd forgotten "reader" (as in `ReaderT`). Thanks.
_jackdk_··on A dictionary of single-letter variable names
You've got the right general idea but the difference between `s` and `t` is not for nesting - you can write a composition operator for a "simple" lens where s=t and a=b.

`b` and `t` are just for type-changing updates, like when you replace a field in a tuple or every element in a list.

_jackdk_··on A dictionary of single-letter variable names
I dunno mate, I think GP's a little sharp, but it's great that he asked his question and expressed just how baffled he was instead of closing the tab and going "those Haskellers are a bunch of loonies". But I think you came out swinging here in a way that's not helpful. Accusing GP of bigotry because he didn't understand, and asked a question to understand?!

I agree with the technical points in your post and disagree with its venom. It's important to notice patterns and abstract over them, and to teach people to spot commonalities they haven't yet noticed in their code. Next time I think you'll have better luck starting with the assumption of good faith and going from there.

_jackdk_··on A dictionary of single-letter variable names
(Author here.)

It's a very fair question. I'm pretty sure lenses were invented because Haskell's built-in records are not ergonomic, but then the community discovered that they're extremely useful, general, and flexible.

Here is a Redux (JS) example of the problem that lenses help solve, where you are working with immutable data and want to replace one field of a deeply-nested structure: https://redux.js.org/usage/structuring-reducers/immutable-up...

You can think of a lens as a getter/setter, stored together as a first-class value. I'm not great at TS but hopefully you can see what I'm getting at with these types:

A Lens<S,T,A,B> is a pair of functions:

* get: (s: S) => A

* set: (s: S, b: B) => T

The most obvious lenses you can make are lenses that represent record fields (object properties?). But you can make lenses into other things, like a function that returns a lens into the Nth bit of an integer, or a function that takes a map key and returns a Lens from Map<K,V> to Optional<V>.

You use lenses by passing them to functions that then operate on your data. Three common ones are `view`, `set`, and `modify`:

* view: (lens: Lens<S,T,A,B>, s: S) => A

* set: (lens: Lens<S,T,A,B>, s: S, b: B) => T

* modify: (lens: Lens<S,T,A,B>, s: S, f: (a: A) => B) => T

So far, it doesn't look like we've bought much. But the real payoff comes from when you notice that lenses compose:

* compose: (outer: Lens<S,T,X,Y>, inner: Lens<X,Y,A,B>) => Lens<S,T,A,B>

This solves the "nested record problem" because you can compose the lenses for each field in the chain to get a lens from the outer structure to the field you're interested in, and functions like `set` and `modify` will correctly copy all the fields at each layer when you call `set` or `modify`. Haskell's lens libraries give you compact operators for doing this, so it all looks neat and tidy.

A reasonable response to all this machinery is, "OK, your language has bad records and you built a library to paper over them. So what?" To this I say:

1. Every language has this problem when working with nested immutable data, though not every language has the tools to compactly encode lenses and make them ergonomic to use; and

2. Lenses generalise to give you other "optics". A `Traversal<S,T,A,B>` picks out zero or more `A`s from `S`, and can replace them all with `B`s. `modify` and `set` then generalise, and perform their update on _every_ matching `A` selected by the `Traversal`, and you also get `toListOf`, which collects all matching `A`s into a list. There are other optics too, but this post is getting long.

Once you have the more powerful optics, you get a very expressive toolkit for querying and updating data, that works on any data structure that has optics defined. (And Haskell has tools to derive many optics automatically.)

Chris Penner's wonderful book, _Optics By Example_[1], has a bunch of exercises that involve slicing and dicing a big slab of Kubernetes YAML with queries like "collect a list of all 'containerPort's alongside their Pod's 'name' (from 'metadata')" and operations like "set a resource request of `memory: "256M"` for every container". The answers are extremely compact and elegant, and I don't know how you'd achieve anything similar with other methods.

I hope this helped, because there is definitely a really useful and powerful toolkit hiding behind that absurd-looking type signature.

[1]: https://leanpub.com/optics-by-example/

_jackdk_··on Mozilla wants you to love Firefox again
From the FAQ at the bottom of https://ladybird.org/ :

> Why build a new browser in C++ when safer and more modern languages are available?

> Ladybird started as a component of the SerenityOS hobby project, which only allows C++. The choice of language was not so much a technical decision, but more one of personal convenience. Andreas was most comfortable with C++ when creating SerenityOS, and now we have almost half a million lines of modern C++ to maintain.

> However, now that Ladybird has forked and become its own independent project, all constraints previously imposed by SerenityOS are no longer in effect. We are actively evaluating a number of alternatives and will be adding a mature successor language to the project in the near future. This process is already quite far along, and prototypes exist in multiple languages.

_jackdk_··on Mozilla wants you to love Firefox again
Or at the very least, let me fund the browser directly!
_jackdk_··on I'm funding Ladybird because I can't fund Firefox
Mozilla Foundation != Mozilla Corporation, so donations go where the Foundation wishes, not to Firefox development.
_jackdk_··on I'm funding Ladybird because I can't fund Firefox
The FAQ at the bottom of https://ladybird.org/ has this easily-overlooked section:

> However, now that Ladybird has forked and become its own independent project, all constraints previously imposed by SerenityOS are no longer in effect. We are actively evaluating a number of alternatives and will be adding a mature successor language to the project in the near future. This process is already quite far along, and prototypes exist in multiple languages.

So it looks like there's a plan to refactor away from C++ as it spreads its wings. I'm in favour of people funding any browser engine with a realistic shot at viability.

_jackdk_··on Autoconf makes me think we stopped evolving too soon
> If you add a new source file, you must rerun autoreconf to regenerate your Makefile

No you don't: automake generates rules for `Makefile.am -> Makefile.in` (`automake`) and `Makefile.in -> Makefile` (`config.status`). You should just be able to edit `Makefile.am` and run `make`.

> Is it really less effort to write Makefile.am than to write a straight Makefile? Wouldn't that be simpler?

I don't think so. Not if you want to get DESTDIR support right, and proper dependency tracking (dep info as a side-effect of compilation, so you get all the headers and don't slow down one-off builds), and support for all the standard Makefile targets, etc.

As for your "which build tool to actually use?" question, I wrote a longer post about that in the context of bootstrappable software: http://jackkelly.name/blog/archives/2024/04/01/which_build_t...

It really does seem like autotools remain one of the least bad options, but maybe muon (C99!meson) and samurai (C99!ninja) might be a workable option.

_jackdk_··on Cosmopolitan Third Edition
This is the only concession to mobile that I have on my website, and it seems to do okay:

    <meta name="viewport" content="width=device-width, initial-scale=1">
_jackdk_··on The Maddest My Code Made Anyone
Not that I've actually heard it, but you could describe the creator as "the man in the arena", which alludes to a Theodore Roosevelt speech:

https://www.theodorerooseveltcenter.org/Learn-About-TR/TR-En...

> It is not the critic who counts; not the man who points out how the strong man stumbles, or where the doer of deeds could have done them better. The credit belongs to the man who is actually in the arena, whose face is marred by dust and sweat and blood; who strives valiantly; who errs, who comes short again and again, because there is no effort without error and shortcoming; but who does actually strive to do the deeds; who knows the great enthusiasms, the great devotions; who spends himself in a worthy cause; who at the best knows in the end the triumph of high achievement, and who at the worst, if he fails, at least fails while daring greatly, so that his place shall never be with those cold and timid souls who neither know victory nor defeat.

_jackdk_··on The Maddest My Code Made Anyone
I'm the author, and I agree with pretty much everything you wrote - NS was special and trying to preserve that specialness was why I put the buildings into Combat in the first place. I miss that era of experimentation both in mods and commercial releases. I would've loved to be online during the heyday of Tribes 2 or Planetside.

Ironically, I don't think I ever consciously built in xmenu<->buildmenu support (though I don't have the .amxx file to hand to check); that was a surprise to me. IIRC, both ExtraLevels and Combat Buildings used the NS module for AMXX to read write the "unspent level points" for the player. Placing buildings was a lot less crazy when you had to spend one of your nine precious levelups on a welder or a gorge morph, and then spend another on each building.

Combat Buildings made Combat pretty stupid, ExtraLevels made Combat really stupid, but the combination was way stupider than the sum of its parts.

← PreviousPage 4 of 4