Gleam: A Basic Introduction

(peq42.com)

139 points | by Alupis 9 months ago ago

79 comments

  • cedws 9 months ago ago

    I've been interested in Gleam, but I didn't realise it just transpiles to Erlang, I thought it compiled directly to BEAM bytecode. Bit of a turnoff to be honest, I really don't want to deal with transpilation.

    • Ndymium 9 months ago ago

      Which part do you feel like would be an issue? When you run `gleam compile`, it will automatically call the Erlang compiler to finish the job.

      I find it very handy that the intermediate Erlang (or JS) files are available in the build directory. It lets you easily see what form your code will take when compiled.

      • rtorr 9 months ago ago

        Also prevents lock-in if you ever need to move away from gleam.

      • pan69 9 months ago ago

        I don't think it's the transpile part that would the issue, it's the runtime aspect. If Gleam transpiles to Erlang/Javascript that's great but once you run the program, you have to potentially deal with runtime issues specific to those environments which you might not be familiar with.

        It seems that Gleam is really useful for those who are already in either the Erlang/Javascript ecosystem.

        • Alupis 9 months ago ago

          On the contrary, it's a great first BEAM language to learn because of it's simplicity - both in terms of the grammar as well as it's tooling/compiler.

          For me personally, the Javascript target is the least interesting bit - the BEAM/Erlang target is where it's at for backend work. The BEAM is fascinating and full of ideas that were once ahead-of-their-time but now are really coming into their own with compute performance having caught up.

          Gleam is a strongly typed language, and is unapologetically very functional. Error handling in general is quite different than it would be on a normal stack-based language/vm. In my experience, the Erlang target doesn't make debugging any harder or more difficult than you would expect for an exception-less language.

          • giraffe_lady 9 months ago ago

            The JS target is also very interesting to me. I like erlang fine and elixir's nascent type system is promising. But the frontend (and js fullstack for that matter) currently does not have a good alternative to typescript, and the ML type system is an especially good fit for it. Elm has too much reputational baggage and rescript/reason/bucklescript/whatever squandered its momentum and is floundering.

      • cedws 9 months ago ago

        Another layer of abstraction, another thing to go wrong, another thing to rot.

        • Ndymium 9 months ago ago

          But, you need a runtime. If Gleam had its own runtime, that would be another layer in a similar vein. Here Gleam is using Erlang (or a JS runtime) which is bound to be more supported and have a longer lifetime than something they cooked up themselves.

          Besides, Gleam's original aim was basically "Erlang but static types", so the choice of Erlang as runtime was always there.

        • 9 months ago ago
          [deleted]
    • hosh 9 months ago ago

      Gleam used to compile to Core Erlang (Erlang Intermediate Representation) but looks like it now compiles to pretty-printed Erlang.

      https://blog.lambdaclass.com/an-interview-with-the-creator-o...

      • Alupis 9 months ago ago

        The relevant quote:

        > The Gleam compiler has had a few full rewrites. The previous version compiled to BEAM bytecode via Core Erlang, which is an intermediate representation with the Erlang compiler, but the current version compiles to regular Erlang source code that has been pretty-printed. This has a few nice advantages such as providing an escape hatch for people who no longer wish to use Gleam, and enabling Erlang/Elixir/etc projects to use libraries written in Gleam without having to install the Gleam compiler.

        Pretty good reasoning in my opinion.

    • Muromec 9 months ago ago

      It makes perfect sense to target erlang and not BEAM directly as allows erlang compiler to optimize the code for the newer BEAM runtime with newer fancier opcodes.

    • josevalim 9 months ago ago

      Yes, unfortunately transpilation comes with real downsides. Up until recently, logger events, error messages and stacktraces were displayed in Erlang formatting. It has improved in few cases, but not all, and the line numbers in stacktraces do not align with the source code. And if you want to use a REPL, you must use Erlang/JS ones, etc.

      • lpil 9 months ago ago

        This is fine in practice as unexpected runtime errors are rare in Gleam. I’ve yet to have any in any and I’ve been using it in production for years.

        It would be great if the BEAM had a source map like system so it can have the same high quality runtime errors and debugger support that JS has, but I think that’s unlikely to ever happen.

        A REPL would be nice, but there hasn’t been enough demand to motivate any work so far. Language server features are overwhelming the majority of demand when it comes to tooling.

      • josevalim 9 months ago ago

        Although if you want to get into Erlang/BEAM and static types are a requirement, I still strongly recommend checking it out. :)

    • lpil 9 months ago ago

      What do you mean by deal with transpiration? Elixir and LFE also both compile to Erlang and then run the BEAM compiler to generate bytecode. Erlang is the only BEAM language which goes to bytecode directly today.

      • josevalim 9 months ago ago

        Transpilation usually refers to the fact the target of your compiler is another language source code, which then has to be parsed again. Which leads to issues such as mismatched lines in stacktraces at runtime. Elixir and LFE use the same compiler tooling as Erlang, but the source is parsed just once, and all line information pertains to the original Elixir/LFE source code.

        • lpil 9 months ago ago

          Gleam also uses that same compiler tooling, though it does not embed the line information presently.

          • josevalim 9 months ago ago

            Yes, it all goes through the same pipe, but we enter the pipe through different places, which leads to different outcomes. You get different limitations and results depending if your input is Erlang source (Gleam), Erlang AST (Elixir), or Core Erlang AST (which is what LFE used in the past but I am not sure if it is still the case).

            • lpil 9 months ago ago

              Exactly! Beyond line numbers in stack traces it’s all the same.

    • 9 months ago ago
      [deleted]
  • drdaeman 9 months ago ago

    One niche feature of Erlang that I love is live module upgrades, particularly via gen_server's code_change/3 callback. It's a neat little trick that seems to be rarely used in practice, but every single time I've used it I just loved how everything simply clicked together, providing zero disruption with nearly minimal effort (for simple state upgrades or downgrades).

    I wonder if it's supported with Gleam and/or gleam_otp? Don't see it in the docs.

    • sbrother 9 months ago ago

      Do you know the current status of gleam_otp? I’ve been in the erlang (and more recently elixir) world for a long time and am quite excited about gleam, but when I looked at it early on it didn’t seem to have a great solution for OTP. Can I write genservers in gleam now, and can they run as part of an elixir/erlang application/supervision tree?

      • e3bc54b2 9 months ago ago

        Elixir directly uses OTP because it is a dynamically typed language. Gleam folks concluded that OTP isn't really designed with static types in mind, and so set out to build their own version of OTP[0]. It is still early days, and lot of stuff is remaining, but what is already there is pretty good.

        [0] https://hexdocs.pm/gleam_otp/

      • lpil 9 months ago ago

        Yes, and this has been possible for a couple years now.

    • lpil 9 months ago ago

      No particular support but you can use all the regular Erlang OTP APIs for this. The trade off is that you lose type safety.

  • jeremy_k 9 months ago ago

    Gleam has been great as I've started messing around with it recently. Coming from primarily Ruby, it feels much different and I'm liking expanding my thought process around programming. I'm struggling a bit with learning how to think in the type system though. Without unions and a requirement that case statements all return a single type, I just haven't quite grasped the right pattern to make it all click. Enjoying the process none the less.

  • systems 9 months ago ago

    the gleam tour is also very good https://tour.gleam.run/

    very very good

    • xorvoid 9 months ago ago

      From the tutorial:

      // Division by zero is not an error

      io.debug(3.14 /. 0.0)

      It prints 0

      Yuck. Division by zero is an unfortunate reality but basically nobody with mathematical background thinks that just defining x/0 = 0 is a good solution.

      Often in numerical computing, getting an NaN or Inf is a blessing in that it’s a hint that your algorithm is numerically buggy, in the same way that a crash or a exception would indicate a program bug.

      This approach is the numeric equivalent of a program continuing on after an undefined variable, just assuming it’s 0. That was tried by scripting languages in the 90s and these days most folks think it was a bad approach.

      • Alupis 9 months ago ago

        The divide-by-zero thing is explained here[1]. The relevant bits:

        > Gleam does not implicitly throw exceptions, so throwing an exception is not an option. The BEAM VM does not have a Infinity value, so that is not an option. Therefore Gleam returns 0 when dividing by zero.

        > The standard library provides functions which return a Result type for division by zero which you can use if that is more suitable for your program.

        You can also use Guards[2] to prevent handle a divide-by-zero situation before you attempt it.

        [1] https://gleam.run/frequently-asked-questions/#why-does-divis...

        [2] https://tour.gleam.run/everything/#flow-control-guards

        • josevalim 9 months ago ago

          I know you are quoting the docs, but Gleam absolutely throws implicit exceptions, for exactly the same reason why it returns 0 when dividing: the Erlang/VM does not support Infinity/NaN, which means floating point operations can also overflow/underflow. For example, any division with a subnormal will raise:

              1.0 /. 5.0e-324
          
          Or addition between really large floats:

              1.0e308 +. 1.0e308
          
          In fact, even the `float.divide` function, which is meant to be safe, will raise:

              float.divide(1.0, 5.0e-324)
          
          In other words, most functions that returns floats have an unmapped codomain and because of how floats work, and it is not simply a matter of checking if one of the inputs is equal to 0.0.

          If Gleam wants to be consistent with division, all float operations would have to return a `Result` type (which I assume would have a direct impact in both performance and user convenience). Plus `let assert` provides a hatch for any function to raise too, and that includes matching on unmapped floats:

              let assert <<a:float>> = <<0x7FF0000000000000:64>>
      • Ndymium 9 months ago ago

        I wouldn't call myself a person with a mathematical background, but there are those people who believe it's just fine. [0] I don't have enough knowledge to debate that, but it would seem to disprove "basically nobody". Zero is a convention, like NaN or Inf are conventions.

        A problem that Gleam has here is that the Erlang runtime does not have NaN or Inf in its float type (or integer type for that matter). It could be represented with an atom, but that would require an atom and a float having the same type in Gleam, which is not something the type system can do (by design). The operator could, in theory, return a Result(Float, DivisionByZeroError), but that would make using it very inconvenient. Thus zero was chosen, and there is an equivalent function in the stdlib that returns a result instead, if you wish to check for division by zero.

        [0] https://www.hillelwayne.com/post/divide-by-zero/

        • lolinder 9 months ago ago

          > but there are those people who believe it's just fine. [0]

          Just fine mathematically, but Hillel does specify that he's not comfortable with the concept from a safety perspective. The whole piece is a defence against a particular type of criticism, but he leaves wide open the question of whether it's a good idea from a PL perspective.

      • Fire-Dragon-DoL 9 months ago ago

        I have no math background but every line of code I wrote that involved a division, I just wished that division by 0 results in 0, so this actually resonated with me

        • beanjuiceII 9 months ago ago

          why? if 0 was getting divided with i would want to know otherwise i'll be using wrong calculations

          • Fire-Dragon-DoL 9 months ago ago

            Because "0" often means "show none", so when dividing by 0, I'm fine "showing none".

            I'm sure it doesn't work for everybody, but I never had a specific need to deal with zero in the division that didn't result with "actually let's count it as 0"

            • josevalim 9 months ago ago

              There are several domains where 0 is different from none, for example, most computations involving rates.

              Imagine that you are running some A/B tests and you want to track conversions. If one of the experiments received 10 users and had 5 conversions, you want to show 50%. If it received 10 users and had no conversions, you will show 0%.

              However, if it has received 0 users, while you could show zero conversions, the correct answer is to say that you don't know the conversion rate. Because maybe, if you had had 10 users, they could have all converted, and the rate would be 100%. You simply don't know.

              Same logic applies over computing ROI, interest, velocity, etc.

              • Fire-Dragon-DoL 9 months ago ago

                Agree, I'm not saying there aren't counter examples, I'm just stating that making the call of returning zero when dividing by zero it's not an insane call, there are valid reasons for doing that. It's a judgement call (and they do provide a function that does the right thing)

      • whalesalad 9 months ago ago

        Regardless of what happens in the language, this needs to be handled.

        In python for instance, the developer needs to be prepared to catch a divide by zero exception.

        In gleam, the same consideration is required but the implementation will just differ.

        I don't actually see an issue here. It's a potential gotcha, but once you are aware of this feature of the language, it's no different than any other.

        • lolinder 9 months ago ago

          > In python for instance, the developer needs to be prepared to catch a divide by zero exception.

          > In gleam, the same consideration is required but the implementation will just differ.

          These aren't remotely the same. If a developer fails to catch an exception or a NaN then the program either crashes or returns an obviously wrong result. If a developer fails to guard against a zero returned from division then they get a number out that's wrong in subtle ways that may not be obvious until the wrong numbers are already in use somehow.

          The question isn't whether you can work around the error, it's how likely you are to notice that you screwed something up before it's too late.

          • beanjuiceII 9 months ago ago

            I'm with you on this for sure, seems dangerous to just use 0 how will anyone know something is going wrong?

            • throwawaymaths 9 months ago ago

              No it's fine. The 0 will just propagate to other divide by zeros and you'll always have an answer, so it's all good.

        • miki123211 9 months ago ago

          No.

          In Python and languages with similar behavior, a division by 0 will immediately crash your program with a pretty stack trace, showing you exactly where the problem is and how the program got there.

          In languages where division by 0 produces infinity, NaN, 0 or similar, your calculation just returns a nonsensical result.

          Zero is even worse than inf or NaN, as you may not even realize that there was an error in the first place, as the result of your calculation is a number and not a strange-looking value.

      • waluigi 9 months ago ago

        Defining x/0 as 0 doesn’t break anything mathematically; all the relevant field axioms have an x != 0 hypothesis so this really is undefined behavior.

        Moreover, it’s actually pretty common for proof assistants to adopt the x/0 = 0 convention, as it turns a partial function into a total one! Having something like NaN or Inf is definitely a better solution in practice, but x/0 = 0 does have its merits!

      • throwawaymaths 9 months ago ago

        Yeah you can really get yourself into trouble if you make dividing by zero zero. It's a strong indication that you have done something horribly wrong in your code upstream of that point. Why would you throw away that signal?

      • Buttons840 9 months ago ago

        INTERCAL. It just skips lines if they're syntactically invalid or cause a runtime error; it just skips them and keeps going.

  • floodfx 9 months ago ago

    I like the look of Gleam over Elixir for sure. I’d love to see some example code showing Gleam-based LiveViews but I haven’t been able to find it anywhere. Is it possible? Anyone have some code to point me at?

    • innocentoldguy 9 months ago ago

      I like the look of Elixir over Gleam for sure because:

      • Elixir allows for more flexibility and faster prototyping. • Elixir's ecosystem is superior and more mature. • Elixir compiles to BEAM bytecode whereas Gleam compiles to Erlang which then compiles to BEAM bytecode. • Elixir supports Lisp-style macros. • Elixir excels at web development, data processing, and distributed systems. • Elixir's OTP implementation is better.

      Since Gleam doesn't support macros, my guess is that Gleam isn't going to be able to run Phoenix-like or LiveView-like frameworks, but I haven't really looked at it, so I could be wrong (edited because what I said wasn't clear, and probably still isn't).

      I don't mean to knock Gleam, but it feels to me like a project that came about to add static typing to what Elixir already does, and while it has accomplished that, it has failed, so far, to live up to all the other great things Elixir and Erlang do. I know it's still a young language, so maybe someday they'll get there, but they're not there yet. After coding for 33+ years, I just don't find static typing to be all that compelling or important to me, so I would never choose Gleam over Elixir as things currently stand.

    • lpil 9 months ago ago

      The Lustre framework supports LiveView as well as entirely in-browser state management, which Elixir LiveView cannot do.

      • innocentoldguy 9 months ago ago

        I think "cannot do" is a misnomer. Shouldn't it be, "consciously designed not to do"?

        Server-side state management offers several advantages over in-browser state management, such as:

        • Better security and privacy.

        • More reliability and control.

        • Better and more predictable performance since it removes the burden from the client device.

        • More accurate user metrics.

        • Minimizes browser compatibility issues.

        • Better scalability when prioritizing user data security.

        • Easier debugging.

        (Edited to fix bullet formatting.)

        • lpil 9 months ago ago

          It does offer advantages, but also disadvantages. Lustre lets the programmer pick whichever technique is most suited to each component in their user interface.

  • pipeline_peak 9 months ago ago

    > fn add(x: Int, y: Int) -> Int

    Why do language authors insist the majority of programmers want to type this way? Meaningless arrows and redundant colons.

    Is it constructive, like it will lead us to think differently? It feels more like a contest in overcomplicating something as innocent as:

    int add(int x, int y)

    • mcintyre1994 9 months ago ago

      I think it’s because generally the type annotations are optional and it’s much easier to parse that version. Typescript uses a colon instead of arrow for the return type so I think that’s just preference though.

      In particular if you removed the types from yours it’d be add(x, y) and the parser wouldn’t be able to distinguish that from a function call. I think that’s why the fn keyword is really useful for the parser.

    • giraffe_lady 9 months ago ago

      I think there's a pretty good case for the arrow being easier to reason about especially with anonymous functions or if currying gets involved. The other way is lacking a "verb" and it becomes harder to keep track of what's going on in some cases.

      The arrow is also conventional in ML family languages, which are a venerable branch of programming whose traditions I respect and enjoy. That's not enough reason alone to keep it maybe but it's not nothing either.

      The colon thing whatever, I truly just can't bring myself to care about such fine-grained specifics of syntax. It sounds like a rough life honestly.

    • mkehrt 9 months ago ago

      The answer is this is how theoretical programming languages have done it since the 1920s, borrowing from the mathematical notation of the time. As programming language theory has made more inroads into practical programming languages in the last 10-20 years, newer languages have borrowed the notation.

      I'm biased, having been immersed in PL thoery for a while, but I prefer the colon notation. It works better with type inference, for example. Consider declaring a variable in two ways:

        var x: int = 3;
        // Now add type inference
        var x = 3;
      
      Vs

        int x = 3;
        // Now add type inference
        var x = 3; // We've just changed the type to a keyword and that's weird.
      
      But that's just a personal preference.
      • bmacho 9 months ago ago

        I don't like how symmetrical the type theoretical colon denotation is, compared to the regular "element" notation. (They are more or less the same.) If instead of

            F : A -> B
        
        we wrote

            F ∈ A -> B
        
        (or any other not symmetrical character) then we could flip it, and use

           A -> B ϶ F, and
           B <- A ϶ F too.
    • 9 months ago ago
      [deleted]
    • lpil 9 months ago ago

      It’s so they are readable left to right, which is typically easier. The difference can be seen most clearly with writing the type of functions as arguments.

      • bmacho 9 months ago ago

        No, they both read equally "left to right". What GP asks, why "a : A" instead of "A a" and "fn -> A" instead of "A fn" has billions of right answers, but "readable left to right" is not one of them.

        • lpil 9 months ago ago

          Try adding some parameters that function! Including more function parameters.

          • bmacho 9 months ago ago

            Sure.

                A f(a,b,c)
            
            becomes

                A f(a,b,c,d)
            
            and

                f(a,b,c) -> A
            
            grows to

                f(a,b,c,d) -> A
  • written-beyond 9 months ago ago

    I honestly gave gleam a serious look, considering it to build a system that might really benefit from it's concurrency model. However the lack of Macros/macro-style reflection capabilities really put me off. It makes working with SQL databases needlessly verbose. It's the same with go, though go posses the capabilities to directly unmarshal SQL rows into a structure with tags, it's far from straightforward.

    • Alupis 9 months ago ago

      This is the sentiment many have when transitioning from OOP -> FP paradigms.

      That's not to say ORM's don't exist in FP, but they are not nearly as common because their concept doesn't directly translate into what you expect from a functional language.

      That is to say this is not a Gleam problem, it is a FP problem, if we can even call it a problem (it's mostly just different).

      • written-beyond 9 months ago ago

        There are two ways to understand your reply, one is that you're talking ORMs that provide query building as an alternative to writing raw SQL. The other is you're talking just about the deserialisation into structures.

        If what you meant was the first one then, no I'm not expecting anything like that. I honestly like using a language that gets off of the way and let's me focus on what I want to build. I've done very little OOP and I've written a lot of Rust. There are many situations where I feel like r rusts verbosity is limiting my freedom but the grind of unmarshaling hashmaps into structures is way too much for me. Why shouldn't I want to use my languages typing support to help me write more maintainable code?

        I can hardly get over how dart sometimes outright refuses to cast Object types to dynamic types without some syntactical voodoo.

        • Alupis 9 months ago ago

          You might be interested in looking at the Squirrel library for Gleam[1]. It kind of reverses the SQL problem in a very nice, elegant way I've found. It gets rid of some of the issues you are bringing up, which are quite valid.

          [1] https://hexdocs.pm/squirrel/

      • lawn 9 months ago ago

        Nah this is wrong. Ecto for Elixir is a big counterexample.

        You're focused on OO ORMs as the way to simplify working with queries, but FP approaches it slightly differently.

      • throwawaymaths 9 months ago ago

        Lisp and elixir, Julia are all fps with macros? Hell even Erlang has macros

        • Alupis 9 months ago ago

          I was speaking to the ORM situation, or lack-thereof the parent seemed to be expressing.

          Regarding macros - Gleam has stated they are interested in adding metaprogramming, but it's not a huge priority because of the goals of the language.

          Macros, and metaprogramming in general have a tendency to complicate a language, and encourages ad-hoc DSL's. One of Gleam's goals is to be dead simple to pick up, read, and contribute - metaprogramming makes that much harder.

          Macros are not necessary, even if their absence is a bit of a shock at first. I used to firmly think they were necessary, but now my mind has changed on this for the most part.

    • klabb3 9 months ago ago

      > It's the same with go, though go posses the capabilities to directly unmarshal SQL rows into a structure with tags

      Go also has best-in-class support for code generation. It isn’t as good as the best macro systems, but it works really well in my experience, despite the duct-tape. It also doesn’t obliterate compile times. Check out Ent - it’s a really pleasant “ORM”.

    • lawn 9 months ago ago

      For me the big problem with the lack of macros is the unergonomic json conversions.

      In Rust for example you use a simple declarative approach and it uses the type system to serialize/deserialize properly.

      But in Gleam you need to do everything manually. Even with libraries that's a ton of manual work you need to do.

      It's the single reason why I shy away from using Gleam actually.

  • asplake 9 months ago ago

    “Gleam is a statically-typed language, meaning you must declare the type of a variable before using it.”

    That second part is wrong. Gleam has type inference.

    • pxc 9 months ago ago

      In the era of type checkers for dynamic languages, it might be better to write

      > Gleam is a statically-typed language, meaning if you declare the type of a variable before using it, that will actually do something.

      :)

  • vivzkestrel 9 months ago ago

    golang concurrency vs gleam concurrency vs rust concurrency? for a webserver?

  • 9 months ago ago
    [deleted]
  • 9 months ago ago
    [deleted]
  • rixed 9 months ago ago

    > Gleam is a statically-typed language, meaning you must declare the type of a variable before using it.

    What year is this from?

    • lpil 9 months ago ago

      It’s incorrect, Gleam has full type inference and doesn’t require variable annotation.

  • oldpersonintx 9 months ago ago

    [dead]