[00:00.000 --> 00:15.240] Thank you. Hello for them. I'm Yanam Dawi. I'm working on a [00:15.240 --> 00:29.680] twig on a project called Nickel, and Nickel is a configuration language. And is it okay? [00:29.680 --> 00:36.560] And so in this talk, I want to talk about Nick Cell, which is a framework to use Nickel as [00:36.560 --> 00:41.760] an alternative front-end language for Knicks. And my dear friend and colleague Guillaume, [00:41.760 --> 00:48.800] who just got off stage, has a strange morning routine where he stands in front of me and [00:48.800 --> 00:55.200] says, I have a question. When? So when can I use Nickel for Knicks? And so my primary [00:55.200 --> 01:00.880] motivation is just to be able to enjoy my morning coffee in peace. And my second motivation is to [01:00.880 --> 01:07.120] try to get you as excited as Guillaume is about Nickel, or at least 10% would be already quite, [01:07.120 --> 01:14.560] because he's a very enthusiastic man. So beside maybe a few people who got lost, because for them [01:14.560 --> 01:19.760] is huge. I think we all agree in this room that Knicks is a powerful tool. There are a bunch of [01:19.760 --> 01:26.480] things that only Knicks is capable of. My personal favorites are Dev Shares. So you need to hack on [01:26.480 --> 01:32.800] a project, you just enter the directory, type Knicks develop, you have all your tools you need, [01:32.800 --> 01:38.400] then you exit the directory, everything is back to normal. Knicks OS, we talk about that, [01:39.440 --> 01:45.280] being able to manage your whole configuration in.files, rollbacks, competing versions of the [01:45.280 --> 01:52.160] same package is pretty nice. And I guess each one of you has their own usage for embedded or [01:52.160 --> 01:58.480] whatever of Knicks, either personal or professional. And if that's the case, one of your main interface [01:58.480 --> 02:03.280] you have with Knicks is the language, Knicks Expressions. And in fact, it's a pretty simple [02:03.280 --> 02:09.440] language. It's mostly JSON plus functions plus a bit of small strings things, but mostly. And [02:09.440 --> 02:15.680] paradoxically, if the language is simple, I find it quite hard to use actually, yes, it's two with [02:15.680 --> 02:23.600] only one O, to use in practice for a bench or resin, at least for Knicks. And one of the main [02:23.600 --> 02:30.240] issue is error reporting. And I think it's a pretty fundamental problem in the language which is [02:30.240 --> 02:36.560] dynamically typed and lazy, is that when you make a mistake, your little mistake travels all around [02:36.560 --> 02:41.680] the code base. And only when something consume your value, then everything blows up. And the [02:41.680 --> 02:47.200] error usually points out deep inside Knicks code, because that's what is consuming your value. [02:47.200 --> 02:51.920] And I would like those errors to point out like the point when I made the mistake originally. [02:53.040 --> 02:58.640] My favorite one is infinite recursion in the module system. So I was in UB at Knicks. I tried to [02:58.640 --> 03:04.240] move my Knicks OS config to Flakes. I made a typo and an argument to a simple module and I got [03:04.240 --> 03:09.200] like infinite recursion, but nothing was recursive. I didn't know what was happening at all. [03:11.360 --> 03:15.680] Something can be said about discoverability, in particular when you're writing code. So [03:15.680 --> 03:19.760] I'm writing some Knicks code. I would like to know what are the standard library functions [03:19.760 --> 03:23.360] that are available? What are the least functions from Knicks packages that I can use? What are [03:23.360 --> 03:28.880] their type? What argument should I put there? I'm writing a flake. What is the schema of a flake? [03:28.880 --> 03:34.400] Could I have some completion or at least some in code information to know what field I'm supposed [03:34.400 --> 03:43.200] or attribute I'm supposed to feed? And the last point is that Knicks is simple and usually it's [03:43.200 --> 03:48.160] a good thing in language design. Like you build a rock city core and then the rest can be done as [03:48.160 --> 03:53.680] library function. But Knicks is not a general purpose language. It's a domain-specific language. [03:53.680 --> 03:59.760] And if users of your domain found themselves having to solve the same problem again and again [03:59.760 --> 04:05.920] and again, then maybe the domain-specific language should provide a native list way to solve this [04:05.920 --> 04:12.080] problem. And one example is overwriting. Something that you do a lot in Knicks is taking a module [04:12.080 --> 04:18.160] or a configuration or whatever object, tweaking a parameter and get the new result with all the [04:18.160 --> 04:23.600] dependency updated and so on. And it's pretty not trivial to do in Knicks. There are a lot of [04:23.600 --> 04:28.160] different ways, a lot of different abstraction implemented by different people, and that makes [04:28.160 --> 04:37.840] for hard experience in my opinion, especially as a newbie. And it's not me saying that. It's [04:37.840 --> 04:43.680] actually Elko, the creator of Knicks, who wrote a gist some long time ago, which is partly one [04:43.680 --> 04:48.240] of the origin of Knickl, about the deficiencies of the Knicks language. And one thing he says [04:48.240 --> 04:52.800] that Knicks is a DSL for package and configuration management, but it doesn't have any notion of [04:52.800 --> 05:01.440] package nor configuration. So to recap, one of the main things is developer experience in general. [05:01.440 --> 05:05.920] Error reporting is one of the main interface with the language or something goes wrong. It's [05:05.920 --> 05:11.920] important. There is something to be said about Knicks being too simple somehow or too bearable [05:11.920 --> 05:17.200] for its own good. And so people reinvent the wheel in a lot of different ways. And I mean, [05:17.200 --> 05:24.560] sometimes it's fine to have competing libraries and so on. But for fundamental things, it's like [05:24.560 --> 05:28.800] when you want to put something in the standard library of a language, that should be only one [05:28.800 --> 05:38.400] way to do it. And it's efficient and so on. So what can we do about it? Well, I propose to do [05:38.400 --> 05:44.640] Knickl. Knickl is a general purpose domain-specific language, if that makes sense, for configuration. [05:44.640 --> 05:49.040] And what Knickl has, it has sound-gradual typing, it has opting static typing with higher rank [05:49.040 --> 05:53.920] polymorphism, structural typing with full polymorphism. Contract is like I'm going to find that. No. [05:53.920 --> 06:00.080] I mean, yes, in fact, but that's not the point. The point, I mean, those are means to an end. [06:00.080 --> 06:08.000] And the end is that your practice is nice. So here it's a little video demo. On the right, [06:08.000 --> 06:13.040] we have something called a contract. It's like Knicks-Wes types, something that is checked at [06:13.040 --> 06:19.600] one time by the Knickl interpreter. And you write it actually pretty much like a type or a schema. [06:19.600 --> 06:24.320] You say, oh, Knickl derivation, this is taken from Knicks-Wes. This contract defined by Knicks-Wes [06:24.320 --> 06:29.520] should have a name, a version, dependencies, system, and so on. You can attach other contracts [06:30.080 --> 06:33.440] and meta data in general to those fields. You can say, oh, name must be a string, [06:33.440 --> 06:37.520] version must be a string. Dependency should be an array of derivation. Derivation is another [06:37.520 --> 06:42.240] contract that you'll define somewhere. You can attach default value. Dependencies are empty by [06:42.240 --> 06:48.880] default. You can attach, you can say that a field is optional, for example, because I think Knicks [06:48.880 --> 06:55.280] is not strictly required by a built-in derivation that version is defined. And the thing is that [06:55.280 --> 07:00.800] all those meta data can be leveraged by the tooling. On the left, we are trying to write [07:00.800 --> 07:05.600] something looking like a derivation. That doesn't matter at this point. But we define an output [07:05.600 --> 07:12.320] field. Field is just Knickl name for attribute. And we apply this contract. We just import it. And [07:12.320 --> 07:19.040] let's see how it turns out. It turns out that we get completion for what we should put inside this [07:19.040 --> 07:24.720] output stuff. Like name, okay, we have documentation. We have the type. Actually, type in a string is [07:24.720 --> 07:30.720] named whatever the contract. We get completion for built-in command. And for nested recall, [07:30.720 --> 07:37.040] like, oh, what should I put in that built-in command? You can leverage also this information, [07:37.040 --> 07:42.000] not only from the LSP, but from the CLI. Oh, no, sorry, I forgot. You get completion for [07:42.000 --> 07:46.560] the standard library and actually any library. Those functions are statically typed, but there's [07:46.560 --> 07:52.960] another subject. You can leverage this information from the CLI. Using Knickl query, you can say, [07:52.960 --> 07:57.200] oh, what's inside a contract that's named? What is the field Knickl derivation? You get [07:57.200 --> 08:01.840] documentation. And what are the available fields? You can say, oh, okay, what is built command in [08:01.840 --> 08:07.760] particular? I get documentation and field. Now, what happens if I make a silly mistake [08:07.760 --> 08:12.720] and build command, which should be a record of strings, I just made it a string, like, [08:12.720 --> 08:20.000] instead of an attribute set? And I try to run Knickl on that. What I get is a normal message. [08:20.000 --> 08:24.720] The first blue part says, what is a contract that I just broke? You should have a record with [08:24.720 --> 08:30.400] args and so on. The second light points at where I define the value. Now, it's used. It's read. [08:30.400 --> 08:36.320] And it says, oh, this is one. This doesn't respect this contract. The third part is not really useful [08:36.320 --> 08:41.040] there, but it's giving you the evaluated value, which means that if build command was a complex [08:41.040 --> 08:47.280] expression bit out of map and fold, you still get the final stuff that it builds. And this green [08:47.280 --> 08:54.480] thing here is taking who the L is imposing this contract. So these points to build command field [08:54.480 --> 09:00.800] inside the Knickl derivation contract inside the Knickl library. And so this is just runtime [09:00.800 --> 09:06.400] validation. You could do it with libraries. Knickl does it. But first, I think this kind of [09:07.120 --> 09:12.240] nice structural syntax for it, as well as this advanced real-world reporting, it's really hard [09:12.240 --> 09:16.400] if not impossible to achieve purely in library code. Because there are special things in the [09:16.400 --> 09:20.960] Knickl interpreter to handle contract application and track argument and the stack and so on. [09:20.960 --> 09:29.680] So what Knickl is about is relevant, thorough, and early error reporting as much as possible. [09:30.480 --> 09:36.560] Discourability, you can attach all those meta information to fields and they are understood [09:36.560 --> 09:42.240] by the tooling. And in particular, by the LSP, giving you interactive development process. [09:43.040 --> 09:46.960] And in the end, arguably, the language is more sophisticated than Knicks, [09:46.960 --> 09:54.320] but as a user, I find it easier. Okay, great. That's fine. That's just my great 80,000 package. [09:54.320 --> 10:02.160] Not a big deal. Nope, not going to happen. Knicks package is a huge behemoth. It's probably the, [10:02.880 --> 10:07.120] I mean, the most important thing in Knicks. I mean, the value of Knicks is all this domain [10:07.120 --> 10:12.560] knowledge on how to build package encoded in a code that can be actioned by the machine. [10:12.560 --> 10:17.120] And it's not going anywhere. So whatever we do, if we want to use an alternative front end, [10:17.120 --> 10:24.880] we have to be able to use Knicks packages. So meet your first Knickl derivation. It's a DevShell. [10:25.760 --> 10:31.520] And from a distance, I want you to notice that there is no function at the top. So usually, [10:31.520 --> 10:35.280] Knicks packages, the first thing you do when you define a package is to define a function. [10:35.280 --> 10:39.280] This has a number of problems. One being that before doing anything with it, [10:39.280 --> 10:43.680] like getting the name or the version, you have to apply it to some arguments. And this argument [10:43.680 --> 10:47.360] may be packages. So you need to apply them and so on. So you need to evaluate the transitive [10:47.360 --> 10:51.760] dependency before doing anything. Here is just a flat record. Records are recursive by default [10:51.760 --> 10:58.080] in Knickl. So line one, we import some things called builders. We'll see later what is from the [10:58.080 --> 11:05.040] Knicks cell. Builders is given by Knicks cell. And line four to six, for now, or API is that you [11:05.040 --> 11:08.800] need to declare a bit like a flake. But at the level of the derivation, what you are going to [11:08.800 --> 11:13.520] take from the Knicks world. So here I say I want to take CRL from Knicks packages. [11:16.000 --> 11:22.480] Line nine to 12, I'm defining the actual derivation, so to speak, even if it seems way smaller. [11:23.040 --> 11:30.080] So I give a name. And then I put this input dot CRL in the pass. So this funny-looking string, [11:30.080 --> 11:37.200] I won't have time to detail, but it's called a symbolic string, is a way to simulate Knicks [11:37.200 --> 11:41.040] string context. But not only actually, it's a pretty generic mechanism, but to have the same [11:41.040 --> 11:45.360] namesities like input dot CRL is not actually a string, it's a derivation which has store pass and [11:45.360 --> 11:50.560] so on. And you do that in Knicks, and it's not trivial to do it in a different language. But [11:50.560 --> 11:56.800] yeah, this has all solutions to that. And we are using this input dot CRL, but we haven't seen any [11:56.800 --> 12:04.880] inputs yet. The other was called input spec. So input is defined, but not really, but is defined [12:04.880 --> 12:11.280] line seven. This is just a field without a definition. And in Nickel, the idea is that [12:11.280 --> 12:17.680] we call a recursive, and we have something called merge operation, which is the unpercent. It's a [12:17.680 --> 12:23.600] bit like the slash slash of Knicks, that is combining records, but it doesn't give priority to the [12:23.600 --> 12:27.040] left hand side, right hand side. You just try to combine and see if there is a conflict, you have [12:27.040 --> 12:32.560] to use priorities. A bit like the Knicks or the module system. But it does what you expect naively [12:32.560 --> 12:37.280] when you start Knicks, that it works on nested record, and that it works on recursive record. [12:37.280 --> 12:41.200] That is, if you override something there, everything that depends on it recursively [12:41.200 --> 12:46.400] will be automatically overridden. So what we do, line seven is a bit like defining [12:47.360 --> 12:53.600] a function argument. So we're just doing function in different way, so to speak, but in a way that [12:53.600 --> 12:59.360] is way nicer for Knicks, because it just looks like configuration. Overriding is trivial. I just [12:59.360 --> 13:04.480] add one line, and I merge with something that redefines the value. Combining stuff is trivial. [13:04.480 --> 13:10.320] So for example, line 14 and 15, I use some predefined builders, which are mostly looking [13:10.320 --> 13:16.240] like this derivation, and that has rest-day environments and a seed-developer environment [13:16.240 --> 13:22.560] in my shell. So I will end up with a shell that has URL, all the rest-toolchain, and C. [13:22.560 --> 13:30.400] So I think I won't have time to dwell into the detail too much, but it's a bit convoluted right [13:30.400 --> 13:36.320] now. We have a lot of the back and forth between Knicks and Nickel. Knicks is a driver. What's [13:36.320 --> 13:41.920] important is that these parts will get improved, but somehow it's not truly, it's a bit orthogonal [13:41.920 --> 13:47.280] to all the design of the Nickel side, what do the API look like, what are the builders, [13:47.280 --> 13:51.360] how we do overriding. It's orthogonal. This part is just how do we communicate with [13:51.360 --> 13:57.760] Knicks packages. Right now, Knicks is leading, and everything that crosses boundaries can't be [13:57.760 --> 14:03.200] functions. It has to be data. So in practice, it's JSON, and so you have a bit of back and [14:03.200 --> 14:07.600] forth like, what's your input? Oh, I will extract that from Knicks packages. I give you the derivation [14:07.600 --> 14:12.640] at JSON. Nickel has almost everything to build a derivation, but it cannot build it, so it kind [14:12.640 --> 14:18.400] of gives an argument to Knicks saying, please, can you call derivation for me? But that works [14:18.400 --> 14:24.640] for now, at least. That's something. So the limitation of this model is that you have a lot [14:24.640 --> 14:29.200] on back and forth, and the error messages at the boundary are pretty bad. If you try to import [14:29.200 --> 14:36.240] the packages that don't exist in Knicks packages, that's going to be ugly. And you can't override [14:36.240 --> 14:43.440] the Knicks package from within Nickel. That's kind of an important limitation because the only thing [14:43.440 --> 14:48.000] you can get is data. You can do it on the Knicks side in the overarching flake, but it's kind of [14:48.000 --> 14:55.040] defeat the purpose. We like to be able to do that from Nickel. The roadmap to solve that is to be [14:55.040 --> 14:59.280] able to import and involve Knicks expression directly in Nickel. It's actually not that [14:59.280 --> 15:04.560] unreasonable because Knicks is simple and close to being a subset of Nickel. So we're already able [15:04.560 --> 15:10.160] to transpire most of Knicks as far as the language is concerned, but we are missing all the built-in [15:10.160 --> 15:15.120] dot atro of derivation and things like that to make it work, and I think it's the hardest part, [15:15.120 --> 15:21.840] actually. Yeah, having a Nickel built-in to build derivation would probably piggyback on Knicks, [15:21.840 --> 15:31.280] but so that at least we don't have to do the last back and forth. We have three minutes [15:31.280 --> 15:36.800] including question left. Okay, we'll go quick. And so we can do all those things and Nickel becomes [15:36.800 --> 15:42.240] a driver and you don't have to go through this back and forth. You can override from from Nickel. [15:42.240 --> 15:47.280] For Knicks, what does it mean? I hope it means an improved developer experience. Unified approach [15:47.280 --> 15:50.800] to configuration. This looks like configuration more than Knicks. I find like you just define a [15:50.800 --> 15:56.880] bunch of fields and you merge steps together and a smoother learning curve for the newcomers. [15:56.880 --> 16:03.360] I didn't cover performance, but also having this merging being native and not library function [16:03.360 --> 16:10.000] as more room for optimizations. And beyond, my secret dream is that Nickel is general [16:10.000 --> 16:14.400] purpose for configuration. So you could use the same language with the same notion, [16:14.400 --> 16:19.760] same contract, same data model for all of your stack to reform Kubernetes, Knicks, [16:19.760 --> 16:26.720] exchange instinct between the layer. And something we are working on is incremental [16:26.720 --> 16:32.800] evaluation. It's a bit like incremental build, but at the level of evaluation, I have this huge [16:32.800 --> 16:37.040] Knicks-based configuration. I change one option. I want the interpreter to only propagate the [16:37.040 --> 16:43.840] changes to what needs to be actually recomputed. So to answer the initial question, [16:43.840 --> 16:48.480] Nickel, Knicks, when? Well, now you can already do this stuff. Well, next week because we haven't [16:48.480 --> 16:53.040] merged everything. But Knicks-L will be releasing the 0.1. You could do derivation and basic [16:53.040 --> 17:00.080] Dev shells. Knicks-L will be itself will be reaching 1.0 in the coming months. And it's still [17:00.080 --> 17:04.880] rough around the edge. You can't do everything you would like to do in Knicks. But the point is [17:04.880 --> 17:10.960] that I think we did the hardest. Like arriving at the first derivation was really complex. [17:10.960 --> 17:15.680] And now everything is aligned. And somehow we just have to build the same to polish the API [17:15.680 --> 17:19.040] and so on. And there is the same for Terraform, Nickel. Thank you. [17:19.040 --> 17:35.520] Before that, I would like to know if Rodrigo at Paul is in the room. Okay, you're here. Okay, great. We have one question I think we can take maybe to you. Yes. [17:35.520 --> 18:03.520] So did I understand and write yet that you are transpiling between Knicks and Nickel? So not yet. But that's okay. Sorry, did you finish your question or? Yes. So I'm asked if we are transpiling Knicks to Nickel. Right now, no, we are doing this back and forth with Knicks and Jason and so on. But that's the plan in the end. To transpile Knicks. You could import food at Knicks from Nickel and that would give you an idea of what's going on. [18:03.520 --> 18:28.560] Food at Knicks from Nickel and that would give you a Nickel value and then you can do whatever you want with it. Yes. Yes, so you mentioned. Yeah. So I've used Terraform Knicks before. Could you just use that? Yeah, yeah, it's true. I guess you could. [18:28.560 --> 18:51.440] But probably the idea here is to reuse the Nickel overriding mechanism, which is, we hope, way simpler. Somehow you don't have to do anything to get your things to be overriding easily. So if I think Terraform Knicks is not using a module like system. Yeah. And there is Terra Knicks maybe that is doing that. I don't remember. Yes. [18:51.440 --> 19:05.440] But yeah, you could do that. Actually, you could wrap any Knicks package with a Nickel interface somehow like a FFI or you could redo it a bit to have a more both are possible. I guess. One more round of applause, please.