Live data from Hacker News

After 7 years in production, Scarf has reluctantly moved away from Haskell

avi.press

191–200 of 320 posts

Re: After 7 years in production, Scarf has reluctantly moved away from Haskell

#191
post #17

Wow. Not a Haskell user, but a big user of other languages with expressive type systems (mostly Scala; some Rust). My experience is the complete opposite. I can't imagine using a language without a good type system to catch all the junk the LLM produces. In fact I thought people would move away from languages from poor type systems, like Python, given the cost of using languages with expressive type systems has decre…

Recently had to touch a Python project at work. Just setting up the editor needed me to use 2-3 tools out of: pyright, basedpyright, ruff, ty, mypy, and possibly other tools I'm forgetting that kind of do the same thing but throw errors in different parts of the codebase. Also, for some reason Optional[T] became deprecated, just as the ecosystem finally embraced types ~3 years ago. In fact, one my company's greenfiel…

> for some reason Optional[T] became deprecated, just as the ecosystem finally embraced types

Deprecated in favour of `T | None` exactly because of that embrace. It's cleaner, more consistent (you can `T | U` arbitrarily), and helps slim down the `from typing` imports.

Re: After 7 years in production, Scarf has reluctantly moved away from Haskell

#192
post #174

Earlier quoted context omitted.

Having worked professionally with Common Lisp, I can tell you that’s not even remotely true. Besides, “machine code generation” is not a fundamental part of Lisp. Some of the most famous implementations were pure interpreters.

Such as? Having known Lisp since 1996, and avid digital archaeologist, I wonder which famous implementations are those.

Even Emacs Lisp has had a byte code compiler since forever, and since Emacs 28 it compiles to native machine code via libgccjit.

Toy lisp interpreters are easy and fun and everywhere, often written in lisp itself, but the same books that teach them go on to teach compilers. SICP chapter 4 is the metacircular evaluator, chapter 5 is the compiler. That ordering is the point: interpretation is the pedagogy, compilation is the destination. Lisp was designed to be easy and efficient to compile (so was Self, and that tech lineage runs straight into HotSpot and V8). The archaeology cuts the other way too: the only famous pure interpreter was LISP I on the 704, and within two years Hart and Levin's Lisp 1.5 compiler (1962) became the first self-hosting compiler in any language, written in Lisp, compiling itself. Lisp practically invented compilation as we know it. And today SBCL doesn't even interpret by default: a form typed at the REPL is native code before it runs. Clojure compiles every form to JVM bytecode.

So antonvs, I'd genuinely like to know which famous implementations you mean, because the honest counterexamples aren't the famous Lisps at all. They're the embeddable extension languages, and I can speak to those from direct experience, not just archaeology.

A digression about Elk, since hardly anybody remembers it and Oliver Laumann deserves the recognition. Elk, the Extension Language Kit, was Laumann's Scheme out of TU Berlin, started in 1987 to be the extension language of the ISOTEXT document editor, released to the world in 1989. Its stated goal was "to end the recent proliferation of mutually incompatible Lisp-like extension languages": instead of inventing yet another one, you linked the Scheme interpreter into your C or C++ application and taught it your app's types and primitives. Emacs-style extensibility as a kit, years before Guile existed. And it was a pure interpreter on purpose. Laumann said it plainly in the USENIX paper: performance was "uninspiring (no compiler is available)" and it didn't matter, because any bottleneck got recoded as a C primitive. The compiled half of an Elk system was the application itself. It shipped that way in real products, including the TELES.VISION video conferencing system, which used Elk continuations as its multithreading mechanism. Elk's embedding design went on to inspire other interpreter authors, including Matz for Ruby.

I hacked on Elk back then and wrote the SPARC port, credited in the Elk 1.2 release notes, and it taught me why "just an interpreter" is never just an interpreter. Elk implemented full call/cc the brute force way: capture the registers setjmp-style, then copy the entire live C runtime stack aside onto the heap, Scheme frames, Xt frames, qsort frames and all, and copy it back whenever the continuation is applied. That works on machines where the stack actually lives in memory. On SPARC it does not: the register windows keep the most recent frames cached in the register file, invisible to any memcpy of the stack. So before copying you trap into the kernel to force every window to spill to RAM (ta ST_FLUSH_WINDOWS), grab the stack while it's momentarily telling the truth, and pray it all lines up with the calling conventions when you copy it back so returns don't sail off into the weeds. Frightening! As hairy as anything inside a code generator. The "simple interpreter" strategy relocates the machine-level work, it doesn't remove it: stack layout, register windows, calling conventions, incremental linking against the running executable so compiled C extensions could be loaded into the interpreter, unexec-style dumping of customized interpreters back out as executables. Compiler and linker work in everything but name.

