Gleam, a statically typed language for the Erlang VM

@lpil Are you at the point where randos like me could be help implement things?

Ah! I was hoping you would show up with your OCaml skills @OvermindDL1 :slight_smile:

It’s quite unfair of me to attribute the implicits idea to Scala alone, OCaml is a huge influence. I have lately been more focused on Scala because I’ve found clearer documentation for their system, and because I can today write Scala code and see how it works in practice.

Can you recommend any documentation (or a working compiler with implicits) that I could try?

let show (type t) (module Show : ShowWitness with type t = t) = Show.show

Is (type t) an annotation bringing the type into scope, like show :: Show a => a -> String ? Is there a reason it’s not written as let show (module Show : t ShowWitness) = Show.show?

Can values other than modules be implicit?

Are values marked as implicit when they are brought into scope, or would I open a module that contains values that are marked as implicit there?

i.e.

open ModuleThatHasImplicit

let "true" = show true

vs

implicit module Bool_ShowWitness = ModuleThatHasImplicit.Bool_ShowWitness

let "true" = show true

Even a stripped down version of modular implicits would be a good start as it can always be expanded later.

I would be interested in what you’d think a stripped down version might look like if you’re up for sharing :slight_smile:

Actually OCaml’s Algebraic Effects system (currently in fork but being brought into mainline very piecemeal) handles processes, messages, and OTP behaviours very well.

This is something I don’t know anything about. I’d love to learn more if you have some resources to share.

Thanks again, I am very grateful for your insight and your help.

Possibly! However I have not yet created any kind of fine grained TODO list that would enable people to find tasks to work on.

It’s also be good to have people other than myself try and write small programs in Gleam, though the compiler may not be ready enough for that.

Would you be more interested in writing Rust or Gleam?

FWIW, the partial type checking thing (everything except Process/messages) is generally a matter of consistency. There’s value in following the same set of rules across the entire language. At the same time, dealing with unstructured data in statically typed languages tends to create a lot of unnecessary pain (like microservices in Java).

Regardless, good luck. There’s clearly demand out there for it.

3 Likes

The main proposal documentation (lots of examples and all) is at: https://arxiv.org/pdf/1512.01895.pdf
But googling will reveal lots of discussions too. There is a prototype compiler floating around that people experiment with as well.

Well in this case you could actually write it as let show {module Show : ShowWitness} = Show.show with the modular implicits support as it would bind the t for you (via the {...}), but in the non-implicit form the (type t) is a free generic type binding in OCaml, it is just a declaration that “I’m going to use this same type as a type for matching others types”, I.E. I’m pulling the t “out” of the ShowWitness into a type I can properly match on, but as stated, in this example it wasn’t needed. ^.^;

No, for a variety of reasons, the biggest of which is ambiguity in what to pull in. Modules in OCaml (unlike SML and so forth) are higher polymorphic types so it is both trivial and very fast to decompose types inside of them, and as they are generic containers for, well, anything (values, types, declarations, signatures, anything) then they are the main ‘container unit’ in OCaml.

If you want to bring in only implicits then you can open implicit Blah. Doing open Blah will open everything that is in the main public interface for that module. You can of course do Blah.(1 + 2) or whatever as well (locally scoped open is ModuleName.(open scope here)).

Both methods would actually work.

Mostly what OCaml is already planning for the basic modules, ignore functor modules (functors would be an awesome feature to swipe too for note). It’s quite simple in features and implementation but featureful enough to handle all of the features listed in this thread so far. :slight_smile:

What do you know about algebraic effects so far?

1 Like

I’m more interested in writing Gleam, though this could be my excuse to finally learn Rust (I come from a C++ background and feel like learning Rust needs to happen at some point). I’ll start by writing some experimental applications in Gleam and let you know what I come up with. Though, if you do get a TODO list up or have some specific things you want to be worked on (Rust or Gleam) let us know.

1 Like

Nothing at all. I’ve done a little googling but I couldn’t find anything that answered my questions. It seemed like there were a few different things with the same name.

1 Like

In typing systems it’s just a way to control the context that is being executed. I even made a play library to emulate it (as much as I could do without performing manual CPS expansion, which is much easier to do when you are making a language from scratch rather than modifying Elixir AST) in Elixir. ^.^

In OCaml there are some good examples of it in a variety of places using it’s forked system:

(the top two links in the readme are great!)

(this is both how to acquire the forked ocaml effects compilers and how to use it, just the docs on how to use it are very useful)

And of course the main discussion on OCaml’s AE system at ocamllabs:
http://ocamllabs.io/doc/effects.html

I can also detail anything from what it is good for (though the above links do very good at that I think) all to how to implement it (on the beam even, the beam doesn’t support proper continuations but with a little bit of CPS transformation in your code generation, which you might be doing already for other purposes, its actually quite efficient at it because of immutability) so feel free to ask anything in detail. :slight_smile:

