Thank you. Hello for them. I'm Yanam Dawi. I'm working on a
twig on a project called Nickel, and Nickel is a configuration language. And is it okay?
And so in this talk, I want to talk about Nick Cell, which is a framework to use Nickel as
an alternative front-end language for Knicks. And my dear friend and colleague Guillaume,
who just got off stage, has a strange morning routine where he stands in front of me and
says, I have a question. When? So when can I use Nickel for Knicks? And so my primary
motivation is just to be able to enjoy my morning coffee in peace. And my second motivation is to
try to get you as excited as Guillaume is about Nickel, or at least 10% would be already quite,
because he's a very enthusiastic man. So beside maybe a few people who got lost, because for them
is huge. I think we all agree in this room that Knicks is a powerful tool. There are a bunch of
things that only Knicks is capable of. My personal favorites are Dev Shares. So you need to hack on
a project, you just enter the directory, type Knicks develop, you have all your tools you need,
then you exit the directory, everything is back to normal. Knicks OS, we talk about that,
being able to manage your whole configuration in.files, rollbacks, competing versions of the
same package is pretty nice. And I guess each one of you has their own usage for embedded or
whatever of Knicks, either personal or professional. And if that's the case, one of your main interface
you have with Knicks is the language, Knicks Expressions. And in fact, it's a pretty simple
language. It's mostly JSON plus functions plus a bit of small strings things, but mostly. And
paradoxically, if the language is simple, I find it quite hard to use actually, yes, it's two with
only one O, to use in practice for a bench or resin, at least for Knicks. And one of the main
issue is error reporting. And I think it's a pretty fundamental problem in the language which is
dynamically typed and lazy, is that when you make a mistake, your little mistake travels all around
the code base. And only when something consume your value, then everything blows up. And the
error usually points out deep inside Knicks code, because that's what is consuming your value.
And I would like those errors to point out like the point when I made the mistake originally.
My favorite one is infinite recursion in the module system. So I was in UB at Knicks. I tried to
move my Knicks OS config to Flakes. I made a typo and an argument to a simple module and I got
like infinite recursion, but nothing was recursive. I didn't know what was happening at all.
Something can be said about discoverability, in particular when you're writing code. So
I'm writing some Knicks code. I would like to know what are the standard library functions
that are available? What are the least functions from Knicks packages that I can use? What are
their type? What argument should I put there? I'm writing a flake. What is the schema of a flake?
Could I have some completion or at least some in code information to know what field I'm supposed
or attribute I'm supposed to feed? And the last point is that Knicks is simple and usually it's
a good thing in language design. Like you build a rock city core and then the rest can be done as
library function. But Knicks is not a general purpose language. It's a domain-specific language.
And if users of your domain found themselves having to solve the same problem again and again
and again, then maybe the domain-specific language should provide a native list way to solve this
problem. And one example is overwriting. Something that you do a lot in Knicks is taking a module
or a configuration or whatever object, tweaking a parameter and get the new result with all the
dependency updated and so on. And it's pretty not trivial to do in Knicks. There are a lot of
different ways, a lot of different abstraction implemented by different people, and that makes
for hard experience in my opinion, especially as a newbie. And it's not me saying that. It's
actually Elko, the creator of Knicks, who wrote a gist some long time ago, which is partly one
of the origin of Knickl, about the deficiencies of the Knicks language. And one thing he says
that Knicks is a DSL for package and configuration management, but it doesn't have any notion of
package nor configuration. So to recap, one of the main things is developer experience in general.
Error reporting is one of the main interface with the language or something goes wrong. It's
important. There is something to be said about Knicks being too simple somehow or too bearable
for its own good. And so people reinvent the wheel in a lot of different ways. And I mean,
sometimes it's fine to have competing libraries and so on. But for fundamental things, it's like
when you want to put something in the standard library of a language, that should be only one
way to do it. And it's efficient and so on. So what can we do about it? Well, I propose to do
Knickl. Knickl is a general purpose domain-specific language, if that makes sense, for configuration.
And what Knickl has, it has sound-gradual typing, it has opting static typing with higher rank
polymorphism, structural typing with full polymorphism. Contract is like I'm going to find that. No.
I mean, yes, in fact, but that's not the point. The point, I mean, those are means to an end.
And the end is that your practice is nice. So here it's a little video demo. On the right,
we have something called a contract. It's like Knicks-Wes types, something that is checked at
one time by the Knickl interpreter. And you write it actually pretty much like a type or a schema.
You say, oh, Knickl derivation, this is taken from Knicks-Wes. This contract defined by Knicks-Wes
should have a name, a version, dependencies, system, and so on. You can attach other contracts
and meta data in general to those fields. You can say, oh, name must be a string,
version must be a string. Dependency should be an array of derivation. Derivation is another
contract that you'll define somewhere. You can attach default value. Dependencies are empty by
default. You can attach, you can say that a field is optional, for example, because I think Knicks
is not strictly required by a built-in derivation that version is defined. And the thing is that
all those meta data can be leveraged by the tooling. On the left, we are trying to write
something looking like a derivation. That doesn't matter at this point. But we define an output
field. Field is just Knickl name for attribute. And we apply this contract. We just import it. And
let's see how it turns out. It turns out that we get completion for what we should put inside this
output stuff. Like name, okay, we have documentation. We have the type. Actually, type in a string is
named whatever the contract. We get completion for built-in command. And for nested recall,
like, oh, what should I put in that built-in command? You can leverage also this information,
not only from the LSP, but from the CLI. Oh, no, sorry, I forgot. You get completion for
the standard library and actually any library. Those functions are statically typed, but there's
another subject. You can leverage this information from the CLI. Using Knickl query, you can say,
oh, what's inside a contract that's named? What is the field Knickl derivation? You get
documentation. And what are the available fields? You can say, oh, okay, what is built command in
particular? I get documentation and field. Now, what happens if I make a silly mistake
and build command, which should be a record of strings, I just made it a string, like,
instead of an attribute set? And I try to run Knickl on that. What I get is a normal message.
The first blue part says, what is a contract that I just broke? You should have a record with
args and so on. The second light points at where I define the value. Now, it's used. It's read.
And it says, oh, this is one. This doesn't respect this contract. The third part is not really useful
there, but it's giving you the evaluated value, which means that if build command was a complex
expression bit out of map and fold, you still get the final stuff that it builds. And this green
thing here is taking who the L is imposing this contract. So these points to build command field
inside the Knickl derivation contract inside the Knickl library. And so this is just runtime
validation. You could do it with libraries. Knickl does it. But first, I think this kind of
nice structural syntax for it, as well as this advanced real-world reporting, it's really hard
if not impossible to achieve purely in library code. Because there are special things in the
Knickl interpreter to handle contract application and track argument and the stack and so on.
So what Knickl is about is relevant, thorough, and early error reporting as much as possible.
Discourability, you can attach all those meta information to fields and they are understood
by the tooling. And in particular, by the LSP, giving you interactive development process.
And in the end, arguably, the language is more sophisticated than Knicks,
but as a user, I find it easier. Okay, great. That's fine. That's just my great 80,000 package.
Not a big deal. Nope, not going to happen. Knicks package is a huge behemoth. It's probably the,
I mean, the most important thing in Knicks. I mean, the value of Knicks is all this domain
knowledge on how to build package encoded in a code that can be actioned by the machine.
And it's not going anywhere. So whatever we do, if we want to use an alternative front end,
we have to be able to use Knicks packages. So meet your first Knickl derivation. It's a DevShell.
And from a distance, I want you to notice that there is no function at the top. So usually,
Knicks packages, the first thing you do when you define a package is to define a function.
This has a number of problems. One being that before doing anything with it,
like getting the name or the version, you have to apply it to some arguments. And this argument
may be packages. So you need to apply them and so on. So you need to evaluate the transitive
dependency before doing anything. Here is just a flat record. Records are recursive by default
in Knickl. So line one, we import some things called builders. We'll see later what is from the
Knicks cell. Builders is given by Knicks cell. And line four to six, for now, or API is that you
need to declare a bit like a flake. But at the level of the derivation, what you are going to
take from the Knicks world. So here I say I want to take CRL from Knicks packages.
Line nine to 12, I'm defining the actual derivation, so to speak, even if it seems way smaller.
So I give a name. And then I put this input dot CRL in the pass. So this funny-looking string,
I won't have time to detail, but it's called a symbolic string, is a way to simulate Knicks
string context. But not only actually, it's a pretty generic mechanism, but to have the same
namesities like input dot CRL is not actually a string, it's a derivation which has store pass and
so on. And you do that in Knicks, and it's not trivial to do it in a different language. But
yeah, this has all solutions to that. And we are using this input dot CRL, but we haven't seen any
inputs yet. The other was called input spec. So input is defined, but not really, but is defined
line seven. This is just a field without a definition. And in Nickel, the idea is that
we call a recursive, and we have something called merge operation, which is the unpercent. It's a
bit like the slash slash of Knicks, that is combining records, but it doesn't give priority to the
left hand side, right hand side. You just try to combine and see if there is a conflict, you have
to use priorities. A bit like the Knicks or the module system. But it does what you expect naively
when you start Knicks, that it works on nested record, and that it works on recursive record.
That is, if you override something there, everything that depends on it recursively
will be automatically overridden. So what we do, line seven is a bit like defining
a function argument. So we're just doing function in different way, so to speak, but in a way that
is way nicer for Knicks, because it just looks like configuration. Overriding is trivial. I just
add one line, and I merge with something that redefines the value. Combining stuff is trivial.
So for example, line 14 and 15, I use some predefined builders, which are mostly looking
like this derivation, and that has rest-day environments and a seed-developer environment
in my shell. So I will end up with a shell that has URL, all the rest-toolchain, and C.
So I think I won't have time to dwell into the detail too much, but it's a bit convoluted right
now. We have a lot of the back and forth between Knicks and Nickel. Knicks is a driver. What's
important is that these parts will get improved, but somehow it's not truly, it's a bit orthogonal
to all the design of the Nickel side, what do the API look like, what are the builders,
how we do overriding. It's orthogonal. This part is just how do we communicate with
Knicks packages. Right now, Knicks is leading, and everything that crosses boundaries can't be
functions. It has to be data. So in practice, it's JSON, and so you have a bit of back and
forth like, what's your input? Oh, I will extract that from Knicks packages. I give you the derivation
at JSON. Nickel has almost everything to build a derivation, but it cannot build it, so it kind
of gives an argument to Knicks saying, please, can you call derivation for me? But that works
for now, at least. That's something. So the limitation of this model is that you have a lot
on back and forth, and the error messages at the boundary are pretty bad. If you try to import
the packages that don't exist in Knicks packages, that's going to be ugly. And you can't override
the Knicks package from within Nickel. That's kind of an important limitation because the only thing
you can get is data. You can do it on the Knicks side in the overarching flake, but it's kind of
defeat the purpose. We like to be able to do that from Nickel. The roadmap to solve that is to be
able to import and involve Knicks expression directly in Nickel. It's actually not that
unreasonable because Knicks is simple and close to being a subset of Nickel. So we're already able
to transpire most of Knicks as far as the language is concerned, but we are missing all the built-in
dot atro of derivation and things like that to make it work, and I think it's the hardest part,
actually. Yeah, having a Nickel built-in to build derivation would probably piggyback on Knicks,
but so that at least we don't have to do the last back and forth. We have three minutes
including question left. Okay, we'll go quick. And so we can do all those things and Nickel becomes
a driver and you don't have to go through this back and forth. You can override from from Nickel.
For Knicks, what does it mean? I hope it means an improved developer experience. Unified approach
to configuration. This looks like configuration more than Knicks. I find like you just define a
bunch of fields and you merge steps together and a smoother learning curve for the newcomers.
I didn't cover performance, but also having this merging being native and not library function
as more room for optimizations. And beyond, my secret dream is that Nickel is general
purpose for configuration. So you could use the same language with the same notion,
same contract, same data model for all of your stack to reform Kubernetes, Knicks,
exchange instinct between the layer. And something we are working on is incremental
evaluation. It's a bit like incremental build, but at the level of evaluation, I have this huge
Knicks-based configuration. I change one option. I want the interpreter to only propagate the
changes to what needs to be actually recomputed. So to answer the initial question,
Nickel, Knicks, when? Well, now you can already do this stuff. Well, next week because we haven't
merged everything. But Knicks-L will be releasing the 0.1. You could do derivation and basic
Dev shells. Knicks-L will be itself will be reaching 1.0 in the coming months. And it's still
rough around the edge. You can't do everything you would like to do in Knicks. But the point is
that I think we did the hardest. Like arriving at the first derivation was really complex.
And now everything is aligned. And somehow we just have to build the same to polish the API
and so on. And there is the same for Terraform, Nickel. Thank you.
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.
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.
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.
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.
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.