I use it for kicks and giggles but every time I’ve asked it something non-trivial the output has been bad. Bad enough to reveal that there’s no intelligence there.
The people for whom these tools are a productivity boost are telling you they do work at incredibly low complexity/quality levels.
Boilerplate is low enough complexity that you can deal with it with tooling that behaves deterministically and doesn’t boil the oceans. Templates, snippets, IDE scaffolding, macros…
I have editor templates but I use multiple editors. There’s not a common format between vim and vscode that I know of. Text chat seems to be a very general interface. I think the point of the article is not “are LLMs good” but the fact that people outright reject them, without giving them a good faith effort.
Developers seem to care about vim’s editing speed. Developers seem to care about mechanical keyboards and typing ability. But if you give us a text editing tool which types at 2240WPM, an order of magnitude beyond world records, typing speed does not matter? Because of accuracy (which is valid). But then we have the axiom “code is read more than it is written”. Yes. So, we read the code that the LLM generates more than we write it. We say that programming is more than just typing. Yes, so we think about our problem and break it down into something that the LLM can understand or we have a sense of intuition that our query/prompt will not yield good results, just like SO and Google. Or that typing does not matter so it doesn’t matter how I code.
So I feel that some developers are not giving LLMs a good faith effort and feel threatened so they try to gatekeep by saying that people who code with LLMs are not doing high complexity work. I was trying to say that my work complexity changes over time even on a single branch/feature.
I have tried to use it for this exact case and it generates CI files that do not work. It is especially bad at figuring out github CI’s weird text interpolation rules for when a variable is or is not available.
It’s very hard to say this exact case without basically doing an eval. Because my exact case was not using variable interpolation. So, your experience is valid and mine is valid. Finding an accuracy percentage would involve a lot of work, like developing a benchmark.
My prompt to Claude was something like this but I have recreated it here for this thread so it may have not been this exactly:
Generate me a Gitlab CI config file with setup / test / build stages, one job in each stage with empty commands so I can fill it in myself.
And what it gave back was this:
stages:
- setup
- test
- build
setup_job:
stage: setup
script:
- # Add setup commands here
test_job:
stage: test
script:
- # Add test commands here
build_job:
stage: build
script:
- # Add build commands here
The prompt is a bit verbose imo and probably not what I originally prompted.
And just to see what determinism is involved, and this is not enough iterations, I did this two more times in completely new chat sessions and I deleted previous chat sessions out of history just in case. The second gen had - # Add your setup commads here in script: and the third gen was exactly the same as the output above. It definitely could be the case that it continues to be random and it could definitely generate some text that is completely wrong.
I’m just using CI boilerplate as an example because that was the last thing I did. But since YAML is so picky about indentation and my configuration between vscode and vim is not synchronized (nor will it ever be), I found that generating YAML with perfect indents was a good use of my time. Except now I’ve spent more time on lobsters talking about how I feel it saved me time so I will now jump into a volcano.
I tried out Kamal for a deployment job I had but it wasn’t really well suited for Rust and the configuration was extremely shaky/undocumented/obscure. It definitely was not a 2 minute job.
Also I could not for the life of me figure out how to best install a Ruby devtool on my machine and felt it was just a rather anachronistic concept.
I use asdf for all languages on my dev machine. I think it’s been about 5 years straight I’ve been using asdf and to me it’s great. I rarely think about it. To me, it’s solved. You get a single CLI interface instead of having node version manager and ruby version manager.
You can set shell defaults or activate them temporarily just like the single language managers (nvm etc). You can bind projects with .tool_versions so it switches on cd. You get project isolation on the runtime with asdf and dependency isolation with a package manager. My projects don’t get weird and my dev machine doesn’t get weird. I can list all runtimes for all languages, for when security news happens or if I wonder “do I have Python 8.0 installed?”
So, when I wrote an asdf upgrader shell function to replace a language (hey, replace nodejs 42 with nodejs 43) it works for all languages because asdf is a language manager not a node language manager.
While I like the trend, calling it “no PaaS required” when you have to set up your own servers and manage SSH keys is a bit of a stretch. It’s a big jump in operational complexity from a Heroku-like PaaS which doesn’t need any of that.
Doesn’t Heroku require you to set up SSH keys as well? Or set up your own “applications” (servers) before you can push to them?
Most VPS providers I’ve used let you save public SSH keys to your account, including at the point of creating the server, to (automatically) place them into the servers you create.
I don’t think it’s that big a jump, if at all. I’m not a big fan of Rails, but this criticism seems misplaced.
Doesn’t Heroku require you to set up SSH keys as well?
Not necessarily. Almost every PaaS has integrations for your forge to auto deploy. Having to set up an SSH key and pushing from local is the “classic” way of deploying to heroku, but unless you’re on a very small team you’re not doing that in practice.
Or set up your own “applications” (servers) before you can push to them?
What? Are you saying creating an application on a PaaS is functionally the same as setting up and maintaining a server?
Most VPS providers I’ve used let you save public SSH keys to your account,
As I said before, this works when you’re a solo dev or a very small team. As soon as you’re doing basic things like automating deployments or need fallbacks you need to do a lot more to just manage your SSH keys than you would with a PaaS.
Not necessarily. Almost every PaaS has integrations for your forge to auto deploy. Having to set up an SSH key and pushing from local is the “classic” way of deploying to heroku, but unless you’re on a very small team you’re not doing that in practice.
I mean, that’s just pushing the issue back a bit. Presumably, you had to set up your SSH keys on your forge. Sure, there’s some repetition, but unless you’re a huge company to the point that enough developers churn, I wouldn’t exactly call it a big jump in operational complexity, but when you grow like that, everything becomes more complex.
What? Are you saying creating an application on a PaaS is functionally the same as setting up and maintaining a server?
Well, no, but that’s what it sounds like they’re trying to solve. From what the article makes it sound like, you only need to create a server in your VPS provider’s web UI, get your SSH key onto it (which is what I was saying is not that much harder than a PaaS), and Rails 8 will basically do the server setup (and presumably, some basic level of maintenance) for you.
As I said before, this works when you’re a solo dev or a very small team. As soon as you’re doing basic things like automating deployments or need fallbacks you need to do a lot more to just manage your SSH keys than you would with a PaaS.
Just to clarify, you mentioned that before in a previous paragraph within the same comment, but not in an earlier comment in this conversation/thread.
So, as I said earlier in this same comment :P, everything becomes a lot more complex when you grow. They probably feel perfectly fine about releasing stuff that’ll work for the majority of their users, which won’t be large companies. Presumably, as time goes on, they’ll refine it for larger users.
Anyway, at really large scale, like GitHub, having dedicated ops instead of PaaS goes back to being worth it, so while they won’t use this, they’re probably not even using PaaS, anyway.
I guess PaaS is for a certain size band (medium businesses, or profitable or long runway small businesses), and this solution aims to serve a chunk of that band.
As soon as you’re doing basic things like automating deployments or need fallbacks you need to do a lot more to just manage your SSH keys than you would with a PaaS.
That’s fair. When you said, “you have to set up your own servers and manage SSH keys”, I was thinking about managing SSH keys for those “own servers”, not extra stuff like CI, CD, branch deploys, and so on (not sure what you mean by fallbacks), where you need to manage your SSH keys on further machines and create SSH keys for machines instead of people, etc.
I see where you’re coming from now. I guess a better title would be, “No More PaaS Required (For Many)”. But I can see a lot of this stuff being solvable with the same infra/foundation they developed here. I can also see it being made a lot easier for teams to solve themselves using these tools.
I’m just speculating now, though, I was initially responding to your point about managing the production app servers and the SSH keys for those.
Presumably, you had to set up your SSH keys on your forge.
There is a huge difference between dev SSH keys that individual devs have and a deploy SSH key that can be used to deploy to the live, production system. One of them is managed by the dev locally and is cheap to cycle and manage. The other has to be managed by the team or company, is shared and can be time consuming or difficult to cycle. Ask me how I know!
Just to clarify, you mentioned that before in a previous paragraph within the same comment,
My bad - I had to edit the comment and missed this, I’d mentioned team sizes before in a previous version of my reply itself.
From what the article makes it sound like, you only need to create a server in your VPS provider’s web UI,
This is only true in the very very very simple case where you have only one server, and this still ignores other moving parts like the database or a cache service. Also, sure, you can create a server from the web UI, but what about maintaining it? What about regular software updates, security patches, database updates, etc? These are all things PaaS free you up from thinking about. Saying “you can create a VM and you’re all set!” Is only true if you literally only need a fixed number of VMs, are using SQLite, and don’t care about any operational resiliency or even what happens to that VM a week, month or year from now.
And please, please, please don’t think solo devs or small teams don’t need to do that of this. For my hobby projects I mostly use VMs, and very often I spend more time dealing with ops instead of programming. I don’t mean to shill for PaaS services, but I also think people seriously underestimate the ops work it takes to keep a service going, and how much a PaaS can free you up to just code.
There is a huge difference between dev SSH keys that individual devs have and a deploy SSH key that can be used to deploy to the live, production system.
To be clear, I wasn’t suggesting that the same keys would be used, merely that integrating with a forge doesn’t get rid of the requirement to manage SSH keys. For small teams (as you say), where SSH keys are managed manually, including deploy keys and other keys for machines, I don’t see why this couldn’t remove the need for a PaaS.
I don’t think we’re really disagreeing. I’ll say again:
I see where you’re coming from now. I guess a better title would be, “No More PaaS Required (For Many)”.
With Heroku you can add someone to the team and they are able to deploy, get a shell, etc. You can limit permissions as needed or use Single Sign On.
With Kamal you need to get their SSH keys on the server somehow and give them the list of IP addresses (then keep both of those things up to date). You can’t really set granular permissions and you are giving them (effectively root) access to the host, not just the inside of the container.
You could pair Kamal with Tailscale to get something a little closer to the Heroku experience.
Well, no, but that’s what it sounds like they’re trying to solve. From what the article makes it sound like, you only need to create a server in your VPS provider’s web UI, get your SSH key onto it (which is what I was saying is not that much harder than a PaaS), and Rails 8 will basically do the server setup (and presumably, some basic level of maintenance) for you.
What exactly is it doing for you? Is it configuring logging and monitoring somehow? What about network firewalls? Databases (TFA mentions SQLite support but absent some clustering support, that’s an antifeature)? There’s a lot of stuff that PaaSes provide that I have a hard time imagining this software doing properly.
My own mental model is IaaS | PaaS | SaaS. To me, SSH keys and servers is infrastructure. To me, PaaS is a big mystery in the industry right now. I think many are trying to solve it. IDPs, Kubernetes, Backstage and others. One isn’t better than the other and you can run the “aaS types” in parallel. But things at the PaaS level of abstraction isn’t IaaS. When you use k8s you don’t and cannot SSH into your servers. You don’t even call them servers.
Each layer has trade-offs but I find the middle PaaS one especially confusing right now. I think there’s a need for a mid-complexity thing, something like Nomad. So, I think Rails 8’s opinions are pretty interesting given the alternatives.
I think of your PaaS layer as two different layers.
K8s, Nomad, Swarm, etc. are orchestrators. They do provide an abstraction on top of the compute but is still fairly low level.
Tools like Kamal provide the developer facing abstraction: apps, deploys, rollbacks, etc. Kamal is not the best example but you can imagine an internal developer platform with everything Heroku offers on top of the company’s K8s cluster.
I think that split will continue to happen just because of Conways law. And if it does fully hosted PaaS makes less sense for most orgs.
Why are there not Rails-like frameworks for Go and Rust? I mean, there are some attempts, but in those communities it seems much more popular to roll your own APIs.
Is anyone working on a serverless Rails-like framework? I know there are things like Serverless Framework, but they don’t fit the goal of rapidly creating an API and storage from specification and then being able to modify it.
There have certainly been a number of attempts at making “Rails but for Go.” See https://gobuffalo.io for example. I think it’s not really a market demand, so it hasn’t taken off the way Sinatra/Flask-style API routers for Go have.
Why are there not Rails-like frameworks for Go and Rust?
Because the old server side “webapp” with a sprinkle of JavaScript doesn’t make too much sense in the age where people expect GUI style interactivity in their browsers. Rails os from the age of clicking from document to document and add some forms for data input.
It survived because it has inertia, and because it is still a useful way of building web applications, but it’s not what the market wants.
I for one, wouldn’t mind a simpler web, retaining more of the old click and refresh paradigm. This very website is a good example. But that’s me. The market has spoken loud and clear the other direction.
Imo, microframeworks are easier to write so there are more of them by quantity. Fuller frameworks take many years so there are only a few of them (if any) in each language. I like to think that web apps or frameworks are near the tippy-top of complexity and high-level-ness. I like to imagine that a full framework that could tackle many of the common problems you will run into will take 5-7 years to make and refine under real developer use. Most people won’t go that far. So you end up with http routers, I wrote why I think this isn’t enough.
If you want to go back, in a way, Remix is sort of a throw-back. I wouldn’t say Remix is an auto-win framework to pick but I think it’s pretty interesting. Remix has a kind of actions/submits going to the same thing/file/route you are currently on, it stays close to web standards and you can get end-to-end type safety like trpc if you use typedjson. Remix is not a click to refresh type of experience though.
Why are there not Rails-like frameworks for Go and Rust?
Go’s web story was really bizarre to me when I was still spending time in that language. Rust has Axum and even Typescript has something like Hono. API based microservice frameworks can (should?) be small and tight.
The entire JVM ecosystem, stodgy as it is, is a huge breath of fresh air. Stuff just works. There’s no npm involved. And you have types backing up everything.
Spring has a rough learning curve. Some parts are awful to set up (Security). And a lot of info online about it is either dreadfully bad and/or outdated. But Spring itself is a much better framework than most anything I’ve used in the past. It barely qualifies as a framework, because it really isn’t about saying, “you must put your controllers in this exact directory!” and more about “here are tools to help with building and wiring up an application.”
Spring is happy to fade into the background. Most frameworks aggressively assert themselves through the codebase as if they are as important as your code.
For one thing, Maven avoids the need for lockfiles because (in the absence of rarely-used antifeatures), dependency resolution is already deterministic by default.
I haven’t used Maven as a build tool but I have a lot of experience with using it to resolve dependencies, and I feel confident saying it’s the best of the “classic” systems that predate content-addressable dependencies.
Do you have a source on this? I was under the impression that Maven could benefit from a lock file like all other package managers which is why maven lock file plugins (I found two) exist. It seems to have a transitive dependency resolution algorithm that tries to be repeatable by always selecting the nearest dependency? Admittedly, the workflow of deciding to name your transitive dependencies to force a version is very similar in my view to using a lock file. Hmm.
My understanding is the only time when a lockfile would benefit you is when you accidentally pulled in a dependency that declares a version range. (The rarely-used antifeature mentioned above.) Ranges are inherently nondeterministic what they resolve to depends on the state of the repository at the time of resolution rather than being strictly determined from immutable data.
Overall it’s better to banish ranges altogether; if you add a dependency that has a transitive range dependency, you add an exclusion for the range and override it to a specific deterministic version. But I don’t know if Maven itself makes this situation easy to identify; you might accidentally pull in a range without knowing it. I maintain Leiningen which uses the Maven dependency resolution library but adds range detection on top of it to ensure you have repeatability unless you specifically opt out of it.
I’ve been thinking about why we don’t have one language. I’m aware of “use the best tool for the job” but when I ask “why can’t we have just one tool” then I think there’s a discussion of trade-offs. But why is there automatically a trade-off? What is the trade-off? So, in thinking about this, I think I’ve come to the same conclusion as you.
seems to occupy a sweet spot
Java is sort of in the middle of a few trade off spaces and categories. I am going to write more on this later but when I am illustrating a dichotomy, Java doesn’t exactly fit on the extremes. It’s mid.
I’ve been thinking about why we don’t have one language.
Most decisions in programming are made based on folklore. It’s cultural with path dependence.
It’s mid.
Mediocre is fine. Go, PHP and Python are also extremely mediocre languages. But do we really need 4 mediocre languages?
Also there’s the trade-off of having too much crap out there and not enough people to maintain all of it. I’m tired of seeing survey after survey of open source developers saying they’re “soooo tired” but maybe nobody asked for Typescript ORM framework number 10?
Interesting question. This is my own/adopted view and sort of top of mind because I was talking with someone about this recently. I’m not implying that you don’t know of all these things already. I think you do based off your other comments, so, sorry about any unintentional tone over text issues.
When I separate out environments to deal with changes, they are like this:
Development - My laptop, where I work on features and branches, changes are fast/often/informal
because that’s what dev is. I could try making changes live on the server but that’s not great because of users.
Test - It could be my laptop but it could also be in CI. I’m trying to catch things here before the user would see it.
If I skip this step then the users see my change, aka: a new bug.
Production - The place where users are. I don’t want to change this recklessly because of the users, that’s pretty > much it. I can’t DROP TABLE for a schema change, the users’ data is there.
So, from this perspective, production is simply the environment where the users are using the system. If I don’t have users, my general approach shouldn’t change because I could have users at any moment. Just like I design my system to account for expected usage bounds, not current usage or non-usage. “I might have 1TB of files, so I need to buy 2TB.”
There are down-sides to this approach. Making Production match Dev is sort of a fool’s errand. In some cases, making Prod match Dev is simple and routine. I can select a database migration tool that will help me migrate my schema locally that benefits myself solo, on a team and deal with changes to database state in Production. So, I can make Dev match Prod in terms of changing the database fairly easily. So, I might as well do that. But using a load balancer like haproxy in Development is sort of silly. So, if I don’t have that in Dev, where do I have it? How do I develop my haproxy.cfg?
So, some people would try to develop a staging environment at this point. I have conflicting thoughts on this. It probably matters what the team and project is like (as with all things) but I think I would try to figure out what I’m trying to do. If I’m trying to test my infrastructure configs and not my app then I should just do the dev/test/prod progression from the operations standpoint. Like, figure out how to test infrastructure (but this can be tricky and different because of the context).
Another term instead of staging, might be beta. If you had production running with no users, you could also have a beta site with no users forever. Simply allow-list your home IP address to your beta site (or similar) and you’ll be the only user forever. You could do this for production too. Sometimes this is known as stealth launching or stealth release. In games, this could be on purpose or by accident. If something is stealth released it could be because the dev had no marketing or it could be because a very popular game released on the same day that they tried to release. Other positive terms might be “hidden gem”, “freshly launched”, “new”, “undiscovered”. Negative terms might be “failure” or “unpopular”.
I guess I don’t like the rapidly iterate on production part just because it’s lucky timing, hopefully a single moment that will never repeat again. Once you have users, you probably always have users. If I had a side project that was not popular and someone started using it, maybe this is my best customer. Maybe I have a very niche product and this user is my best hope of getting feedback. I don’t want to disrupt them. So, I should probably have some idea or plan of how to make changes. If that plan needs me to rearchitect on distributed systems, then I think I would want to anticipate that. So, I guess I would ask what you want to do before you have users?
I am currently in this situation and I know I have a debt item. :( The place where I want to have throw-away users is Beta and the place where I’m trying to make changes and verify them is Test but Test changes in different contexts (ops sort of resists it imo).
Having worked with Ruby for close to two decades I’m convinced there is no point anymore for large software systems. It’s a nice language for sure but after a few ten thousand lines it becomes impossible to deal with. Add just a couple inexperienced engineers and the whole thing unravels. You’re constantly fighting an uphill battle for good code and the language does not help you: it has no package-level segregation, no type system, no way to enforce interfaces, implicit method definitions all over…you have workarounds for all of these but they’re all non-idiomatic and no one uses them.
It’s just as easy to write “beautiful” code as it is bad code, you gotta keep yourself to an opinionated subset of the language and be vigilant about it. And what you get in the end is not encouraging—if you manage to keep good separation of concerns and extensible code you get the big prize: one of the worst performance profiles in major languages.
Over the years I’ve seen speed of development fall off a cliff while systems get bigger, taking away one of Ruby’s main advantages. This is also an overrated aspect of software systems and languages: you’re in a “I want this to be stable and efficient” state for much more time than you’re in “I need to prototype this fast” state, so it doesn’t make sense to optimize for the later. If you take an extra week to get to market in a “slow to write, statically typed” language nothing bad will happen to your company and you won’t need to worry about any of the above.
I think the “speed of development” point applies to Node.JS as well - at least with vanilla JavaScript. I’ve been working with node since the very beginnings, and the prototyping speed was always one of the major wins.
Performance never bit me unless I actually needed performance. But you can usually hack around the problems. It is a hack, but it works.
But the “easy to write good or bad code” - that’s the real problem. When you’re in a couple dozen thousand lines, if you’ve at least half-disciplined, it’s not very obvious but you know the problems are there.
And especially then you get to compare this with the languages who enforce good structure on you.
But when you go into really big systems, I think it’s not that much worse actually.
You’re usually well separated into different teams, domains, microservices, or whatever by this point, so the problems if bad JavaScript don’t get a chance to scale into major blockers. I think the fact that ruby/rails has been used significantly longer for big systems, and (to the best of my knowledge) those systems managed to grow into big monoliths is what brought out the really major points to be visible.
As much as I liked Ruby initially, I learned the truth of what you said here in only 8 years or so by ending up working on a million-plus-line Ruby codebase @ Desk.com (prior to SFDC acquisition). I noticed that I started to write very modular testable code (functional code) after dealing with some hideously deep bugs that could be summed up as “mutability/inheritance run amok”*, but that I got NO guarantees out of it since I was always depending on library code that was not designed with that in mind, and because the language didn’t enforce anything.
I decided I needed the guarantees. That’s why I’ve been using Elixir for at least 7 years now. It makes some of the nastiest bugs I’ve seen in Ruby simply impossible, while maintaining SOME of the familiar syntax of Ruby (but very different semantics; it wasn’t a very tough transition for me). But I haven’t yet worked on a million-line Elixir codebase. I will report back…
As a simple example of the bugs I worked on, I once spent an entire month debugging a seemingly random session drop that was affecting a small but loud set of customers. Turns out that it was partly a result of someone merging a Hash with a HashWithIndifferentAccess in the ENV, deep in the Rack middleware somewhere, which Ruby happily allows without any complaint whatsoever but which results in nondeterministic key overwriting (depending on which you’re merging first, as well as the specific values of the key(s) that are present…)
I said the syntax was similar but the semantics were very different; I think this is the exact opposite of how you read it!
As far as “things I had to pick up” per my recollection: 1) Rewriting mutable algorithms as immutable ones (I actually find this fun to figure out) and algorithms that rely on random-access arrays to use sequentially-iterated lists (or other data structures) instead; 2) what looks like reassignment in Elixir is really just rebinding, and if you don’t quite grok this, it WILL lead to surprising-for-you behavior, 3) pattern-matching with inline name-binding and destructuring (which ended up being my favorite language feature ever, as it turns out, due to its elimination of the logical boilerplate that doing the same thing would require in basically every other language) 4) macros (I still rarely use them but it is neat to move into and out of the AST context when I do, and when writing a macro makes sense) 5) case, cond, if, for comprehensions, and them all being expressions that return values (something which I think every language should do, now) 6) writing recursive functions in a tail-call-optimizable fashion 7) the carat (^) operator to force a pattern match to match on an existing bound value instead of rebinding (I simply didn’t like it at first)
If you take an extra week to get to market in a “slow to write, statically typed” language nothing bad will happen to your company and you won’t need to worry about any of the above.
IMO static types/compile-time type-checking end up having negligible impact on productivity. What does have a massive >100x impact on productivity is the quality of the ecosystem (frameworks, libraries, documentation, forums). But it’s weird that languages without compile-time type-checking have historically had many more “breakout” libraries. Is it just coincidence?
Take Java. Could there have been a world where Rails, NumPy, or PyTorch were written in Java? (In that world I wouldn’t have had to have learned Enterprise Java Beans!) Certainly Rails and PyTorch use a lot of runtime syntactical magic and NumPy depends on operator overloading, which Java does not support at all.
I don’t know why we don’t live in that world. But as a consequence, my opinion is that if you’re making a simple CRUD app or training a model, using Java or JavaScript instead of Rails/Django or PyTorch is definitely the riskier option. This isn’t saying anything about the language! For example, if you had a good usecase for Spark (which is written in Scala but supports Python), using some hypothetical alternative written in Ruby would be the riskier option.
TLDR: IMO libraries matter much more than the language for productivity. But why—for web development in particular—have the best libraries historically been for languages without compile-time type-checking? I don’t know.
But why—for web development in particular—have the best libraries historically been for languages without compile-time type-checking? I don’t know.
I think the answer is simple. It’s very, very easy to go from zero to something-that-almost-works-if-you-squint in a language like python or ruby as compared to java or rust. There are an influx of new developers in web and data-science-y roles, so this is a huge advantage in those areas.
I think this played out with MongoDB as well – not a great database, but they nailed the out-of-the-box experience, which helped them a lot with the younger generation of developers.
I agree, which is why I don’t think the “extra week to market” argument holds.
For one, it’s an extra week per feature. So those weeks keep piling up.
The other factor is the business context, namely time and money.
It’s not “unreasonable” for a major enterprise to need six months to go from “hey, can you add a column with last name here in this report” to having it in production. They have all that money to burn. A startup will not be there in six months.
And the time factor works on another dimension as well. An enterprise adding that column will get agreements from an the stakeholders first. After all, this will affect half a million employees for the next five years. You want the time to do it well, cover all the edge cases, make sure it’s tested nicely. And enterprise frameworks give you some tools to make sure of all those.
In a startup, the column you added this week has a good chance to be changed even before you deploy it to production.
So “an extra week to prod” argument is kinda moot. It is relevant from the technical side perhaps, but nobody cares about technical side anyway, except us techies.
It’s not “unreasonable” for a major enterprise to need six months to go from “hey, can you add a column with last name here in this report” to having it in production. They have all that money to burn. A startup will not be there in six months.
In my meager $BIGCORP experience, that timeline seems to come from questions like:
Do we only need last name? How do we know we only need last name? Go talk to 20 people.
Where should we source the data from? Sometimes teams don’t want you to take a dependency on them for various reasons.
Are there any privacy concerns around selecting that data? Go talk to the privacy and/or governance team about it.
Do we need to change the retention requirements around upstream data source?
Who is an SME in this system? Oh, nobody on your team? Everyone who built it left?
Is this the only project that you are working on? Or do you have a half dozen other things you need to do?
I don’t think the static typing factors in to the timeline at all. If anything it helps with issues like number 5 since everyone who built the system probably left so it’s much harder to know if you are breaking things.
True, you’re right. But that’s irrelevant, I’m talking about what an extra week means.
The point is that if you have six months to deploy a change, it doesn’t matter if it takes an extra week to also decide on proper types, it’s a fraction of the time.
But if the change you need is detected last week and you want to deploy it tomorrow evening, the extra week is more then doubling the time.
And, the enterprise code needs to run even after that SME leaves so you wanna have the type safety.
The startup code will be gone and changed a few months after you deploy it. So it’s maybe tomorrow’s problem, and maybe not a problem at all.
I get your point, but I disagree that it will take a week to figure out the static typing. If you are writing your dynamic language data to a SQL database you probably have to figure out your column types anyways. Plus, most data has pretty obvious static types. The transformations might be complicated, but I don’t think it takes that much extra time once you have worked in a statically typed language for a while.
I still think prototyping is faster with something like node or ruby then something like Java and Spring . It’s more then just database columns and static types - it’s about all the interfaces and domain boundaries and a lot more.
I’ve seen time and time again, people just go in head-first, maintainability be damned. I also saw that costing you some flexibility in the long run, but anything that survives to the long run deserves a decent refactor then. And at those stages, the startup is usually well along the way not to disappear by next week if you need time do do something properly this time.
But you’re right, static typing alone isn’t that much if a blocker.
And I don’t disagree with you that prototyping is probably faster in a dynamic language and prototyping is more important at a startup. I was talking to someone about this continuum of development the other day. At a certain, point the priorities shift from building to maintaining. That often involves shifting the people from builders to maintainers and not everyone wants to make that shift. So you can lose people the environment shifts around them. But there is definitely a need for both modes.
I wanna add a disclaimer: not all companies are the same and your mileage may vary if course. But my experience and the second-hand reading and third-party reports tell me it’s a relatively known pattern.
But why—for web development in particular—have the best libraries historically been for languages without compile-time type-checking?
Interestingly, these libraries that you mention are an argument in complete opposition to what you’re saying. NumPy and PyTorch both both use Python’s type annotations extensively now (as is very common with newer Python projects). TypeScript also seems to have completely taken over the JS world, with the most popular projects like React all using static types.
So even in the web world where dynamic typing reigned supreme for the longest time, a huge chunk of web devs have moved towards static types.
And the amount of times where None gets used inadvertently in Django code bases written by junior collagues at $dayjob dwarfs any type-related screwups happening in TypeScript code.
Django devs dislike types.
Also, fastify with Typebox integration is pretty nice to build API with. I have mixed feelings about how HTML pages are served, but SPA’s work super well for e.g. internal company systems.
IMO static types/compile-time type-checking end up having negligible impact on productivity. What does have a massive >100x impact on productivity is the quality of the ecosystem
I find it a bit funny, because to me this reads like saying “diet ends up having a negligible impact on health. What does have a massive impact on health is bodyweight and blood values.
Well, one heavily impacts the other. It’s a whole different world to build upon some library in a language that supports features like typeclasses vs. one that does not (i.e. all dynamically typed languages).
In Scala (but also other languages) I can do that:
1.) Import an http library for http requests/responses
2.) Import a json library for converting to and from json
3.) Import yet another library that creates interoperability between the http and the json library
Then, finally, I can just call httplib.response(myObject) and as long as I told the json library how to serialize the type of myObjectand as long as I import the interoperability-library, I don’t have to do anything else. No conversion, no runtime-magic, nothing. myObject will be serialized into json and the http library will return that json. And both the http and the json library know absolute nothing of each other. They don’t share a single common interface or type.
Furthermore, if anything in the process is wrong, then it will fail at compiletime and not at runtime. E.g.: if I forgot to define how myObject is to be serialized into json, then it won’t compile. If there is any kind of compatibility issue between one of the 3 libraries, it will fail to compile.
This is super powerful and allows to create a much better ecosystem compared to languages where this feature (namely, typeclasses) is not available, because then doing the above is simply impossible.
In other words: the language and the ecosystem cannot be just separeted. To a good degree they are one unit and influence each other.
But why—for web development in particular—have the best libraries historically been for languages without compile-time type-checking? I don’t know.
You had to know that this was gonna catch a bunch of flak, right? :p
But, I’m going to pile on, anyway. I’m deeply skeptical of this assertion- can you elaborate with some examples? Because, my experience with web backend stuff is mainly in: JVM languages (Java, Kotlin, Scala, Clojure), PHP, Node.js, and Rust.
I’d say that the Rust ecosystem probably aligns with your assertion: all of the web frameworks have converged to be extremely similar–and not particularly fun to work with for various reasons, and the ecosystem is just generally lacking in a lot of various tools and integrations. But, I chalk that up to Rust being not particularly well-suited to high-level business logic web dev and to the fact that the number of people using it for such is minuscule compared to the other languages I’ve worked with.
But, of the other languages, I feel like the NPM ecosystem is the least impressive to me. The quality of even the most popular libraries is often… lacking. PHP is a garbage language, but the main frameworks/libraries are pretty solid–especially ones that were either started or totally rewritten after PHP 7-ish. The Java ecosystem is very complete, and the quality is usually very high.
But it’s weird that languages without compile-time type-checking have historically had many more “breakout” libraries. Is it just coincidence?
Most people who go into programming were/ are learning Java, C, and C++ in college. They walk away hating type systems because they use these languages and run into so many godawful errors, often due to the type system but not always, and overall the experience of these languages sucks quite badly for newer devs (things like packages).
Then they learn python and it’s just so easy. They write code and it does what they want. At their “scale”, these languages are just an absolute breath of fresh air. I’ve heard it time and time again.
Basically, people learn about types using some of the worst languages that have horrible type errors, and so they think of types as a burden. They don’t see the value because obviously as a student types aren’t doing anything for you - you’re writing entire projects yourself or with a very small scope, types are just in the way.
Types are ruined for them before they even really had a chance.
For any CRUD app (both simple and very complex) there aren’t too many better options than Java’s Spring, in my opinion, it’s easily up there for even prototyping purposes as Rails or Django.
Once you get past the giant hurdle that is learning the framework itself (and all the annotations/lifecycle/hooks) and the awful project management tools that are Maven and Gradle.
As opposed to the completely trivial to use other frameworks? Like, please enlighten us which one isn’t a “giant hurdle to learn”? It’s almost like web development is a complex domain, and if you want a system that can actually fit every use case, you will get a complex framework. Essential complexity can’t ever decrease.
Also, annotations are just metaprogramming. Funny how it’s considered complex to denote in plain that this method will be called when this endpoint is called, and such. Especially that that’s pretty much how it works in every other framework.
No, I have to agree with the “giant hurdle” comment. Spring is genuinely harder to learn than almost any other framework I’ve ever used.
Annotations are just metaprogramming, but they are an extremely constrained and awkward kind of metaprogramming. Spring’s problem, IMO, is that it has too many Annotations that have too many edge cases and caveats, and too many of them end up doing surprising things when you mix them. Many frameworks–even some in Java–have moved away from this approach and gone more toward “real code”, like Jooby and Vert.x.
Funny how it’s considered complex to denote in plain that this method will be called when this endpoint is called, and such. Especially that that’s pretty much how it works in every other framework.
I’m calling you out for being intentionally dishonest here. You know that if Spring’s only/primary annotations were simply marking methods with an HTTP route and method, that people wouldn’t complain about it at all.
There are already four annotations. If you simply forget any one of them, your application will not work. And this is without any kind of authentication/authorization, JSON encoding/decoding, HTTP header reading/writing, etc.
In what way is this example complex? It’s a single annotation marking the entrypoint for a spring boot application (which usually lives in its own file), then an annotation denoting that you want to create a rest API, setting your http responses appropriately (e.g. the return values will be turned into json), and finally a single method which takes and sanitizes a GET parameter named ‘name’, and returns a String response as JSON, on the /hello endpoint.
Last time I checked Django, ROR, Laravel all have complex project initializers setting up a bunch of files. Are these really simpler? Also, vertx sits at a much lower level, so this is not even a valid comparison - it’s not a full-blown web framework, it’s a server framework, on top of which you could build one (like quarkus is).
Like, frameworks are tools - you are expected to learn them. You wouldn’t be able to drive a forklift either without former experience, would you? Does it make it a bad tool?
As for the more imperative configuration mode, that is also absolutely possible with Spring, but it’s usually better to let statically knowable data be statically available, in the form of annotations. Thanks to that Intellij can easily autocomplete valid endpoints within template files.
The debate has been ongoing for years so I don’t expect my meager comment box to solve anything. Ktor’s style is more like a DSL, which annotations are sort of similar in purpose (avoids explicit code) but end up looking like the problem domain, which is nice to me.
fun main() {
embeddedServer(Netty, port = 8000) {
routing {
get ("/") {
call.respondText("Hello, world!")
}
}
}.start(wait = true)
}
So, by comparison, I think this is simpler than Spring (boot) and closer to other microframeworks.
The thing that started this tiny example thing was Ruby’s Sinatra. Sinatra is the thing that made microframework envy start because it’s version of this is even simpler.
require 'sinatra'
get '/hello' do
'Hello, world!'
end
Then, everyone had to be like Sinatra. The cuter the README, the better. But Sinatra/Flask/FastAPI/Express isn’t very cute when you add a database. The thing that Rails really nailed is: conventions and omakase. Rails kind of expects that you might need a database and other common things. Rails was embraced at the time because Struts etc were so heavy on XML config. I haven’t done Java in a long time but I tried Dropwizard with a junior who just needed a Java job and it was mostly XML config churn (to me). It’s unfair to Dropwizard and because I spent so little time on it but I was really surprised since Dropwizard was supposed to be nice. There are some other ones in Java that I think are fine but I don’t really know. But microframeworks in any language … whelp … I posted about that.
.. all have complex project initializers setting up a bunch of files
Yeah, project generators have a trade-off. But so do copy-paste configs. That’s an interesting topic.
In what way is this example complex? It’s a single annotation marking the entrypoint for a spring boot application (which usually lives in its own file), then an annotation denoting that you want to create a rest API, setting your http responses appropriately (e.g. the return values will be turned into json), and finally a single method which takes and sanitizes a GET parameter named ‘name’, and returns a String response as JSON, on the /hello endpoint.
I was afraid to even post the snippet because I expected this rebuttal. You’re right- that example isn’t particularly complex. But it’s also literally the “hello, world” example- it shouldn’t be complex at all, and yet it’s already a little complex- the first obvious question is: “why do I need to annotate @SpringBootApplication and call SpringApplication.run(Foo.class)? To the untrained eye, it seems redundant- like only one of those should be necessary for a standard “hello, world”.
And, yes, each annotation can be justified by itself (although, I must be missing the part where the response is sent as JSON). But, the point is that there are a lot of annotations- not necessarily that every single one is unjustifiable. And the problem with this line of argumentation (that I admittedly started) is that I can post example after example of an annotation situation that seems tricky or unnecessary, and someone could reply with a justification for why that specific thing is done that way or with an explanation of how that example isn’t actually all that complex, etc. But, the pain comes in the aggregate: when you have to learn 100 annotations and 10 or 20 of them have tricky edge cases or caveats, it starts taking up a lot of mental bandwidth.
Some other funny ones I remember off the top of my head (it’s been years since I’ve actually used Spring) the @Service vs @Component annotations (lol- they’re the same! We just want even more annotations!), all of Spring Data (“How do I make sure a column is not nullable, again?”), and stuff like the @Value annotation just existing…
Also, vertx sits at a much lower level, so this is not even a valid comparison - it’s not a full-blown web framework, it’s a server framework, on top of which you could build one (like quarkus is).
Vert.x core sits at a lower level, but Vert.x also has Vert.x Web, which is a full-blown web framework, with plugins for all the normal web stuff: auth, cookies, file uploads, redirects, etc. But, it also doesn’t do any annotations. You configure a Router, by adding Handlers; e.g.,
and then attach the Router to a Server and start the Server. You do all of this configuration “by hand” instead of with annotations.
Like, frameworks are tools - you are expected to learn them. You wouldn’t be able to drive a forklift either without former experience, would you? Does it make it a bad tool?
Two things: First, I never made a value statement about whether Spring was worth learning–I simply agreed with a previous comment that Spring is harder to learn than other frameworks that I’ve used. Second, I would counter your general argument by saying that if you have two tools that are roughly equally productive, but one is much harder to learn to use, then yes, that would make the harder-to-learn one a worse tool.
I have to agree on a) Vert.x being lower level than you’d like. (And a royal pain in the arse with the whole event loop, I had to use some god-awful hackery to write tests that verified code that had been blocking the event loop no longer did, because the only way I could hook into the code that checks what’s blocking involved some ninja shit at a classloader level that was brittle AF to override the logger it used, because that was the only way to get into that code. A few versions later, Vert.x did make the event loop blocking checker easier to access, but at the time, kill me).
And b) Spring Boot being comparable to Django et al.
I’m not at all a fan of Spring anything, but Boot’s magic is no worse than Django’s, possibly even less worse.
And, unlike Django, at least in Spring Boot, I can easily separate out the model, and switch out ORMs if I choose.
Django’s “it just works” comes at a massive cost - ridiculously tight coupling.
This is also an overrated aspect of software systems and languages: you’re in a “I want this to be stable and efficient” state for much more time than you’re in “I need to prototype this fast” state, so it doesn’t make sense to optimize for the later.
I’m not sure this is universally true… if you are trying to secure your initial round of funding and find a product fit, I think it does make sense to optimize for the later.
If you manage to keep good separation of concerns and extensible code you get the big prize: one of the worst performance profiles in major languages
This is what I love about working with Elixir and Phoenix. You get most of the Rails golden path for rapid iteration without sacrificing a solid foundation for future growth should you succeed and grow your application.
I feel really similarly about Python (for web backends, specifically, but probably also any large project). I can’t wrap my head around how anyone can try to write and maintain a large, long-lived, project with Python. And if you do somehow get a bunch of people to agree on conventions and style, and write reasonably correct code, your reward is just like you described for Ruby: really bad performance (both execution speed and memory consumption).
I can’t wrap my head around how anyone can try to write and maintain a large, long-lived, project with Python.
I’m working on a Django project that still has the occasional comment in code of “#when we upgrade to 1.0, we can fix this”, so you know, it’s old.
And hoooooly fuck, the complexity is out of control, this project is earning millions PA, but everyone’s terrified of changing code because it’s really hard to refactor Python in confidence without great test coverage, and Django’s “unit tests” aren’t, they always spin up the whole app and require a DB. Sure, you can use SQLite, but a) there’s some fun incompatibilities been that and whatever DB you’re using and b) it’s still slow AF.
I think my ragiest moment I had with Django is when I realised that the Django test runner’s test discovery only considers test case classes that subclass a Django test class.
You created a test class that derives from unittest.TestCase? Because you’re just trying to do an actual test that doesn’t need a DB and the kitchen sink?
Django’s test runner has no time for your lack of faith, so it’ll ignore your tests that don’t inherit from a Django test class because… …I don’t know why, fuck you, that’s why, is my best guess.
So your unit tests could be failing, but you’ll never know if your CI pipeline uses django test.
I’m trying to bring in mypy to provide more confidence, but because Django is so “open minded your brain fell out” enamoured of metaprogramming, it’s ridiculously hard, there’s no type annotations in the Django code, no official type stubs in Typeshed.
I absolutely loathe any Python class that implements __new__ these days, because of Django and its metaclass fuckery.
I used to love Python, I’m a self-taught dev, it’s what I learned with before getting my first job in a Java shop. Now that I’m working on a large Python codebase, I really miss the JVM world.
It’s funny, I got onto Ruby after about a decade of Perl, because Perl was sooo unmaintainable, omg write-only language wtf are all these dollar signs. In 2024, there’s cultural/community/hiring reasons not to use Perl, but I think it compares favorably to Ruby in long-term maintainability at a language level.
This is also an overrated aspect of software systems and languages: you’re in a “I want this to be stable and efficient” state for much more time than you’re in “I need to prototype this fast” state, so it doesn’t make sense to optimize for the later.
Not necessarily: if you write a bad system then someone will come along and want to do a re-write. If they quickly prototype something and it gets traction then someone will try to rewrite that. This may sound like a vicious cycle, but since everyone is writing new systems everyone gets promotions. Maybe that’s the real value prop.
There’s a chart of scores in the Model Evaluation section, each methodology and scoring system would have some component of accuracy but you’d have to go find those details.
I think this is a nice curated list of manual steps and suggestions for tools. To me, it’s like an early design doc. The problem with manual steps and long READMEs is that they have no version. Let’s say I do all of this and this advice changes, am I on “Mar 2024 as written tech independence stack”? How do I get to the new thing when mutt is replaced by neomutt or whatever?
If this was called tech_independence-v0.deb then it could have a version and there would be a name for it, a diff, a migration something, updates, improvements, replacements and other changes as the world changes. As it stands, I think it’s like a design doc for a meta-something. Unfortunately, it’s really hard to ship compositions. :( If you look at some homelab bootstrap repos, it kind of reads the same but they attempt to have scripts. They still don’t have a way to name a meta-version but at least there is a git ref and each component’s version is known usually.
This one is also personal pain avoidance (like others mentioned) and admittedly boring. My manager wanted a report in a certain format (.xls) but it was basically devlog stuff or .plan type of information. It was just annoying to do every week. I had the data, I just needed to make a spreadsheet dated in a certain format and email it.
I automated it in Ruby (this is a long time ago) with a spreadsheet gem. Pretty straight-forward. I did the emailing myself (using a template) and this was way easier than doing it by hand or forgetting to.
What I thought was funny was what my manager said after a few months:
You’re the only one who does it on-time and in the right format every week
First one that comes to mind was that I used to have a small automation that rotated my Twitter profile picture by two six degrees every minute, so that each hour it would make a full rotation. I shut that down when I left Twitter, though.
Nowadays the most useful automation I have is a daily script that sends me a push notification whenever the Cubs are playing a home game and the Red line will be packed.
Wait, would it do image manipulation stuff? Or would it update a URL or the asset of a set of pre-rotated pictures? I guess you got around image caching problems?
If Ruby had threading, actors, anything then you’d probably at least consider inlining the process (maybe as async or as a channel) and then @vhodges is right. Even if you had a Job abstraction with retries and other features. In Ruby, we use Sidekiq etc very early (imo) to have a sister app process in the middle tier because we don’t have a concurrency story. The same is true in Python and (less so) Node.
However, I’ve been thinking about what you are saying beyond the runtime nature of the app in this gist. I think events are a 4th tier, even in a monolith. There’s usually some very important business event buried in the app and if it was extracted, exposed or sort of emitted out of the system then other neat things can happen. If you look at other long-lived processes like games and GUIs, this is usually how they are built or at least events / callbacks / reactors etc are very useful because things are happening while the thing is running. I think servers follow the same pattern. Emit events to decouple.
Some random thoughts your comment made me think of:
If you run the tasks as threads, actors, multiprocess, etc on the same host (or container or etc), then you run the risk of the task getting lost if the host goes down. Moreover, since the load balancer isn’t aware of async tasks, it becomes likely that the host will get terminated in response to a scale down event while the async task is running.
At a minimum, you will need to persist the task (on a database or queue—not host or container ephemeral storage) and you will need a task runner that pulls from your persistent task queue. If your tasks are idempotent and you can tolerate a fair bit of latency in some cases, then it’s probably okay if they run on the same host as your main application because if the host gets deleted some other host will pick up the task after it times out, but at this point you’re well on your way to building a task system.
It’s a good point. Blocking the thread might have knock-on effects in an auto-scaling or ephemeral cloud environment or something with a health check like a load balancer. Depending on what the job represents, you might want to persist it (and have job-ish features beyond). I feel like single thread languages would add a worker system because you notice this sync pause even on a development server. Do you think sync pauses are noticed like this? I think Node is sort of a strange and confusing one and might not be observed having issues until in production.
I’m coding a login form. At some point an email is sent, maybe to a fake email server. In my dev server, there’s a pause when I click “sign up”.
I may or may not think “oh no” is this what users will see?
I may or may not think “wait, how do threads work? would my production server pause like this for everyone even if one person is not signing up? wait, what is our prod config?”
Or you might think promises make Node.js parallel just by itself, with no other considerations needed. And on the other side of the coin, I once wondered since Elixir has OTP, I don’t need a job queue, right? But then oban exists. Just random thoughts too. :)
Fails: the client will show a failure and the customer can retry if they choose.
Timeouts: the client will get a spinner. No real way around this one.
If send_template…
Fails: the client will show a failure and the customer can retry if they choose.
(This is why we use find_or_create… rather than create; junior engineers trip on this all the time!)
Timeouts: the client will get a spinner. We can prevent this by doing this work in a task.
If render_template fails or timeouts, then the client will (eventually) show a failure and the customer can retry if they choose. But if they do, they’ll receive another confirmation email. That’s not good! /signup has become an unauthenticated API for spamming. We could put a rate-limiter around send_template; but, what happens when the rate-limiter fails? And so on and so forth.
In short, the general idea is to do a little as possible in the request-response hot path and move as much as possible into tasks. We have just so much more flexibility in how we deal with failure in tasks.
I’m sort of hesitant to jump in and comment. It’s been a week.
I’m sure the hardware difficulty and requirements will decrease year over year. What used to be rare and difficult will be easy and commodity. And we’ll find optimizations or tricks like quantization (as mentioned). We’ll have a tiny device similar to a RPI running something previously very difficult as a technical meme, then create a new challenge for ourselves. The current and future problem in my view is data. There’s no Moore’s Law for data prep, training practices and evaluation. Training a language model at home with public data isn’t much different than using existing services. Adding your own data is not guaranteed to make the performance better. Finding out if your model is any good could be ad-hoc anecdotes or you could use metrics. Both are tough. Using metrics is difficult. Using metrics is what the service that you are perhaps avoiding is using. Sometimes, they have used anecdotes themselves (HumanEval).
On top of that, you have the work that OpenAI did with humans to refine the chat interface, curate responses and make ground truth in a sort of mechanical turk, massive effort kind of way. I don’t think we can or should distribute and re-create that effort. That said, I think many teams and organizations could get a lot of value out of leveraging their private data. But I think many orgs don’t know how hard it is yet. Buying GPUs is just the first gate to finding out. This is what worries me, that there’s the large GPU spend and then realization?
We did “the training” and it didn’t work very well, now what?
What I think is interesting is the business model of answer.ai and how it compares to others. I’m not saying we shouldn’t have McDonald’s at home. I’m asking, where are the results? Where is the success? I have heard of small model success, but in the large language model space? Does anyone know of one or one underway?
I have very broad programming language skills and know only a few very deep (Ruby, Rust and Java, chiefly, JavasScript, HTML, CSS[1], too). However, this stems from my previous endeavour: working close to databases and logging systems. This means that I need to know enough about almost any programming stack that a client could throw at me to made the database interaction work well.
Also, I’m very interested in programming languages as an artifact in an by itself, which means I can probably also pick them apart on a higher level than needed for most programming tasks. So I have a broad experience with things I had tried out.
Recall is another interesting thing: it works slowly, but recalling works much faster than learning afresh. e.g. if I were to pick up Haskell up against today, I have a model of how Haskell works. I however have probably completely forgot the exact syntax. But that only means I lack exercise. I can read Haskell just fine however.
[1] Yes, both HTML and CSS are languages and I’d even pick a fight that they can be seen as programming languages - turing-incomplete languages have a lot of utility.
Maybe I’m not correct in my base assumption but a good starting point would be “I can make a TODO list”. For example, I’ve touched Node.js many times but it’s not my goto dynamic language. So … do I say “I know Node”? What does that even mean? So I’m tired of speaking like this so I’m just going to create TODO lists in every language I can.
Pretty boring. I took notes of what I looked up etc as I did them. This repo is not current with what I consider in my toolbelt.
IMO it’s a bit similar to natural languages. The ones you use daily you get proficient with but if you ever stop using them they fade in memory. At the same time it’s really hard to get traction in a language that sounds fun to learn but gives you no opportunity to actually use it.
I was really competent in at least 3 programming languages that I have thoroughly forgotten since. At any time tho it doesn’t feel like am proficient in more than a couple languages simultaneously, but maybe it’s just my tiny head. The concepts you learn however, the tricks and techniques still leave useful residue which is transferrable across the jobs.
I wonder if there’s also an analogy to “native” languages.
I can be quite competent with a few languages at a time, but if I switch back to an old one I need some time to refresh. But I never seem to forget how to read and write C, even with a break of a few years.
Now, maybe that’s just because there’s not a lot to C in the first place. But I first learned C at a young age and then used it intensely at school and my first professional job, so I sort of “grew up” with it. Maybe it’s my “native language” equivalent?
At least for me personally, there’s something to this. 11 years ago I learned to program in Ruby, my first language. Since then I’ve written almost exclusively Go and Rust at my day jobs.
I still feel like I “think” in Ruby. Code in my primary languages is idiomatic, and no one would pin me for a Rubyist by looking at it. But if I design an algorithm on paper, it definitely takes on the general shape of the pass-a-block/lispy style of Ruby that was popular when I learned. With some type annotations or pointer/deref operators added for clarity.
I have no doubt I could sit down and write vanilla Ruby just fine.
I’ve had that similar feeling of knowledge fading out after a few years without regular use. I think it’s very similar to natural languages! I started using Anki about 10 years ago for ASL. That went so well I expanded into programming and other topics.
For example, roughly once per year I inspect a sqlite db to debug something, and that’s just not often enough for the dot commands to stick, so I repeatedly had the frustrating experience of having to look up commands with the full knowledge that I have done so many times before. I made Anki cards for the couple commands useful in my debugging (.dbinfo, .schema, .dump, .recover, .mode csv) and now I always recall them when needed.
I also make cards for languages and tools that I do work regularly in, mostly for features I use infrequently or corner cases that have led to bugs. I suspend cards when I’ve stopped using the language, and I delete the cards about a job’s proprietary tooling.
Anki has primarily been a timesaver, the rule of thumb is that you’ll spend about 5 minutes studying a card over your life, so it’s worth making cards that you’d spend more time looking up. But it’s more than remembering, I feel I also design better systems because I have more concepts and tools comfortably to hand.
I’m not sure why I’ve never considered using Anki for this kind of thing before. I’ve used it for (spoken) language learning to some limited success (limited due to my own efforts). I also used to way back when I was learning boolean logic for the first time, to great success.
Using it for something like SQLite dot commands is genius. I think I would also benefit from using it for the more esoteric Linux commands I used infrequently enough to have to look up how to use them every time.
At the same time it’s really hard to get traction in a language that sounds fun to learn but gives you no opportunity to actually use it.
In a typical software developer career, the limit is certainly in the single-digits, but that’s not due to any hard limit in the human mind; it’s just because it’s extremely unlikely that an employer would pay a developer would get the opportunity to use a large number of languages nontrivially. If you did find some kind of dream job where using a wide variety of languages were part of your responsibilities, I’d imagine a skilled developer could get it up to 20, given that there’s a lot of overlap between languages on the same runtime, (Erlang, Elixir, LFE, for instance) or similar paradigms, etc.
Maybe it’s orthogonal. Maybe you could integrate the two with GPT plugins. If infoboxer really does something novel and you think it’s unique or interesting, it might not be invalidated. Also, ChatGPT or any company can still enshitify, we don’t know what’s going to happen.
There have been other sort of sources or truths, parsers or attempts at world modeling. There is ConceptNet, HowNet and WordNet. I think these things still have value in the era of LLMs because ChatGPT still does not have a world model perse or it’s possible that the party trick of attention is all you need ends and some other technique is needed to continue performance increases.
This tale reminds me of The Bitter Lesson which is bitter for complex reasons. I think the thing to ask is, if computation doubled, would this project benefit from it? If computation in GPUs doubled next year, would it still?
Thanks for posting the youmightnotneedjquery link, I was hoping someone would. Although I guess you are saying the opposite of why I wanted it linked. I think it’s a great site for when I don’t want any dependencies, for very small projects and I want to do the thing I used to do. I use that site to translate I guess. If the DOM manipulation is getting bad then I’d move up to React or something. But hopefully in these small projects, I am doing very small things. Contact Us form. Something trivial.
In my view, developers could do nothing else other than create spaghetti because there was no direction or opinion. That’s what backbone.js was at that time. It was the structure (backbone) to jquery projects. It was always possible to make some kind of organizational system yourself but I only saw it done once and even then it was bespoke.
What’s the natural pivot or lateral? Solid so it’s very React-like or is that too close? Vue because it’s more different and there are many great tools coming out of Evan’s head? Vue because it has a Next analog? Svelte because it’s even more different and compilers are a good idea? I wonder what OP would try next.
Reminds me of an early career job I had. I hadn’t thought about this stuff in a while. Getting access to the building, understanding the ticket (restore the right drive), dealing with customers’ data loss in retrospect / mourning while also deciding or not-deciding to be educational (don’t). As a job, I think it’s really tough but I think you gain lots of insight about how people work and what they value. You are overhead, you are a generic IT person but that means you can slip in the door and meet Oprah without an interview or any relevant domain experience. But it also means when you’re done, they say thanks and that’s it. You get a cool story. I think it’s interesting how much lateral slipping-in there is. I’m sure there are other fields that have similar lateral slipping-in but within digital information jobs, this was my take-away from being a traveling repair person for a bit.
I use it for kicks and giggles but every time I’ve asked it something non-trivial the output has been bad. Bad enough to reveal that there’s no intelligence there.
The people for whom these tools are a productivity boost are telling you they do work at incredibly low complexity/quality levels.
Boilerplate is low complexity. “Gen me a CI config for Gitlab with setup/build/test stages.” It does it and I fill it in at a higher complexity level.
Boilerplate is low enough complexity that you can deal with it with tooling that behaves deterministically and doesn’t boil the oceans. Templates, snippets, IDE scaffolding, macros…
Energy efficiency is a good point.
I have editor templates but I use multiple editors. There’s not a common format between vim and vscode that I know of. Text chat seems to be a very general interface. I think the point of the article is not “are LLMs good” but the fact that people outright reject them, without giving them a good faith effort.
Developers seem to care about vim’s editing speed. Developers seem to care about mechanical keyboards and typing ability. But if you give us a text editing tool which types at 2240WPM, an order of magnitude beyond world records, typing speed does not matter? Because of accuracy (which is valid). But then we have the axiom “code is read more than it is written”. Yes. So, we read the code that the LLM generates more than we write it. We say that programming is more than just typing. Yes, so we think about our problem and break it down into something that the LLM can understand or we have a sense of intuition that our query/prompt will not yield good results, just like SO and Google. Or that typing does not matter so it doesn’t matter how I code.
So I feel that some developers are not giving LLMs a good faith effort and feel threatened so they try to gatekeep by saying that people who code with LLMs are not doing high complexity work. I was trying to say that my work complexity changes over time even on a single branch/feature.
I would simply not have boilerplate.
I have tried to use it for this exact case and it generates CI files that do not work. It is especially bad at figuring out github CI’s weird text interpolation rules for when a variable is or is not available.
It’s very hard to say this exact case without basically doing an eval. Because my exact case was not using variable interpolation. So, your experience is valid and mine is valid. Finding an accuracy percentage would involve a lot of work, like developing a benchmark.
My prompt to Claude was something like this but I have recreated it here for this thread so it may have not been this exactly:
And what it gave back was this:
The prompt is a bit verbose imo and probably not what I originally prompted.
And just to see what determinism is involved, and this is not enough iterations, I did this two more times in completely new chat sessions and I deleted previous chat sessions out of history just in case. The second gen had
- # Add your setup commads here
inscript:
and the third gen was exactly the same as the output above. It definitely could be the case that it continues to be random and it could definitely generate some text that is completely wrong.I’m just using CI boilerplate as an example because that was the last thing I did. But since YAML is so picky about indentation and my configuration between vscode and vim is not synchronized (nor will it ever be), I found that generating YAML with perfect indents was a good use of my time. Except now I’ve spent more time on lobsters talking about how I feel it saved me time so I will now jump into a volcano.
I tried out Kamal for a deployment job I had but it wasn’t really well suited for Rust and the configuration was extremely shaky/undocumented/obscure. It definitely was not a 2 minute job.
Also I could not for the life of me figure out how to best install a Ruby devtool on my machine and felt it was just a rather anachronistic concept.
asdf or mise abstract quite a bit of it for you. For Ruby and almost anything else.
I stopped using those now preferring just to stick with nvm/uv/rustup.
fnm is the better nvm.
It never ends!
And fnm pollutes your filesystem with thousands of symlinks over time. So I switched to mise… uv can detect mise-installed python fwiw.
In that case you’re probably going to get along better with
ruby-install
+chruby
than other install options.I use asdf for all languages on my dev machine. I think it’s been about 5 years straight I’ve been using
asdf
and to me it’s great. I rarely think about it. To me, it’s solved. You get a single CLI interface instead of having node version manager and ruby version manager.You can set shell defaults or activate them temporarily just like the single language managers (nvm etc). You can bind projects with
.tool_versions
so it switches oncd
. You get project isolation on the runtime withasdf
and dependency isolation with a package manager. My projects don’t get weird and my dev machine doesn’t get weird. I can list all runtimes for all languages, for when security news happens or if I wonder “do I have Python 8.0 installed?”So, when I wrote an asdf upgrader shell function to replace a language (hey, replace nodejs 42 with nodejs 43) it works for all languages because asdf is a language manager not a node language manager.
While I like the trend, calling it “no PaaS required” when you have to set up your own servers and manage SSH keys is a bit of a stretch. It’s a big jump in operational complexity from a Heroku-like PaaS which doesn’t need any of that.
Doesn’t Heroku require you to set up SSH keys as well? Or set up your own “applications” (servers) before you can push to them?
Most VPS providers I’ve used let you save public SSH keys to your account, including at the point of creating the server, to (automatically) place them into the servers you create.
I don’t think it’s that big a jump, if at all. I’m not a big fan of Rails, but this criticism seems misplaced.
Not necessarily. Almost every PaaS has integrations for your forge to auto deploy. Having to set up an SSH key and pushing from local is the “classic” way of deploying to heroku, but unless you’re on a very small team you’re not doing that in practice.
What? Are you saying creating an application on a PaaS is functionally the same as setting up and maintaining a server?
As I said before, this works when you’re a solo dev or a very small team. As soon as you’re doing basic things like automating deployments or need fallbacks you need to do a lot more to just manage your SSH keys than you would with a PaaS.
I mean, that’s just pushing the issue back a bit. Presumably, you had to set up your SSH keys on your forge. Sure, there’s some repetition, but unless you’re a huge company to the point that enough developers churn, I wouldn’t exactly call it a big jump in operational complexity, but when you grow like that, everything becomes more complex.
Well, no, but that’s what it sounds like they’re trying to solve. From what the article makes it sound like, you only need to create a server in your VPS provider’s web UI, get your SSH key onto it (which is what I was saying is not that much harder than a PaaS), and Rails 8 will basically do the server setup (and presumably, some basic level of maintenance) for you.
Just to clarify, you mentioned that before in a previous paragraph within the same comment, but not in an earlier comment in this conversation/thread.
So, as I said earlier in this same comment :P, everything becomes a lot more complex when you grow. They probably feel perfectly fine about releasing stuff that’ll work for the majority of their users, which won’t be large companies. Presumably, as time goes on, they’ll refine it for larger users.
Anyway, at really large scale, like GitHub, having dedicated ops instead of PaaS goes back to being worth it, so while they won’t use this, they’re probably not even using PaaS, anyway.
I guess PaaS is for a certain size band (medium businesses, or profitable or long runway small businesses), and this solution aims to serve a chunk of that band.
That’s fair. When you said, “you have to set up your own servers and manage SSH keys”, I was thinking about managing SSH keys for those “own servers”, not extra stuff like CI, CD, branch deploys, and so on (not sure what you mean by fallbacks), where you need to manage your SSH keys on further machines and create SSH keys for machines instead of people, etc.
I see where you’re coming from now. I guess a better title would be, “No More PaaS Required (For Many)”. But I can see a lot of this stuff being solvable with the same infra/foundation they developed here. I can also see it being made a lot easier for teams to solve themselves using these tools.
I’m just speculating now, though, I was initially responding to your point about managing the production app servers and the SSH keys for those.
There is a huge difference between dev SSH keys that individual devs have and a deploy SSH key that can be used to deploy to the live, production system. One of them is managed by the dev locally and is cheap to cycle and manage. The other has to be managed by the team or company, is shared and can be time consuming or difficult to cycle. Ask me how I know!
My bad - I had to edit the comment and missed this, I’d mentioned team sizes before in a previous version of my reply itself.
This is only true in the very very very simple case where you have only one server, and this still ignores other moving parts like the database or a cache service. Also, sure, you can create a server from the web UI, but what about maintaining it? What about regular software updates, security patches, database updates, etc? These are all things PaaS free you up from thinking about. Saying “you can create a VM and you’re all set!” Is only true if you literally only need a fixed number of VMs, are using SQLite, and don’t care about any operational resiliency or even what happens to that VM a week, month or year from now.
And please, please, please don’t think solo devs or small teams don’t need to do that of this. For my hobby projects I mostly use VMs, and very often I spend more time dealing with ops instead of programming. I don’t mean to shill for PaaS services, but I also think people seriously underestimate the ops work it takes to keep a service going, and how much a PaaS can free you up to just code.
To be clear, I wasn’t suggesting that the same keys would be used, merely that integrating with a forge doesn’t get rid of the requirement to manage SSH keys. For small teams (as you say), where SSH keys are managed manually, including deploy keys and other keys for machines, I don’t see why this couldn’t remove the need for a PaaS.
I don’t think we’re really disagreeing. I’ll say again:
With Heroku you can add someone to the team and they are able to deploy, get a shell, etc. You can limit permissions as needed or use Single Sign On.
With Kamal you need to get their SSH keys on the server somehow and give them the list of IP addresses (then keep both of those things up to date). You can’t really set granular permissions and you are giving them (effectively root) access to the host, not just the inside of the container.
You could pair Kamal with Tailscale to get something a little closer to the Heroku experience.
What exactly is it doing for you? Is it configuring logging and monitoring somehow? What about network firewalls? Databases (TFA mentions SQLite support but absent some clustering support, that’s an antifeature)? There’s a lot of stuff that PaaSes provide that I have a hard time imagining this software doing properly.
Classic 37s marketing.
My own mental model is
IaaS | PaaS | SaaS
. To me, SSH keys and servers is infrastructure. To me, PaaS is a big mystery in the industry right now. I think many are trying to solve it. IDPs, Kubernetes, Backstage and others. One isn’t better than the other and you can run the “aaS types” in parallel. But things at the PaaS level of abstraction isn’t IaaS. When you use k8s you don’t and cannot SSH into your servers. You don’t even call them servers.Each layer has trade-offs but I find the middle PaaS one especially confusing right now. I think there’s a need for a mid-complexity thing, something like Nomad. So, I think Rails 8’s opinions are pretty interesting given the alternatives.
I think of your PaaS layer as two different layers.
K8s, Nomad, Swarm, etc. are orchestrators. They do provide an abstraction on top of the compute but is still fairly low level.
Tools like Kamal provide the developer facing abstraction: apps, deploys, rollbacks, etc. Kamal is not the best example but you can imagine an internal developer platform with everything Heroku offers on top of the company’s K8s cluster.
I think that split will continue to happen just because of Conways law. And if it does fully hosted PaaS makes less sense for most orgs.
Aha! :)
When I measured the
<html>
element in pixels, it was larger on the inside than the outside. :OThis blog is not for you.
Two related questions:
There have certainly been a number of attempts at making “Rails but for Go.” See https://gobuffalo.io for example. I think it’s not really a market demand, so it hasn’t taken off the way Sinatra/Flask-style API routers for Go have.
Because the old server side “webapp” with a sprinkle of JavaScript doesn’t make too much sense in the age where people expect GUI style interactivity in their browsers. Rails os from the age of clicking from document to document and add some forms for data input.
It survived because it has inertia, and because it is still a useful way of building web applications, but it’s not what the market wants.
I for one, wouldn’t mind a simpler web, retaining more of the old click and refresh paradigm. This very website is a good example. But that’s me. The market has spoken loud and clear the other direction.
Imo, microframeworks are easier to write so there are more of them by quantity. Fuller frameworks take many years so there are only a few of them (if any) in each language. I like to think that web apps or frameworks are near the tippy-top of complexity and high-level-ness. I like to imagine that a full framework that could tackle many of the common problems you will run into will take 5-7 years to make and refine under real developer use. Most people won’t go that far. So you end up with http routers, I wrote why I think this isn’t enough.
If you want to go back, in a way, Remix is sort of a throw-back. I wouldn’t say Remix is an auto-win framework to pick but I think it’s pretty interesting. Remix has a kind of actions/submits going to the same thing/file/route you are currently on, it stays close to web standards and you can get end-to-end type safety like trpc if you use typedjson. Remix is not a click to refresh type of experience though.
Go’s web story was really bizarre to me when I was still spending time in that language. Rust has Axum and even Typescript has something like Hono. API based microservice frameworks can (should?) be small and tight.
I never got into Rails and am glad I didn’t.
How about:
Kotlin/Spring especially seems to occupy a sweet spot when it comes to language, runtime and framework maturity.
The entire JVM ecosystem, stodgy as it is, is a huge breath of fresh air. Stuff just works. There’s no npm involved. And you have types backing up everything.
Spring has a rough learning curve. Some parts are awful to set up (Security). And a lot of info online about it is either dreadfully bad and/or outdated. But Spring itself is a much better framework than most anything I’ve used in the past. It barely qualifies as a framework, because it really isn’t about saying, “you must put your controllers in this exact directory!” and more about “here are tools to help with building and wiring up an application.”
Spring is happy to fade into the background. Most frameworks aggressively assert themselves through the codebase as if they are as important as your code.
Maven seems an awful lot like npm. Help me understand what you mean by no npm?
For one thing, Maven avoids the need for lockfiles because (in the absence of rarely-used antifeatures), dependency resolution is already deterministic by default.
I haven’t used Maven as a build tool but I have a lot of experience with using it to resolve dependencies, and I feel confident saying it’s the best of the “classic” systems that predate content-addressable dependencies.
Do you have a source on this? I was under the impression that Maven could benefit from a lock file like all other package managers which is why maven lock file plugins (I found two) exist. It seems to have a transitive dependency resolution algorithm that tries to be repeatable by always selecting the nearest dependency? Admittedly, the workflow of deciding to name your transitive dependencies to force a version is very similar in my view to using a lock file. Hmm.
My understanding is the only time when a lockfile would benefit you is when you accidentally pulled in a dependency that declares a version range. (The rarely-used antifeature mentioned above.) Ranges are inherently nondeterministic what they resolve to depends on the state of the repository at the time of resolution rather than being strictly determined from immutable data.
Overall it’s better to banish ranges altogether; if you add a dependency that has a transitive range dependency, you add an exclusion for the range and override it to a specific deterministic version. But I don’t know if Maven itself makes this situation easy to identify; you might accidentally pull in a range without knowing it. I maintain Leiningen which uses the Maven dependency resolution library but adds range detection on top of it to ensure you have repeatability unless you specifically opt out of it.
Unless you need some JavaScript?
I’ve been thinking about why we don’t have one language. I’m aware of “use the best tool for the job” but when I ask “why can’t we have just one tool” then I think there’s a discussion of trade-offs. But why is there automatically a trade-off? What is the trade-off? So, in thinking about this, I think I’ve come to the same conclusion as you.
Java is sort of in the middle of a few trade off spaces and categories. I am going to write more on this later but when I am illustrating a dichotomy, Java doesn’t exactly fit on the extremes. It’s mid.
Most decisions in programming are made based on folklore. It’s cultural with path dependence.
Mediocre is fine. Go, PHP and Python are also extremely mediocre languages. But do we really need 4 mediocre languages?
Also there’s the trade-off of having too much crap out there and not enough people to maintain all of it. I’m tired of seeing survey after survey of open source developers saying they’re “soooo tired” but maybe nobody asked for Typescript ORM framework number 10?
Sunset your work. Delete it. Set yourself free.
Interesting question. This is my own/adopted view and sort of top of mind because I was talking with someone about this recently. I’m not implying that you don’t know of all these things already. I think you do based off your other comments, so, sorry about any unintentional tone over text issues.
When I separate out environments to deal with changes, they are like this:
So, from this perspective, production is simply the environment where the users are using the system. If I don’t have users, my general approach shouldn’t change because I could have users at any moment. Just like I design my system to account for expected usage bounds, not current usage or non-usage. “I might have 1TB of files, so I need to buy 2TB.”
There are down-sides to this approach. Making Production match Dev is sort of a fool’s errand. In some cases, making Prod match Dev is simple and routine. I can select a database migration tool that will help me migrate my schema locally that benefits myself solo, on a team and deal with changes to database state in Production. So, I can make Dev match Prod in terms of changing the database fairly easily. So, I might as well do that. But using a load balancer like haproxy in Development is sort of silly. So, if I don’t have that in Dev, where do I have it? How do I develop my
haproxy.cfg
?So, some people would try to develop a staging environment at this point. I have conflicting thoughts on this. It probably matters what the team and project is like (as with all things) but I think I would try to figure out what I’m trying to do. If I’m trying to test my infrastructure configs and not my app then I should just do the dev/test/prod progression from the operations standpoint. Like, figure out how to test infrastructure (but this can be tricky and different because of the context).
Another term instead of staging, might be beta. If you had production running with no users, you could also have a beta site with no users forever. Simply allow-list your home IP address to your beta site (or similar) and you’ll be the only user forever. You could do this for production too. Sometimes this is known as stealth launching or stealth release. In games, this could be on purpose or by accident. If something is stealth released it could be because the dev had no marketing or it could be because a very popular game released on the same day that they tried to release. Other positive terms might be “hidden gem”, “freshly launched”, “new”, “undiscovered”. Negative terms might be “failure” or “unpopular”.
I guess I don’t like the rapidly iterate on production part just because it’s lucky timing, hopefully a single moment that will never repeat again. Once you have users, you probably always have users. If I had a side project that was not popular and someone started using it, maybe this is my best customer. Maybe I have a very niche product and this user is my best hope of getting feedback. I don’t want to disrupt them. So, I should probably have some idea or plan of how to make changes. If that plan needs me to rearchitect on distributed systems, then I think I would want to anticipate that. So, I guess I would ask what you want to do before you have users?
I am currently in this situation and I know I have a debt item. :( The place where I want to have throw-away users is Beta and the place where I’m trying to make changes and verify them is Test but Test changes in different contexts (ops sort of resists it imo).
Having worked with Ruby for close to two decades I’m convinced there is no point anymore for large software systems. It’s a nice language for sure but after a few ten thousand lines it becomes impossible to deal with. Add just a couple inexperienced engineers and the whole thing unravels. You’re constantly fighting an uphill battle for good code and the language does not help you: it has no package-level segregation, no type system, no way to enforce interfaces, implicit method definitions all over…you have workarounds for all of these but they’re all non-idiomatic and no one uses them.
It’s just as easy to write “beautiful” code as it is bad code, you gotta keep yourself to an opinionated subset of the language and be vigilant about it. And what you get in the end is not encouraging—if you manage to keep good separation of concerns and extensible code you get the big prize: one of the worst performance profiles in major languages.
Over the years I’ve seen speed of development fall off a cliff while systems get bigger, taking away one of Ruby’s main advantages. This is also an overrated aspect of software systems and languages: you’re in a “I want this to be stable and efficient” state for much more time than you’re in “I need to prototype this fast” state, so it doesn’t make sense to optimize for the later. If you take an extra week to get to market in a “slow to write, statically typed” language nothing bad will happen to your company and you won’t need to worry about any of the above.
Having worked significantly in Ruby for the better part of a 15 year career myself I have to completely agree with everything said in this post.
I think the “speed of development” point applies to Node.JS as well - at least with vanilla JavaScript. I’ve been working with node since the very beginnings, and the prototyping speed was always one of the major wins.
Performance never bit me unless I actually needed performance. But you can usually hack around the problems. It is a hack, but it works.
But the “easy to write good or bad code” - that’s the real problem. When you’re in a couple dozen thousand lines, if you’ve at least half-disciplined, it’s not very obvious but you know the problems are there.
And especially then you get to compare this with the languages who enforce good structure on you.
But when you go into really big systems, I think it’s not that much worse actually.
You’re usually well separated into different teams, domains, microservices, or whatever by this point, so the problems if bad JavaScript don’t get a chance to scale into major blockers. I think the fact that ruby/rails has been used significantly longer for big systems, and (to the best of my knowledge) those systems managed to grow into big monoliths is what brought out the really major points to be visible.
As much as I liked Ruby initially, I learned the truth of what you said here in only 8 years or so by ending up working on a million-plus-line Ruby codebase @ Desk.com (prior to SFDC acquisition). I noticed that I started to write very modular testable code (functional code) after dealing with some hideously deep bugs that could be summed up as “mutability/inheritance run amok”*, but that I got NO guarantees out of it since I was always depending on library code that was not designed with that in mind, and because the language didn’t enforce anything.
I decided I needed the guarantees. That’s why I’ve been using Elixir for at least 7 years now. It makes some of the nastiest bugs I’ve seen in Ruby simply impossible, while maintaining SOME of the familiar syntax of Ruby (but very different semantics; it wasn’t a very tough transition for me). But I haven’t yet worked on a million-line Elixir codebase. I will report back…
I am surprised to hear this, because I would’ve expected lower switching costs if the semantics were similar. Are you able to elaborate?
I said the syntax was similar but the semantics were very different; I think this is the exact opposite of how you read it!
As far as “things I had to pick up” per my recollection: 1) Rewriting mutable algorithms as immutable ones (I actually find this fun to figure out) and algorithms that rely on random-access arrays to use sequentially-iterated lists (or other data structures) instead; 2) what looks like reassignment in Elixir is really just rebinding, and if you don’t quite grok this, it WILL lead to surprising-for-you behavior, 3) pattern-matching with inline name-binding and destructuring (which ended up being my favorite language feature ever, as it turns out, due to its elimination of the logical boilerplate that doing the same thing would require in basically every other language) 4) macros (I still rarely use them but it is neat to move into and out of the AST context when I do, and when writing a macro makes sense) 5) case, cond, if,
for
comprehensions, and them all being expressions that return values (something which I think every language should do, now) 6) writing recursive functions in a tail-call-optimizable fashion 7) the carat (^) operator to force a pattern match to match on an existing bound value instead of rebinding (I simply didn’t like it at first)IMO static types/compile-time type-checking end up having negligible impact on productivity. What does have a massive >100x impact on productivity is the quality of the ecosystem (frameworks, libraries, documentation, forums). But it’s weird that languages without compile-time type-checking have historically had many more “breakout” libraries. Is it just coincidence?
Take Java. Could there have been a world where Rails, NumPy, or PyTorch were written in Java? (In that world I wouldn’t have had to have learned Enterprise Java Beans!) Certainly Rails and PyTorch use a lot of runtime syntactical magic and NumPy depends on operator overloading, which Java does not support at all.
I don’t know why we don’t live in that world. But as a consequence, my opinion is that if you’re making a simple CRUD app or training a model, using Java or JavaScript instead of Rails/Django or PyTorch is definitely the riskier option. This isn’t saying anything about the language! For example, if you had a good usecase for Spark (which is written in Scala but supports Python), using some hypothetical alternative written in Ruby would be the riskier option.
TLDR: IMO libraries matter much more than the language for productivity. But why—for web development in particular—have the best libraries historically been for languages without compile-time type-checking? I don’t know.
I think the answer is simple. It’s very, very easy to go from zero to something-that-almost-works-if-you-squint in a language like python or ruby as compared to java or rust. There are an influx of new developers in web and data-science-y roles, so this is a huge advantage in those areas.
I think this played out with MongoDB as well – not a great database, but they nailed the out-of-the-box experience, which helped them a lot with the younger generation of developers.
I agree, which is why I don’t think the “extra week to market” argument holds.
For one, it’s an extra week per feature. So those weeks keep piling up.
The other factor is the business context, namely time and money.
It’s not “unreasonable” for a major enterprise to need six months to go from “hey, can you add a column with last name here in this report” to having it in production. They have all that money to burn. A startup will not be there in six months.
And the time factor works on another dimension as well. An enterprise adding that column will get agreements from an the stakeholders first. After all, this will affect half a million employees for the next five years. You want the time to do it well, cover all the edge cases, make sure it’s tested nicely. And enterprise frameworks give you some tools to make sure of all those.
In a startup, the column you added this week has a good chance to be changed even before you deploy it to production.
So “an extra week to prod” argument is kinda moot. It is relevant from the technical side perhaps, but nobody cares about technical side anyway, except us techies.
In my meager $BIGCORP experience, that timeline seems to come from questions like:
I don’t think the static typing factors in to the timeline at all. If anything it helps with issues like number 5 since everyone who built the system probably left so it’s much harder to know if you are breaking things.
True, you’re right. But that’s irrelevant, I’m talking about what an extra week means.
The point is that if you have six months to deploy a change, it doesn’t matter if it takes an extra week to also decide on proper types, it’s a fraction of the time.
But if the change you need is detected last week and you want to deploy it tomorrow evening, the extra week is more then doubling the time.
And, the enterprise code needs to run even after that SME leaves so you wanna have the type safety.
The startup code will be gone and changed a few months after you deploy it. So it’s maybe tomorrow’s problem, and maybe not a problem at all.
I get your point, but I disagree that it will take a week to figure out the static typing. If you are writing your dynamic language data to a SQL database you probably have to figure out your column types anyways. Plus, most data has pretty obvious static types. The transformations might be complicated, but I don’t think it takes that much extra time once you have worked in a statically typed language for a while.
That’s true. I fully agree.
I still think prototyping is faster with something like node or ruby then something like Java and Spring . It’s more then just database columns and static types - it’s about all the interfaces and domain boundaries and a lot more.
I’ve seen time and time again, people just go in head-first, maintainability be damned. I also saw that costing you some flexibility in the long run, but anything that survives to the long run deserves a decent refactor then. And at those stages, the startup is usually well along the way not to disappear by next week if you need time do do something properly this time.
But you’re right, static typing alone isn’t that much if a blocker.
And I don’t disagree with you that prototyping is probably faster in a dynamic language and prototyping is more important at a startup. I was talking to someone about this continuum of development the other day. At a certain, point the priorities shift from building to maintaining. That often involves shifting the people from builders to maintainers and not everyone wants to make that shift. So you can lose people the environment shifts around them. But there is definitely a need for both modes.
Agreed. It’s the same old “it depends”, as with most questions in the industry.
I wanna add a disclaimer: not all companies are the same and your mileage may vary if course. But my experience and the second-hand reading and third-party reports tell me it’s a relatively known pattern.
Interestingly, these libraries that you mention are an argument in complete opposition to what you’re saying.
NumPy
andPyTorch
both both use Python’s type annotations extensively now (as is very common with newer Python projects). TypeScript also seems to have completely taken over the JS world, with the most popular projects likeReact
all using static types.So even in the web world where dynamic typing reigned supreme for the longest time, a huge chunk of web devs have moved towards static types.
And the amount of times where
None
gets used inadvertently in Django code bases written by junior collagues at $dayjob dwarfs any type-related screwups happening in TypeScript code.Django devs dislike types.
Also,
fastify
with Typebox integration is pretty nice to build API with. I have mixed feelings about how HTML pages are served, but SPA’s work super well for e.g. internal company systems.I find it a bit funny, because to me this reads like saying “diet ends up having a negligible impact on health. What does have a massive impact on health is bodyweight and blood values.
Well, one heavily impacts the other. It’s a whole different world to build upon some library in a language that supports features like typeclasses vs. one that does not (i.e. all dynamically typed languages).
In Scala (but also other languages) I can do that:
1.) Import an http library for http requests/responses
2.) Import a json library for converting to and from json
3.) Import yet another library that creates interoperability between the http and the json library
Then, finally, I can just call
httplib.response(myObject)
and as long as I told the json library how to serialize the type ofmyObject
and as long as I import the interoperability-library, I don’t have to do anything else. No conversion, no runtime-magic, nothing.myObject
will be serialized into json and the http library will return that json. And both the http and the json library know absolute nothing of each other. They don’t share a single common interface or type.Furthermore, if anything in the process is wrong, then it will fail at compiletime and not at runtime. E.g.: if I forgot to define how
myObject
is to be serialized into json, then it won’t compile. If there is any kind of compatibility issue between one of the 3 libraries, it will fail to compile.This is super powerful and allows to create a much better ecosystem compared to languages where this feature (namely, typeclasses) is not available, because then doing the above is simply impossible.
In other words: the language and the ecosystem cannot be just separeted. To a good degree they are one unit and influence each other.
You had to know that this was gonna catch a bunch of flak, right? :p
But, I’m going to pile on, anyway. I’m deeply skeptical of this assertion- can you elaborate with some examples? Because, my experience with web backend stuff is mainly in: JVM languages (Java, Kotlin, Scala, Clojure), PHP, Node.js, and Rust.
I’d say that the Rust ecosystem probably aligns with your assertion: all of the web frameworks have converged to be extremely similar–and not particularly fun to work with for various reasons, and the ecosystem is just generally lacking in a lot of various tools and integrations. But, I chalk that up to Rust being not particularly well-suited to high-level business logic web dev and to the fact that the number of people using it for such is minuscule compared to the other languages I’ve worked with.
But, of the other languages, I feel like the NPM ecosystem is the least impressive to me. The quality of even the most popular libraries is often… lacking. PHP is a garbage language, but the main frameworks/libraries are pretty solid–especially ones that were either started or totally rewritten after PHP 7-ish. The Java ecosystem is very complete, and the quality is usually very high.
Most people who go into programming were/ are learning Java, C, and C++ in college. They walk away hating type systems because they use these languages and run into so many godawful errors, often due to the type system but not always, and overall the experience of these languages sucks quite badly for newer devs (things like packages).
Then they learn python and it’s just so easy. They write code and it does what they want. At their “scale”, these languages are just an absolute breath of fresh air. I’ve heard it time and time again.
Basically, people learn about types using some of the worst languages that have horrible type errors, and so they think of types as a burden. They don’t see the value because obviously as a student types aren’t doing anything for you - you’re writing entire projects yourself or with a very small scope, types are just in the way.
Types are ruined for them before they even really had a chance.
For any CRUD app (both simple and very complex) there aren’t too many better options than Java’s Spring, in my opinion, it’s easily up there for even prototyping purposes as Rails or Django.
Once you get past the giant hurdle that is learning the framework itself (and all the annotations/lifecycle/hooks) and the awful project management tools that are Maven and Gradle.
As opposed to the completely trivial to use other frameworks? Like, please enlighten us which one isn’t a “giant hurdle to learn”? It’s almost like web development is a complex domain, and if you want a system that can actually fit every use case, you will get a complex framework. Essential complexity can’t ever decrease.
Also, annotations are just metaprogramming. Funny how it’s considered complex to denote in plain that this method will be called when this endpoint is called, and such. Especially that that’s pretty much how it works in every other framework.
No, I have to agree with the “giant hurdle” comment. Spring is genuinely harder to learn than almost any other framework I’ve ever used.
Annotations are just metaprogramming, but they are an extremely constrained and awkward kind of metaprogramming. Spring’s problem, IMO, is that it has too many Annotations that have too many edge cases and caveats, and too many of them end up doing surprising things when you mix them. Many frameworks–even some in Java–have moved away from this approach and gone more toward “real code”, like Jooby and Vert.x.
I’m calling you out for being intentionally dishonest here. You know that if Spring’s only/primary annotations were simply marking methods with an HTTP route and method, that people wouldn’t complain about it at all.
Here’s the very first “hello, world” example from https://spring.io/quickstart,
There are already four annotations. If you simply forget any one of them, your application will not work. And this is without any kind of authentication/authorization, JSON encoding/decoding, HTTP header reading/writing, etc.
In what way is this example complex? It’s a single annotation marking the entrypoint for a spring boot application (which usually lives in its own file), then an annotation denoting that you want to create a rest API, setting your http responses appropriately (e.g. the return values will be turned into json), and finally a single method which takes and sanitizes a GET parameter named ‘name’, and returns a String response as JSON, on the /hello endpoint.
Last time I checked Django, ROR, Laravel all have complex project initializers setting up a bunch of files. Are these really simpler? Also, vertx sits at a much lower level, so this is not even a valid comparison - it’s not a full-blown web framework, it’s a server framework, on top of which you could build one (like quarkus is).
Like, frameworks are tools - you are expected to learn them. You wouldn’t be able to drive a forklift either without former experience, would you? Does it make it a bad tool?
As for the more imperative configuration mode, that is also absolutely possible with Spring, but it’s usually better to let statically knowable data be statically available, in the form of annotations. Thanks to that Intellij can easily autocomplete valid endpoints within template files.
The debate has been ongoing for years so I don’t expect my meager comment box to solve anything. Ktor’s style is more like a DSL, which annotations are sort of similar in purpose (avoids explicit code) but end up looking like the problem domain, which is nice to me.
So, by comparison, I think this is simpler than Spring (boot) and closer to other microframeworks.
The thing that started this tiny example thing was Ruby’s Sinatra. Sinatra is the thing that made microframework envy start because it’s version of this is even simpler.
Then, everyone had to be like Sinatra. The cuter the README, the better. But Sinatra/Flask/FastAPI/Express isn’t very cute when you add a database. The thing that Rails really nailed is: conventions and omakase. Rails kind of expects that you might need a database and other common things. Rails was embraced at the time because Struts etc were so heavy on XML config. I haven’t done Java in a long time but I tried Dropwizard with a junior who just needed a Java job and it was mostly XML config churn (to me). It’s unfair to Dropwizard and because I spent so little time on it but I was really surprised since Dropwizard was supposed to be nice. There are some other ones in Java that I think are fine but I don’t really know. But microframeworks in any language … whelp … I posted about that.
Yeah, project generators have a trade-off. But so do copy-paste configs. That’s an interesting topic.
I was afraid to even post the snippet because I expected this rebuttal. You’re right- that example isn’t particularly complex. But it’s also literally the “hello, world” example- it shouldn’t be complex at all, and yet it’s already a little complex- the first obvious question is: “why do I need to annotate @SpringBootApplication and call
SpringApplication.run(Foo.class)
? To the untrained eye, it seems redundant- like only one of those should be necessary for a standard “hello, world”.And, yes, each annotation can be justified by itself (although, I must be missing the part where the response is sent as JSON). But, the point is that there are a lot of annotations- not necessarily that every single one is unjustifiable. And the problem with this line of argumentation (that I admittedly started) is that I can post example after example of an annotation situation that seems tricky or unnecessary, and someone could reply with a justification for why that specific thing is done that way or with an explanation of how that example isn’t actually all that complex, etc. But, the pain comes in the aggregate: when you have to learn 100 annotations and 10 or 20 of them have tricky edge cases or caveats, it starts taking up a lot of mental bandwidth.
Some other funny ones I remember off the top of my head (it’s been years since I’ve actually used Spring) the @Service vs @Component annotations (lol- they’re the same! We just want even more annotations!), all of Spring Data (“How do I make sure a column is not nullable, again?”), and stuff like the @Value annotation just existing…
Vert.x core sits at a lower level, but Vert.x also has Vert.x Web, which is a full-blown web framework, with plugins for all the normal web stuff: auth, cookies, file uploads, redirects, etc. But, it also doesn’t do any annotations. You configure a Router, by adding Handlers; e.g.,
and then attach the Router to a Server and start the Server. You do all of this configuration “by hand” instead of with annotations.
Two things: First, I never made a value statement about whether Spring was worth learning–I simply agreed with a previous comment that Spring is harder to learn than other frameworks that I’ve used. Second, I would counter your general argument by saying that if you have two tools that are roughly equally productive, but one is much harder to learn to use, then yes, that would make the harder-to-learn one a worse tool.
I have to agree on a) Vert.x being lower level than you’d like. (And a royal pain in the arse with the whole event loop, I had to use some god-awful hackery to write tests that verified code that had been blocking the event loop no longer did, because the only way I could hook into the code that checks what’s blocking involved some ninja shit at a classloader level that was brittle AF to override the logger it used, because that was the only way to get into that code. A few versions later, Vert.x did make the event loop blocking checker easier to access, but at the time, kill me).
And b) Spring Boot being comparable to Django et al.
I’m not at all a fan of Spring anything, but Boot’s magic is no worse than Django’s, possibly even less worse.
And, unlike Django, at least in Spring Boot, I can easily separate out the model, and switch out ORMs if I choose.
Django’s “it just works” comes at a massive cost - ridiculously tight coupling.
I’m not sure this is universally true… if you are trying to secure your initial round of funding and find a product fit, I think it does make sense to optimize for the later.
This is what I love about working with Elixir and Phoenix. You get most of the Rails golden path for rapid iteration without sacrificing a solid foundation for future growth should you succeed and grow your application.
I feel really similarly about Python (for web backends, specifically, but probably also any large project). I can’t wrap my head around how anyone can try to write and maintain a large, long-lived, project with Python. And if you do somehow get a bunch of people to agree on conventions and style, and write reasonably correct code, your reward is just like you described for Ruby: really bad performance (both execution speed and memory consumption).
I’m working on a Django project that still has the occasional comment in code of “#when we upgrade to 1.0, we can fix this”, so you know, it’s old.
And hoooooly fuck, the complexity is out of control, this project is earning millions PA, but everyone’s terrified of changing code because it’s really hard to refactor Python in confidence without great test coverage, and Django’s “unit tests” aren’t, they always spin up the whole app and require a DB. Sure, you can use SQLite, but a) there’s some fun incompatibilities been that and whatever DB you’re using and b) it’s still slow AF.
I think my ragiest moment I had with Django is when I realised that the Django test runner’s test discovery only considers test case classes that subclass a Django test class.
You created a test class that derives from
unittest.TestCase
? Because you’re just trying to do an actual test that doesn’t need a DB and the kitchen sink?Django’s test runner has no time for your lack of faith, so it’ll ignore your tests that don’t inherit from a Django test class because… …I don’t know why, fuck you, that’s why, is my best guess.
So your unit tests could be failing, but you’ll never know if your CI pipeline uses
django test
.I’m trying to bring in
mypy
to provide more confidence, but because Django is so “open minded your brain fell out” enamoured of metaprogramming, it’s ridiculously hard, there’s no type annotations in the Django code, no official type stubs in Typeshed.I absolutely loathe any Python class that implements
__new__
these days, because of Django and its metaclass fuckery.I used to love Python, I’m a self-taught dev, it’s what I learned with before getting my first job in a Java shop. Now that I’m working on a large Python codebase, I really miss the JVM world.
It’s funny, I got onto Ruby after about a decade of Perl, because Perl was sooo unmaintainable, omg write-only language wtf are all these dollar signs. In 2024, there’s cultural/community/hiring reasons not to use Perl, but I think it compares favorably to Ruby in long-term maintainability at a language level.
Not necessarily: if you write a bad system then someone will come along and want to do a re-write. If they quickly prototype something and it gets traction then someone will try to rewrite that. This may sound like a vicious cycle, but since everyone is writing new systems everyone gets promotions. Maybe that’s the real value prop.
Have they even mentioned improved accuracy?
There’s a chart of scores in the Model Evaluation section, each methodology and scoring system would have some component of accuracy but you’d have to go find those details.
I think this is a nice curated list of manual steps and suggestions for tools. To me, it’s like an early design doc. The problem with manual steps and long READMEs is that they have no version. Let’s say I do all of this and this advice changes, am I on “Mar 2024 as written tech independence stack”? How do I get to the new thing when
mutt
is replaced byneomutt
or whatever?If this was called
tech_independence-v0.deb
then it could have a version and there would be a name for it, a diff, a migration something, updates, improvements, replacements and other changes as the world changes. As it stands, I think it’s like a design doc for a meta-something. Unfortunately, it’s really hard to ship compositions. :( If you look at some homelab bootstrap repos, it kind of reads the same but they attempt to have scripts. They still don’t have a way to name a meta-version but at least there is a git ref and each component’s version is known usually.This one is also personal pain avoidance (like others mentioned) and admittedly boring. My manager wanted a report in a certain format (.xls) but it was basically devlog stuff or
.plan
type of information. It was just annoying to do every week. I had the data, I just needed to make a spreadsheet dated in a certain format and email it.I automated it in Ruby (this is a long time ago) with a spreadsheet gem. Pretty straight-forward. I did the emailing myself (using a template) and this was way easier than doing it by hand or forgetting to.
What I thought was funny was what my manager said after a few months:
First one that comes to mind was that I used to have a small automation that rotated my Twitter profile picture by two six degrees every minute, so that each hour it would make a full rotation. I shut that down when I left Twitter, though.
Nowadays the most useful automation I have is a daily script that sends me a push notification whenever the Cubs are playing a home game and the Red line will be packed.
Wait, would it do image manipulation stuff? Or would it update a URL or the asset of a set of pre-rotated pictures? I guess you got around image caching problems?
It rotated it with a Python image library.
If Ruby had threading, actors, anything then you’d probably at least consider inlining the process (maybe as async or as a channel) and then @vhodges is right. Even if you had a Job abstraction with retries and other features. In Ruby, we use Sidekiq etc very early (imo) to have a sister app process in the middle tier because we don’t have a concurrency story. The same is true in Python and (less so) Node.
However, I’ve been thinking about what you are saying beyond the runtime nature of the app in this gist. I think events are a 4th tier, even in a monolith. There’s usually some very important business event buried in the app and if it was extracted, exposed or sort of emitted out of the system then other neat things can happen. If you look at other long-lived processes like games and GUIs, this is usually how they are built or at least events / callbacks / reactors etc are very useful because things are happening while the thing is running. I think servers follow the same pattern. Emit events to decouple.
Some random thoughts your comment made me think of:
If you run the tasks as threads, actors, multiprocess, etc on the same host (or container or etc), then you run the risk of the task getting lost if the host goes down. Moreover, since the load balancer isn’t aware of async tasks, it becomes likely that the host will get terminated in response to a scale down event while the async task is running.
At a minimum, you will need to persist the task (on a database or queue—not host or container ephemeral storage) and you will need a task runner that pulls from your persistent task queue. If your tasks are idempotent and you can tolerate a fair bit of latency in some cases, then it’s probably okay if they run on the same host as your main application because if the host gets deleted some other host will pick up the task after it times out, but at this point you’re well on your way to building a task system.
It’s a good point. Blocking the thread might have knock-on effects in an auto-scaling or ephemeral cloud environment or something with a health check like a load balancer. Depending on what the job represents, you might want to persist it (and have job-ish features beyond). I feel like single thread languages would add a worker system because you notice this sync pause even on a development server. Do you think sync pauses are noticed like this? I think Node is sort of a strange and confusing one and might not be observed having issues until in production.
Or you might think promises make Node.js parallel just by itself, with no other considerations needed. And on the other side of the coin, I once wondered since Elixir has OTP, I don’t need a job queue, right? But then oban exists. Just random thoughts too. :)
A login form is a great example! Imagine something like the following:
What’s wrong with this?
find_or_create_unconfirmed
…send_template
…(This is why we use
find_or_create…
rather thancreate
; junior engineers trip on this all the time!)render_template
fails or timeouts, then the client will (eventually) show a failure and the customer can retry if they choose. But if they do, they’ll receive another confirmation email. That’s not good!/signup
has become an unauthenticated API for spamming. We could put a rate-limiter aroundsend_template
; but, what happens when the rate-limiter fails? And so on and so forth.In short, the general idea is to do a little as possible in the request-response hot path and move as much as possible into tasks. We have just so much more flexibility in how we deal with failure in tasks.
I’m sort of hesitant to jump in and comment. It’s been a week.
I’m sure the hardware difficulty and requirements will decrease year over year. What used to be rare and difficult will be easy and commodity. And we’ll find optimizations or tricks like quantization (as mentioned). We’ll have a tiny device similar to a RPI running something previously very difficult as a technical meme, then create a new challenge for ourselves. The current and future problem in my view is data. There’s no Moore’s Law for data prep, training practices and evaluation. Training a language model at home with public data isn’t much different than using existing services. Adding your own data is not guaranteed to make the performance better. Finding out if your model is any good could be ad-hoc anecdotes or you could use metrics. Both are tough. Using metrics is difficult. Using metrics is what the service that you are perhaps avoiding is using. Sometimes, they have used anecdotes themselves (HumanEval).
On top of that, you have the work that OpenAI did with humans to refine the chat interface, curate responses and make ground truth in a sort of mechanical turk, massive effort kind of way. I don’t think we can or should distribute and re-create that effort. That said, I think many teams and organizations could get a lot of value out of leveraging their private data. But I think many orgs don’t know how hard it is yet. Buying GPUs is just the first gate to finding out. This is what worries me, that there’s the large GPU spend and then realization?
What I think is interesting is the business model of answer.ai and how it compares to others. I’m not saying we shouldn’t have McDonald’s at home. I’m asking, where are the results? Where is the success? I have heard of small model success, but in the large language model space? Does anyone know of one or one underway?
Well, what means “proficient”? The topic of what “language proficiency” even means is open, even in general linguistics: https://en.wikipedia.org/wiki/Language_proficiency
I have very broad programming language skills and know only a few very deep (Ruby, Rust and Java, chiefly, JavasScript, HTML, CSS[1], too). However, this stems from my previous endeavour: working close to databases and logging systems. This means that I need to know enough about almost any programming stack that a client could throw at me to made the database interaction work well.
Also, I’m very interested in programming languages as an artifact in an by itself, which means I can probably also pick them apart on a higher level than needed for most programming tasks. So I have a broad experience with things I had tried out.
Recall is another interesting thing: it works slowly, but recalling works much faster than learning afresh. e.g. if I were to pick up Haskell up against today, I have a model of how Haskell works. I however have probably completely forgot the exact syntax. But that only means I lack exercise. I can read Haskell just fine however.
[1] Yes, both HTML and CSS are languages and I’d even pick a fight that they can be seen as programming languages - turing-incomplete languages have a lot of utility.
Great question, I was wondering the same thing so I made my own definition. A TODO List.
Pretty boring. I took notes of what I looked up etc as I did them. This repo is not current with what I consider in my toolbelt.
IMO it’s a bit similar to natural languages. The ones you use daily you get proficient with but if you ever stop using them they fade in memory. At the same time it’s really hard to get traction in a language that sounds fun to learn but gives you no opportunity to actually use it.
I was really competent in at least 3 programming languages that I have thoroughly forgotten since. At any time tho it doesn’t feel like am proficient in more than a couple languages simultaneously, but maybe it’s just my tiny head. The concepts you learn however, the tricks and techniques still leave useful residue which is transferrable across the jobs.
I wonder if there’s also an analogy to “native” languages.
I can be quite competent with a few languages at a time, but if I switch back to an old one I need some time to refresh. But I never seem to forget how to read and write C, even with a break of a few years.
Now, maybe that’s just because there’s not a lot to C in the first place. But I first learned C at a young age and then used it intensely at school and my first professional job, so I sort of “grew up” with it. Maybe it’s my “native language” equivalent?
At least for me personally, there’s something to this. 11 years ago I learned to program in Ruby, my first language. Since then I’ve written almost exclusively Go and Rust at my day jobs.
I still feel like I “think” in Ruby. Code in my primary languages is idiomatic, and no one would pin me for a Rubyist by looking at it. But if I design an algorithm on paper, it definitely takes on the general shape of the pass-a-block/lispy style of Ruby that was popular when I learned. With some type annotations or pointer/deref operators added for clarity.
I have no doubt I could sit down and write vanilla Ruby just fine.
I’d say yes. There are videos of hyperpolyglots talking about language maintenance . I think it’s a strong analogy.
I’ve had that similar feeling of knowledge fading out after a few years without regular use. I think it’s very similar to natural languages! I started using Anki about 10 years ago for ASL. That went so well I expanded into programming and other topics.
For example, roughly once per year I inspect a sqlite db to debug something, and that’s just not often enough for the dot commands to stick, so I repeatedly had the frustrating experience of having to look up commands with the full knowledge that I have done so many times before. I made Anki cards for the couple commands useful in my debugging (
.dbinfo
,.schema
,.dump
,.recover
,.mode csv
) and now I always recall them when needed.I also make cards for languages and tools that I do work regularly in, mostly for features I use infrequently or corner cases that have led to bugs. I suspend cards when I’ve stopped using the language, and I delete the cards about a job’s proprietary tooling.
Anki has primarily been a timesaver, the rule of thumb is that you’ll spend about 5 minutes studying a card over your life, so it’s worth making cards that you’d spend more time looking up. But it’s more than remembering, I feel I also design better systems because I have more concepts and tools comfortably to hand.
I’m not sure why I’ve never considered using Anki for this kind of thing before. I’ve used it for (spoken) language learning to some limited success (limited due to my own efforts). I also used to way back when I was learning boolean logic for the first time, to great success.
Using it for something like SQLite dot commands is genius. I think I would also benefit from using it for the more esoteric Linux commands I used infrequently enough to have to look up how to use them every time.
What do your SQLite cards look like?
The front is the thing I want to do, “export data in csv”, the reverse is the dot command,
.mode csv
.In a typical software developer career, the limit is certainly in the single-digits, but that’s not due to any hard limit in the human mind; it’s just because it’s extremely unlikely that an employer would pay a developer would get the opportunity to use a large number of languages nontrivially. If you did find some kind of dream job where using a wide variety of languages were part of your responsibilities, I’d imagine a skilled developer could get it up to 20, given that there’s a lot of overlap between languages on the same runtime, (Erlang, Elixir, LFE, for instance) or similar paradigms, etc.
Maybe it’s orthogonal. Maybe you could integrate the two with GPT plugins. If infoboxer really does something novel and you think it’s unique or interesting, it might not be invalidated. Also, ChatGPT or any company can still enshitify, we don’t know what’s going to happen.
There have been other sort of sources or truths, parsers or attempts at world modeling. There is ConceptNet, HowNet and WordNet. I think these things still have value in the era of LLMs because ChatGPT still does not have a world model perse or it’s possible that the party trick of attention is all you need ends and some other technique is needed to continue performance increases.
This tale reminds me of The Bitter Lesson which is bitter for complex reasons. I think the thing to ask is, if computation doubled, would this project benefit from it? If computation in GPUs doubled next year, would it still?
To this day I still think the JQuery APIs for interacting with the DOM are much nicer than the built-in browser alternatives.
Most of the examples at https://youmightnotneedjquery.com/ seem to just reinforce that idea
Shame the library gets a bad rep because of the spaghetti that developers created.
Thanks for posting the youmightnotneedjquery link, I was hoping someone would. Although I guess you are saying the opposite of why I wanted it linked. I think it’s a great site for when I don’t want any dependencies, for very small projects and I want to do the thing I used to do. I use that site to translate I guess. If the DOM manipulation is getting bad then I’d move up to React or something. But hopefully in these small projects, I am doing very small things. Contact Us form. Something trivial.
In my view, developers could do nothing else other than create spaghetti because there was no direction or opinion. That’s what backbone.js was at that time. It was the structure (backbone) to jquery projects. It was always possible to make some kind of organizational system yourself but I only saw it done once and even then it was bespoke.
What’s the natural pivot or lateral? Solid so it’s very React-like or is that too close? Vue because it’s more different and there are many great tools coming out of Evan’s head? Vue because it has a Next analog? Svelte because it’s even more different and compilers are a good idea? I wonder what OP would try next.
Reminds me of an early career job I had. I hadn’t thought about this stuff in a while. Getting access to the building, understanding the ticket (restore the right drive), dealing with customers’ data loss in retrospect / mourning while also deciding or not-deciding to be educational (don’t). As a job, I think it’s really tough but I think you gain lots of insight about how people work and what they value. You are overhead, you are a generic IT person but that means you can slip in the door and meet Oprah without an interview or any relevant domain experience. But it also means when you’re done, they say thanks and that’s it. You get a cool story. I think it’s interesting how much lateral slipping-in there is. I’m sure there are other fields that have similar lateral slipping-in but within digital information jobs, this was my take-away from being a traveling repair person for a bit.