HN Simulatornew | past | comments | lists | submit | mrkeen's commentslogin

> A bigger problem is the tedious necessity of having to reconstruct the desired state every single run. When you have something small with limited functionality, that is fine, but as your application grows, rebuilding the state can take a significant effort.

This is what tests are for. There should be no buildup of internal state that can't be arrived at with a simple invocation in a unit test.


Tests help to a point, but anybody who's worked on a large project knows that unit tests miss a lot of stuff because they don't focus on end-to-end relationships in code. There's a whole genre of memes about having unit tests and lack of integration tests for example. Of course you can add, integration tests, and regression tests, and so on. And you should, but doing that in the middle of you figuring how to best implement a particular feature is a lot of drag.

Often, when you're building something new, you're not sure what the best approach is. So, you need room to experiment and try different things to see what works best. Writing tests boxes you into an approach out of the gate.


Sure but do coding agents need this? They can create fixtures or scripts very quickly. No offence, but this article reads like a list of reasons you like Clojure.

I like many of the ideas in Clojure, but I never learned it, so it doesn't matter to me what advantages it provides to an agent if I can't understand the code its creating. This year I've done a lot of work modifying open source applications I use with agents, some in languages I don't know. One is Metabase, which is written in Clojure. Another is Forgejo, written in Go. I had no real experience with either language.

Go was much, much easier to understand in that context. I could review the agents changes and follow data flow, follow tests etc.

And no, its not because I'm more familiar with similar imperative languages. The last 14 years I've written the most code in Elixir and Haskell. Lots of Javascript, C++ and Python as well. Admittedly no Lisp other than a bit of emacs but semantically Elixir is probably closer to Clojure than most Lisps are.


In my experience they do. I use LLMs a lot, and one of the most common failure modes I see is that they implement stuff, but fail to wire it up end to end, or don't consider the broader context. The features of Clojure I outline in the post directly addressed the failure modes I've experienced using most other languages.

In fact, I've actually tried using Python and Js with LLMs initially because my logic was that these languages are more widely used and there's more training data. It's been a miserable experience for me, and I'm having a much better time with Clojure. I'm sharing my own experience here having actually used both types of languages, and the benefits I see in my workflow.

The fact that you're unable to learn Clojure is very much a you problem. I worked at a Clojure shop a while back where we hired university students regularly. They were able to pick up Clojure within a week or two and write useful code with it. The fact that somebody with over 14 years experience can't learn this language is absolutely surreal.


I never said I can’t learn it. I said I haven’t.

My point is for someone who hasn’t learned the language, Clojure is one of the most inscrutable languages you could choose to use with an agent, and I doubt people who don’t know it will put the time in at this point. I’d expect exactly the same is true of Haskell. I know it well, so sure I can use it with AI, but I wouldn’t expect many will learn it now.


Inscrutable to you does not imply inscrutable to everyone. Haskell is a different beast since it has to type check everything in a fine grained way.

Having built a ton of software with agents using Clojure that's huge news to me. Seems like you've already made up your mind though, so it's clear that you don't care to learn from people with actual experience or have a rational discussion on the subject. You do you.

I probably haven't expressed myself well. Your experience with Clojure is exactly the reason you haven't experienced what I'm talking about.

If you haven't used Haskell, I'd encourage you try to adding a small feature to an application written in it, such as Pandoc. Tell the agent what you want, then try to understand what it did.

Personally I love Haskell, but I think its over with in the age of agents. People will not put the work in to learn it because telling the agent it to do it is so tempting, but you've really got to struggle with it quite a lot to get over the initial hump.


If you actually bothered reading my post, you'd see that it is about why it's worth learning Clojure. Nowhere am I suggesting that you should let agents run wild on a language you aren't comfortable using. And the only people I've seen actually struggle with it are largely those who're deeply invested in the imperative style.

I actually started my FP journey with Haskell, and I found Clojure very easy to pick up after learning it because most of the concept transfer directly, and it's a much simpler language. If you love Haskell, I'm very curious what challenges you ran into that students I worked with, who had little to know programming experience, didn't.

