Gleam Is Pragmatic

197 pointsposted 15 hours ago
by crowdhailer

87 Comments

jazzypants

11 hours ago

> I won’t fall into the trap of trying to define Monads in this post. Instead, let’s talk about monadic-style APIs – that is, APIs that allow you to do a bunch of things one after another, with the ability to use the result of a previous computation in the next computation, and also allows some logic to happen between steps.

Am I crazy, or did he just give a really good definition of monads in programming? I think that it benefits by not letting itself get bogged down in Category Theory nomenclature which doesn't actually matter when programming.

marcosdumay

8 hours ago

He described a problem people use monads to solve, not monads themselves.

Haskell people do talk about monadic vs. applicative combinators that are different by whether you can use the results of a previous step on the next ones. But that doesn't have a direct relation with the actual definition of those.

But yes, if you are teaching a programming language that uses monads to someone, you will probably want to explain the problem they solve, not the actual structures. As most things in math, the structures become obvious once you understand the problem.

orthoxerox

35 minutes ago

Not really. The big important part of monads is flattening/unnesting the output.

Basically, if you can convert a `Foo<T>` into a `Foo<U>` by applying a function `T -> U`, it's a monoid. Think `map` or `fold`.

But if you can convert a `Foo<T>` into a `Foo<U>` by applying a function `T -> Foo<U>`, it's a monad. Flattening is "some logic", but not any logic, it's inherent to `Foo<>` itself.

bos

9 hours ago

No, this isn’t a good description of monads. It merely describes a case that shows up sometimes.

memco

7 hours ago

Yin the OOP world I’ve seen this pattern called chaining : usually either method or object chaining.

riffraff

2 hours ago

Smalltalk (and Dart) also have "cascading" which is method chaining with special supporting syntax e.g. in ST you'd send four different messages to the same object with something like

    scene add: sprite; 
          add: otherSprite;
          setBackGround: stage;
          start
I'm not sure if it matches the "reuse values from previous computation" but it should since messages will affect the object, you just don't have local variables.

pxc

7 hours ago

It's a style I really enjoy, and it's definitely not exclusive to one language or paradigm, exactly. I see it as more of less of a kind with pipelines in Unix shells, too.

In Scala, a language with OOP heritage and support, plus lots of functional programming features, some of the most common methods you use in such chains are monads.

skybrian

7 hours ago

Gleam's 1.0 release was in May and it's still adding major features.

JavaScript support looks interesting. Browsing the package repo, I don't see how to tell which packages are supported on Erlang's VM, when compiling to JavaScript, or both. JavaScript-specific documentation seems pretty thin so far?

zombot

35 minutes ago

The syntax doesn't look like it supports partial application? Big no-no. Also, no compilation to native code. Another big no-no.

patte

14 hours ago

This is a very concise overview! I have made a small example chat app [1] to explore two interesting aspects of gleam: BEAM OTP and compilation to javascript (typescript actually). If anyone is interested...

[1]: https://github.com/patte/gleam-playground

fire_lake

13 hours ago

Gleam looks nice but if an F# comparisons was added, I think that would come out ahead based on the authors priorities.

devmunchies

11 hours ago

One thing I dislike with erlang based languages (both gleam and elixir) is that they use “<>” for string concatenation.

In F#, “<>” is the equivalent of “!=“. Postgres also uses <> for inequality so my queries and f# code have that consistency.

sodapopcan

10 hours ago

Ha, ok so I gotta give one of these "that's a really strange thing to get hung up on" responses.

