Cookies   I display ads to cover the expenses. See the privacy policy for more information. You can keep or reject the ads.

Video thumbnail
Welcome everyone, to my talk about Lua.
My name is Andreas.
You might have seen me on the nickname of ComicSansMS
which is a really silly nickname, so it's usually not taken,
except on Twitter, because Twitter's full of crazy people,
so I had to use an even sillier nickname there.
I'm one of the organizers of the Munich C++ User Group,
so yeah, if you ever happen to be in our around Munich
and want to get in touch with the local community,
give us a call, we'd be happy to set up a meeting with you.
And my day job is incredibly working as a software architect
for BMW on the autonomous driving software platform.
So today I'm going to talk about Lua.
So can I maybe get a quick show of hands,
how many people have used Lua in like a serious project,
like not a toy project but like,
something production, yeah, couple of hands.
Okay.
For those of you who have not used it,
hopefully I can convince you that it's a language
that is worth investigating even for serious projects.
So, what is it that is distinctive about Lua
that sets it apart from other languages.
I'd say there is two features that are kind
of unique to it, the first one is that it's actually
not designed to be a standalone language.
It's an embeddable language, how they call it.
So usually you have like some application,
like the most famous use case for Lua is being games,
that is written in some other
programming language, often C++.
And then you have Lua in there to customize
like some small parts.
But, it's also being used
for programming microcontrollers, embedded microcontrollers,
or also like serious, serious desktop applications,
like Adobe Lightroom.
The other thing that is kind of unique about Lua
is that it's small.
It's really small.
So you're not supposed to be able
to read this thing on the left,
but that is actually the full EBNF syntax
of the language, which fits exactly on one A4
sheet of paper, the compiled binary is really small.
These 180 KB is actually the full Lua package
including the standard library
if you strip out the libraries
and the parser if you only need to run compiled BiCode,
you can make this significantly smaller.
The complete reference manual is only 82 pages
is really the complete documentation including
the full CAPI, that is required for integrating it
with a enclosing program,
it only has eight basic data types.
As you can imagine, from these numbers,
there's not a whole lot of features in there.
Sort of if you take like the Python
approach of batteries included,
Lua is sort of the opposite of that conceptually.
But, the cool thing about Lua is that,
for them is actually not a limitation.
Is actually a virtue.
And not only because this allows them to run on devices
where other languages wouldn't fit, it's also
that from a language design perspective,
they really embrace this property of being small,
and turn that into a very elegant language.
So, I always like to say, the whole language
fits into your head at the same time.
Which is something that I cannot say of most
other programming languages.
I certainly cannot say that of C++,
unless maybe you're one of those guys,
but like for me, if I use C++, I have to constantly
swap in and out of the different features of my head,
and with Lua I don't need to do that.
Which is really nice.
So, hopefully I will be able to convince you,
in the progress of this talk, that although Lua
is small it is quite a powerful and interesting language.
However, I have to say, the problem with this talk,
is like, um, I cannot assume
that there are any Lua experts in the audience,
so on the one hand it's going to be an introductory talk
to Lua, but on the other hand I also want to show
interesting examples, right.
And the problem with that is that these interesting
examples, they're sometimes a little bit contrived,
so and a little bit too clever, so, what you see here
might not be representative
of what you would write in every day Lua.
Like, typically it might be a lot simpler.
In particular what I want to say is,
if at any point during the talk you have the impression
that oh no, this is like, really unnecessarily complex,
and I don't get it and I don't really
want to use this stuff, please blame that
on the talk, and not on the language.
That out of the way, this is Hello World.
Notice that although my syntax highlighter
likes to make the print blue, it's actually
not a keyword, it's just a function
from the standard library, which in Lua's case means
it's just an ordinary function it does not
get any special treatment whatsoever.
So this is how we declare a function.
It's a dynamic language, so we don't have to give any types,
we just say that we have three arguments
and give them names, the interesting thing about Lua,
this is the first thing where we actually see it,
it being small and lightweight,
is that functions are actually not like this, this
special kind of thing that they are in C,
but all functions are actually lambdas,
all functions are anonymous, and this declaration
that we see up there, is just syntactic sugar
for what we see down there, which is the construction
of an anonymous function, which is then being assigned
to a variable of type f.
Right?
So, f is just a variable like any other,
and you just assign to it a value of type function.
And what that really means is like functions
are true first-class values.
Function values are not any different from strings
or numbers or anything else.
Okay?
So that way we can reuse all of the properties
that we have for the other values also for functions.
That keeps it small, but that also enables
some interesting things.
So, for example, let's say I'm not happy
with the print function here.
I want to replace it by my own.
I can just do that.
It's just a value.
I just assigned to it a different function to it.
No problem.
Let's say I want to count the number of print calls.
That could be a use case for this, right?
I just replace the vanilla print function
by the new function, and there's actually valid syntax,
this is a very adept function, which just increases
the counter, and then calls
the original print function within.
Now, you notice that there's actually not
any variable declarations in this code.
I just assigned values directly to variables
that were not previously declared.
This is something that most scripting language
actually will allow you to do, so if you just assign
to a new variable, you don't need to declare it,
it just automatically creates a new global variable
and puts the value into it.
Which is not a behavior that is always useful,
but we will get to that later how we can.
Maybe work around that if we don't like it.
What is really not nice about this code
is that we now polluted our global name space
with a variable named count.
So, if we only ever count the print function,
don't do much else in the program, that might be okay,
but really it doesn't look that nice.
So what we would like to do instead is keep
this count function at a smaller scope,
so, for example, have a function enable counting,
which like performs this hooking of the print function,
and have the count function and the old print function
as local variables in there.
So here notice if I prefix the variable with the local
keyword that declares that variable as a local variable,
it works pretty much exactly the same
as local variables in C++.
Now the interesting thing is if I now write the same code
that I wrote before, what will happen is that
the local variables from the end closing scope
will be implicitly captured in that new local function.
So like in C++ if you wanted to do this,
you would have to write in the area brackets
of lambda, the captures explicitly.
So like the names of count and old print,
you would have to specify them, write them out.
In Lua you don't need to do this.
Just as lexical scoping, so it knows
that it can pull in the stuff from the outer scope.
The interesting question is now.
I just made the count variable a local variable,
so how can I access my count now.
Like if I want to find out of often print was called.
I can just return a function that gives me back
the value of the counter.
And this is interesting now, right.
Because the count is an integral value, so it has
value semantics, and it also has value semantics
in Lua, just as it has in C++.
So, now have captured the same value
in two different lambdas, in two different closures.
And it still works.
So this is actually where the full
in full lexical scoping comes from.
That allows you to do something like this.
This is actually really difficult to pull off
in the implementation, and Lua couldn't do this
from the beginning, but since version 5.0, I think,
they support it, and it's a really powerful feature.
Notice that of course since we capture stuff
that means that our function objects are actual objects,
so whenever you read function, interpret that
as construction of a closure.
So there's actually memory potentially being allocated there
and this needs to be garbage collected,
potentially, at some point.
Okay.
Let's talk about tables.
Tables are the most important data structure in Lua.
In fact it's the only complex data structure in Lua.
And, I actually don't know of any other language
that can get by with just one data structure.
Which is quite fascinating.
So what is a table, like, this is the simplest table,
it's just constructing an empty table with nothing in it.
But a table, imagine it as being an associative array.
So, you can use it just as you would use a C array,
but you can also use it as a dictionary.
Using a, using it to map keys to values.
And the interesting thing about Lua tables
is that they actually allow you to map any type of value,
to any other value, so for example, you can use functions
as keys, you can use other tables as keys, if you want.
This is again, very much, the Lua philosophy.
Right, like you only provide very few features,
but you make those as flexible as possible.
Notice that tables, unlike the numbers that you saw before,
have reference semantics.
So, for example, what we do here, is we basically built
assembly link list, we construct like two note tables,
each of them holding a value,
and then we link them together,
through the next field of the first note.
You don't usually need to build link lists like this,
because like if you just use plain tables,
they usually powerful enough for what you need to do,
but it's useful to know that you can do it.
As with functions before, whenever you,
read the curly braces, think of it as table construction,
so this means that you will potentially allocate memory.
Since tables are only complex data type,
if we want to build like a struct, or like a record type,
you also have to do that with tables,
which means like we simply create entries
in our dictionary, in our table,
for the different fields of the struct.
And since the syntax that we already know for accessing
the tables is a bit cumbersome for this use case,
Lua provides syntactic sugar with the,
for accessing the fields with a dot.
So the last line is just syntactic sugar
for the line buff there, completely equivalent otherwise.
So we want to add member functions to our record now,
we can just assign a function to one of the fields,
and then call it as member function.
So notice Lua's tables have reference semantics,
so this function actually mutates,
the value inside the table, and the call down there,
this is, think of it as a member function calling
in C whether to give this pointer explicitly.
Seems we don't want to pass it explicitly
because it's kind of silly, we also get syntax sugar
for that, with the colon syntax.
So, the thing about tables is that,
we don't really have types right,
so all of our complex numbers have the same type table.
So if we want to build multiple values of this table,
we usually have to supply a constructive function
like we see here.
So let's say now we have these complex numbers
and we want to add to some.
For this we would actually need operator overloading, right?
And Lua provides this through a feature called Metatables.
So everything in Lua is a table right?
So Metatable, the idea here is basically,
I provide a table which I then attach to a value,
and this table tells me how to treat this type
that is attached to, in the language.
So, there's a couple of special fields,
and they allow you to specify how it treats
the other authentic operators, the comparison operators,
how it treats the element access
with the outright brackets and so on.
So, I just changed my construction function
to add this Metatable, call that,
assigns the Metatable to my newly constructed
complex number, and then I can
just add them with the plus operator.
So, let's say now I have more complicated data type,
where I actually have invariance between the different,
on the different fields.
Might not want to allow the user to access them directly,
but rather encapsulate them.
So, instead of exposing your month and date
of the date directly, I actually want to provide
getter and setter functions.
And the way that we do this is in the construction,
we actually build a local table that contains
the actual data, then what goes in the table that we return,
is just to getter and setter functions,
which again access the actual data through the closure.
So some of the similar trick that we saw
with the counter before, and that allows us to give,
to implement encapsulation without any explicit
language support for it.
Which is kind of cool.
Of course, since a table is the end
just an associative array,
we also get a certain reflection properties for free.
Like, a table is a complex number if it has these fields,
real and imaginary.
And of course I can also inspect all of the fields
by just iterating over the table
which in Lua I do with this syntax, so the pairs function
just gives me back an iterator function,
which with each invocation returns the next field
of the table, and then I have this generic form,
that processes iterators.
So this allows me to perform reflection on individual
table that I have already in my hand,
but what about global variables?
Right?
I might need to know, which objects
are aligned around in my environment.
So Lua has a solution for that.
Global variables are actually not global variables,
they are entries in a table called _G.
So, as I said, everything was a table,
and global variables are just syntactic sugar
for accessing elements in this global table _G.
And this has a really interesting implication.
Cause like, as we saw before, we don't need
to be clear variables.
So what people really don't like about this,
if I make a silly typo like this, where I assign
to fobar instead of foobar, the language
will just silently create a new global variable.
Which is not what I wanted here.
But, G is just a table, right?
So this creates a new entry fobar into this table.
And I can just use Metatables to forbid this.
So, I can use, say, you're not allowed to create
any new entries into this table.
And Lua actually provides on their homepage module
that does this in a little more complicated way,
and the idea there, is that you can sort of restrict
where you are allowed to create global variables.
So like only allow to declare them in like certain
parts of your script, or only be allowed to declare
them through declare function,
which then bypasses the Metatable.
And this is I think a pretty cool feature
and it basically comes for free
from the properties of the tables,
and the fact that everything in Lua is a table.
So, this is all pretty cool, but if you have ever tried
to integrate scripting language with C++,
you know that this is usually not a lot of fun.
So, how that does work with Lua.
So as we said before, Lua is an embedded language,
that means the main functions still belongs to C++.
And in there, we use Lua more or less as a library.
So, we create a Lua State object, and then
we can just run some Lua code.
So who thinks that doing something like this
with a scripting language should
be any more complicated than this?
No one.
Okay, that's what I thought.
So, just running Lua, chunks of Lua code
in isolation is not very interesting.
We also want to communicate with our C++ program.
So in order to export functions from C into Lua,
so that Lua code can call them, we actually
have to wrap them in functions of this signature.
Like any function with this signature can be directly
injected into the Lua VM.
And as you might notice there, this actually
does not take any function parameters
and it also does not provide any return values.
It just takes the Lua State object.
And that is by design.
So the Lua API is all designed around
this concept of a stack.
And the idea of the stack is that whenever
you want to get a value out of the VM
or into the VM, you have to put it on the stack,
and then you can grab it from the stack.
And this is, this might seem a limited design at first,
because like if you imagine if you want to set a value
inside a table, you have to put the table on the stack,
you have to put the key on the stack,
and you have to put the value on the stack.
And then you have to make the call that does the actual
assignment, which is a little bit verbose.
But there are two fundamental advantages here.
The first advantage is that it keeps the API very small,
because we have only eight data types overall,
and you only need two functions per data type,
one to put it on the stack and one to get it from the stack.
The second implication is a little bit more subtle
but I think it's even more important,
is that it solves the ownership problem.
Cause the problem with garbage collector languages is,
if I now grab like a table, from inside the Lua VM,
and expose it to the C++ code, I actually need
to make sure that the VM's aware of the fact
that I am pointing to this table,
so that the garbage collector
doesn't pull it out from under me.
And with Lua, I cannot access the table
if it's not on the stack.
But as soon as it's on the stack,
the stack has ownership on it.
This might not seem like a big deal,
but if you're using this in everyday code,
this is actually really powerful.
So, pushing values on the stack,
I'm just gonna use number and string
as the example types here,
some of the types are a little
more complicated, but not much.
So let's say I just have two push functions,
so two overloads for push functions,
one that pushes a number, so Lua numbers
are usually double, but if you compile
for a platform where you don't have double available
you can actually change that
very easily to like pure interval types.
I would push a string.
And then we can very easily write a function like this
in C++ 17, which uses the fold expression,
to just push an arbitrary number of arguments to the stack.
Is everyone familiar with this syntax?
I've seen it quite a lot this week already.
So if you don't have C++ 17, available,
you can also use a library to do the same,
so here's an example that uses boost hana.
And if that also doesn't work for you
there was actually a talk, I think yesterday
by Joe Fan-Cue, where he have like some examples
on how you can emulate this feature with all the C++
versions, I just put the code up for reference,
you don't have to read it,
you're certainly not expected to understand it.
Because it's horrible.
But it's possible.
So what about the other way around.
How can I get values from the stack,
so out of the VM into C++.
So here the thing is that since Lua is a dynamic language,
I don't know what the type is of, of the value on the stack.
So I need to do the switch case here.
But, the thing is that, this is actually the only,
the only point where I'm willing to stick
to this factor where I don't know the type.
Because like, as soon as this function returns,
I'm actually again in the C++ type system,
so I want this to be like, as rigorous as possible again.
So, what can we use to as a return value here?
So we could of course use a base class,
like, solve the problem with virtual inheritance,
but that's just so 90's, right.
So, in this example, you have to define what like
your unified interface of all the different types is,
in my case it's just a type function.
So each value can tell you what it's type is.
And then you could just use a C++ 17 variant, for example.
So, like in a full implementation you would have,
there's only eight types in Lua.
And you know them all beforehand.
So variant is actually really good fit,
and you can also implement it with
very little size overhead.
Like you might lose some bytes when saving numbers
or bullions in there, but for tables, functions
and the more complex types, it's pretty much uniform
for memory consumption.
And then of course, this is how you get stuff
out of the variant with visitation.
If you don't like variants
and you would like to use a different kind of type erasure,
Louis Ti-Yong was giving a very nice talk
yesterday about runtime polymorphism,
where he explained all the different options that you have
if you don't want to use a variant.
Okay, so let's call a function now from C++.
Let's call a function.
So, we load the function on the stack,
get it by its name, put it on the Lua stack,
and you push all of our arguments onto the stack.
And we call the function, and then we inspect
the stack to get the return values
that were returned by the Lua function.
And I noticed that in Lua, a function can have
an arbitrary number of return values,
so I actually have to check, after the Lua call,
API call returns, I have to inspect the stack
to see like how many values were put on there.
This is also the reason why here I have to return
a vectoral value, because I'm not sure how many
arguments am I going to get.
And then I can just call a Lua function,
like here I'm calling a print function,
from the Lua standard library with a couple of arguments,
and we just print them to the console.
So of course this is the most general
signature that you can give
for such a core function.
You could constrain it, like if you know the number
of return values and the number of arguments beforehand,
or maybe even know their types.
Right?
So, you probably want to go to like the most
restrictive signature that applies for your case
to make sure you get the maximum support
from the C++ type system.
Okay.
So to wrap up.
This is actually the description that the Lua people
give like how they understand the language,
their elevator pitch.
It's a powerful efficient lightweight
embeddable scripting language.
And I hope through this talk I could give you an idea
of what this attributes in the context of Lua.
I compiled a small list of literature,
the reference manual for Lua
is actually really good, there's a book by the authors
of Lua called Programming in Lua, which is,
it's not a big book, it's around 300 pages,
if you're interested in the language
I would really recommend purchasing the book,
not only because it's a great book,
but also because it's a great way to support
the creators of Lua financially.
Because it's created by a university,
and like they don't get a lot of funding otherwise.
And that, I think we have some time left for questions.
Thank you.
(clapping)
- [Moderator] If you have a question,
please go up to the microphone.
Hello.
Thanks for the talk.
Yeah.
You mentioned that you have an application
that uses several libraries, for example, dynamic libraries,
and each of these library want to do some
of its own stuff, and as separate Lua interpreters.
So, how do they get, do they conflict with this
or okay for them to have several
interpreters in several application?
You mean, I think, several Lua interpreters,
in one application?
Yes, I mention several modules are developed
independently, and all of them rely on their own Lua,
something, so I want those to be independent.
Yeah, so what you can always do, like we saw
in the simple example that you have to create
this Lua State at one point, and if you want
the different modules to be truly independent,
you can just have each module use its own Lua State.
And then they can even run concurrently
in multiple threads, there's like zero overlap
between the different VMs, but then of course
you have to take care that if you do want communication
from one Lua VM to the next, than you need to model that,
but if you're fine with them being totally separate
then that's actually a good way.
The problem there is that there's some memory overlap
to those Lua States, so if you're in a very constrained
environment, you might not have that memory.
And what you can do there,
is you can also restrict the environment.
So this _G table, that we saw, that stores
all the global variables, you can also restrict that.
So saying that I'm now executing a chunk of Lua code
under this environment.
And sometimes that's enough of a separation,
but it's a little bit weaker
than having truly separate States.
Yeah great.
And the second question is,
it seems like to use Lua functions conveniently
from C++ coding, it's some kind of boilerplate,
but we demonstrate this, so I wonder,
is there some standard solutions.
Some what?
Standard solutions, some libraries
which contains such boilerplate.
A library which takes care of the bindings for you.
Well that provides such functions,
functions like Kol, like that is shown.
So, there are libraries that help with the integration,
a pretty famous one is I think called Luabind,
you can use those, but the interesting thing,
is that the API is so simple, and you want to roll
your own, this is not an unreasonable thing to do.
Like you can probably get a decent
library up and running within a weekend.
So, there are libraries available, but you don't
have to use them, like it's easy enough
to do it by hand, if you want.
Thanks.
Sure.
[Audience Member 1] So how do you debug Lua?
Oh yeah, that's a good point.
So the interesting thing is actually part
of the standard library of Lua is actually
a debug module, so you can actually write
your debugger for Lua inside Lua.
Which is pretty cool.
[Audience Member 1] You can separate points
and things like that.
Yeah.
And you can inspect like local variables
from closure and stuff like that.
So.
[Audience Member 1] And then you write your own.
Like if you want it to go to Standard Out,
and have some sort of interactive mode, you --
The thing is that this is just an API.
This is not a ready-to-use debugger.
So you would still need to write a user interface
in order to be able to actually debug.
But since Lua's an embedded language,
so you don't know what the environment is going to be,
they don't take care of that for you.
So if you only use vanilla Lua, you will have
to write it for yourself, but it gives you
all the tools that you need to be able to this.
Thank you.
[Audience Member 2] Just wanted to make a quick comment
if you don't mind, on the question of something
to help generate binding?
Sure.
Sol2, that's S-O-L two, is a really well-written modern
C++ binding generator for Lua.
Okay, cool.
Thanks for the info.
Okay?
And I think we're through.
Thank you very much.
(clapping)