CppCon 2017: Andreas Weis “Howling at the Moon: Lua for C++ Programmers”

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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)
Info
Channel: CppCon
Views: 21,755
Rating: undefined out of 5
Keywords: Andreas Weis, CppCon 2017, Computer Science (Field), + C (Programming Language), Bash Films, conference video recording services, conference recording services, nationwide conference recording services, conference videography services, conference video recording, conference filming services, conference services, conference recording, conference live streaming, event videographers, capture presentation slides, record presentation slides, event video recording
Id: pfwHCiP1HFM
Channel Id: undefined
Length: 32min 32sec (1952 seconds)
Published: Thu Oct 26 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.