Erlang and Elixir don't overload the `+` operator. In fact, they don't overload ANY operators. If you can forgive the syntactic choice of the operator itself (which I think it pretty fair considering Erlang predates Postgres by a decade and F# by two decades), this allows them to be dynamic while maintaining a pretty high level of runtime type safety. For example, one of the "subtle bugs" people refer to when criticizing dynamic languages (even strongly typed dynamic languages) is the following would work when both args are given strings or numbers:

    function add(a, b) { a + b }
Erlang/Elixir eliminate this particular subtle bug (and it goes beyond strings and numbers) since:

    def add(a, b), do: a + b
will only work on numbers and raise if given strings.

mixmastamyk

7 hours ago

It doesn’t predate sql and certainly not it’s use in mathematics. There are other options for concatenation so this is an unfortunate error.

Shouldn’t copy Erlang, otherwise might as well use it.

Jtsummers

6 hours ago

> One thing I dislike with erlang based languages (both gleam and elixir) is that they use “<>” for string concatenation.

Erlang doesn't use <> for concatenation so it's odd to name it in this comment, like that language and its developers have anything to do with your complaint. If it upsets you so much, lay it at the feet of the actual groups that chose <> for concatenation instead.

devmunchies

4 hours ago

I just assumed it was an erlang thing since elixir and gleam both do it. Now it seems even more odd that erlang doesn’t do it but they both chose it.

trenchgun

an hour ago

- in Haskell <> is binary operator of a Monoid

- in Elixir <> is Binary concatenation operator. Concatenates two binaries. This seems like it might be kind of a joke, actually, purposefully confusing "binary operator" with "an operator that takes two binaries" for humorous effect?

- in Gleam <> is string concatenation operator

As far as I can see it, they are taking inspiration from Haskell, where <> denotes the monoid binary operation, one concrete example being in the monoid of Lists binary operator being list concatenation, of which String is one example.

But really, <> for inequality is also kind of dumb and nonstandard idea (from mathematical notation perspective), originating from Algol. != which C popularized is more clear, and corresponds to the mathematical symbol, of course =/= would be even more close, but that is one more character.

ML originally used <> for inequality, following the standard (in CS) of Algol, and it was Haskell which deviated from that tradition. So F# uses still Algol tradition, but Haskell uses /= and C and others use !=, for more mathematical and logical notation.

greydius

7 hours ago

One thing I hate about F# and SQL is that they use <> as a "not equals" operator. In Haskell, <> is the binary operator of any Semigroup instance.

throwawaymaths

9 hours ago

Well binaries are <<>> so that's consistent at least. And <<>> is quotation marks in several languages, including French.

mixmastamyk

7 hours ago

Guillemets are not the same and have their own symbols.

throwawaymaths

7 hours ago

Yeah, ok. Go back to 1986 and tell the Erlang team to go use Unicode guillemets

mixmastamyk

4 hours ago

Gleam is from the past few years.

gorgoiler

5 hours ago

Then Gleam (and others) use “|>” when piping with “|” would make more sense, except that’s a bit wise OR, not to be confused with “||” which is… string concatenation (in Postgres).

cipehr

12 hours ago

The author links to a blog post talking about railway oriented programming in f#.. it might be fair to assume they are aware of f#

munchler

5 hours ago

All the more reason to include it in the comparison.

munchler

5 hours ago

I converted the example on the Gleam home page [0] to F#:

    let spawn_task i =
        async {
            let n = string i
            printfn $"Hello from {n}"
        }

    // Run loads of threads, no problem
    seq { 0..200_000 }
        |> Seq.map spawn_task
        |> Async.Parallel
        |> Async.RunSynchronously
        |> ignore
The two are pretty similar, but I would give F# the nod on this one example because it doesn't actually have to create a list of 200,000 elements, doesn't require an explicit "main" function, and requires fewer brackets/parens.

[0]: https://gleam.run/

jorams

2 hours ago

The creation of a list in the Gleam example is a choice, you could replace 'list' with 'iterator' and it would be lazy.

stonethrowaway

10 hours ago

I brought up the next logical step in pragmatism, which is that c# is adopting a lot of f# things and hence on a solid path to becoming the pragmatic language du jour, however my comment is currently at -4, probably flagged. To be fair I did skip a half-step. Maybe rabid fanboys but unlikely, probably just passionate posters that don’t see where things are headed.

macintux

10 hours ago

Or maybe your "no one else is smart/brave enough to say it" wrapper detracted from your message.

Additionally, throwing yet more syntax/features at a language to make it pseudo-functional doesn't appeal to everyone. A grab bag of features doesn't an appealing language make (for some).

munchler

5 hours ago

C# is indeed adopting some functional techniques from F#, but C# is still so bogged down with imperative cruft that the resulting combination of styles is a mess.

rossng

14 hours ago

The `use` syntax is interesting - don't recall seeing anything similar before. But I'm struggling to understand how exactly it is executed and a glance at the Gleam docs didn't help.

Is the `use` statement blocking (in which case it doesn't seem that useful)? Or does it return immediately and then await at the point of use of the value it binds?

jyjasdfsssd

14 hours ago

It is syntax sugar for CPS [1].

[1]: https://en.wikipedia.org/wiki/Continuation-passing_style

EDIT: I believe prior art is Koka's with statement: https://koka-lang.github.io/koka/doc/book.html#sec-with

rossng

14 hours ago

Hmm, it definitely looks more interesting in combination with effect handlers. Still not sure I find it super compelling in Gleam vs just not using callbacks.

jitl

13 hours ago

It’s a generalization of async/await syntax in languages like JavaScript or Swift. I like that it provides a generalized syntax that could be used for coroutines, generators, or async/await without adding any of those specifically to the language syntactically.

One level of callback nesting in a function is totally fine, two is a bit confusing, but if you have many async things going on do you really want 10, 15, 20 levels of nesting? What to do about loops?

I certainly greatly prefer async programming with async/await languages that keep the appearance of linear function execution to stacking my callbacks and having a ton of nesting everywhere

vips7L

12 hours ago

Sounds like the new “capabilities” stuff in Scala.

taberiand

13 hours ago

The equivalent in F# is let! (F# computation expressions are quite powerful); in rust the ? operator. Other languages have similar features.

It's syntactic sugar, but the readability is worth it

skybrian

7 hours ago

Everything after the line containing '<-' happens in a callback.

Since it's a callback, I assume it's up to the function whether to call it, when to call it, and how many times to call it, so this can implement control statements.

I would guess that it also allows it to be async (when the callback isn't called until after an I/O operation).

steve_adams_86

15 hours ago

Wow, this is a great overview. I’ve been playing with Gleam a bit and this was really helpful. I’ll definitely refer to this later.

I’d like to dig into the OTP library (I’m curious if anyone has worked with it much?) and create a state chart library with it, but I’m still firmly in the “I don’t totally get it” camp with a few parts of Gleam. I don’t deny that it’s pragmatic. Maybe it’s more so that I’m not up to speed on functional patterns in general. I was for years, but took a hiatus to write code for a game engine and supporting infrastructure. It was so Wild West, but I kind of liked it in the end. Lots of impure, imperative code, haha.

conradludgate

11 hours ago

I've tried to get my head around functional programming and also OTP but I also just never got my head around it.

Functional programming seems too limiting and OTP seems more complicated than I would have hoped for a supposedly distributed concurrency system.

I'm sure it's just a skill issue on my part. Right now I'm way too rust-brained. I've heard lots of things about gleam being good for productivity but I don't feel unproductive writing web apps in Rust but I felt every unproductive trying to write a non-trivial web app in gleam

beanjuiceII

15 hours ago

tried gleam but the fact i have to manually serialize/deserialize things, pretty annoying, that doesn't seem very pragmatic

steve_adams_86

15 hours ago

Isn’t manual ser/de pretty common? I like it personally. Being explicit at program boundaries usually means far fewer bugs inside the program. In JS I can pile whatever JSON I want into an object, but eventually I need to throw Zod or something at it to tame the crazy.

Maybe a generic “pile this data into this value and pretend it’s safe” tool might be nice for prototyping.

beanjuiceII

14 hours ago

i dont think manual ser/de is common at all, and languages like dart where it was used is a massive pain point for people so much that they are adding macros to the language and the first macro they add is for serialization. whats not explicit about saying hey i have a struct this is the data i expect, serialize/deseralize in this shape, validation is a another but separate concern. in javascript you are not doing anything manually so i'm not sure why thats an example?

__MatrixMan__

13 hours ago

I'm a bit confused. How can you control how your data is serialized if not manually? Are there languages that use some kind of magically-figures-it-out layer that negotiates the appropriate serialization on the fly?

yawaramin

13 hours ago

Many languages have some kind of macro or codegen system that allows serializing or deserializing based on type definitions. Eg (pseudocode):

    @deriving(json)
    class Person:
      id: int
      name: str
Would give you something like:

    def parse(s: str): Person: ...
    def print(p: Person): str: ...

__MatrixMan__

13 hours ago

I see, thanks. I thought maybe we were talking about the choice of json vs something else being automatic and chosen at runtime.

googledocsftw

11 hours ago

C# (or more precisely .NET libraries) does it using reflection. Attributes let you adjust the behaviour.

neonsunset

11 hours ago

Or with build-time source generation (because this specific pattern of reflection is AOT-unfriendly). It's not as convenient if you are using default serializer options, but if you don't - it ties together JsonTypeInfo<T> and JsonSerializerOptions, so it ends up being a slightly terser way to write it. I do prefer the Rust-style serde annotations however.

  record User(string Name, DateOnly DoB);

  [JsonSerializable(typeof(User))]
  partial class SerializerContext: JsonSerializerContext;

  ...
  var user = new User("John", new(1984, 1, 1));
  var response = await http.PostAsJsonAsync(
    url, user, SerializerContext.Default.User);

steve_adams_86

13 hours ago

Sorry I wasn’t clear; I meant to use JavaScript as an example where it isn’t manual.

Despite it being easy to use, I find I inevitably wind up requiring a lot of ceremony and effort to ensure it’s safe. I’m not a huge fan of automatic serialization in that it appears to work fine when sometimes it shouldn’t/won’t. I agree that it’s a lot of effort though. I guess the question is if you want the effort up front or later on. I prefer up front, I guess.

wonger_

10 hours ago

This is one of the complaints people have with Elm too. Json.Encode/Decode is a pain

lawn

13 hours ago

This is the biggest reason I cooled a bit on Gleam and whenever I want to do some backend stuff I'd much rather use Rust (using serde to convert to structs) or Elixir (put it in dynamic maps).

I wish Gleam would implement some kind of macro system, making a serde-like package possible.

atemerev

15 hours ago

The greatest power of BEAM-based languages is the fully preemptive actor model. Nobody else supports it. This is a superpower, the solution of most problems with concurrent programming.

In Erland and Elixir, actors and actor-based concurrency hold the central place in the corresponding ecosystems, well supported by extensive documentation.

In Gleam, actors and OTP are an afterthought. They are there somewhere, but underdocumented and abandoned.

steve_adams_86

15 hours ago

This is exactly what I want from Gleam. It does seem to be under documented and abandoned. Is there any understanding of why? Like you say, this seems like a super power. I see so much potential. A language that’s ergonomic, pragmatic as the author says, great performance, low-ish barrier to entry, etc. It seems like it could be an awesome tool for building highly reliable software that’s not so difficult to maintain.

cassepipe

14 hours ago

It is a very young language that may explain the why

sodapopcan

9 hours ago

Are there any articles that do a deeper dive into this? I ask because straight up I've been curious about Gleam, but not enough to do a really deep dive because Elixir is too good and, like Erlang, is a very special kind of dynamic language that doesn't leave me feel too lacking.

As I understand it, there have been a few "high profile" attempts to bring static typing to Erlang, all of which gave up when it came to typing messages. Your comment essentially confirms my bias, but is Gleam making real strides in solving this, or is it poised to merely cater to those who demand static-typing with curly braces--everything-else-be-dammed?

dullcrisp

13 hours ago

I understand things best by comparing across different languages so don’t take this the wrong way but I wonder if you can help me understand: If say I start a goroutine in Go and give it a channel to use as a mailbox, concurrency in Go is cooperative but it’ll automatically use OS threads and yield whenever it reads from the channel. Does Erlang/OTP do something different? If so what does it do and what are the advantages? Or is it more that the library and ecosystem are built around this model?

throwawaymaths

11 hours ago

I believe go yields after every function exit. Erlang does the same, but there are no loops (you must use tailcall) so you can't lock up the CPU with a while(true).

Jtsummers

11 hours ago

Erlang gives a reductions budget to processes. After a certain number of reductions, or if a process hits a yield point (like waiting to receive a message), the process will yield allowing another process to run.

Go uses preemption now (since 1.14), but it didn't always. It used to be that you could use a busy loop and that goroutine would never yield. Yield points include things like function entries, syscalls, and a few other points.

samatman

11 hours ago

That used to be true, but no longer, goroutines are truly preëmptive, in 10ms time slices.

vereis

15 hours ago

Gleam runs on the BEAM

atemerev

15 hours ago

It does. However, its actor implementation is not built upon Erlang/OTP, and currently is “experimental” and not even mentioned on the main site.

lolinder

14 hours ago

> its actor implementation is not built upon Erlang/OTP

This seems to be the opposite of pragmatic.

The most pragmatic approach to actors when you're building a BEAM language would be to write bindings for OTP and be done with it. This sounds kind of like building a JVM language with no intention of providing interop with the JVM ecosystem—yeah, the VM is good, but the ecosystem is what we're actually there for.

If you're building a BEAM language, why would you attempt to reimplement OTP?

okkdev

14 hours ago

Because of type safety. The OTP lib is already great, but there are still some things missing, most requested being named processes. But there is work being done to figure out how to best make it work for gleam.

lolinder

13 hours ago

The question of type safety has come up so often here that I guess it's worth replying:

That's exactly what I mean by this not seeming pragmatic. Pragmatic would be making do with partial type safety in order to be fully compatible with OTP. That's the much-maligned TypeScript approach, and it worked for TypeScript because it was pragmatic.

Now, maybe Gleam feels the need to take this approach because Elixir is already planning on filling the pragmatic gradually-typed BEAM language niche. That's fine if so!

okkdev

an hour ago

Type safety is one of the goals of the language I don't see a reason to throw it out of the window now. I see what you mean, but the type system is one of the things that makes gleam pragmatic. If you really need some missing OTP feature you can super easily step into Erlang using FFI and get it. That's one of the reasons the article doesn't call gleam pure.

arcanemachiner

14 hours ago

I believe their implementation was written to support static typing (since Gleam is a statically-typed language).

pmontra

13 hours ago

I agree with the part about reusing OTP but some of the server syntax of Erlang and Elixir is not good IMHO. I never liked using those handle_* functions. Give them proper names and you cover nearly all the normal usage, which is mutating the internal state of a process (an object in other families of languages.) That would be the pragmatic choice, to lure Java, C++ programmers.

throwawaymaths

11 hours ago

Elixir gives you Agent, which is what you want, but for reasons, Agent is a bad choice.

What you're not seeing with the handle_* functions is all the extra stuff in there that deals with, for example, "what if the thing you want to access is unavailable?". That's not really something that for example go is able to handle so easily.

H12

13 hours ago

IIRC the re-implementation was necessary for type-safety.

d--b

3 hours ago

Doesn’t it compare mostly to F#, rather than Haskell or OCaml? The examples in the post really look like F# to me

vivzkestrel

6 hours ago

newbie here, how does gleam compare to golang, rust and python?

behnamoh

14 hours ago

It's not pragmatic if you have to import these basic libs:

```

import gleam/dict.{type Dict}

import gleam/int

import gleam/io

import gleam/result

import gleam/string

```

eterm

14 hours ago

Why not?

What's wrong with a standard library the bits of which you want you choose to import?

orthoxerox

12 hours ago

I can understand having to import the "dirty" parts of the stdlib, like I/O, or the "heavy" parts, like Unicode or timezones. But why force someone to import every single type? Most functional languages have a prelude that covers the types every non-trivial program uses: booleans, numbers, strings, collections.

Jtsummers

12 hours ago

> But why force someone to import every single type?

That's not importing the types, it's importing a suite of functions related to the types.

https://hexdocs.pm/gleam_stdlib/gleam/int.html - gleam/int for example. The int type is already in the language and usable, this import brings in some specific functions that are related to operations on int.

mixmastamyk

7 hours ago

Why not methods of the type?

Jtsummers

7 hours ago

The answer to "why not methods" would be because it doesn't have methods.

reikonomusha

14 hours ago

It's not that it's wrong—at least I don't think so. It's that it's an example of a choice that is not pragmatic.

I suppose we should agree on what "pragmatic" even means, since it has become something of a cliché term in software engineering. To me, it roughly means "reflective of common and realistic use as opposed to possible or theoretical considerations".

So is having to import basic functionality a pragmatic design? I would argue no. Having to import basic functionality for integers, strings, and IO is not pragmatic in the sense that most realistic programs will use these things. As such, the vast majority of ordinary programs are burdened by extra steps that don't appear to net some other benefit.

Importing these very basic functionalities appeals to a more abstract or theoretical need for fine-grained control or minimalism. Maybe we don't want to use integers or strings in a certain code module. Maybe we want to compile Gleam to a microcontroller where the code needs to be spartan and freestanding.

These aren't pragmatic concerns in the context of the types of problems Gleam is designed to address.

To give a point of comparison, the Haskell prelude might be considered a pragmatic design choice, as can be seen from the article. It is a bundle of common or useful functionality that one expects to use in a majority of ordinary Haskell programs. One doesn't need to "import" the prelude; it's just there.

I don't personally find Gleam's design choice a bad one, and while GP was a bit flippant, I do agree that it is not an example of a pragmatic design choice.