 Okay, now let's talk about something that I think is more interesting, something that we're actually doing in Bangalore here. My team is placed here in Bangalore. Engineering work. That's what I'm going to be talking about today. So all of this work is actually happening here in Bangalore. So I work on something called Alasio Marketplace. This is being engineered here in Bangalore. And Marketplace looks a bit like this. There's a website you can go on and download plugins, extensions, add-ons for Jiro Confluence, Bitpocket, and so on. So if you want to extend Jiro Confluence in some sort of way, if you're using it, which chances are pretty high that I've talked to a lot of people here so far and a lot of people are using Jiro Confluence, if you want to extend it in some way, you can go on to Alasio Marketplace and find maybe some sort of extension that works for you to extend it in something that you want to, in some way that you want to extend it in. So the way that this is all created is, sorry, I'll talk about the agenda first. So we're going to talk a little bit about something called Nix. Nix is a particular tool that we use, but I wanted you to focus more on the ideas. Then we're going to talk about how functional programming relates to the idea of packaging and building software. Then we'll talk about the idea of functional programming, how it relates to operating systems. And then I'll show you some two examples that we have, which is development shells that we use inside of Marketplace and then Docker images, which is how we actually deploy Alasio Marketplace. We use Nix to build Docker images and we deploy like that. So if you're interested in how you can build Docker images without ever looking at a Docker file ever again, stay until the end. Okay, so Nix is the particular tool that we use and Nix has got a lot of problems. I'm not going to be here defending and defending a lot of the decisions that were used in the process of making Nix, but the ideas I think are really important. The ideas give us a lot of benefits. I'll talk about some of the benefits. I want you to focus more on these ideas. Yes, you might see some weird syntax. You might not like parts of it. That's fine. I want you to focus more on the ideas than the implementation that we're using. Okay, so in the 12th century, we're going back quite a bit in time, there was the idea of a function. There's some argument about if this was the 12th century, 13th century, 14th century, it doesn't matter. A long time ago, functions were created and one of the properties of functions is that if you give a function the same input, you'll always get the same output. So if I've got a function that is plus one and I call that function with, for example, 10, I'll always get 11. There's no day in the year where I call that function and I give it a 10 and I'm gonna get it back at 13. It's never gonna happen. So the functions have the property, that same input always have the same output. And so there was a guy called Ilco Dostra who was doing some research and at the time, he was interested in the idea of applying functional programming to building some software that he had to build. And so that was back in 2004, he created this idea of NICS, which was an application of the idea of taking functions and applying it to build. Then we'll talk about how that looks in a minute. Four years later, he actually evolved that idea of NICS. So Father, he was able to build a Linux-based operating system on that idea. So within four years, he got it. It wasn't a very advanced operating system, but he progressed the idea fast enough that he was able to get an operating system built on top of that. And then I think in 2014, late 2014, I actually started using the NICS package manager. So I was using it on Mac OS. I was using the NICS package manager and I was installing packages and using it as a replacement for Homebrew. About a year later, I actually switched over to using NICS OS. The operating system was usable enough that I actually just completely switched over it. And in fact, this presentation is being displayed from NICS OS at the moment. I am running this NICS OS operating system. Okay, sorry. So support, so everyone, first thing I want to mention, NICS, is the first question is always about support. Can I run NICS today? What can I use it with? So NICS, when we talk about NICS, there's actually many things that people refer to. There's the NICS programming language. They use the NICS programming language to specify builds and packages for your software. And then there's the NICS package manager. And this can get pretty confusing pretty quickly because you talk about NICS, you might be talking about any part of this. So the package manager runs on Linux. That's primarily where people use NICS. But there is, a lot of people do use it on Mac OS as well. And it's pretty good support for a lot of packages on Mac OS. Not every package, like NICS, has definitely got more packages, but Mac OS has got quite a few packages as well. I was definitely reusing it as a replacement for Homebrew back in 2014. So I'm sure it's probably good enough to replace Homebrew with today. On top of that, sorry, there's also a WSL, which allows you to run Linux executables on Windows. I've played around with this and it's actually pretty impressive. You can actually use NICS to build Windows executable files. So windows.exe files using NICS. So if you're interested in building Windows software, I don't think this has been explored very much. I've explored it very quickly, but it could be explored a lot more and I think there'll be some really cool stuff. So if you're interested, if you use Windows and you're interested in these ideas, come talk to me, I'm happy to talk more about that. And then there's NICS OS, which is the operating system that builds on top of the package manager, which builds on top of the programming language. It makes an operating system out of these ideas. And then on top of that, we have something called NICS Ops. So NICS Ops is built on top of NICS OS, which is built on top of the NICS package manager, which is built on top of the programming language. So you can see at each level, we kind of build on top of the previous idea. And there's a lot more tools around NICS, but these are the kind of the top four kind of popular NICS tools that are around. Okay, so if you use any of these at the moment, so if you have some sort of minor configuration, this is, so there's a guy called Steve Trugo who came up with the idea of a divergent convergent and congruent configuration. So if you are doing any type of divergent configuration, that is you've got a system and you just run manual scripts on it or you install Debian packages, or you're doing anything manually on a system, you can think about using NICS to automate that. If you have some sort of convergent configuration being a puppet, ansible, chef, something like that, you can also see NICS is a bit of a replacement for that. And we've started going into the idea of like immutable infrastructure, like using Docker images and deploying using Docker images, using Kubernetes and things like that. You can use NICS as a replacement for those ideas as well. This is the last one here is actually GUIX, which is a fork of NICS, and it just replaces parts of NICS. So if you don't like the particular programming languages, we're gonna use the NICS programming language, if you don't like that and you like Scheme, GUIX replaces the NICS programming language with Scheme. So just really quickly, this is divergent configuration, you have some sort of current state, which I've represented as a dotted line, as the current state, and then at the bottom we've got the intended state. And so state, the intended state might change over time, we might have new requirements come in, so we've got an updated specification, and so we're trying to get a system to be along this solid line at the bottom. But what usually happens in a divergent system is if you're doing manual sort of management, as you're running scripts, you might not know the actual state of the system, and it's eventually diverging from the intended state. And so people go on there, they'll make us some sort of file, that file might not ever get deleted, when it should have been deleted. People go on there, they modify slash ETC, and they put some sort of configuration in there. So eventually your system starts to diverge, and especially when you have more than one system, if you have more than one computer that's running, these, that you're trying to get to be consistent to try and be this intended design, eventually over time, they diverge. They start becoming very different. So I've been here. A lot of tools come up, so Puppet and Chef and so on, have come up to try and make it so that you can take some sort of existing state, so I've represented them as a dotted line here, so some sort of existing state, and you can try and get it to converge towards some sort of intended state. And so over time, if you give it sufficient time, eventually you can kind of say that you want this package to be absent, and so now on all your systems, you know that package is not installed. So it's the ability of saying that you don't want certain state, and you do want this state. And so you can apply this to different systems, and you can try and get towards a state, but it's always trying to take a current state, a current unknown state into a known state. What we've started moving towards with Docker images and Kubernetes and so on is this idea of convergent configuration, sorry, congruent configuration. So with congruent configuration, your state should always be what you intended to be. So you fully specify what your state is, and you might notice that there's a tiny gap here of kind of like the actual state and the intended state, and that's because we can allow things like we can say that we have all of this state known, except the mount directory. So we can still have mounts that have state in them that we don't know, but at least we know everything else about the system. So we can say that we know everything about this system except this part. That's the idea of congruent configuration. And that's what NICS applies, not only to system configuration, but also to packages and everything. We always say that we know what the output is going to be of our system, which I think is kind of the important thing here. So we're always saying that we know, we always can reproduce the state of a system. Okay, and so this idea actually applies to a lot more than what Puppet and Chef and Ansible kind of apply to. This thing applies to everything. We can use this next thing to basically do any type of system configuration that we need so we can do virtual machines, we can do, what else we've got up there? We've got cross compiling, GUIs, people use this for testing GUIs, people use it for CI. Actually, my team uses it for CI as well. You can build Debian packages, you can build app images, all of these different systems you can use just one tool, which I think is really, really incredible. Now, my team uses NICS for continuous integration. So we use it to kind of give us all of our CI tools. We also use it for two other things. I'm going to talk about development shells. So local developers, people when they're trying to develop our system, we use these development shells, which is supplied using NICS, and also we deploy all of our infrastructure using Docker images that are built using NICS. All of Marketplace, we use NICS for development shells, CI and Docker images. Now I'm just going to talk about the last two, but I'm going to first talk about how all of this works. So we can think about package management using the idea of how can we apply functions to the idea of package management. So you can think of a package as something that takes some sort of build input. And so that build input will be the source code, other dependencies that it needs, and some sort of configuration. So you can combine all those together and say that's the input that we need to be able to build a package. Then you've got some sort of build steps, and those are the steps that you need to take that input into some sort of output, and the output's going to be some sort of artifact. So some sort of build artifact that might be a demo package, or it could be just some sort of binary that you want or so on. So you've got the idea of build input, some sort of steps, and then some sort of build output. So that's how we can represent this as a function. Here's a graph of what we see as package management, right? We've got a package D that depends on package B, which depends on package A or something like that. So you've just got like a tree of packages. So we can represent these things using just an expression language. So next we've got this thing called expression language or a programming language, but some people have a bit of a problem calling it a programming language because it's not very advanced, but it's enough to kind of just give you the idea of functions and values. So some people call it just an expression language, the next expression language. And we can represent these build inputs and these steps using this expression language. This is just a programming language, just things that allow you to write some sort of expression. So once we've got this, then we can represent build inputs, the build inputs to our program. So these build inputs being source code and configuration and dependencies. We can represent all of those just values in our program. And we can represent the build states as just values as well, but these values can be produced using different functions. So here's one example. We're going to call, this is an example of the next expression language. And this is the part where Nick starts showing some of its problems and that's fine. If you want to replace parts of Nick's, I'm happy for that. But I want you to focus more on the ideas. The idea here is that we're calling a function called make derivation, mkderivation. And we're giving it some values. We're giving it a name and we're giving it a build command. And that build command is just a shell script. So we're saying we're making a package, derivation here you can think of as a package. We're making a package. It has this name and here's how to actually execute and build that package. So it's echoing hello world into some sort of out file. Okay. Now we can actually refactor this. So there's something called GNU Hello. So it's a hello world application. GNU actually publishes this. You can download it from the internet. So we can actually say that this is an input to our program. And so hello is not defined here, it's defined in some other file. But it is just again just another derivation that tells you how to build GNU Hello. Or we can use any now an expression instead of echoing hello world directly we can use this hello program and echo it into out. So now we're just using another value. So we've got this idea of functions. So we just got this other function here which is hello and we can bring that into scope and use it. We can refactor it once again. There's this other next function called run command. And it does basically the same thing as this. So make derivation and run command. Do very similar things. Run command says I'm going to run just a shell script. So we don't have to say before previously we had to say build command equals something. Run command kind of factors out that duplication. We see this over and over again. We see this build command equals, build command equals, build command equals. So we can just use this function called run command which does exactly that. So we are able to use functions to reduce duplication in our package management and our configuration and our build input and so on. So we can start using functions to minimize the amount of code that we have to write which I think is really cool. Something that's very difficult to do when you start using YAML, you'll often find duplication, duplication, duplication. When Nix you can just factor that out into a function which I think is really, really cool. Okay, so I keep using this word derivation. You can think of it as something like a package. But a derivation is kind of like the actual serialized output that represents the build. So previously we had the idea of functions. We've got a function that calls another function which calls another function. Once you evaluate that all down, you'll get a data structure. And that data structure represents, so that data structure's called a derivation and that represents your whole build. There's everything that you need to be able to build that pit of software. And it's actually a tree. So it's a tree of the package that you're trying to build and then all of its parents are the other derivations that you need which will be the build inputs which will be the dependencies basically. It'll be the source code that you need and also the dependencies that you need. So all of this gets compiled down to a tree and it gets serialized to disk as a .drv file and there's a program called Knicks instantiate that takes the expressions. So something like this that we've written. It'll take this expression and if you Knicks instantiate it, it'll compile it down to a .drv file, put it on your hard drive for you to be able to build. So it's a serialized representation of your whole build. Okay, so this is calling Knicks instantiate on the Knicks file. So this is the Knicks expression. Now we've got a .drv file. And notice how it's SN8i, I've actually truncated that. It's a lot longer, it's like a 32 character hash and that hash is of the derivation file. So we actually have content addressable derivation files. So if you were to instantiate this twice, you'd actually get the exact same file as an output. So it's all content address. And inside of this file, it'll have, it's basically a JSON file. It's not actually on disk, but basically a JSON file. You can represent it as JSON. There's actually like a code format that represents it as JSON for you. And it has outputs. And these outputs are gonna be what, where the artifact is gonna go. And it also has this input derivation. So these are other derivations that it's going to refer to. So in this case, we're going to refer to, oh sorry, this is input sources. This is gonna refer to the default builder. A default builder is just like a thing in Knicks where it brings in like a heap-ish helper. A lot of Knicks builds happen very similarly. So this is just kind of like a lot of helper functions that we use, a lot of helper bash functions that we use. Now, here's the input derivations. And these are the dependencies that we have. So we depend on bash, we depend on the standard environment for Linux, and we depend on the hello derivation. So the hello program. So to build this artifact, which is just gonna be piping hello, hello program throughout, we need to depend on hello. These are just the dependencies. Here we have x86 to 64 Linux that's what I've instantiated this Knicks expression on. And so because I've run it on a Linux machine, it says that it's going to be able to be built on a Linux machine. It's got a few other things in there. It's got the builder, which is gonna be bash, we're gonna be running bash, and we're gonna run it with default builder. These are just, you know, we're just gonna run bash. We're just running a shell script, right? And then we have here just a lot of different environment variables that get put on things. So when the bash runs with the default builder, it's going to use a little of these environment variables primarily it's gonna use the build command, it's gonna run that build command, and it's gonna pipe it out into a file and run out on our program. So this is gonna be building an artifact. So now that we've got the kind of full definition of a build as a file on the system, we can do something called realization, and realization gives you an output. I know this is all bizarre terminology, instantiation and realization, and this is maybe some of the problems with Knicks. But we're going from, the idea here is that we're just going from this serialized build on disk to actually running it and building it. Now realization is actually not called building because realization can happen in a few different ways. You can build just the software, and in our case, we will just build the software. But there's potential that because everything's content-addressed, right? And we have this content-addressed hash of the derivation. We could also realize it by going off to the internet and saying, hey, do you have an output for this derivation? Do you have this file available for us? And then we could just download it. So caching just comes for free. It's absolutely for free. So caching just happens, which I think is super, super cool. We just get it for free. No magic involved. Just content-addressable files. Yes, and we've got a different notion of purity. So the idea of purity we had before is that in the Knicks expression language, everything is just an expression. All functions will always return the same output. So if you call make derivation and you give it a heap of steps, you'll always get the same derivation as an output. So if you give it the same steps, you'll always get the same derivation. That's just always true. So that's the Knicks expression language. It's got the idea of purity because the same input always gives the same output. They're always functions. Now there's another notion of purity, which is that if we go to realize a derivation, realizing it twice, the same derivation, realizing the same derivation twice, should always give the same build artifact, byte for byte copy of the same artifact. So if I was to run my build at the moment, which is gonna pipe hello world into a file, running that twice, I should always get exactly byte for byte, the same file, which would just be hello world. Now in theory, it's possible for us to kind of violate this. Knicks provides some tools to try and make it possible that builds are reproducible, but you can still use call out to date, for example. You can call out to date, pipe that into a file. Knicks is not going to stop you from doing that. It's just gonna provide some tools to help you to stop from doing that, but it's not going to prevent you. Now in theory, you're able to put in the dates and so on into build artifacts. But in practice, the Debian project and Knicks have kind of gotten together and they've made like this reproducible builds.org. They've put a lot of effort into making it so that all packages have reproducible builds. So pretty much all the packages that you get by default in Knicks have reproducible builds and Knicks actually provides tooling for you to run the same build like 20 times and verify that they are byte for byte the same every single time. So there are some tools provided by Knicks and in practice, this is actually pretty good. And in practice, our last year marketplace, because we're using this tool, we are byte for byte reproducible. We can take the same build and do it twice and have exactly the same output. Okay, so this is how to realize you call Knicks Store and you can say realize. There's actually commands that do basically instantiation and the realizing together. I'm just showing you kind of the underlying concepts of how Knicks works. You've got the idea of realization of a derivation which will give you an actual artifact. So we can cut that file and we got Hello World. That's how to do Hello World and Knicks. I'm sorry, it was a bit complicated, I know, but there are easy ways to do it. I'm just showing you kind of how it works underlying. Cool, so now let's talk about how to apply the idea of functions. So we've got the idea of functions to packages, right? We've got the idea of functions being same inputs, same output. And to packages, same input is the same source code, same, there's two levels. So let's start with the expression language. With the expression language, same values that you provide to functions should always return the same value. So with the next expression language, calling make derivation with the same thing will always give you the same output and will always serialize the same file on disk. That's cool. Second level is that when you go and realize the derivation, so once you've got this fill definition in your system on your disk and you try and realize it twice, it should always get byte for byte, the same artifact as an output. So those are two levels of functions there. So now we can apply that same idea to operating systems. How can we think about functions, sorry, operating systems as function? I'll show you, this is a partial, partial configuration of my system. So this is what I'm running right now. This is only a part of it, there's a lot more. But for example, I have Docker enabled, I've got OpenSSH enabled, and I've got a Brian user, I've got a UID for that Brian user. Now there's a lot more on there, I've got things like, I've got Xmonad, which is a window manager that I use. I've got a HIPAA different system packages and still like WGET and things like that. There's a lot of other configuration in there. I've got firewall rules, firewall rules are all defined using this. So I'm using this NICS programming language to fully specify my operating system, which I think is pretty cool. So we can think about operating systems as functions in the way that we specify the operating system as just kind of like a data structure, and then we use functions in a data structure. When we have configurations, when we go and use NICS OS, we can say, build this operating system for me, like build the configuration for me and it'll get registered as something called a profile. Now those profiles then get put into something called a derivation whenever you say, I want to boot from this build of, like so the profile contains things like the kernel, the packages, the ETC configuration, the path environment variable, things like that. So once we have a profile, we can register as a generation on our system. Once we register as a generation, it kind of gets put onto your grub bootloader and I'll show that in a second. But one thing we can do is after we keep changing configuration, if we change it maybe like 50 times over a month, I can say garbage collector ones I'm not using anymore. So we'll go back through history and say, I don't actually use that configuration anymore and get garbage collected. But until then, what they will do is show up in your grub bootloader. So for example, when I boot up, here's an example, I have generation 24, so anything before 24 has been garbage collected already, but everything from 24 to 33 is still around. So for anybody here who's screwed around with Linux graphics cards before, you're probably familiar with having a system that doesn't boot. It's very frustrating having a system that doesn't boot. And so what you can do with NICS is everything gets registered as a generation. So you can go back to a previous generation. So if I make a change in my NICS OS configuration and it no longer boots, I can go back to the previous version. So this has saved me a lot of times. I've changed configuration, I've changed IP table rules or something like that. I can always go back to the previous one and try again. Really useful. Okay, now to what Marketplace actually uses NICS for. I personally use NICS OS, but what does Marketplace 10 use NICS for? One thing is development shells. So here is that I don't have Python in sort of my system. There's no Python actually on this system. What I use is something called NICS shell and you can open up a batch shell with a particular package installed. In this example, in the top one here, I'm saying NICS shell-P Python, which means give me a batch shell but put Python on the path for me. So any bit of software that I wanna use, I can just open up a NICS shell and start using it. I don't have to install it. I don't have to go and do an apt update or anything like that. I can just do a NICS shell-P, put in the bit of package that I would actually want and I can start using it straight away. Then what you can do is write a shell.NICS file and all of Marketplace's projects have a shell.NICS file and you can go into that directory and type in NICS-shell with no arguments and I'll just open up a shell with all of the dependencies that you have for your package installed. So for us, we have like Node.js, Java, things like that. And then the team actually uses something called Durand which automatically, as soon as you go into a directory, it'll run that NICS shell and give you just a shell with all the dependencies involved. So you can just change between directories and have all your dependencies in scope, which I think is super-super cool. So you could have one thing that has like Node.js 8 and over here you can have Node.js 10. Change between the directories and just silently, it'll just switch between the versions of Node.js. This is an example of a shell. So you can say make derivation. You give it some build inputs. So for example, Node.js, what's this, 0, 8? Node.js 8, Yarn, Git, you know, we've got all of these things that we need when we go to build our software. Last line is interesting, we're calling the function called optionals and we're saying if it is Linux, we also want Firefox, because in our CI system, sorry, in our CI system, we also have like a UI test and that UI test uses Firefox. So we've optionally included Firefox and Linux. Okay, we'll finish up with one last example. Docker using Node Docker files. We don't use any Docker files on my team, but we do use Docker. We have an internal platform as a service inside of Alasium. The internal platform as a service takes Docker files and then spins up AWS instances, puts the Docker image on that EC2 instance and then runs it for us. So we give, we push some Docker images up to a remote registry and then we let the platform as a service take control of everything else. But we don't use any Docker files in the process there. We actually don't even use the Docker daemon at all. So Docker files look a bit like this. You say from, for example, Alpine, install a package, here we're gonna install Calse. Then we're gonna have, we're gonna actually run the Calse, if when we go to run the Docker image, we're gonna run Calse and then we're gonna expose port 8080. I'm just gonna put the port 8080 in there for some example, it's not actually used in this example. I've got some questions about this. What version of Alpine is this using? Latest? What version of Calse is it? Latest? What version of, so Calse is built on Pearl. What version of Pearl is it using? Whatever comes with Alpine, right? And what version of Lib C is it using? So these are the questions that I ask, right? And if security comes to my team and they say, what version of Lib C are you on? We've just found a vulnerability in Lib C. What version are you on? I have to go into the Docker image and have a look, right? Sucks, I've had to do this. So instead, with Nix, right, everything is reproducible. We know what version of Lib C is going to be there. We know what version of Alpine we're running on. Everything has to be fully specified. And this, I think, is a really big benefit in terms of security. We know exactly what version of everything is without even building it. We don't have to go and look at the build, at the artifact. So here's an example of a Docker image built using Nix, and there's no Docker involved at all. There's no Docker files involved. So here we have, so notice how it's not imperative either. It's not kind of saying, do this and then do this and then do this, and then finally we get a Docker image as an output. We're saying, we're having a declarative way of specifying the Docker image. So here we're saying, build the image. Give me Cal State as the content on that Docker image. And we're going to build it on top of Alpine. Now I've not specified either Cal State or Alpine in this file that I'm showing you. They're coming from a different file, but each of these is fully specified as well. Cal State has to say that it's version 3.2.1 or something like that. There's no possibility for it to say that it is Cal State, you know, the latest version of Cal State. It can't say that it has to say exactly what version it is. And the same thing with Alpine. In fact, Alpine, when you say from image, you actually have to give it a sharp 256 hash of the base image that you're going to be using. So it's fully specified. We know exactly what version of Alpine is going to be coming in. Okay, and then we're just going to leave, these are two of the things from the Docker file. We're saying which, what command it is and what expose it is. We're not doing it in an imperative way. We're not saying the line by line, what we want. We're saying, these are the actual, this is the actual configuration that we want. That's how to build Docker images without Docker files. The way that this works is that it, build image goes off and it actually supports the Docker specification for images. It supports the container format. So it actually knows that you need like a table and then that table contains all the files and then it needs a JSON descriptor. That JSON descriptor then has the expose ports command and also the base image that's going to be built on top of. So Nix actually understands like someone, this is not a built-in part of Nix. Someone has written Nix code that understands the Docker format and then builds Docker format images using Nix. So no Docker is involved at all in doing this but we are able to deploy two Docker. Cool. So the idea here is the idea of functional infrastructure by applying the idea of functions. So functions taking same input to same output. The idea that I'm trying to show is the idea of taking this idea, this same input to same output and applying it to packages, applying it to the language that we use to write these packages and then applying it to offering systems. And my team takes out, gets really good benefits for deploying a lasting marketplace. We've got byte for byte reproducible builds. If I build on my system today and I build on my co-worker system in a year from now, I can build byte for byte, exactly the same thing. Which I think is super, super impressive. And yes, we know, so CI and dev exactly the same system. So when I go and deploy something on CI, I go through CI and I'm doing something on development, and now I've got exactly the same versions every single time. No different. And finally, we've got no inversions for everything. So if someone comes to me and says, what version of curl is on your system? I can look, I can tell them without even going and building the software. I can just look at the code and see everything. Or I can instantiate and get a derivation, which is the build definition, right? I can go and say, here's the build definition. Does this tree contain curl 1.3 in it? Answer the question. Cool. Thank you. I actually didn't see you pin a version in any of the code slides that you showed me. So where exactly does the pinning happen? That's a really good question. Pinning. You know, I'm gonna actually just show you, I'm gonna jump to next packages and I'll show you a package. I have no idea what this package is, but in here it will say, the version is 1.9. And here's the chart 256 of the... Oh, I'm sorry. I'm very sorry. So supposedly there's a package called Galaxis. Supposedly this is a package and it has a version 1.9. So we're specifying the version. And here's the chart 256 of the source code. And this is like, this is pinned in the next packages at the moment. And I can refer to exactly which version of next packages I want. Or I could just copy and paste this file into my project or whatever I want. So pinning happens through this. This question, hello. This question is related to the same question which he asked. So the Docker file which we saw earlier, it didn't have the version, right? So are you referring to this version only? That it would take the version from this file? So there's a question around, are you talking about this file? Yeah. So yes, we haven't specified here exactly what version we're talking about, but we're referring to something called Alpine. These are variables, right? These are declarations that are made somewhere else. So somewhere else, there is a declaration of Alpine. And in that declaration of Alpine, it does say exactly which chart 256 of Alpine that we want. And there will also be CalCa. So I could find the CalCa in next packages and say this is exactly the version of CalCa that's specified. So yes, not here we haven't specified it, but in another file that this file refers to, we have fully specified it. Okay. I have a question, Brian. So for somebody who's learning next expressions, what type of editor support is available? Syntax highlighting or, you know? Yeah. Errors and. I use Emacs. I use Emacs which works well. But outside of Emacs is VS Code support. You're gonna have like learning, Nix is a very difficult process. And so Syntax is very minor part of it. The rest of it is very difficult. But yeah, VS Code, Nix. I think VS Code, Emacs and VM all have pretty good support. Any other questions? Is one. Other than the DSL, is there like any first class language support for, like is it extendable with some kind of a language? There is, yes. There is work on extending it with Haskell, which might not be the direction you wanna go in, but I'm very excited for that.