> Chomsky has focused on the generative side of language
The answers to "why" that Chomsky pushes so hard for are very valuable to adult language learners. There are basic syntactic rules to generating broadly correct language. Having these rules discovered and explained in the simplest possible form is irreplaceable by statistical models. Neural networks, much like native speakers can say "well this just sounds right," but adult learners need a mathematical theory of how and why they can generate sentences. Yes, this changes with time and circumstances, but the simple rules and theories are there if we put the effort in to look for them.
There are many languages with a very small corpus of training data. The LLMs fail miserably at communicating with them or explaining things about their grammar, but if we look hard for the underlying theories Chomsky was looking for, we can make huge leaps and bounds in understanding how to use them.
I'm a big Chomsky nerd, Chomsky fan, and card-carrying ex Chomskyan linguist. I hate to break it to you, but not even Chomsky thought that the Chomsky hierarchy had any very interesting application to natural languages. Amongst linguists who (unlike Chomsky) are still interested in formal language classes, the general consensus these days is that the relevant class is one of the so-called 'mildly context sensitive' ones (see e.g. https://www.kornai.com/MatLing/mcsfin.pdf for an overview).
(I suppose I have to state for the record that Chomsky's ties to Epstein are indefensible and that I'm not a fan of his on a personal level.)
I'm so happy I made the jump to NeoVim 6 months ago.
I finally got good RTL support with iTerm, language server stuff works great, and best of all, navigating and selecting things SYNTACTICALLY with nvim-treesitter-textobjects is life-changing.
Nice move and nice write-up! There has been so much propaganda around serverless, so we need to hear more of these voices.
I moved away from FreeBSD to Debian for hosting my things because the process/daemon management was too tricky. You seem to have figured out a good solution, but I wanted something simpler like PM2 for automatic process management/restarting/logs. Unfortunately PM2 has an issue [1] that makes it unworkable with FreeBSD. It would be so nice if FreeBSD had a smooth, more declarative way of managing processes.
> I moved away from FreeBSD to Debian for hosting my things because the process/daemon management was too tricky.
It indeed is tricky. To be honest, I wasn't "put off" by it because I've been using BSDs and old-style Linux startup systems for almost 30 years now... but the lack of abstraction shows, and I don't think it's great.
The daemon(8) wrapper is neat to integrate pre-existing servers into rc.d, but I do not fancy having to deal with that "by hand" nor to create a shell script to manage my own service (related from a few years ago: https://jmmv.dev/2020/08/rcd-libexec-etc.html) nor to have something entirely separate to manage log rotation.
As much hate as systemd gets, I do think being declarative (and doing so in a DSL that's not a programming language) and having a true process "supervisor" is a better model. BUT, as I mentioned in this article, I also like the "no churn" of the BSDs because what I learned and refined over ~30 years is still similar to this day and that I won't be bitten by surprises.
Not GP, but I do prefer the very direct control you get with rcctl (OpenBSD), openrc (Alpine),... Systemd often feels like autoconf. It's needed when you really want the capabilities, otherwise the opaqueness and complexity feels very much cumbersome when you're dealing with a simple service.
I do like the Unix way of having different components handling different tasks instead of having different things which are entangled with each other. It encourages transparency.
Even with daemon(8), PID files and the lack of process supervision might be my least favorite aspect of FreeBSD, an OS I like overall. Not long ago, I wanted to avoid running a custom service that way on a fresh FreeBSD server. After researching my options, I found an adequate solution in the daemontools family. I'd heard of daemontools but hadn't paid much attention to it.
My service has been managed by runit and, most recently, nitro (https://github.com/leahneukirchen/nitro). Both have run as the service's user. They supervise the process and handle logging. I have found the design of daemontools and its derivatives runit and nitro elegant; it lives up to the reputation.
I have been using daemontools for managing my services on FreeBSD servers that have run 24/7 for almost a quarter of century, with down times of an hour or so only at intervals of a few years of continuous running, whenever I made a hardware upgrade or a complete OS replacement (by passing to another major version of FreeBSD).
Now there are several daemontools derivatives that bring it more up-to-date, but even the ancient original version did most of one would need for reliable service management.
> As much hate as systemd gets, I do think being declarative (and doing so in a DSL that's not a programming language) and having a true process "supervisor" is a better model.
I've been playing with dinit for a bit now; it combines a lot of the nice advantages of systemd with a finite scope and being portable across OSs.
I remember having same problem with Linux, tried to compensate with Monit but it never just worked quite right.
Systemd might add a bunch of unnecessary complexity in places, but for a sysadmin it's a fucking blessing. Just write one simple file, set binary, user, done, add some limitations and dependencies if you want to be fancy, set auto restart to true and It Just Works.
Can even set some memory limits if you want to be fancy so any memory leak won't get you into too much trouble but just gets the service restarted
I'm thankful for Go because it was an easy first introduction to static typing.
I remember making a little web app and seeing the type errors pop up magically in all he right places where I missed things in my structs. It was a life-changing experience.
Yep. You feed files annotated with those to Dialyzer or similar for not-runtime typechecking. That plus the basic runtime checking provided by guard expressions [0] gives you a bunch of coverage... and I think some of the folks who like Elixir are working on their own typechecker thing? I don't really know, because I don't use Elixir.
Compiler and linker output is totally different. Compiler and linker output comes from determnistic, hard-coded logic that you can trust and build on. It's in a totally different category.
Compiler and linker output is like trusting well-proven math theorums written in standardized symbols, published in peer-reviewed books. LLM output is like asking random people on the street for random ideas with ambiguous language based on who knows what.
The jump to LLMs is in no way analogous to the jump to a higher level programming language.
> when judging the relative merits of programming languages, some still seem to equate "the ease of programming" with the ease of making undetected mistakes.
This hits so hard. cough dynamic typing enthusiasts and vibe coders cough
> Another thing we can learn from the past is the failure of characterizations like "Computing Science is really nothing but X", where for X you may substitute your favourite discipline, such as numerical analysis, electrical engineering, automata theory, queuing theory, lambda calculus, discrete mathematics or proof theory. I mention this because of the current trend to equate computing science with constructive type theory or with category theory.
I’m not sure that is the focus of most serious dynamic language. For me, it’s the polymorphism and code re-use it enables that the popular static languages generally aren’t close to catching up to.
I’m curious, can you give an example that wouldn’t be solved by polymorphism in a modern statically typed OO language? I would generally expect that for most cases the introduction of an interface solves for this.
Most examples I can think of would be things like “this method M expects type X” but I can throw in type Y that happens to implement the same properties/fields/methods/whatever that M will use. And this is a really convenient thing for dynamic languages. A static language proponent would call this an obvious bug waiting to happen in production when the version of M gets updated and breaks the unspecified contract Y was trying to implement, though.
That’s basically the main example I’d give. I think the static proponents with that opinion are a little myopic. Those sorts of relationships could generally be statically checked, it’s just that most languages don’t allow for it because it doesn’t fit in the OOP/inheritance paradigm. C++ concepts seem to already do this.
The “bug waiting to happen” attitude kind of sucks, too. It’s a good thing if your code can be used in ways you don’t originally expect. This sort of mindset is the same trap that inheritance proponents fall into. If you try to guess every way your code will ever be used, you will waste a ton of time making interfaces that are never used and inevitably miss interfaces people actually want to use.
> The “bug waiting to happen” attitude kind of sucks, too. It’s a good thing if your code can be used in ways you don’t originally expect.
Rather than call it myopic I would say this is a hard won insight. Dynamic binding tends to be a bug farm. I get enough of this with server to server calls and weakly specified JSON contracts. I don’t need to turn stable libraries into time bombs by passing in types that look like what they might expect but aren’t really.
> If you try to guess every way your code will ever be used
It’s not about guessing every way your code could be used. It’s about being explicit about what your code expects.
If I’m stuffing aome type into a library that expects a different type, I don’t really know what the library requires and the library certainly doesn’t know what my type actually supports. There’s a lot of finger crossing and hoping it works, and that it continues to work when my code or the library code changes.
> Rather than call it myopic I would say this is a hard won insight. Dynamic binding tends to be a bug farm.
I run into typing issues rarely. Almost always the typing issues are the most trivial to fix, too. Virtually all of my debugging time is spent on logic errors.
> It’s not about guessing every way your code could be used. It’s about being explicit about what your code expects.
This is not my experience in large OOP code bases. It’s common for devs to add many unused or nearly unused (<3 uses) interfaces while also requiring substantial refactors to add minor features.
I think what’s missed in these discussions is massive silent incompatibility between libraries in static languages. It’s especially evident in numerical libraries where there are highly siloed ecosystems. It’s not an accident that Python is so popular for this. All of the interfaces are written in Python. Even the underlying C code isn’t in C because of static safety. I don’t think of any of that is accident. If the community started over today, I’m guessing it would instead rely on JITs with type inference. I think designing interfaces in decentralized open source software development is hard. It’s even harder when some library effectively must solely own an interface, and the static typing requires tons of adapters/glue for interop.
> Almost always the typing issues are the most trivial to fix, too.
For sure. My issue is with the ones I find in production. Trivial to fix doesn’t change the fact that it shipped to customers. The chances of this increases as the product size grows.
> It’s common for devs to add many unused or nearly unused (<3 uses) interfaces while also requiring substantial refactors to add minor features.
I’ve seen some of this, too. The InterfaceNoOneUses thing is lame. I think this is an educational problem and a sign of a junior dev who doesn’t understand why and when interfaces are useful.
I will say that some modern practices like dependency injection do increase this. You end up with WidgetMaker and IWidgetMaker and WidgetMakerMock so that you can inject the fake thing into WidgetConsumer for testing. This can be annoying. I generally consider it a good trade off because of the testing it enables (along with actually injecting different implementations in different contexts).
> I think what’s missed in these discussions is massive silent incompatibility between libraries in static languages.
What do you mean by this?
> It’s especially evident in numerical libraries where there are highly siloed ecosystems. It’s not an accident that Python is so popular for this. All of the interfaces are written in Python.
Are we talking about NumPy here and libraries like CuPy being drop-in replacements? This is no different in the statically typed world. If you intentionally make your library a drop in replacement it can be. If you don’t, it won’t be.
I am not personally involved in any numeric computing, so my opinions are mostly conjecture, but I assume that a key reason python is popular is that a ton of numeric code is not needed long term. Long term support doesn’t matter much if 99% of your work is short term in nature.
It is just a classical Dijkstra strawman - hiding a weak argument behind painting everybody else as idiots. In fact it is much easier to make dangerous undetected mistakes in C than it is in Python.
I downvoted you. First of all, your explanation of what "classical Dijkstra strawman" is lacks the substantiation. Second, your statement about C vs Python is a sort of strawman itself in the context of static vs dynamic typing. You should compare Python with things like Java, Rust or Haskell. (Or C with dynamic languages from similar era - LISP, Rexx, Forth etc.)
Presumably no one thinks "I love using [dynamically-typed language] because I can make mistakes easier", but on the other hand, isn't it the case that large codebases are written with low initial friction but high future maintenance?
A charitable interpretation would be he critizises e.g JavaScripts silent type coercion which can hide silly mistakes, compared to e.g Python which will generally throw an error in case of incompatible types.
reply