Correct Arity

not endorsing whole foods but it is clever marketing

Gleam - it's what's not in the bag

In the bag: data and function

All you need is data and functions shows how to, without traits as a feature in the language itself, cobble together something similar out of custom types:

tl;dr

Instead of a trait, just make a type that implements the generic behavior you want, and then write a function to convert your data-type into your trait-type. If you need some data-type specific logic, then pass around functions as necessary (usually from your conversion function).

Traits make sense in Rust, because the alternative I've proposed wouldn't work well with Rust's memory model or focus on performance, and using functions in this first-class manner would be much more complicated. For a language like Gleam, that doesn't have those concerns, but that is very concerned with being simple and keeping concept count low, they don't make as much sense. After all, all you really need is data and functions. :^)

So how far can you get with just data and functions? Or in other words, what else does Gleam omit?

A not exhaustive list: if/else if/else, for/while, exceptions, function overloading, operator overloading, curried functions, objects with methods, ORM, automatic JSON serialization, special HTML DSL, macros, reflection, null.

if, else if, else

No if/else may be the most striking example. Pattern matching is really great though, you always know what type you operate on, and it's exhaustive i.e. checks you handle all cases.

Case expressions for everything also highlights where representing data with bools can be an anti-pattern vs an enum which is easier to read and extend.

for, while

Honestly recursion and the various list functions may take a bit more getting used to for many people compared to the familiar for and while loops. The stdlib list module with list.map, list.fold, etc. is pretty good. The real advantage of no loops though is loops make more sense with mutable state, so omitting loops makes it easier for Gleam to make everything immutable.

exceptions

