1. 64
  1. 8

    Congrats to the team! I love seeing the steady progress on the project and seeing these ideas explored on the BEAM. In particular, making all assignments expressions instead of statements is going to be a big quality-of-life improvement.

    1. 6

      Congrats on moving to full-time Gleam, Louis! I heard your episode with Inference and understand how difficult it is to have to switch between open source and paid work, especially with such a demanding project as Gleam and your work in Elixir.

      I’m hoping others start seeing the value of Gleam more and that you don’t have to worry about switching back and forth.

      1. 4

        Do you mind sharing the name of the episode or a link to it? I don’t seem to be able to find it…

          1. 1

            Thank you!

        1. 1

          Thank you! I hope so too!

          I’m going to try and only pick up short term contracts in future, as to be less disruptive to Gleam development.

        2. 4

          Love to see it. I use Elixir everyday at work, and am a big fan of static types, so in principle I’m the target audience. However, I’m reluctant to give up the Elixir ecosystem I love do much. E.g. I’m really excited LiveView, Livebook, Nx, etc.

          What are the benefits / necessity of a whole new language as opposed to an improved dialyzer, or maybe a TypeScript style Elixir superset with inline type annotations?

          1. 11

            One issue is that existing Elixir code will be hard to adapt to a sound type system, more specifically in how pattern matching is used. For example, consider this common idiom:

            {:ok, any} = my_function()

            (where my_function may return {:ok, any} or {:error, error} depending on whether the function succeeded)

            Implicitly, this means “crash, via a badmatch error, if we didn’t get the expected result”. However this is basically incompatible with a sound type system as the left-hand side of the assignment has the type {:ok, T} and the function has the return type {:ok, T} | {:error, Error}.

            Of course we could add some kind of annotation that says “I mismatched the types on purpose”, but then we’d have to sprinkle these all over existing code.

            This is also the reason why Dialyzer is based on success typing rather than more “traditional” type checking. A consequence of this is that Dialyzer, by design, doesn’t catch all potential type errors; as long as one code path can be shown to be successful Dialyzer is happy, which reflects how Erlang / Elixir code is written.

            1. 5

              Of course we could add some kind of annotation that says “I mismatched the types on purpose”, but then we’d have to sprinkle these all over existing code.

              This is what Gleam does. Pattern matching is to be total unless the assert keyword is used instead of let.

              assert Ok(result) = do_something()

              It’s considered best practice to use assert only in tests and in prototypes

              1. 1

                What do you do when the use case is “no, really, I don’t care, have the supervisor retry because I can’t be bothered to handle the error and selectively reconcile all of this state I’ve built up, I’d rather just refetch it”?

                1. 1

                  Maybe we need another keyword:

                  assume Ok(result) = do_something()
                  1. 1

                    That is what assert is for.

                  2. 1

                    That’s what assert is. If the pattern doesn’t match then it crashes the process.

                    1. 1

                      So why not use it in production?

                      1. 2

                        You can for sure. I was a bit too simple there. I should have said “It is best to only use assert with expected non-exceptional errors in prototypes`. There’s place for Erlang style supervision in Gleam in production.

                2. 3

                  Great, succinct explanation!

                  1. 2

                    This is an interesting example. I’m still not sure I understand how it’s “basically incompatible”, though. Is it not possible to annotate the function with the possibility that it raises the MatchError? It feels kind of like Java’s unchecked exceptions a bit. Java doesn’t have the greatest type system, but it has a type system. I would think you could kind of have a type system here that works with Elixir’s semantics by bubbling certain kinds of errors.

                    Are you assuming Hindley Milner type inference or something? Like, what if the system were rust-style and required type specifications at the function level. This is how Elixir developers tend to operate already, anyway, with dialyzer.

                    1. 1

                      I don’t see how that’s a problem offhand. I’m not sure how gleam does it, but you can show that the pattern accommodates a subtype of the union and fail when it doesn’t match the :ok.

                      1. 4

                        The problem is the distinction between failing (type checking) and crashing (at runtime). The erlang pattern described here is designed to crash if it encounters an error, which would require that type checking passes. But type checking would never pass since my_function() has other return cases and the pattern match is (intentionally) not exhaustive.

                        1. 1

                          Ah, exhaustive pattern matching makes more sense. But also feels a little odd in Erlang. I’ll have to play with Gleam some and get an understanding of how it works out.

                    2. 4

                      One thing is that TypeScript is currently bursting at the seams as developers aspirationally use it as a pure functional statically-typed dependently-typed language. The TypeScript developers are bound by their promise not to change JavaScript semantics, even in seemingly minor ways (and I understand why this is so), but it really holds back TS from becoming what many users hope for it to be. There’s clearly demand for something more, and eventually a language like PureScript / Grain / etc will carve out a sizable niche.

                      So, I think starting over from scratch with a new language can be advantageous, as long as you have sufficient interoperability with the existing ecosystem.

                      1. 2

                        I won’t go too much into Dialyzer as I’ve never found it reliable or fast enough to be useful in development, so I don’t think I’m in a great place to make comparisons. For me a type system is a writing assistant tool first and foremost, so developer UX is the name of the game.

                        I think the TypeScript question is a really good one! There’s a few aspects to this.

                        Gradual typing (TypeScript style) offers different guarentees to the HM typing of Gleam. Gleam’s type system is sound by default, while with gradual typing you opt-in to safety by providing annotations which the checker can then verify. In practice this ends up being quite a different developer experience, the gradual typer requires more programmer input and the will to resist temptation not to leave sections of the codebase untyped. The benefit here is that it is easier to apply gradual types to an already existing codebase, but that’s not any advantage to me- I want the fresh developer experience that is more to my tastes and easier for me to work with.

                        Another aspect is just that it’s incredibly hard to do gradual typing well. TypeScript is a marvel, but I can think of many similar projects that have failed. In the BEAM world alone I can think of 4 attempts to add a type checker to the existing Elixir or Erlang languages, and all have failed. Two of these projects were from Facebook and from the Elixir core team, so it’s not like they were short on expertise either.

                        Lastly, a new language is an oppotunity to try and improve on Elixir and Erlang. There’s lots of little things in Gleam that I personally am very fond of which are not possible in them.

                        One silly small example is that we don’t need a special .() to call an anonymous function like Elixir does.

                        let f = fn() { 1 }

                        And we can pipe into any position

                        |> first_position(2)
                        |> second_position(1, _)
                        |> curried_last_position

                        And we have type safe labelled arguments, without any runtime cost. No keyword lists here

                        replace(each: ",", with: " ", in: "A,B,C")

                        Thanks for the questions

                        edit: Oh! And RE the existing ecosystem, you can use Gleam and Elixir or Erlang together! That’s certainly something Gleam has been built around.

                        1. 1

                          Two of these projects were from Facebook and from the Elixir core team, so it’s not like they were short on expertise either.

                          Oh, wow, I don’t think I’ve heard of these! Do you have any more info? And why was Facebook writing a typechecker for Elixir? Are you talking about Flow?

                          1. 1

                            Facebook were writing a type checker for Erlang for use in WhatsApp. It’s not flow, but it is inspired by it. I’m afraid I don’t think much info is public about it.

                      2. 3

                        As someone who hasn’t tried any of the Beam languages; what’s the package ecosystem like? Can Gleam consume elixir/erlang packages?

                        1. 6

                          Yes, there should be no problem. However there may be some additional work to be done in Gleam to provide typing for such functions.

                          Here you have docs for that. It should be also perfectly possible to call Gleam functions from other BEAM languages as well.

                          1. 2

                            Thanks to the great work of Elixir and Erlang there’s a good package ecosystem which can be used by all the languages on the VM. The website has details https://hex.pm/

                          2. 2

                            Sounds and looks very interesting. Can’t wrap my mind around the way message passing can be done, though.

                            1. 2

                              We use a channels abstraction to type messages, similar to Go or Rust. They can be combined to make type safe selective receives.

                              See the OTP library for details -> https://github.com/gleam-lang/otp

                              1. 1

                                IDK how Caramel does it, but one way to do it is to type functions on the messages it can receive. This type then gets “inherited” by processes that are spawned with that function. This means all match clauses inside the function’s receive / all messages sent to the process must match the function’s “receive type”

                                1. 2

                                  We originally tried this but found it was too limiting. It was impossible to implement things like supervisors and gen:call, so at best you could write a thin typed layer on top of untyped Erlang code.

                                  Gleam OTP by contrast is a small ~200 line Erlang core, and the rest is type safe Gleam.