And look what happened to every extension language that survived: Tcl grew a bytecode compiler in 8.0. Emacs Lisp went from bytecode to native code. Guile, after fifteen years as an interpreter, got a compiler in 2.0 and a JIT in 3.0. Interpreters are how these languages are born. Compilers are how they grow up. Even Ousterhout's own student proved it during the Tcl wars: Adam Sah's Berkeley thesis (TC, 1994, first reader John Ousterhout) got 5-10x by caching parsed representations of values, essentially the design Tcl 8.0 later adopted, and his Rush language with Jon Blow (yes, that Jon Blow) compiled a Tcl-feel language to Scheme at 50-300x stock Tcl. RMS cited Rush by name in the GNU extension language announcement. The Rush source is lost now, which is a small tragedy.

Since Guile came up: Guile existed before the Tcl war, not because of it. Tom Lord had already forked Aubrey Jaffer's SCM into an embeddable library at Cygnus in 1993, called it GEL, and talked RMS into blessing it as the official GNU extension language. The rename to Guile came after trademark trouble, partly because it sounds a bit like "Guy L." The war started when Sun swaggeringly declared Tcl would be "the ubiquitous scripting language of the Internet" and a colleague skunked Tom's Scheme-based GDB GUI project with a quick Tcl/Tk one. Tom vented to RMS, and shortly afterward "Why you should not use Tcl" landed on comp.lang.tcl. Who actually wrote and posted it stayed deliberately murky: the Cygnus NNTP server showed it coming from Tom's dormant gnu.org account, some archived messages have RMS in the From line and Tom's signature at the bottom, and Tom would only ever call it a semi-prank by "the Scheme underground."

Why didn't Guile take over the world? Partly because the plan was always grander than the resources: the idea was one Scheme engine executing many surface languages, including a repaired Tcl and eventually Emacs Lisp itself. But Emacs Lisp turned out to be immovable. It isn't just a Lisp, it's a deeply weird Lisp: dynamic scoping by default, buffer-local variables, text properties, semantics tangled into the C core of the editor. There were decades of plans and experiments to put Emacs on Guile, and Guile 2.x even shipped an Elisp compiler on its VM, but the ecosystem never budged, and in the end Emacs went the other way and grew its own native compiler instead. Guile's own arc is instructive: Andy Wingo gave it a real compiler in 2.0, a register VM in 2.2, a native JIT in 3.0, and it found its killer app in Guix. Not the ubiquitous extension language of GNU, but a living vindication of the design argument Tom was making in 1993: a general Scheme engine underneath, other languages and applications on top.

Tom was a good friend of mine. We argued for decades, about Tcl and Tk and everything else, the way you can only argue with someone who has actually built the thing being argued about. He died suddenly in 2022, and the field is short one fiercely original mind. His body of work and his philosophical posts about extension languages, universal engines, and who gets to control the substrate are a rich source of wisdom that hardly anybody mines. His account of the war is him on the page: brilliant, funny, hard on himself, and fair even to the people who did him wrong. Read it, and the detail I love most for this thread: even Tom, patron saint of the embeddable Scheme interpreter, compiled the performance-critical parts of his Scheme editor with the Hobbit compiler. Nobody stays pure.

An Account of the Tcl War, by Thomas Lord:

https://web.archive.org/web/20110102015130/http://basiscraft...

Glenn Vanderburg's archive of the original flamewar:

http://vanderburg.org/old_pages/Tcl/war/

Elk: The Extension Language Kit, Laumann and Bormann, USENIX Computing Systems, 1994:

https://www.usenix.org/legacy/publications/compsystems/1994/...