Gleam can panic on panic or assert but there is no type of "catch" to handle exceptions (well maybe OTP crash handling but that's its own thing). Exceptions are not for control flow; errors in control flow are normal values.

Error handling with normal values makes calling functions and using libraries much less surprising! From Exceptions (Spolsky, 2003)

The reasoning is that I consider exceptions to be no better than “goto’s”, considered harmful since the 1960s, in that they create an abrupt jump from one point of code to another. In fact they are significantly worse than goto’s:

  1. They are invisible in the source code. Looking at a block of code, including functions which may or may not throw exceptions, there is no way to see which exceptions might be thrown and from where. This means that even careful code inspection doesn’t reveal potential bugs.
  2. They create too many possible exit points for a function. To write correct code, you really have to think about every possible code path through your function. Every time you call a function that can raise an exception and don’t catch it on the spot, you create opportunities for surprise bugs caused by functions that terminated abruptly, leaving data in an inconsistent state, or other code paths that you didn’t think about.
...

(The only languages I have used extensively that do let you return multiple values nicely are ML and Haskell.)

Fortunately it is not 2003 anymore, other languages exist such as Gleam with a nice little Result(a, b) type that can be either Ok(a) or Error(b), as well whatever custom types you want to define.

function overloading

In some languages you can call a function with different arguments, but not Gleam:

You'd need 2 separate function names

Gleam is maybe more explicit at the cost of more boilerplate for optional arguments. One way is to create a function that takes all the args, then create a second function that calls the first function with defaults. Another way for a bunch of optional args is to create an args type that the user can intialize to all None and use the .. record update syntax to fill in. This is more work for you when you define the function, but it keeps the language simpler, maybe making it easier for people to use your function.

operator overloading

Even built in + for Int and +. for Float are different (try changing +. to + and see the helpful compiler error).

Curried functions

Why aren't Roc functions curried by default? is a nice writeup:

In Gleam, this may suffice for your problem:

objects with methods

Object Oriented Programming with inheritance, encapsulation, etc., is a whole jungle I'm not going to really get in to, but once nice thing about objects is for discoverability you can type mything dot in your editor and see everything you can do with it. It's also sometimes nice to chain methods, eg mything.set_wibble(3).set_wobble("high").delete_wumble().

In Gleam a module is the unit of related types and functions, so you get some discoverability by typing mymodule dot and seeing autocompletes. The type system also enforces what functions you can or can't use, although one feature I'd like added is typing mything |> and getting functions that take mything as their first arg sorted first in autocomplete (this is trickier due to function capture or something, but the LSP already filters functions by correct return type when known, which is nice).

A chain of functions in Gleam would be mything |> set_wibble(3) |> set_wobble("high") |> delete_wumble (As fun as writing long pipelines is, maybe the higher expression complexity is a downside, but one nice thing is you can slap echo literally anywhere in the pipeline to debug).

ORM

People have strong feelings on ORMs. Here's cases mostly against them:

SQL doesn't map perfectly to Gleam (or whatever language), which motivates ORMs but also makes them hard. You may end up asking lot of questions about the ORM itself, maybe eventually spending more time on the glue code it gives you than if you generated code for SQL yourself, such as with squirrel in Gleam.

Arguably an ORM ties your code to a library that may wax and wane, whereas plain SQL will live on forever and even makes your project portable across languages. That said an ORM like Django's that has existed for many years will exist for many more.

I have not done enough projects with/without ORMs to really talk from a point of experience, and would like to see case studies of production applications using squirrel. Interestingly curling.io (the funny Canada sport) seem to be making a version of squirrel called marmot for SQLite instead of Postgres. Theoretically ORMs abstract over different databases but eh, people seem to argue over to what extent you can actually paper over the database.

Automatic JSON serialization

Even people who like Gleam's JSON decoding mention how it seemed painful at first:

In praise of Gleam's decode

When first encountering Gleam’s approach to encoding/decoding data to/from outside of Gleam, I (just as many other people new to Gleam) think it strange that such a modern and well-thought out language still has a relatively verbose and manual-ish[1] way of doing it.

Reflecting on deserialization. Why you should explicitly decode your JSON.

At first glance, this seems like an incredibly error prone and involved way to parse JSON. And you'd be right (or not, see below). There's a ton of boilerplate code required to map every single field in an expected JSON payload and decode it using a specific decoder for each and every field.

It's probably uncharitable to take these quotes out of context, if you click the links you'll see they both come to the conclusion that explicitly writing decoders is better than the more magical approaches in other languages such as Rust or C#. Imo this is simple vs easy: in most languages serializing a struct is ready at hand, very easy to initially use, but internally handles missing fields, pascal vs camel case, etc., which you have to fiddle with to parse weird JSON payloads. The Gleam approach is itself simpler, which leaves a lot of boilerplate to you, but also allows you to handle custom tricky JSON by writing ordinary functions.

Gleam definitely leans towards making hard cases possible, both articles end with how the Gleam language server can generate decoders for you, but eh, LSP seems like an awkward protocol that you can't really configure, for example there's no way to tell it to generate lowercase strings, and you can't easily hook into it for codegen. I hope I don't sound too ungrateful though, people are doing good work on this and I couldn't come up with a better approach to decoding myself. The current decoder api is kind of confusing imo in that it constantly passes continuation functions around, but I believe the point of that is to keep decoding and accumulating errors even after hitting one initial error, so that you get a list of all errors decoding the json rather than just one. A resulting wart is for decode.failure you need to create an unused value of your type, which is not totally trivial as Gleam omits null and default zero values, again all good reasonable decisions but it's definitely a wart in the decoder API. On the bright side recently the LSP can generate some zero values for you.

Kind of rambling here idk, Gleam's decoder approach makes sense for Gleam but I don't want to oversell it, parse a bunch of JSON yourself and form your own opinion xd. The Gleam pov is probably JSON takes boilerplate or magic, pick your poison, and Gleam is decidedly unmagical.

special HTML DSL

Similarly, humans enjoy HTML templating that lets them write HTML the way they're used to and the way it looks in the browser, but that takes, if not magic, at least a second little HTML templating language. People who want to read and write "<p>" in source will definitely try to create this additional templating layer, although it will probably be janky, even if well implemented.

Doing HTML in pure Gleam is less friendly in that it doesn't look like HTML, but simpler in that you only need one language, and critically gets you all the great control flow, composition with functions, tooling, etc. that makes Gleam nice. Once you start making little functions for components, the ergonomics (kind of a cliche word but not sure what else to call the general good design and tooling) of Gleam probably win over dedicated HTML DSL.

macros

Metaprogramming is so powerful you can't really even enumerate all the things it could possibly do for you, so it's hard to say how data and functions cover all the cases you might use macros for. Imo omitting metaprogramming leads to a few specific pain points eg json and html, but not enough to justify adding metaprogramming. Not that macros are always bad but it's good that there exists a language without them.

reflection

Like macros, reflection is very powerful and you can do a lot with it; reflection is also never clear (not never good, but if some other solution eg generic types works, it's probably clearer than reflection).

null

Billion dollar bla bla bla. Pattern matching on Option(a):

Tempering the zeal of the convert

New language users seem much more likely to say some language is strictly the greatest and others are stinky because xyz; language designers seem much more circumspect, constantly complaining of tradeoffs and far reaching consequences. Even when language designers omit or add a feature they recognize it might make sense in one language but not another, and even when languages go different directions the designers respect one another for the work they do under the constraints and goals they have.

Gleam is a simple language with sum types and immutable data. Sometimes that simplicity is great, sometimes it's frustrating. The question now is what have/what will people make in Gleam? I'd be interested in production Gleam projects as proof that you can do things without these features and to see what projects without them look like.