35 comments

[ 1.9 ms ] story [ 71.7 ms ] thread
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.

Can’t wait to play with it. Great design!
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.
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

Go is simple. People like complexity. So the write abstractions to feel better about themselves.
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.

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

A bit too bleeding edge for me, but it does look super nice (ie exactly like Elm).
[flagged]
The resulting binary is, well, binary. Not Go. Or I miss something.
Great work :). Go doesn't have TCO. That means functional languages (no for loops) could blow up the stack. How did you solve that?
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

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?

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.

Compiles to Go or transpiles to Go?
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.
If you allow FFI are you really inspired by Elm? ;)
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.

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!

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!

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.

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.

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.

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.

Do you have a solution for this in Sky?