I was on all sides of that war at once. Tom and I had heated arguments because I was shipping on the other side: I ported SimCity to Unix with Tcl/Tk, and Tk was the driving factor, not Tcl (and Tcl's weaknesses were real, and Tom and RMS's arguments against it were largely correct: I used Tcl/Tk extensively and pushed them in ways they were not originally designed to be used; multiplayer Tk was a challenge, I had to fix multi-head bugs in X resource management and Tcl UI menu and other mouse-tracking code, and I built a fully functional multiplayer coordinating voting collaborative game on top of it instead of just getting it to draw googly eyeballs on two screens at once; I am painfully aware their arguments are right).

But having an extension language at all is a higher-order bit than having a perfect one, especially when you are designing a GUI toolkit, whose concerns partially overlap those of an extension language. Look at what the X toolkit intrinsics and the toolkits and window managers built on top of them abused X resources for: stringly typed nano-domain-specific languages and semantics and protocols between differently named resources in the same file. Key and mouse bindings, user configuration, fonts and colors and measurements and dimensions and scales, state machines, commands, preconditions, postconditions, parameter bindings, and and and I could go on forever. All of that is what a real extension language is for. Tk worked so well precisely because a real scripting language (no matter how arguably weak) was part of its design from day one, so it never had to badly reinvent one, which is Greenspun's Tenth Rule, the curse of every extensible app before and since.

And the ecosystem kept proving the point: Python's tkinter still embeds a Tcl interpreter with each Tk root, assembles Tcl/Tk command strings, and passes them through _tkinter to be evaluated, and you can register Python functions as Tcl commands and trampoline back across the boundary (the same class of plumbing obsession as SWIG: who owns the glue, and what shape is the trampoline?). Tk never really grew a clean pluggable "bring your own scripting language" extension point; Perl Tk, Ruby's tk.rb, and the rest generally still talk Tcl to the toolkit. The design win is simpler and more Self-like than that: assume a scripting language, any scripting language, is available at runtime, and the toolkit can stay small and SIMPLE.

Which loops back to one of the greatest pieces of language design literature the Tcl War left behind, one that historians and archaeologists will study for centuries: what even counts as a scripting language? Ousterhout's answer was the system-programming-language versus glue-language split, two languages for a large system, typelessness and strings as the uniform wire format between components.

Scripting: Higher-Level Programming for the 21st Century, John Ousterhout, IEEE Computer, 1998:

https://web.stanford.edu/~ouster/cgi-bin/papers/scripting.pd...

I even demoed multiplayer SimCity to Ousterhout in his office at Berkeley.

And then Sun, having hired Ousterhout and his team, then anointed Tcl the ubiquitous scripting language of the internet, pivoted on a dime to Java, got its lunch eaten by JavaScript, a language whose deepest debt is to Self, which Sun also starved. The Self people who left applied the ideas at Animorphic, got bought back, and built HotSpot. As an OG NeWS developer I know exactly how it feels to be promised the world by Sun and left on the back porch in the rain, so I have nothing but empathy for the Tcl/Tk and Self teams. I've been posting these receipts for a decade:

https://news.ycombinator.com/item?id=28669698

https://news.ycombinator.com/item?id=25888450

And the one I treasure, a conversation I had with Tom in 2006, two years before V8, about running into Dave Ungar and the irony that JavaScript credits Self for its prototypes while missing everything else the paper was actually about. It's right there in the title: "Self: The Power of Simplicity." Simplicity first.

And behind that, the two hard tricks that made the simplicity affordable: compiling a radically dynamic language to fast native code, and keeping it debuggable at the same time. That last one is the sleeper. Anyone can pick two of simplicity, performance, and debuggability. Self's dynamic deoptimization let you debug fully optimized code as if it were running unoptimized, source-level, mid-flight, and that machinery went straight into HotSpot too. JavaScript took the prototypes, which were the cheap part:

Self: The Power of Simplicity, Ungar and Smith, OOPSLA 1987:

https://bibliography.selflanguage.org/_static/self-power.pdf

Debugging Optimized Code with Dynamic Deoptimization, Hölzle, Chambers, and Ungar, PLDI 1992:

https://bibliography.selflanguage.org/_static/dynamic-deopti...

https://news.ycombinator.com/item?id=33527618

Which brings this all the way back to Haskell and the article. timcobb joked about being driven "from Haskell to Lisp," but that move would arguably have been more defensible than Python, because the real lesson of seventy years of Lisp is that interpreted versus compiled was never the actual choice. Batch versus incremental is the choice.

