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
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.
Thanks for putting so succinctly exactly how I feel about Go!
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.
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.
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!
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?
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.
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.
A transpilation step though? I'll accept that in Typescript (barely) but not for any other language really.
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.
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.
https://www.infoq.com/presentations/Null-References-The-Bill...
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.
I'm sure there are lots more. I'm still waiting for someone to write an "Elm retrospective" and examine its rise and stagnation
- 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.
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!
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 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.
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.
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.
Do you have a solution for this in Sky?
Phoenix LiveView (the inspiration) defaults to using WebSockets because it's much more efficent, but falls back to Long polling if not available.
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.
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.
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.
If the function can be written as an idiomatic loop I probably would do so in the first place.
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 ;-)
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?
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.
"Yes, but you didn't."
> 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.
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?
``Formally speaking, "Transpiler" is a useless word''
Now that you got foundation created, let's see how to move it forward.