EDIT: Just to ‘whet your whistle’, some features that Algebraic Effects can add to a fully-typed fully-immutable language like compiling to the BEAM, and all of these can be implemented in usercode itself, not as a language construct:

  • Mutable variable (yes, as a language construct!)
  • Exceptions (ditto!)
  • Concurrency, even Actor-style concurrency (Erlang/Actor style concurrency is one of the examples for concurrent work in ocaml AE examples on occasion)
  • And so so much more, all type safe and functional.

Now ‘proper’ AE of course requires a backend with multi-resumeable continuations, and normally doing those in a non-immutable language is…not cheap, but the BEAM is fully immutable so doing a trivial CPS transformation on all calls (or only on needed calls if your function types can hold that information, which is a very useful optimization) gets you it mostly for free. :slight_smile:

6 Likes

I’ve been thinking about this and I’m not sure how implicits would work alongside generics without something like functor modules that are automatically constructed when needed.

(here be pseudo-code)

Say I have this implicit using function:

type Show(a) = fn(a) -> String

fn show(implicit impl: Show(a), a) -> String

If I call show(1) the compiler has to find an implicit function where a is Int.

If I call show(1.0) the compiler has to find an implicit function where a is Float.

These seem reasonable, the user would likely be able to use the int and float modules for these.

If the user calls show([1]) the compiler has to find an implicit module where a is List(Int).

It seems unlikely that there is going to be a Show implementation for this specific type, rather you would have to construct it

let implicit listIntShow = list:make_show(int:show) // fn(List(Int)) -> String

show([1])

At this point it seems we have lost some of the convenience of the implicit Show type.

Is it still more useful then explicitly using (for example) list:show(int:show, [1, 2]) ?

Is there some other trick I’m missing here? :slight_smile:

Why does the function type have a fn in it? Why not just a -> String? The -> is pretty well known as the application operator in type systems. :slight_smile:

Yep, that’s the pattern OCaml takes with Witnesses as well.

In OCaml’s implicit modules there is a Functor that is defined for lists of any type, so first it resolves the lists part, then it sees there is another unbound type inside that and it resolves it again. It talks about this in the PDF I linked. :slight_smile:

Technically you don’t even need Functors for this, just module definitions with free bound types is enough as long as you can resolve ‘through’ those, but that could potentially have some corner cases I can see… Functors would be best.

1 Like

Why does the function type have a fn in it?

I just found it easier to read once I put the fn in. :slight_smile:

Aha, this is the key part I missed. Thanks again!

1 Like

Hi! In case you’re still interested in working on Gleam I’ve listed some outstanding pieces on the issue tracker and added a “good first issue” label. https://github.com/lpil/gleam/issues

I’ve also added a CONTRIBUTING file which documents the development process.

If you do decide to contribute something it’d be great if you kept note of anything that is confusing and could do with some documentation. Thanks :slight_smile:

9 Likes

Hello all! I’m looking on some opinions on the syntax used for tuples and records.

Currently they are quite similar. {1, 2} for tuples, {x = 1, y = 2} for records. {} is an empty record and there is no literal for empty tuple.

The type syntax is also similar. {Int, Int} is a tuple, {x = Int, y = Int} is a record. {} is an empty record and there is no literal for an empty tuple type.

There is also a syntax for a record type that contains specified fields as well as zero or more unspecified fields. {other_fields | x = Int, y = Int}.

My question is what should the syntax be for a record type with unspecified fields and no specified ones? I instinctively wrote { some_fields } but this clashes with the syntax for a single item tuple (i.e. {Int}). The syntax could possibly be { some_fields | }, but this doesn’t seem very appealing to me.

We would also have a clash if we wanted to introduce a short-hand syntax for pattern matching on records. Note how both patterns would be the same- this is not possible as it would prevent us from inferring the type of the pattern.

let {x, y} = {x = 1, y = 2}
let {x, y} = {1, 2}

One possible solution is to adopt a different syntax for tuples, clearly differentiating it from the syntax for records. This syntax cannot be (1, 2, 3) because this would clash with function call arguments. (edit: It may be possible to resolve this clash),

tuple(1, 2, 3)
<1, 2, 3> // This loops like an Erlang PID
#(1, 2, 3)
@(1, 2, 3)

Or we could use a different shorthand syntax for records.

let {x=, y=} = {x = 1, y = 2}

What syntax would you like to see here? :slight_smile:

1 Like

I’m unclear how (1, 2, 3) clashes with function call arguments. Isn’t that disambiguated with whitespace? Personally I think that is the best choice if only because it is used in Haskell, OCaml and Scala. I like your existing record syntax the way it is but I don’t know if I’d use a wildcard match with no field matches since that is not different than just binding the entire record right?

2 Likes

I’m unclear how (1, 2, 3) clashes with function call arguments. Isn’t that disambiguated with whitespace?

Whitespace is not syntactically relevant in Gleam I’m afraid.

Thinking about it some more it may be possible to do this just for function calls with some trickery, I’ll need to do some experimentation. :slight_smile:

I don’t know if I’d use a wildcard match with no field matches since that is not different than just binding the entire record right?

The wildcard is the default for the inference algorithm and in my opinion one of the best features of Gleam. It enables functions that operate on records to be polymorphic as detailed here: https://gleam.run/tour/record.html#parameterised-record-fields

Without this feature implementing behaviours would not be possible, so it’s extremely useful.

Why not to use the Elixir map syntax %{} for records thar way you can have the empy tuple and the empty record

1 Like

I would be careful with < and > as containers because that may end-up forcing Gleam to be whitespace relevant in some places if you have comparison operators.

Can you have an issue with record disambiguation? For example, if I have two records: cartesian coordinates {X::float, Y::float} and polar coordinates {A::float,D::float}, and you just write {float, float}, how do you know which one is which? This is a good paper on the topic.

Similarly, do you allow assigning to variables anywhere in your pattern? Because in Elixir I can do this: {x = 0, y = 0} and that’s just plain assignment. So you may have ambiguity there too. But for instance, this doesn’t happen in Erlang though, as record labels are atoms which are downcased and vars are upcased.

Something I used to do that a lot when designing Elixir was to look at Rosetta Code, although I would usually stay confined to functional languages.

I made a similar decision in Elixir early days where we had hashes as {key1: foo, key2: bar} and tuples as {foo, bar} and an empty hash would be {:} while the empty tuple was {}. It was very early on, so I did not get a lot of feedback from it but it always felt a bit weird. I think that not allowing an empty tuple to be expressed, even though I can’t remember ever using one, is a bit weird. I think eventually you may run out of single-letter containers (from the get go you likely have tuples, records, lists, binaries, and maps and only so many characters) so thinking about a strategy how you want to represent them in general may be more important than a single case. At some point I made a decision that I would stay mostly with Erlang syntax when it came to data types and that simplified the decision process quite a bit.

7 Likes

Why not to use the Elixir map syntax %{} for records thar way you can have the empy tuple and the empty record

@mrkaspa I want to have the record syntax be the more concise {}, though copying Elixir is certainly an option.

Why not to use the Elixir map syntax %{} for records thar way you can have the empy tuple and the empty record

@josevalim Great point, I didn’t think that one through.

Can you have an issue with record disambiguation? For example, if I have two records: cartesian coordinates {X::float, Y::float} and polar coordinates {A::float,D::float}, and you just write {float, float} , how do you know which one is which? This is a good paper on the topic .

To be clear when talking about records in Gleam we are talking about a data structure similar to structs in Elixir, though they do not need to be pre-declared. This name is a confusing as it clashes with the Erlang record, which is a tuple with named fields.
I haven’t yet found a name that doesn’t have a similar problem. Struct implies fixed fields and contiguous in memory, Object has OOP connotations.

All types except enums are structural in Gleam, so the two examples given there would be equivalent and could be used interchangeably. If you want to differentiate them at type level (which seems like a good idea) you could use a single variant enum.

pub enum Cartesian =
  | Cartesian(Float, Float)

pub enum Polar =
  | Polar(Float, Float)

Later I plan to add some kind of opaque zero-overhead wrapper type (like Haskell’s newtype) that will allow the programmer to opt-in to nominal typing for any data type.

Similarly, do you allow assigning to variables anywhere in your pattern? Because in Elixir I can do this: {x = 0, y = 0} and that’s just plain assignment. So you may have ambiguity there too. But for instance, this doesn’t happen in Erlang though, as record labels are atoms which are downcased and vars are upcased.

There will be a syntax for this, but it will most likely not use =.

I think eventually you may run out of single-letter containers (from the get go you likely have tuples, records, lists, binaries, and maps and only so many characters) so thinking about a strategy how you want to represent them in general may be more important than a single case.

A good point, thank you.

3 Likes

But are they still tuples behind the scenes? I have a bunch of other questions but they depend on the answer to the first one. :smiley: I’d love to chat about this. Is there a gleam channel on IRC or similar?

3 Likes

I hate implicit in Scala … This is was drove me out this language.
The implicit give you a lot of power but from other side is low level tool and can you drove crazy.
The implicit in Scala will give you only mess and pain … :slight_smile:

There is also discussion to replace implicit in next version of Scala 3 / Dotty something useful for programmers.