andersmurphy and jwr have it exactly right about the REPL. Common Lisp and Smalltalk systems compile one function at a time, to native code, inside the running program, in milliseconds. Avi's headline brag, fixing a bug before the customer hangs up, was routine practice in Lisp shops in the 1980s. NASA once did it to a Lisp system running on a spacecraft a hundred million miles away: the Remote Agent on Deep Space 1 deadlocked in flight, and they diagnosed and fixed it through a REPL running on the spacecraft itself.

Lisping at JPL, Ron Garret's firsthand account of debugging Deep Space 1 in flight:

https://flownet.com/gat/jpl-lisp.html

giraffe_lady named the two axes precisely: speed and accuracy of feedback. GHC gives you accuracy at the price of batch-world speed. Python gives you speed at the price of accuracy. The Lisp lineage, from Hart and Levin through SBCL to the Self-descended JITs your JavaScript runs on today, has been demonstrating for decades that you can have both, if you design the language and the compiler to work incrementally, together, as one live system. And I can't resist pointing out that SBCL's native code compiler is named Python, and carried that name years before Guido picked it for his language. Lisp hackers have been getting sub-second feedback from Python since the 1980s. Theirs emits machine code.

CMU Common Lisp, whose compiler has been named Python since the mid 1980s:

https://en.wikipedia.org/wiki/CMU_Common_Lisp

Oliver Laumann and Tom Lord were exploring exactly these tradeoffs with practical, load-bearing, shipping code while most of the industry wasn't looking. The agents everyone in this thread is arguing about are just the newest programmers to want what Lisp hackers always wanted: a fast, honest conversation with a running system. It would be a shame if we rediscovered everything except the part where it compiles.

Re: After 7 years in production, Scarf has reluctantly moved away from Haskell

#193
> The important metric: how long does your entire development feedback cycle take, and what portion of that time is spent waiting on your compiler?

> So far, we haven’t lost much in the switch. The type safety we gave up hasn’t been noticeable in any concrete way yet, especially considering our test coverage has never been better.

Moving to Python means you'll have quicker compile times but now you'll need a bigger test suite which will take longer to run to get feedback after code edits? In TypeScript, you can compile and execute without waiting on type checking, an option like that would help?

Re: After 7 years in production, Scarf has reluctantly moved away from Haskell

#194
post #174

Earlier quoted context omitted.

Such as? Having known Lisp since 1996, and avid digital archaeologist, I wonder which famous implementations are those.

Even Emacs Lisp has had a byte code compiler since forever, and since Emacs 28 it compiles to native machine code via libgccjit. Toy lisp interpreters are easy and fun and everywhere, often written in lisp itself, but the same books that teach them go on to teach compilers. SICP chapter 4 is the metacircular evaluator, chapter 5 is the compiler. That ordering is the point: interpretation is the pedagogy, compilation…

Great comment Don, should have been one level up on the thread chain, though. :)

Re: After 7 years in production, Scarf has reluctantly moved away from Haskell

#195
post #8

Earlier quoted context omitted.

exactly, i find the article a wierd take. i would have thougt that being able to catch errors at compile time is the assurance that the LLM generated code is actually decent. so does this mean that the LLM writes code that is so good that the compiler does not find any more errors? or is it due to the nature of haskell that makes it hard to write bad code to begin with? or just that because the haskell compiler catch…

"proving code correct has been a research topic at some point." It has been an area of active research for 40 years. But almost all the research returned the null result, meaning that the program proving didn't improve code quality (basically it didn't work). Yet somehow a group of programmers, usually fresh out of academia falls for program proving each generation. Strong types do really help but you need a good com…

That's literally the opposite of reality; the research and adoption have consistently produced code of extremely high quality, at great cost.

The problem has always been:

- It's extremely labour-intensive, and even small changes to the code can require an enormous amount of new proof work.

- The skills required to formally verify software are very different from the skills required to write it, and the set of people capable of doing so is much smaller.

- Code has to be written with verification in mind, against a specification that expresses invariants that can actually be verified, and that is coherent enough that you can derive useful high-level properties of the system from it.

You needed two teams — verification and development. Verification would always be behind the development team, while also having to feed requirements and design changes back to the developers. Everything slowed to a crawl.

AI changes this. A coherent, verifiable, useful specification isn't easy to write — but it's far easier to write than the software itself, and AI can do most of that work: both drafting the spec and proving that it's consistent and that the high-level properties you actually care about follow from it.

More importantly, a high-level spec is far easier to read and reason about than the reams of code required to actually implement something. Which means:

- AI does the grunt work of writing and proving the spec; humans only have to carefully review that high-level artifact.

- AI writes code it must also prove conforms to the spec, so humans can be assured it's correct without babysitting the AI.

- Changes are driven top-down: evolve the spec first, then have the AI fix the implementation and re-prove conformance.

Our (very, very large) company is rapidly going all-in on formal verification across projects we never would have dreamed of verifying before; the velocity hit and the man-hour cost were only worth paying for truly critical infrastructure.

Re: After 7 years in production, Scarf has reluctantly moved away from Haskell

#196

Earlier quoted context omitted.

"proving code correct has been a research topic at some point." It has been an area of active research for 40 years. But almost all the research returned the null result, meaning that the program proving didn't improve code quality (basically it didn't work). Yet somehow a group of programmers, usually fresh out of academia falls for program proving each generation. Strong types do really help but you need a good com…

The issue with formal proofs is that we might end up with correct code that does the wrong thing. I mean, something that the market doesn't care about. At the same time a buggy set of PHP scripts does what people care about and captures the market. Think about 20+ years ago and you find a lot of examples.

Well, sure — and the issue with tests is we might end up with well-tested code that does the wrong thing.

Re: After 7 years in production, Scarf has reluctantly moved away from Haskell

#197
post #194

Earlier quoted context omitted.

Even Emacs Lisp has had a byte code compiler since forever, and since Emacs 28 it compiles to native machine code via libgccjit. Toy lisp interpreters are easy and fun and everywhere, often written in lisp itself, but the same books that teach them go on to teach compilers. SICP chapter 4 is the metacircular evaluator, chapter 5 is the compiler. That ordering is the point: interpretation is the pedagogy, compilation…

Great comment Don, should have been one level up on the thread chain, though. :)

You provoked me by being right, and I just wanted to say that I strongly agree, but it's "nuanced"! ;)

I didn't get a chance to post about my memories of Tom in the hn discussion of his death, because I hadn't really processed it in time to comment, so I'm writing them up now and will publish more later.

But for now, a few memories of Tom Lord, as promised.

In April 2010 Tom posted me a link out of nowhere: "That reminds me (why? I dunno) that - hey: hate man made the news."

Homeless ex-reporter opted for Berkeley streets (SF Chronicle, April 2010, via archive.org):

https://web.archive.org/web/20100413124819/http://www.sfgate...

Hate Man was Mark Hawthorne, a New York Times reporter from 1961 to 1970 who opted out of normal society and spent decades on the streets around Telegraph Avenue and People's Park. He wouldn't talk to you until you told him you hated him. From the Chronicle interview: "It's a new way of hating. It's about being straight with people... My idea is to be straight about negative feelings that we all have, which is what hate is, and then you can have a real conversation."

I replied that I loved Hate Man (but that kind of sounds weird, like I totally missed the point), and that I was honored he'd hated on me once and wished we'd discussed it more deeply, and that he seemed like one of the most sensible people walking around Berkeley.

Then Tom wrote something I've been thinking about ever since. He said that back in the day he'd had some really, really bad days, real existential crises, and that "Hate man and several of the brothers in the park basically saved my life." Hate Man by getting him to play his "let's lean on each other game," the brothers in the park by being good listeners to mumbled words, often enough good counsel, and by whooping his ass at street chess. He described how, as things shut down for the night, Hate Man would gather the rough itinerant runaway kids of Telegraph up in a circle in Sproul Plaza, and they'd all chill and keep safe and share warmth and make peace, "all in the name of Hate. Telegraph was very much a zen temple. Hate man a famous monk."

He also corrected the Chronicle's timeline. The article placed the start of Hate Man's street life in '86, and Tom called bull: Hate Man was the first resident of Berkeley he ever heard of, in 1983, in Massachusetts, from someone who was basically trying to encourage him to run as fast as he could away from the preppy high school that G. W. Bush went to: "There's this guy there. And he's really cool. He likes to shout 'I hate you' at people. And he won't talk to you unless you first tell him that you hate him, too." It made no sense to him at the time. Years later, he said, arriving in Berkeley was like walking into the landscape of a favorite children's book he'd read years before. Many of the familiar characters were there.

I told him that was beautiful.

He replied: "I hate you."

I replied: "Go stick your head in a pig, beeeyatch!"

That's a lyric from Share and Enjoy, the Sirius Cybernetics Corporation jingle from The Hitchhiker's Guide to the Galaxy:

https://www.youtube.com/watch?v=_wSBC5Dyds8

That was sixteen years ago. Tom died suddenly in 2022, and last week I finally took my turn again, on his memorial Facebook page: I HATE YOU!!!!

Hate Man would approve. Be straight about the negative feelings first, and then you can have a real conversation. I hate you, Tom. I miss you.

And if you want one minute of the town where all of this made perfect sense, Tom Lord's Berkeley, here is a shirtless dude riding a cow in the wrong direction up Telegraph Avenue:

Cows In Berkeley? Moooo:

https://www.youtube.com/watch?v=10JSPdTDFsI

I love that town. It suited him perfectly.

Re: After 7 years in production, Scarf has reluctantly moved away from Haskell

#198
post #32
post #17

Earlier quoted context omitted.

Recently had to touch a Python project at work. Just setting up the editor needed me to use 2-3 tools out of: pyright, basedpyright, ruff, ty, mypy, and possibly other tools I'm forgetting that kind of do the same thing but throw errors in different parts of the codebase. Also, for some reason Optional[T] became deprecated, just as the ecosystem finally embraced types ~3 years ago. In fact, one my company's greenfiel…

> Also, for some reason Optional[T] became deprecated, just as the ecosystem finally embraced types ~3 years ago. Optional[T] is now T | None. Means exactly the same thing but doesn't require an import. Support for the older syntax presumably won't be removed for a long while regardless.

Optional[T] was always just an abbreviation for Union[T, None], anyway: it's unsurprising that they didn't choose to give it its own syntax.

Re: After 7 years in production, Scarf has reluctantly moved away from Haskell

#199

Wow. Not a Haskell user, but a big user of other languages with expressive type systems (mostly Scala; some Rust). My experience is the complete opposite. I can't imagine using a language without a good type system to catch all the junk the LLM produces. In fact I thought people would move away from languages from poor type systems, like Python, given the cost of using languages with expressive type systems has decre…

The only complaint against Haskell was about long compilation times.

I agree that short compilation times are very desirable, but I do not see why Python must be the solution for that.

I do not know whether Haskell can be compiled quickly, but from my experience, I am very certain that short compilation times are easily achievable for languages with good static type checking, especially with compilers that have different options that allow choosing between fast compilation and heavily optimized compilation.

An optimized compilation may require a much longer time than a fast compilation, but that has no relationship with the programming language used in the source text, but only with the intermediate representation used by the compiler and the target CPU ISA. Usually, if you compare the compilation times of multiple programming languages, all the compilation times with fast compilation options are much shorter than all the times with high-optimization options, so the programming language choice may be less important than the chosen compiler and its command-line options.

When you try to optimize a project by generating many variants with a LLM, I doubt that all those variants will be generated from scratch, completely independently, even if only for the reason that when using a commercial LLM the cost of a completely new variant will be much higher, by requiring many more tokens, so whenever possible it is preferable to generate other variants by just patching previous variants.

Whenever a variant is generated by editing a previous variant, incremental compilation can be used, which should be pretty much instant on modern computers.

Re: After 7 years in production, Scarf has reluctantly moved away from Haskell

#200
post #78

Earlier quoted context omitted.

Python has so many footguns for server work and the world's worst typing system. It sounds like Golang is perfect for your use-case

Golang has to compile the world iirc, so it'll need more and more time and resources as the slop grows in size. Whereas Python just interprets and gets off to the races. Feels like we had this discussion years ago as humans..the false promise of dynamic languages.

True that an interpreted language has a leg up on any compiled language in the arena of compile time, but worth noting that one of Go's primary design goals was improving compile times of massive code bases. Google was drowning under the weight of compiling huge C++ codebases and Go was the response to that (among other things).
Post reply on HN