That's two new languages compiling to Go making HN frontpage in as many days. It seems people like everything about Go except the language itself. Me? I like everything about Go including the language, these transpiled languages are interesting though.
But I keep wondering if they could integrate at a lower-level than the source code. Like how JVM languages integrate at the bytecode level, or LLVM languages at the LLVM level
> But I keep wondering if they could integrate at a lower-level than the source code.
I’m sure they could, but targeting go source code has the benefit of giving early adopters an escape hatch. If it targeted LLVM directly, I would never consider using this at work since the risk of it being abandoned is too high. But since it targets go source, I would perhaps consider it for some low importance projects at work.
I like Go as well, but I wish the Go team were slightly less conservative about language changes. Only asking for 10-15% less conservatism. It is OK to add one proper new language feature and one good standard library package per year. Or deprecating one language feature and one stdlib package every 2 years.
> But I keep wondering if they could integrate at a lower-level than the source code.
Unfortunately nothing below source code level is stable, so they would constantly be chasing changes after any Go release. I personally wish they would focus on making it accessible, as Go actually has a nice runtime and would make a good language target.
> But I keep wondering if they could integrate at a lower-level than the source code.
For my version (aptly named "Goto" [1]), I forked the go compiler with the intent of keeping it up to date.
All changes I made only apply to .goto files, .go files compile exactly as is and are interoperable both ways with goto.
I paused the project due to hitting type-checking bugs when trying to add non-nillable pointers. Everything before was just desugared directly when converting to AST, but for those I needed to touch the typechecker which was too time-consuming for a hobby project back then (pre-coding agents). I might give it another go sometime as I did like the full interoperability aspect.
> Go rules! It really does. But I HATE writing/reading Go.
Same. I love the Go toolkits, the compile story, the speed at which it compiles, its backwards compatibility, the fact that stale Go code 10 years old still compile and run, etc., just don't care much for the language itself.
I wonder if the positive attributes of Go aren't compatible with clever types and other developer-friendly features?
It’s mostly that Go was already pioneering how to build a programming language that had an amazing scheduler, garbage collector, compiler, package manager, formatter, etc. They spent all of their “innovation budget” on the most important—and most neglected—features of any programming language and allowed the language itself to be pretty boring.
Eventually Go’s runtime and tooling will be bog standard and everyone will think of them as boring and then people will start building more exciting languages on top of them. Assuming AI doesn’t blow everything up.
AI needs strong types just as much as human developers.
Strong types also improve the interaction between humans and AI: shitty code is way more obvious with strong types. Pure strong-type langs like Elm take this to an even higher level: all cases must handled, such that runtime errors are practically impossible to express.
I've worked professionally on a large Elm program that has had 5 devs on it, and the promise held out: no runtime error, ever. Other stories for this exist.
I understand the motivation as I don't really like writing Go code. Interestingly, I don't mind reading it though (as long as the if err != nil isn't too exhausting).
A transpilation step though? I'll accept that in Typescript (barely) but not for any other language really.
LLVM and JVM have stable interfaces. Go has an intermediate representation but it isn’t stable. Anyone who wanted to depend on it would be on the hook when the implementation changes.
Complexity does not equal language features. Sometimes simple is good, but sometimes simple just simply means more bugs in your code.
As a prime example, Go unwillingness to add even the most simple enum kind of type. Having enums (ADTs) with exhaustive pattern matching is NOT complex in any sense or form. It just takes away so, so many bugs you would normally see in production code.
One other low hanging fruit is the fact that zero values are in 90% of all cases not what the dev intended. Sure, the mantra goes "make them useful" but thats hard. How to you know if a value (int) was zero initialised, or if the user did in fact input zero as value. No matter, you will need to validate every one of these "zero values" if you want some sort of robustness.
Adding `null` to C was very simple to add. It added a lot of complexity that the language designer did not see coming (hence the billion dollar mistake he made on that).
`NULL` was originally added to ALGOL back in 1965. C was not even a thing back then. It was obviously a bad choice to port NULL to C, one that ADTs would have perfectly modeled, without the billion dollar cost.
In fact C was built sometime around the early 70s, and at the same time the first MLs where also being developed. One added null, while the other added a better mechanism for "nothingness".
Bottom line is you cant compare "adding null" and adding a feature that is over 50 years old, one that is battle-tested thru generations, and still holds up.
> Go unwillingness to add even the most simple enum kind of type.
Go has enums, under the iota keyword. But I imagine you are really thinking of sum types. Technically Go has those too, but must always have a nil case, which violates what one really wants out of sum types in practice.
Trouble is that nobody has figured out how to implement sum types without a nil/zero case. That is why you haven't seen a more well-rounded construct for the feature yet. This is not an unwillingness from the Go team, it is more of a lack of expertise. Granted, it is an unwillingness from those like yourself who do have the expertise. What stops you from contributing?
> It just takes away so, so many bugs you would normally see in production code.
What bugs do you imagine are making it to production? Each pattern matched case has a behaviour that needs to be tested anyway, so if you missed a case your tests are going to blow up. The construct is useful enough that you don't need to oversell it on imagined hypotheticals.
For example if a have a type with 3 constuctors, and later one new dev adds one more case and forgets to handle it in every call site. No test will catch this 100%, but something like ocaml will just by compiling the program.
I know fo is not (i dont want it to be) like ocaml, bit any modern language should have some of the basic safety features baked in.
> later one new dev adds one more case and forgets to handle it in every call site
Okay, so you are dreaming of a situation where you allow a new developer who doesn't know anything about how to engineer software unfettered access to your codebase and he goes in and starts mucking about to where he ends up doing things that no sensible human would ever consider acceptable?
I accept you like to live dangerously and have no real care for the product you are building. But, still, you've only covered a limited subset of all the similar things this new developer who doesn't know the first thing about building software can make a mess of. Ocaml just isn't going to cut it.
To reach the full gamut you need, say, a proper dependent type system. But are you really going to throw all your Ocaml code away and start writing Rocq just to deal with this guy you should have never let touch your code in the first place? Probably not. So, what now? Your only practical option is to fire him and find someone who actually understands how to write software. And at that point your tests will catch such forgetfulness just fine.
> No test will catch this 100%
Testing cannot exhaust all cases where the search space is infinite as that requires infinite time to evaluate. In this case, you have exactly 4 states. That is evaluated on the order of nanoseconds. If your tests are not capturing every one of those four permutations, it seems clear that you need to stop letting any random Joe who has never done more than some quick vibe coding anywhere near your code. These are solved problems.
And, hell, even if you did switch to a language with a proper type system, like Rocq, so you could mathematically guarantee all aspects of your program, the shitty programmer will still fail to write theorems that are free of holes. So don't think that even lets you of the hook in hiring those who live by the vibes. There is simply no escaping the need for solid engineering.
When you have that, exhaustiveness checking still remains a useful tool, but you aren't going to forget something and ship broken code to production if you don't have it. This isn't a realistic scenario outside of the contrived. Again, The construct is useful enough that you don't need to oversell it on imagined hypotheticals.
> Okay, so you are dreaming of a situation where you allow a new developer who doesn't know anything about how to engineer software unfettered access to your codebase and he goes in and starts mucking about to where he ends up doing things that no sensible human would ever consider acceptable?
Tell me without telling me you have never worked on a large scale business app.
I did long enough to know that in large teams you won't find many accepting of usable type systems. For the same reason the blub developer cannot write good tests, if they can figure out how to write tests at all, they equally fail with types. Which makes sense — testing and types try to solve the exact same problem. The mindset required to grasp and be productive with both is identical. So your imagined hypothetical doesn't even work there. These large scale apps of which you are speak are simply built around constant failure in production. If it is good enough for GitHub...
The only place where the your contrived idea is in any way plausible is where you have a solo developer who thinks he is able to write code without making mistakes or needing to document anything, using a half-assed type system not failing as some kind of proof to himself of that. But in reading this he, unless the hubris is completely off the charts, will be reevaluating that stance.
I will add this to my list of Elm-inspired tools that call to mind Brian Eno's quip about the first Velvet Underground album: "I think everyone who bought one of those 30,000 copies started a band!" With Elm it feels like it's 1% of Elm users creating a language.
- Evan entered hermit mode to create Acadia, a language for an Elm-like experience querying the database. He's given conference talks about it but none have been recorded. https://acadia.engineering/ This seems to have the same goals as Lamdera.
In the meantime the ecosystem is getting crazy evolved. Turns out not changing the language under your feet for a few years leads to lots of development with what's currently there. Frameworks like elm-pages allowed for command line utils in Elm like elm-codegen. Elm-review lets you write linting rules with autofixes unlike anything I've used in other languages. A few people are writing forks and non-forks that target more than the browser or self-host the compiler. Every backend language I've used with decent types has a package to generate Elm types (and their decoders, and a nice way to interact with the JSON API, and deal with glue code generally).
If you read this far and are wondering which to check out, I cannot endorse Lamdera enough. Use the same types from your DB to your frontend and write zero glue code. Migrations required to update the running frontend/backend whenever you change anything. Really changes the way you write code.
You're missing Derw from that list: https://www.derw-lang.com/. Predates all the others, and is from a former core team member (me). I'm also the author of server-side Elm experiment known as [take-home](https://github.com/eeue56/take-home) from 11 years ago. I can see a lot of patterns in Sky's codebase which seem trained on Derw's codebase.
Also authored the first Elm-in-Elm compiler for a limited subset for json-to-elm, then leading to a pure Elm virtual-dom implementation used for elm-html-test!
I KNEW I forgot one. I'm really sorry. This list was off the dome and I was hoping to hit a Cunningham's law situation (which I did) without making anyone feel left out (which I failed).
No worries at all! I didn't take offense, it's very easy to forget one or two projects when quite frankly, Elm has managed to spawn or inspire so many!
Nice to see another language with Haskell / Miranda type syntax, but the vibe-coded implementation sure shows: e.g. src/Compiler/Infer.sky isUpperStart:
isUpperStart : String -> Bool
isUpperStart name =
case String.slice 0 1 name of
"A" ->
True
"B" ->
True
"C" ->
True
... for 23 more cases.
And the corresponding go code in the bootstrap compiler is even worse.
I started my career cleaning up systems written by solo devs in the pre internet era. I’ve seen a lot of starts_with_UCase() implementations like that.
I see now I’m going to end my career cleaning up systems written by chronically online vibe coders, making all the same mistakes, with none of the excuses.
Thanks for the feedback and taking the time to look at the repo.
Yes I must admit this is part of the trade-offs, as I want to iterate fast until a point where the compiler is stable enough to build most common realworld applications, and I will then clean up the "noise" where Claude Code creates/emits.
I'd love hand building everything myself, but given how productive CC & general AI tool can be, it's an ugly trade-off I would take, for now.
These kind of issues, hopefully will be addressed by v0.9-v1.0, please feel free to check on the repo and see where it goes, thanks!
Does this language use ASCII or Unicode strings? I think this implementation is technically correct for the strict subset of ascii characters, but as soon as you add more, it stops working. EG I'm pretty sure Á or Ó are capitals, and are included in Windows-1252 which is apparently extremely common in non-unicode strings, and there are a huge amount of unicode characters that are uppercase, in which case this function isn't even slightly correct
it first started with ASCII, and quickly I realised it has to support unicode. I have since updated it, but some of the compiler string interpolation & friends aren't optimised for unicode yet.
I will fix those by v0.8, and please feel free to visit the repo to check on its progress. And thanks for taking the time!
Haskell/Miranda use `::` instead of `:` for type signatures unlike Elm & basically the rest of the family which prioritize types being less keypresses than list cons.
Sorry, I meant "Haskell / Miranda style syntax" -- e.g. curried functions, concise syntax with little boilerplate, etc. The word type is too overloaded ;-)
at first glance this looks amazing! basically provides everything I have ever wanted in a full stack language. looking forward to experimenting with it.
edit: looking through the docs/examples some more, it looks like javascript interop is fairly clunky, both because it relies on string concatenation to embed fragments of javascript, and because the string concatenation syntax is not great (and the formatter makes it even worse - see the example at https://github.com/anzellai/sky/blob/main/examples/13-skysho...)
I would encourage you to at the least add multiline strings with interpolation support, and ideally add a small compiler for html literals.
Thanks for taking your time to look at the repo! yes I have built so many applications and worked at startups/scaleups where different languages / type systems are used for frontend/backend, and very often we need to bring in schema generation like OpenAPI, protobuf/connectrpc etc. to code generate types/apis that can be shared between teams. But that's ugly and hard to maintain & scale.
I love Elm's ergonomics & Go's simplicity, and Sky is really my serious take to bring 1 language for full stack application (well, at least to cater for most common application types).
back to your feedback, indeed, the multi-line string is one of the things that bug me as well.
They only surface when I start building incremental examples -- skyshop is a realworld like example which expose a bunch of issues & compiler limitations.
That said, my goal is for devs not needing to write/embed JS in Sky code, but perhaps serve as static, and have Sky semantic to "load/init" the JS.
HTML I took similar of Elm to have elements as function, and have an escape hatch using node for custom elements.
I use VNode rather than string for HTML rendering, as that's much more efficient in diffing and for Sky.Live SSE push for updates.
Anyhow, please feel free to check on Sky repo regularly and hopefully I will make enough progress sooner that Sky is useable in realworld side projects.
Functional languages have some good and some bad features and there's no reason to copy them all. For example, you don't need to have a Hindley-Milner type system (bidirectional is better) or currying just because it's a functional language.
We need more pragmatic languages. E.g. Erlang and Elixir are functional, but eschew all the things FP purists advocate for (complex type systems, purity, currying by default etc.)
ocaml has a complex type system but it's also very pragmatic in that it doesn't force you into any one paradigm, you can do whatever works best in a given situation. (scala arguably goes further in the "do whatever you want" direction but it also dials the complexity way up)
Elm's type system and architecture are genuinely pleasant to work with, so seeing those ideas ported to a Go compilation target is interesting. You get the safety and expressiveness of Elm but end up with a Go binary you can deploy anywhere. I wonder how the error messages compare, since that was always one of Elm's strongest features.
This is awesome! I love Haskell's syntax, but its adoption isn't where I'd like it to be.
One thing that I don't see is a way to mitigate the "andThen" pyramid of doom.
This happens when you have a language without early returns and you have chain multiple Result returning operations. You can use nested case expressions:
case operation1 x of
Ok value -> case operation2 value of
Ok value2 -> value2
Err msg -> "error from operation2: " ++ msg
Err msg -> "error from operation1: " ++ msg
Or nested `andThen` calls
operation1 x
>> mapError (\msg -> "error from operation1: " ++ msg)
>> `andThen` (\value -> operation2 value)
>> mapError (\msg -> "error from operation2: ++ msg)
This is nicer to read, but still a lot of noise.
Haskell has `do` notation to alleviate this but that brings with it the type-class that shall not be named.
Some languages, like Rust, introduce different per-type syntactical solutions such as `async/await` for Promises and `?` for Result.
I particularly like Gleam's `use` notation, which is syntactical sugar around functions that take a callback as their final argument.
Wow, this is amazing. I always wanted to love Haskell but never really managed, Elm nailed the balance of usability and correctness, plus the architecture was beautiful.
I've never liked Go, but its strengths are absolutely compiling to single binaries, fast compile times, and concurrency primitives (not necessarily using them) etc. Compiling to Go is a great idea.
Right but recursion is only a smaller part of why the optimization is important. It means tail-called functions still build on the stack and long function chains—as is common with fp—can overflow
Sky does all on the server (more popular lately with HTMX and LiveView), where Elm+Lamdera is basically 2 project and Lamdera ties you into a propietary-ish ecosystem.
Giving even a modicum of care of what Evan has to say in 2026 is a good joke. Only thing he's known outside the Elm community (aside from Elm) is how to antagonize your own community.
It is a very good attempt, but I am quite pessimistic about the prospects of new programming languages in the AI era. Unless, of course, it is a language specifically designed for AI coding — for example, one that is self-contained and carries a great deal of contextual information, making it unsuitable for humans to write but highly suitable for large language models. That kind of future would probably be quite promising.
What's the actual accomplishment here? It seems like the language came into existence a month ago and was written mostly by Claude. If self hosting is a matter of asking Claude to do it and it takes a couple weeks, is it really an accomplishment at all?
Go's runtime is one of the greatest pieces of software ever built.
Assuming this works - which self-hosting guarantees a minimum level of "working" - this is useful!
I didn't want to rely on the unpredictability of a garbage collector, so I chose to build my own runtime, but it's not going to be as good as Go any time soon.
Derw, a human written Elm fork by me, is largely self-hosted. I took that decision with inspiration from Go: 1) it gave me a large codebase which could be used to assess language features on and 2) it gave me motivation to improve generalized performance rather than focusing on the niche use cases (web-rendering).
> The compiler bootstraps through 3+ generations of self-compilation.
I guess it applies to any language compiler, but f you are self-hosting, you will naturally release binary packages. Please make sure you have enough support behind the project to setup secure build pipeline. As a user, we will never be able to see something even one nesting-level up.
I feel like there's too much of a fetish for self-hosting. There's this pernicious idea that a language isn't a 'real' language until it's self-hosted, but a self-hosted compiler imposes real costs in terms of portability, build integrity, etc.
If I ever write a compiler - God forbid, because language design is exactly the kind of elegance bike-shedding I'll never crawl my way out of - it's going to be a straight-up C89 transpiler, with conditional asm inlines for optional modern features like SIMD. It would compile on anything and run on anything, for free, forever. Why would I ever give that up for some self-hosting social cachet?
I'd be dependent on pre-existing binaries that are closely wedded to a particular platform (OS, libc etc), and it over time it would become more and more difficult to attest to build integrity / ensure reproducible builds. (Is the ARM build meant to run an x64 emulator as part of some lengthy historic bootstrapping process?)
I would love to see Java inspired language compiled to Go. I really like Go portability and standard library and Java... verbosity. I prefer explicit names, types and all the syntax around that. Graalvm is not an answer for me because as far as I'm aware it doesn't support cross-compile.
That's two new languages compiling to Go making HN frontpage in as many days. It seems people like everything about Go except the language itself. Me? I like everything about Go including the language, these transpiled languages are interesting though.
But I keep wondering if they could integrate at a lower-level than the source code. Like how JVM languages integrate at the bytecode level, or LLVM languages at the LLVM level
> But I keep wondering if they could integrate at a lower-level than the source code.
I’m sure they could, but targeting go source code has the benefit of giving early adopters an escape hatch. If it targeted LLVM directly, I would never consider using this at work since the risk of it being abandoned is too high. But since it targets go source, I would perhaps consider it for some low importance projects at work.
The standard go toolchain doesn't use LLVM. Go has its own assembly format and machine code generation.
> people like everything about Go except the language itself.
Thanks for putting so succinctly exactly how I feel about Go!
I like Go as well, but I wish the Go team were slightly less conservative about language changes. Only asking for 10-15% less conservatism. It is OK to add one proper new language feature and one good standard library package per year. Or deprecating one language feature and one stdlib package every 2 years.
> But I keep wondering if they could integrate at a lower-level than the source code.
Unfortunately nothing below source code level is stable, so they would constantly be chasing changes after any Go release. I personally wish they would focus on making it accessible, as Go actually has a nice runtime and would make a good language target.
Aha! That would explain things. I was wondering if the Go assembly at least is stable and documented, couldn't really find anything
> But I keep wondering if they could integrate at a lower-level than the source code.
For my version (aptly named "Goto" [1]), I forked the go compiler with the intent of keeping it up to date. All changes I made only apply to .goto files, .go files compile exactly as is and are interoperable both ways with goto.
I paused the project due to hitting type-checking bugs when trying to add non-nillable pointers. Everything before was just desugared directly when converting to AST, but for those I needed to touch the typechecker which was too time-consuming for a hobby project back then (pre-coding agents). I might give it another go sometime as I did like the full interoperability aspect.
[1] https://github.com/goto-lang/goto
What was the other one?
I'm working on a language that transpiles to Zig with a custom Go-like runtime (and no garbage collector, Rust-style Affine movement instead).
Sky seems quite cool, as it's additive to Go in interesting ways.
I originally considered keeping the GC and just transpiling to Go so I didn't need to write a Runtime.
Go rules! It really does. But I HATE writing/reading Go.
So I'm glad more people are doing this!
https://lisette.run/
Awesome, this is very close to what I originally considered.
> Go rules! It really does. But I HATE writing/reading Go.
Same. I love the Go toolkits, the compile story, the speed at which it compiles, its backwards compatibility, the fact that stale Go code 10 years old still compile and run, etc., just don't care much for the language itself.
I wonder if the positive attributes of Go aren't compatible with clever types and other developer-friendly features?
It’s mostly that Go was already pioneering how to build a programming language that had an amazing scheduler, garbage collector, compiler, package manager, formatter, etc. They spent all of their “innovation budget” on the most important—and most neglected—features of any programming language and allowed the language itself to be pretty boring.
Eventually Go’s runtime and tooling will be bog standard and everyone will think of them as boring and then people will start building more exciting languages on top of them. Assuming AI doesn’t blow everything up.
AI needs strong types just as much as human developers.
Strong types also improve the interaction between humans and AI: shitty code is way more obvious with strong types. Pure strong-type langs like Elm take this to an even higher level: all cases must handled, such that runtime errors are practically impossible to express.
I've worked professionally on a large Elm program that has had 5 devs on it, and the promise held out: no runtime error, ever. Other stories for this exist.
I understand the motivation as I don't really like writing Go code. Interestingly, I don't mind reading it though (as long as the if err != nil isn't too exhausting).
A transpilation step though? I'll accept that in Typescript (barely) but not for any other language really.
https://lisette.run/
If we think of Go as different kind of C, then having Go as a compiled target seems to make sense as C is a compiled target.
I dont get your reasoning.
LLVM and JVM have stable interfaces. Go has an intermediate representation but it isn’t stable. Anyone who wanted to depend on it would be on the hook when the implementation changes.
From what the community says, it's pretty stable (as in BW compatible).
Go is simple. People like complexity. So the write abstractions to feel better about themselves.
Complexity does not equal language features. Sometimes simple is good, but sometimes simple just simply means more bugs in your code.
As a prime example, Go unwillingness to add even the most simple enum kind of type. Having enums (ADTs) with exhaustive pattern matching is NOT complex in any sense or form. It just takes away so, so many bugs you would normally see in production code.
One other low hanging fruit is the fact that zero values are in 90% of all cases not what the dev intended. Sure, the mantra goes "make them useful" but thats hard. How to you know if a value (int) was zero initialised, or if the user did in fact input zero as value. No matter, you will need to validate every one of these "zero values" if you want some sort of robustness.
Adding `null` to C was very simple to add. It added a lot of complexity that the language designer did not see coming (hence the billion dollar mistake he made on that).
`NULL` was originally added to ALGOL back in 1965. C was not even a thing back then. It was obviously a bad choice to port NULL to C, one that ADTs would have perfectly modeled, without the billion dollar cost.
In fact C was built sometime around the early 70s, and at the same time the first MLs where also being developed. One added null, while the other added a better mechanism for "nothingness".
Bottom line is you cant compare "adding null" and adding a feature that is over 50 years old, one that is battle-tested thru generations, and still holds up.
Solid maths does no suffer bitrot.
ALGOL indeed. I stand corrected.
https://www.infoq.com/presentations/Null-References-The-Bill...
> Go unwillingness to add even the most simple enum kind of type.
Go has enums, under the iota keyword. But I imagine you are really thinking of sum types. Technically Go has those too, but must always have a nil case, which violates what one really wants out of sum types in practice.
Trouble is that nobody has figured out how to implement sum types without a nil/zero case. That is why you haven't seen a more well-rounded construct for the feature yet. This is not an unwillingness from the Go team, it is more of a lack of expertise. Granted, it is an unwillingness from those like yourself who do have the expertise. What stops you from contributing?
> It just takes away so, so many bugs you would normally see in production code.
What bugs do you imagine are making it to production? Each pattern matched case has a behaviour that needs to be tested anyway, so if you missed a case your tests are going to blow up. The construct is useful enough that you don't need to oversell it on imagined hypotheticals.
For example if a have a type with 3 constuctors, and later one new dev adds one more case and forgets to handle it in every call site. No test will catch this 100%, but something like ocaml will just by compiling the program.
I know fo is not (i dont want it to be) like ocaml, bit any modern language should have some of the basic safety features baked in.
> later one new dev adds one more case and forgets to handle it in every call site
Okay, so you are dreaming of a situation where you allow a new developer who doesn't know anything about how to engineer software unfettered access to your codebase and he goes in and starts mucking about to where he ends up doing things that no sensible human would ever consider acceptable?
I accept you like to live dangerously and have no real care for the product you are building. But, still, you've only covered a limited subset of all the similar things this new developer who doesn't know the first thing about building software can make a mess of. Ocaml just isn't going to cut it.
To reach the full gamut you need, say, a proper dependent type system. But are you really going to throw all your Ocaml code away and start writing Rocq just to deal with this guy you should have never let touch your code in the first place? Probably not. So, what now? Your only practical option is to fire him and find someone who actually understands how to write software. And at that point your tests will catch such forgetfulness just fine.
> No test will catch this 100%
Testing cannot exhaust all cases where the search space is infinite as that requires infinite time to evaluate. In this case, you have exactly 4 states. That is evaluated on the order of nanoseconds. If your tests are not capturing every one of those four permutations, it seems clear that you need to stop letting any random Joe who has never done more than some quick vibe coding anywhere near your code. These are solved problems.
And, hell, even if you did switch to a language with a proper type system, like Rocq, so you could mathematically guarantee all aspects of your program, the shitty programmer will still fail to write theorems that are free of holes. So don't think that even lets you of the hook in hiring those who live by the vibes. There is simply no escaping the need for solid engineering.
When you have that, exhaustiveness checking still remains a useful tool, but you aren't going to forget something and ship broken code to production if you don't have it. This isn't a realistic scenario outside of the contrived. Again, The construct is useful enough that you don't need to oversell it on imagined hypotheticals.
> Okay, so you are dreaming of a situation where you allow a new developer who doesn't know anything about how to engineer software unfettered access to your codebase and he goes in and starts mucking about to where he ends up doing things that no sensible human would ever consider acceptable?
Tell me without telling me you have never worked on a large scale business app.
I did long enough to know that in large teams you won't find many accepting of usable type systems. For the same reason the blub developer cannot write good tests, if they can figure out how to write tests at all, they equally fail with types. Which makes sense — testing and types try to solve the exact same problem. The mindset required to grasp and be productive with both is identical. So your imagined hypothetical doesn't even work there. These large scale apps of which you are speak are simply built around constant failure in production. If it is good enough for GitHub...
The only place where the your contrived idea is in any way plausible is where you have a solo developer who thinks he is able to write code without making mistakes or needing to document anything, using a half-assed type system not failing as some kind of proof to himself of that. But in reading this he, unless the hubris is completely off the charts, will be reevaluating that stance.
I will add this to my list of Elm-inspired tools that call to mind Brian Eno's quip about the first Velvet Underground album: "I think everyone who bought one of those 30,000 copies started a band!" With Elm it feels like it's 1% of Elm users creating a language.
https://quoteinvestigator.com/2016/03/01/velvet/
You should publish your "Elm-inspired tool" list- I bet it's pretty large. Off the top of my head: iced, react redux, bubble tea (Go lib), Roc lang.
I'm sure there are lots more. I'm still waiting for someone to write an "Elm retrospective" and examine its rise and stagnation
I left this comment a while back https://news.ycombinator.com/item?id=45752905 when someone asked what was up with Elm at the current moment. I left a bunch off.
- Evan entered hermit mode to create Acadia, a language for an Elm-like experience querying the database. He's given conference talks about it but none have been recorded. https://acadia.engineering/ This seems to have the same goals as Lamdera.
- Evan hinted that 0.19.2 is coming and has asked for help profiling it https://discourse.elm-lang.org/t/help-me-profile-elm-0-19-2-....
In the meantime the ecosystem is getting crazy evolved. Turns out not changing the language under your feet for a few years leads to lots of development with what's currently there. Frameworks like elm-pages allowed for command line utils in Elm like elm-codegen. Elm-review lets you write linting rules with autofixes unlike anything I've used in other languages. A few people are writing forks and non-forks that target more than the browser or self-host the compiler. Every backend language I've used with decent types has a package to generate Elm types (and their decoders, and a nice way to interact with the JSON API, and deal with glue code generally).
Literal forks of Elm compiler
- Gren (general purpose lang targeting browser, CLI, servers) https://gren-lang.org/
- Zokka (fixes a few compiler bugs and allows for custom package repos) https://discourse.elm-lang.org/t/a-new-zokka-version-bringin...
- Guida (self-hosted compiler) https://guida-lang.org/
Forks that do not ever seek to change the syntax of the language and thus compile .elm files
- Lamdera ("non-fork" fullstack with evergreen migrations) https://dashboard.lamdera.app/docs/starting
- Eco (written in Elm targeting x86 binaries via MLIR and LLVM) https://github.com/eco-lang/eco-runtime
- Elm-run (targeting CLI and servers, self hosted) https://elm-run.dev/roadmap
Languages inspired by it whose creators are big Elm users:
- Roc (general purpose): https://www.roc-lang.org/faq
- Gleam (targets Erlang VM and browser) https://gleam.run/cheatsheets/gleam-for-elm-users/
- Cara https://cara-lang.com/
Things that use the Elm architecture in other languages (linking to pages that mention the connection where possible):
- Foldkit (Typescript) https://discourse.elm-lang.org/t/foldkit-the-elm-architectur...
- Iced (Rust): https://book.iced.rs/
- Bubble Tea (Go): https://github.com/charmbracelet/bubbletea
- Lustre (Gleam): https://github.com/lustre-labs/lustre
- A list of TEA-in-Swift and React in Swift: https://gist.github.com/inamiy/bd257c60e670de8a144b1f97a07ba...
- Redux (Javascript/Typescript): https://redux-toolkit.js.org/rtk-query/comparison
If you read this far and are wondering which to check out, I cannot endorse Lamdera enough. Use the same types from your DB to your frontend and write zero glue code. Migrations required to update the running frontend/backend whenever you change anything. Really changes the way you write code.
You're missing Derw from that list: https://www.derw-lang.com/. Predates all the others, and is from a former core team member (me). I'm also the author of server-side Elm experiment known as [take-home](https://github.com/eeue56/take-home) from 11 years ago. I can see a lot of patterns in Sky's codebase which seem trained on Derw's codebase.
Also authored the first Elm-in-Elm compiler for a limited subset for json-to-elm, then leading to a pure Elm virtual-dom implementation used for elm-html-test!
I KNEW I forgot one. I'm really sorry. This list was off the dome and I was hoping to hit a Cunningham's law situation (which I did) without making anyone feel left out (which I failed).
No worries at all! I didn't take offense, it's very easy to forget one or two projects when quite frankly, Elm has managed to spawn or inspire so many!
Nice to see another language with Haskell / Miranda type syntax, but the vibe-coded implementation sure shows: e.g. src/Compiler/Infer.sky isUpperStart:
And the corresponding go code in the bootstrap compiler is even worse.I started my career cleaning up systems written by solo devs in the pre internet era. I’ve seen a lot of starts_with_UCase() implementations like that.
I see now I’m going to end my career cleaning up systems written by chronically online vibe coders, making all the same mistakes, with none of the excuses.
Thanks for the feedback and taking the time to look at the repo.
Yes I must admit this is part of the trade-offs, as I want to iterate fast until a point where the compiler is stable enough to build most common realworld applications, and I will then clean up the "noise" where Claude Code creates/emits.
I'd love hand building everything myself, but given how productive CC & general AI tool can be, it's an ugly trade-off I would take, for now.
These kind of issues, hopefully will be addressed by v0.9-v1.0, please feel free to check on the repo and see where it goes, thanks!
Does this language use ASCII or Unicode strings? I think this implementation is technically correct for the strict subset of ascii characters, but as soon as you add more, it stops working. EG I'm pretty sure Á or Ó are capitals, and are included in Windows-1252 which is apparently extremely common in non-unicode strings, and there are a huge amount of unicode characters that are uppercase, in which case this function isn't even slightly correct
it first started with ASCII, and quickly I realised it has to support unicode. I have since updated it, but some of the compiler string interpolation & friends aren't optimised for unicode yet.
I will fix those by v0.8, and please feel free to visit the repo to check on its progress. And thanks for taking the time!
Haskell/Miranda use `::` instead of `:` for type signatures unlike Elm & basically the rest of the family which prioritize types being less keypresses than list cons.
Sorry, I meant "Haskell / Miranda style syntax" -- e.g. curried functions, concise syntax with little boilerplate, etc. The word type is too overloaded ;-)
at first glance this looks amazing! basically provides everything I have ever wanted in a full stack language. looking forward to experimenting with it.
edit: looking through the docs/examples some more, it looks like javascript interop is fairly clunky, both because it relies on string concatenation to embed fragments of javascript, and because the string concatenation syntax is not great (and the formatter makes it even worse - see the example at https://github.com/anzellai/sky/blob/main/examples/13-skysho...)
I would encourage you to at the least add multiline strings with interpolation support, and ideally add a small compiler for html literals.
Thanks for taking your time to look at the repo! yes I have built so many applications and worked at startups/scaleups where different languages / type systems are used for frontend/backend, and very often we need to bring in schema generation like OpenAPI, protobuf/connectrpc etc. to code generate types/apis that can be shared between teams. But that's ugly and hard to maintain & scale.
I love Elm's ergonomics & Go's simplicity, and Sky is really my serious take to bring 1 language for full stack application (well, at least to cater for most common application types).
back to your feedback, indeed, the multi-line string is one of the things that bug me as well. They only surface when I start building incremental examples -- skyshop is a realworld like example which expose a bunch of issues & compiler limitations.
That said, my goal is for devs not needing to write/embed JS in Sky code, but perhaps serve as static, and have Sky semantic to "load/init" the JS.
Nonetheless the ugly multi-line string issue is now addressed with https://github.com/anzellai/sky/pull/13.
HTML I took similar of Elm to have elements as function, and have an escape hatch using node for custom elements. I use VNode rather than string for HTML rendering, as that's much more efficient in diffing and for Sky.Live SSE push for updates.
Anyhow, please feel free to check on Sky repo regularly and hopefully I will make enough progress sooner that Sky is useable in realworld side projects.
awesome! will definitely check back regularly.
Functional languages have some good and some bad features and there's no reason to copy them all. For example, you don't need to have a Hindley-Milner type system (bidirectional is better) or currying just because it's a functional language.
We need more pragmatic languages. E.g. Erlang and Elixir are functional, but eschew all the things FP purists advocate for (complex type systems, purity, currying by default etc.)
If you like Erlang, Elixir, and Elm/Haskell, then Gleam + Lustre (which is TEA) is a pretty great fit.
ocaml has a complex type system but it's also very pragmatic in that it doesn't force you into any one paradigm, you can do whatever works best in a given situation. (scala arguably goes further in the "do whatever you want" direction but it also dials the complexity way up)
Ocaml's typesystem is rich, but not as complex as TypeScripts. It seems TS just adds more obscure features every year for little benefit.
Yes! Completely forgot about OCaml because I only spent a couple of months with it
Elm's type system and architecture are genuinely pleasant to work with, so seeing those ideas ported to a Go compilation target is interesting. You get the safety and expressiveness of Elm but end up with a Go binary you can deploy anywhere. I wonder how the error messages compare, since that was always one of Elm's strongest features.
The resulting binary is, well, binary. Not Go. Or I miss something.
Keeps saying "no websocket required" like it's a good thing. The JS client (https://github.com/anzellai/sky/blob/main/docs/design/sky-li...) seems to rely on long polling.
Phoenix LiveView (the inspiration) defaults to using WebSockets because it's much more efficent, but falls back to Long polling if not available.
[dead]
This is awesome! I love Haskell's syntax, but its adoption isn't where I'd like it to be.
One thing that I don't see is a way to mitigate the "andThen" pyramid of doom.
This happens when you have a language without early returns and you have chain multiple Result returning operations. You can use nested case expressions:
Or nested `andThen` calls This is nicer to read, but still a lot of noise.Haskell has `do` notation to alleviate this but that brings with it the type-class that shall not be named.
Some languages, like Rust, introduce different per-type syntactical solutions such as `async/await` for Promises and `?` for Result.
I particularly like Gleam's `use` notation, which is syntactical sugar around functions that take a callback as their final argument.
Do you have a solution for this in Sky?
Wow, this is amazing. I always wanted to love Haskell but never really managed, Elm nailed the balance of usability and correctness, plus the architecture was beautiful.
I've never liked Go, but its strengths are absolutely compiling to single binaries, fast compile times, and concurrency primitives (not necessarily using them) etc. Compiling to Go is a great idea.
Great work :). Go doesn't have TCO. That means functional languages (no for loops) could blow up the stack. How did you solve that?
You can just compile any tail recursive function to a function with a loop and no recursion.
This is in fact how Elm does it! Tail call recursion compiles to a while loop.
That does not address the use case where I find tail recursion most tempting. That would be mutually recursive functions.
If the function can be written as an idiomatic loop I probably would do so in the first place.
You _can_ do trampolines, but that is kind of infectious, or needs to be very explicit with extra code, etc.
Indeed. It's not very efficient though. If I remember correctly Scala does this.
Right but recursion is only a smaller part of why the optimization is important. It means tail-called functions still build on the stack and long function chains—as is common with fp—can overflow
Very cool.
I am comparing this https://github.com/anzellai/sky#tea-architecture with this https://harcstack.org (my thing) ... guess I have some work to do ;-)
I think you have an interesting spot in the design space here...
Have you seen Lamdera? They have a way to use Elm on the server-side that is supposedly acceptable to the Elm-BDFL Evan Czaplicki.
https://lamdera.com/
This talk explains it well: https://www.youtube.com/watch?v=4T6nZffnfzg
Sky does all on the server (more popular lately with HTMX and LiveView), where Elm+Lamdera is basically 2 project and Lamdera ties you into a propietary-ish ecosystem.
Giving even a modicum of care of what Evan has to say in 2026 is a good joke. Only thing he's known outside the Elm community (aside from Elm) is how to antagonize your own community.
Elm is a language I enjoyed the most. I love Ruby, I loved some other languages, even Haskell I enjoyed, but Elm is special. So let's make this work.
Now that you got foundation created, let's see how to move it forward.
It is a very good attempt, but I am quite pessimistic about the prospects of new programming languages in the AI era. Unless, of course, it is a language specifically designed for AI coding — for example, one that is self-contained and carries a great deal of contextual information, making it unsuitable for humans to write but highly suitable for large language models. That kind of future would probably be quite promising.
First - awesome job. Congrats. Self hosting is an accomplishment!
But I'm curious to get your thoughts on the process in hindsight.
I understand why it's valuable: to cast a wide net in catching bugs and give a good signal that your language is generally "ready".
I'm working on a similar language, but worried about going down the self-hosting path, as I think it'd slow me down rather than speed me up.
How did it work for you?
What's the actual accomplishment here? It seems like the language came into existence a month ago and was written mostly by Claude. If self hosting is a matter of asking Claude to do it and it takes a couple weeks, is it really an accomplishment at all?
Anything + Go's runtime is a reasonable language.
Go's runtime is one of the greatest pieces of software ever built.
Assuming this works - which self-hosting guarantees a minimum level of "working" - this is useful!
I didn't want to rely on the unpredictability of a garbage collector, so I chose to build my own runtime, but it's not going to be as good as Go any time soon.
"I could have made that!"
"Yes, but you didn't."
Yes, somebody has to actually do it, and they did.
Derw, a human written Elm fork by me, is largely self-hosted. I took that decision with inspiration from Go: 1) it gave me a large codebase which could be used to assess language features on and 2) it gave me motivation to improve generalized performance rather than focusing on the niche use cases (web-rendering).
Somewhat unrelated to the language itself:
> The compiler bootstraps through 3+ generations of self-compilation.
I guess it applies to any language compiler, but f you are self-hosting, you will naturally release binary packages. Please make sure you have enough support behind the project to setup secure build pipeline. As a user, we will never be able to see something even one nesting-level up.
I feel like there's too much of a fetish for self-hosting. There's this pernicious idea that a language isn't a 'real' language until it's self-hosted, but a self-hosted compiler imposes real costs in terms of portability, build integrity, etc.
If I ever write a compiler - God forbid, because language design is exactly the kind of elegance bike-shedding I'll never crawl my way out of - it's going to be a straight-up C89 transpiler, with conditional asm inlines for optional modern features like SIMD. It would compile on anything and run on anything, for free, forever. Why would I ever give that up for some self-hosting social cachet?
If you wrote the C89 outputting transpiler in your own language it would still be just as portable.
I'd be dependent on pre-existing binaries that are closely wedded to a particular platform (OS, libc etc), and it over time it would become more and more difficult to attest to build integrity / ensure reproducible builds. (Is the ARM build meant to run an x64 emulator as part of some lengthy historic bootstrapping process?)
I would love to see Java inspired language compiled to Go. I really like Go portability and standard library and Java... verbosity. I prefer explicit names, types and all the syntax around that. Graalvm is not an answer for me because as far as I'm aware it doesn't support cross-compile.
You could make it happen in about a week and $50 worth of tokens.
Can’t wait to play with it. Great design!
A bit too bleeding edge for me, but it does look super nice (ie exactly like Elm).
If you allow FFI are you really inspired by Elm? ;)
they're inspired by repeating elm's good features and fixing the bad ones!
Compiles to Go or transpiles to Go?
Either is fine.
``Formally speaking, "Transpiler" is a useless word''
https://people.csail.mit.edu/rachit/post/transpiler-formal/
You are free to use whatever word you want. Even "bloopydoopy", if you so wish. But "compiles" is best.
wow this is pretty cool!
[dead]