I fully expect that people will, in fact, continue to learning new languages going forward. And my bet is that people will see value of working with high level languages like Clojure in agentic workflows. Imperative languages focus on low level details which are precisely what the LLM is great at automating. Languages like Haskell or Clojure focus more on declarative logic, and that's what the developer will need to understand going forward. But the advantage Clojure brings is the live environment which speeds up the development loop.

Your thesis appears to be that the language doesn't really matter when the agent writes code, but that couldn't be further from my own experience. I find the agentic workflow with Clojure is far superior to anything else I've tried for the reasons I articulate in the post. I built a large Rust app using LLMs, and all I can say is I'd never touch Rust again if I can help it. https://github.com/dirge-code/dirge

And of course, people have been talking about the demise of Lisp since before I was born. Yet, it's still here, it's still as useful as ever. And I doubt languages like Clojure are going anywhere in the future.


You have some expectations of the size of the bool (it's simply 1 bit but also 8 bits.)

What expectations do you have of the value?


Bool should be logically 1-bit, when stored in memory only least significant bit should be used and the rest is allowed to be garbage. Such approach gives compilers as much room for optimizations as possible. Forcing them writing some specific bit-pattern may lead to suboptimal code generation.

Bool is 1-bit, but that bit can be defined as signed or as unsigned.

If bool is defined as unsigned, casting it to any size of integers will give 0 for false and 1 for true (using the standard zero-extension operation that converts smaller unsigned integers to bigger unsigned integers).

If bool is defined as signed, casting it to any size of integers will give 0 for false and -1 for true (i.e. an all-1 bit pattern) (using the standard sign-extension operation that converts smaller signed integers to bigger signed integers).

Defining bool to ignore the other bits except the LSB leads to a lower performance on most processors, because in almost all instruction sets it is more efficient to test whether an integer is null or non-null, than to test the value of a bit.

The only efficient way to use a single bit and to ignore the others would be to store the boolean in the most-significant bit, i.e. in the sign bit of a signed integer, because testing the sign is normally as simple as testing whether a value is null. If this convention were used, a boolean result could be 0 for false and -1 for true, but in input arguments negative would be true and non-negative would be false.


« to test whether an integer is null or non-null »

Shouldn't this more correctly read zero or non-zero ?


Null and zero are synonymous, but null is preferable when used as an adjective and zero is preferable when used as a noun.

There are 2 words for the same concept because "null" comes from Latin, while "zero" comes from Sanskrit through Arabic.

Etymologically, "null" means "not even one" (by being a diminutive form of "not one").

The use of "null" in some programming languages to mean things like "undefined", "not applicable" or "nothing" is incorrect. For those the right choice is NIL, as in LISP (NIL means nothing).

While LISP had made the right choice by using NIL, it made later the mistake of calling NULL the predicate that tests if something is NIL. That predicate should have been called something like "is_nil".

When C.A.R. Hoare had introduced the word "null", he applied "null" to references, i.e. to pointers, not to the things pointed by those pointers. So a "null" pointer, whose value is zero, points to NIL, i.e. to nothing, and this is an alternative to devising an encoding for the things that are pointed to, where a special value is reserved to encode NIL (like the Not-a-Number values of floating-point numbers).


I presume you didn't make all that up, but which authorities did you consult and under which circumstances would which audience immediately agree with you on all points?

In British English nil means zero as in a nil-nil draw in football. In this case it is effectively an ordinal number and it means none, not nothing.


In correct British English "nil" means "nothing", not zero, as can be checked in any decent English dictionary. See e.g. at https://www.etymonline.com/search?q=nil

> nil(n.) "nothing," 1833, from Latin nil,

In at least several other European languages a draw in football is correctly called "null-to-null".

If in UK it is now called "nil-nil" that must have originated in people with poor knowledge of English and then it was imitated by others.

Both "null" & "nil" come from Latin, where their meaning is "zero" and "nothing" (they are contracted forms of "ne ullum" and "ne hilum"). When first borrowed in English, they were used by educated people, who knew very well their original meanings.

Nowadays, most people have no knowledge about classic languages and poor knowledge about their own native language as it was spoken a few decades earlier, before the widespread influence all over the world of sources that display frequently incorrect language, e.g. TV shows, Hollywood movies, and now YouTube and the like, which leads to many cases when words are used in inappropriate ways. This is annoying because it results in ambiguous language, whose precise meaning cannot be understood without additional verbose explications of what is really intended and it also results in misunderstandings when people read the older literature.

In mathematical language, "null" has always been used correctly, for "zero", and this is the meaning that should have been inherited in any programming language, but unfortunately most popular programming languages are full of mathematical mistakes.

In non-mathematical English language, "null" has acquired additional meanings, e.g. in legal language it may be used for an invalid contract, sentence, law, etc.


x86-64 and ARM64, at least, let you test an arbitrary bit in a register with one instruction.

The fact that bit testing also needs one instruction does not mean that it is equally efficient.

On x86-64, there are 2 ways to test the value of a bit. If you use the bit testing instruction (BT), that instruction is both longer and slower than testing if a register or memory value is null.

If you use the test-under-mask instruction (TEST), this is fast, but the instruction is significantly longer (by including an immediate constant for the mask). Longer instructions can also cause lower speeds, when various bottlenecks are encountered, e.g. the maximum number of bytes fetched per clock cycle or the capacity of the instruction cache or of the micro-operation cache.

Moreover, testing whether a value is null frequently requires zero instructions, not one instruction, because if the value is the result of computing some expression then the flags register already stores if the value is null or not (and its sign).

On ARM Aarch64, the instruction that tests a bit in a register has a much smaller jumping range than the one that tests whether the whole register is null, so testing a bit in a register may require the insertion of an extra jump instruction to reach the target where execution should continue.


Once upon a time testing whether all bits of a number are zero was slower than checking a single sign bit. Even when MIPS was originally designed, Hennessy and his team had some trouble with making BEQZ/BNEZ fast enough for their intended pipeline.

This happens because testing the sign bit needs just a wire from that bit to the flags, while testing if a register is zero requires a wide OR gate with as many inputs as there are bits.

In CMOS you cannot have an OR gate so wide, so it must be synthesized from a cascade of narrower gates, which add several levels of delays.

While in modern CPU technologies the speed of generating a zero flag is not a problem, when designing with FPGAs, which are much slower, it is useful to be aware that testing for the sign is cheaper than testing for a wide zero.

Many CPUs have an instruction for implementing loops like decrement-and-jump-if-not-zero (which is LOOP in x86-64). When implementing a simple CPU in an FPGA it is cheaper and faster to replace that instruction with 2 instructions for loops like increment-and-jump-if-negative and decrement-and-jump-if-not-negative (it is good to have both these instructions to be able to access an array both in forward order and in reverse order, while using the loop counter also as index register).

The same applies when making a counter in FPGAs, it can count at higher frequencies if you test for the sign bit to determine the end of the counting, instead of testing when the count reaches zero.


This reminds me of my favorite arcane C test: what value is TRUE and FALSE on X bullshit tool chain. My a favorite was 0=TRUE, 2=FALSE. I'd like to shake the hand of the joker who came up with that.

The in-memory representation is a completely different question. The language could easily say that true has an integer value of -1 while still storing it as a single bit.

That's how gcc does it (at least in this one case), but as TFA points out, this is not standard-conforming.

I vaguely remember that Clang and GCC used to disagree what the contents of the upper parts of x64 registers when returning some integer types should be (zeroes or garabge), because the PDF that defined Sys V ABI on x64 left such irrelevant details out, so linking together objects produced by those compilers, both of which claimed to follow the same ABI, would produce malfunctioning executable.

> Forcing them writing some specific bit-pattern may lead to suboptimal code generation.

So? Forcing them to compile "return 42;" as "mov eax, 42; ret" also leads to suboptimal code generation: a plain "ret", returning whatever is in rax already, is optimal. It doesn't generate the specific bit pattern for 42 but that's a small price for the improved efficiency, isn't it?


To support the ordered use-case, put events on the same topic-partition. To support the unordered use-case, don't.

If you built two impls, your compiler wouldn't know which to pick. Or to phrase it differently, if you wanted to be able to choose the right one, it wouldn't be 'orphan impls', it would be 'orphan impls plus some selection mechanism'. E.g. Scala implicits. And once you have Scala implicits, non-orphan impls probably start looking like a pretty sweet alternative!

I'm absolutely stuck on your comment that trees are the wrong approach, or that trees are somehow incompatible with precedence. They encode the precedence more explicitly and unambiguously than the original string itself.

Trees being incompatible with precedence is like parentheses being incompatible with precedence.


>I'm absolutely stuck on your comment that trees are the wrong approach, or that trees are somehow incompatible with precedence. They encode the precedence more explicitly and unambiguously than the original string itself. Trees being incompatible with precedence is like parentheses being incompatible with precedence.

IMO, trees are not incompatible with precedence but I just prefer a more barebones approach to this fairly simple problem.


What's more barebones than a simple tree structure as used by essentially every compiler and interpreter, at least for one stage as an intermediate representation, out there today? (If they cover precedence, Forth implementations for instance don't care about it so don't need to use an AST even for an intermediate representation.)

Agree with 'solve it during parsing'.

I had a look at the link. The BNF looked good:

  Expr =
    Expr '+' Expr
    ...
Then the author fixed the left-recursion and precedence (which also looks good,) but then complains about the fixed version - "the “shape” of expressions feels completely lost in this new formulation." :

  Expr =
    Factor
  | Expr '+' Factor
  ...
Then the author takes us through Pratt parsing and ends up at:

  fn expr_bp(lexer: &mut Lexer, min_bp: u8) -> S { 
    let mut lhs = match lexer.next() {
        Token::Atom(it) => S::Atom(it),
        t => panic!("bad token: {:?}", t),
    };

    loop {
        let op = match lexer.peek() {
            Token::Eof => break,
            Token::Op(op) => op,
            t => panic!("bad token: {:?}", t),
        };
    ...
Yikes! I think he criticised the wrong code. I'll take the '{expression} is a {factor} or an {expression plus a factor}' formulation over the 'mut-loop-peek-panic-lexer-next' approach any day!

* You define initial states and all possible state transitions.

* It will brute force all states.

* You can add a variety of assertions.


Closer to mvar. Tvars support full transactional semantics.


You may not have intended it this way, but your top paragraph could easily be repurposed as a talking point for the current administration. What does the I in ICC stand for?


If that's a talking point it's a stupid one. The Rome Statutes are only valid in territories that have agreed to be bound by them.


It isn't bound, Hamas didn't agree to be bound by ICC.


And the local drug dealer says he's a sovcit but is still somehow in jail. What's your point?


But the equivalent don't hold there is no one enforcing ICC warrants in Gaza and Yahya Sinwar wasn't in jail.


Game programmers have long been the producers of the most impressive applied computer science.

The film Shrek 3 (2007) took 20 million CPU hours of render time. Games push 60 frames a second. For a visual comparison, check Call of Duty world at war (2008).

Also compare to browsers, which can sometimes scroll smoothly through some styled rectangles and text, and consume gigabytes of ram if you have a few tabs open.

Games have directional sound effects and soundtracks. Don't need 800 Spotify engineers to pull that off.

Multiplayer games solve crazy distributed system problems, making it feel like 'now' when players shoot each other, even with historical latencies of 100-200ms.

AI (in terms of LLMs) seems to be a continuation of that. You used to be able to play 7 AIs on 1998 hardware, at a distinctly "non-beginner level".


Game developers cheat like there's no tomorrow, though. In the videogame 3D graphics space, the old mantra was, "if it looks right, it's right".

That barrel you shoot, is really half a barrel when you're up close, a flat rectangle when you're far, a point-with-mass + a vector for purposes of physics, and not even there for purposes of AI because pathfinding uses a precomputed graph of nodes that's carefully aligned with the map so you don't notice the enemies can noclip through everything other than floors and walls. Etc.

And yes, many games would have scripted enemies or other events come out at you so you don't linger in particular areas too long, lest you spot some of the shortcuts they made.

I grew up wanting to make games, spent my teenage years in hobbyist gamedev communities, and to date, this remains to me the most enjoyable and pure form of exercising software development skills.


Guidelines | FAQ | Lists | API | Security | DMCA | Apply to YC | Contact

Search: