So, now we have Harry Berstow with an instruction to Gleam, which is another language running
on Erlang VM, so give it up for him.
Hi, everyone, my name is Harry, and I'm, as was said, an instruction to Gleam.
You might ask, why is Gleam?
Gleam is a programming language for building type-safe systems that scale, it's powered
primarily by the beam, but can also be run on JavaScript targets too.
I thought I'd go first into the three key points which make Gleam what it is.
First it's safety.
Gleam has powerful compile-time type checking built into its core.
This helps you write fast code that's integrated with Erlang and Elixir while giving you the
safety of a statically typed language.
Secondly it's performance, as was just discussed before, building on the success of Discord,
WhatsApp, Ericsson, and more with the beam.
Gleam adds no overhead, so you get the same great type-safety and performance with an
enjoyable syntax.
And finally it's friendliness, both the community and the syntax of Gleam are friendly.
The community is more than happy to help with any problem or just friendly chit-chat, they
even help write some of this talk.
And when you get something wrong, the compiler provides insightful help so that you can hunt
down the issues and stop them.
The syntax of Gleam is similar to that of Rust, but if you're not from one of those
backgrounds, don't worry, there are several guides to get started if you're used to a
syntax from Python, Elm, Erlang, or even Elixir.
Here's an example of the start of the Gleam project.
All Gleam projects have an exported main function in the project name.gleam file, which is within
your source folder.
If you need I.O., you can import the standard libraries I.O. module as shown there.
And the standard library contains several modules to help you with everything you can
think of, from regex to options, iterators, and more.
If you need target-specific standard library features, look at the Gleam, Erlang, and Gleam
JavaScript packages, which are both available on Hex and GitHub.
Let's explore some Gleam examples to get a better understanding of the language.
And once we've done that, you can go away and look at the docs yourself for more examples,
and we'll go on to building some stuff with Shimmer.
Variables in Gleam are created using the let keyword.
They are assigned to a name and a value.
The name can be reused later by other let bindings, but the values contained within
are immutable, meaning the values themselves cannot be changed.
Here's an example of blocks.
Every block in Gleam is an expression.
All expressions in the block are executed, and then the result of the last expression
is returned.
So as you can see here, the response will be false, even though hello and 42 plus 12
are evaluated.
This can be used to build more advanced expressions where the order of operations is important.
Here's an example of using the blocks to convert from Fahrenheit to Celsius, meaning
sure to remove the 32 before multiplying and dividing.
In Gleam, lists are all homogenous.
This means that elements in a list must all be of the same type.
If you try and construct a list of multiple types, this will result in a compiler presenting
you with a type error and showing you where you try to use the multiple different types,
so you can find it and correct it.
Planning to a list in Gleam is very fast, and this is the way that Gleam's documentation
recommends that you should add new values to a list.
In the standard library, there is a list module, which allows you to do more advanced operations
and also add to lists that way.
The above example uses two constant lists, well, a constant and a constant list, but
the same principles apply whether you have one dynamic and the other constant or vice
versa.
If you need multiple types in one place, you can use two pools using the hash and bracket
syntax there.
They can have multiple types and can be pattern matched against.
We'll look at pattern matching in a few slides, but if you want to access the values on a
two-pool, there's always the dot syntax, which I'll show you on the next slide, which is
similar to that that you'd be used to in object-oriented for custom types and objects.
Here's an example of a two-pool, which has two elements, and they're selected using
the dot syntax and assigned to their own variables.
It's not particularly useful here because they're constants, but with runtime variables,
it's easy to access.
Gleam supports custom types, and custom types in Gleam are a collection of keys and their
values, and you can see them as objects.
There's just one caveat though, types in Gleam don't have methods.
Similar to two-pools, you can use the dot syntax to access properties within them, but
instead of dot and position, you use dot and the name.
In Gleam, custom types can have multiple constructors, similar to in the Rust ecosystem
for enums.
This does bring another caveat though, which is that the dot syntax now only works for
keys that are shared across all elements.
In this case, the only key you would be able to use the dot syntax with is name, otherwise
you would have to pattern match against them to make sure that type safety stays.
Case statements can match anything.
In this first example, we use basic integers, but there's more advanced pattern matching
over the next couple slides.
You can see we match the first three numbers and produce a value, and otherwise we just
consume as a variable and say we can either use or discard that variable.
Some pattern match against two-pools here and even extract values from within.
In this example, we're checking for two specific paths where one is no and the other is yes.
The unique thing about the yes path is that we're discarding the integer in the middle,
but we could again take that as a variable and do further checks against it.
If you remember the custom type from earlier, this pattern matches against that, so we can
extract the values into certain variables here, like talks and mic, and the rest can
be thrown away with the two dots.
You can also use the two dots and assign that to a variable so that then you can reconstruct
the type afterwards to pass it back on somewhere else.
There's lots more about Gleam syntax that I don't have time to cover today, such as
external functions, generics, the use keyword, and more, and stuff's always being added
to the syntax.
All of it's documented in the language tour, so feel free to have a look over there and
get a better understanding of what else is available within Gleam.
Now let's get on to building some bots to put our Gleam skills into practice.
Shimmer is a library which I've doubled in and out of over the last 13 months.
I started as a project to learn Gleam and get into the Beam ecosystem, but in the process
I've done much more.
I'm doing this talk now, I've started contributing to the Gleam compiler and the wider ecosystem,
and I use Elixir and Erlang more day-to-day now.
At this point in Shimmer's development, we've moved away from using Erlang foreign functions
and now a majority of it is in Gleam.
Some key features of Shimmer, first, is compatibility.
While Shimmer is built in Gleam, it can be used in Elixir, Erlang, and any other Beam
language, it's published on Hex and the source code is available online.
I've been working on some examples for Erlang and Elixir, which I'll publish into the GitHub
repository once I've got them to a stable point.
Secondly, it's actor-based.
As we discussed before with its resilience, Shimmer is built on top of actors, and when
we're running in single shard mode, you only have one actor, multiple shards, that's not
a problem.
We use a supervisor tree so that all the shards stay alive, and it's built on top of Erlang's
OTP using the Gleam OTP package.
And finally, it's type safety.
As well as Beam Core to Gleam is a useful feature for Shimmer.
While building your Discord bot in Gleam, we leverage all of Gleam's type functionality
to ensure that the code you write for the Beam is type safe.
You only get the full type safety when you write all of your code in Gleam, but you can
always trust that the core of the library will be type safe.
It's a little fun fact, moving more and more of Shimmer to Gleam.
We're currently at 97% Gleam, and the rest is just Erlang foreign functions for small
parts of networking, which are yet to have libraries implemented in Gleam.
For some of you, this might now be the most interesting part of the talk, and for some
of you, it might not.
But I'm just going to quickly touch on how Discord's gateway works so that you have a
better understanding of why we use actors and how that's useful to us in Gleam and
with the OTP package.
Discord Bot is powered by Discord's real-time gateway, which uses WebSockets to send and
receive messages.
For Shimmer, we use Erlang's gun library from 9.9 to receive them, and we use a typed wrapper
on top of that, which is based upon Lewis, the creator of Gleam's Nerf library.
The diagram here shows what happens when Shimmer opens a connection to the gateway.
We use ETF encoding and hand the frames off to actors to pass, manage them, and send them
to the event loop, and eventually either trigger handlers or discard them.
Inside of that, Shimmer has a powerful event loop built on top of actors and messages,
which manages multiple messages as well as its own state, both internally and externally
accepts messages so that you can send updates to Discord, or internally, we can manage the
updates.
The next slide shows a state diagram, which roughly shows how it works.
The state diagram shows what happens at different stages, depending on the initial message.
For example, here, if you have a WebSocket frame, it's then passed.
We then check whether it's what it's asking us to do.
We then either respond, discard it, stop the bot, and then terminate.
This diagram isn't complete at all, but it just shows you how complicated it can be
very quickly, and how Gleam and the beam can easily handle it.
Now that we know some Gleam and understand how Shimmer works under the hood, let's actually
get our bot written.
Above the boilerplate we're going to use, and as a side note, the final code for all
of this is in the GitHub repository, which there's a link to at the end.
Shimmer uses a handler-based system, which allows for one function to be registered for
each event.
For the purpose of this bot, we're only registering two events, but you can always register more
as and when they're implemented in Shimmer.
But before we have a look at that, let's understand how this code uses what we learned earlier
and what it actually does.
Here we create a new Shimmer client.
Here we use a function that wraps around a custom type.
The custom type holds both internal data as well as token, intents, and other data you
pass in.
So we create a function to wrap it.
That way you don't have to manage all of that state yourself.
And then we pipe that into the connect function, where it takes the client, passes that as
the first parameter, and then passes your handlers in as the second.
Normally the token should be an environment variable, but for the purpose of this, we're
just using a string.
Finally, we'll tell Erlang to sleep forever so that our actor and supervisor can run in
the background, accepting messages from the gateway, and passing them to the event loop.
Now that we know what it all vaguely does, let's revisit the handlers.
First we're going to add a handler for the on-ready event.
All handlers are passed in their event, as well as the client.
That way you can use the client to call other methods, such as updating the bot's presence
or sending messages yourself across the gateway.
On the client, there's no private accesses, so you can access all the internal stuff as
well if you want to add your own custom functionality.
The client has its gun connection and all that other stuff in there as well, so you
can adapt that as you please.
Let's quickly zoom into the handler and explore that.
Here, you can see the event in this case is an on-ready event, which provides us crucial
information.
As I said before, there's the client that we have just spoken about.
The Gleams accesses syntax we learned about earlier makes it easy to access fields within
the types, even when they're two levels deep.
As you can see here, we're accessing the user's ID, which is in the user field of the event,
and then we're printing it to the console using the standard libraries IO.
We can then make this into a function, and then we can pass that into our on-ready handler.
That way we could have the functions in multiple different files and import them from across
the project to keep everything tidy.
Let's move on to actually receiving some messages and sending some responses.
When we receive a message, we get the on-message payload as our event.
This contains information about the message itself, as well as the Guild ID mentions the
message content and other variables.
For now, we're going to assign the content to a variable for ease, but we can always
collapse that into the case statement we use later on if that isn't something you need.
Let's have a look how we're going to use our pattern matching to match against the content.
Using Gleams' powerful pattern matching, we can check it as a desired prefix, and then
we can extract the part to the right of the prefix into a separate variable.
If not, we can take the message out itself, and we can just print that for easier debugging
for now.
Let's say we want a specific command, though.
We could either add another case statement onto that, or we could just edit it so it's
exclamation mark on what we want as the string of pattern matching against.
Let's say, for example, you wanted some arguments, though.
You could put the two together, and you could have your prefix with the command and take
all of the arguments out separately to then pass and manage them.
Now we'll match against a specific command, and in the response, we'll use the message
send function to reply to the user by sending another message.
As before, we can use the Handler's Builder to add this in as a function, and the bot
should be done.
Now you have a basic ping pong where you can send and receive messages using basically
everything you learned from the introduction earlier.
This before code, as I said earlier, was available on the GitHub as well, if you want
to have a look and take a deeper dive there.
Just to recap, at the start of the talk, we went over some Gleam syntax before we get
ready on our exploration of Shimmer.
We found out how the Discord's gateway worked on a high level and how to leverage Gleam
OTP, and how Gleam OTP is leveraged within Shimmer for Actors.
Thank you very much for listening, and if there's some QR codes to the Gleam website
as well as the Gleam Discord, if you want to talk there, and if there's any questions,
I'm happy to take them if I have time.
So there's time for questions.
You showed the tuple access syntax, which was tuple dot zero, tuple dot one.
Does that mean that if you use a record, or if it's called, can you still use zero as
a key?
Or is that not?
If you use a custom type, no.
When you use custom types, you have to use the keys you define in the custom type to
access them, the index syntax is only available for tuples.
Question there.
I have a question about the handlers on the library, and about Gleam, I guess.
When I'm writing the handler, do I know what type of the event is, and the client, by the
time of writing?
Yes, so when a Gleam project is put onto Hex, we produce Hex docs, and they're all documented
there as well.
So the types on Hex docs you can look at, and also Gleam has an LSP built into it, which
gives you the information, which is going to give you the information in your editor.
Okay.
Hello.
If you're used to LX0, what are the things that you would miss in Gleam, or is there a
big overlap?
There's a fairly, it has most of the features you're used to, along with your type safety.
The only, I guess, difference would be in Elixir, you can define multiple modules in
one file, whereas in Gleam, that's not really something.
Modules are files themselves.
I guess that's the only thing I could think of off the top of my head.
Right.
Thank you.
No worries.
Is there a microse as well?
No, we don't have macros right now, but there has been several discussions about how we
want to do them and what they're going to be like, so there's potential for that in the
future.
Any more questions?
Okay.
Yeah.
I'm sorry.
Thank you for your talk.
It was very nice.
I have one question.
I think currently it's version 0.25 of Gleam, or 0.26.
0.26, yeah.
I'm sorry.
This week.
Are there any big hurdles before plans for 1.0, for example?
I believe Lewis wants to get LSP features more properly implemented, but you can always
join the Discord and talk there.
I think Lewis is probably better, but I think we also have a GitHub milestone on the GitHub
repository, which says what we want before V1.
Any more questions?
Okay.
Thank you, Aria, again.