dotnet / interactive

.NET Interactive combines the power of .NET with many other languages to create notebooks, REPLs, and embedded coding experiences. Share code, explore data, write, and learn across your apps in ways you couldn't before.
MIT License
2.8k stars 371 forks source link

Magic command API redesign #3567

Open jonsequitur opened 2 weeks ago

jonsequitur commented 2 weeks ago

Magic command API redesign

Background and motivations

In the early days of .NET Interactive, providing magic command features as seen in Jupyter required a parser for a magic command grammar that would be independent of the various languages (C#, F#, PowerShell) that .NET Interactive supports. A POSIX command line-style grammar was a fairly clear choice, and System.CommandLine was robust and ready to use, so rather than build something custom, we took a dependency on System.CommandLine. This API has served .NET Interactive well for several years now. But as System.Commandline's design is being reset for inclusion as a core .NET library, some of the features that .NET Interactive uses are likely to be removed. At the same time, .NET Interactive's input system is undergoing a long-planned set of improvements whose ergonomics can be much better if we support features that are well outside of what a command line parser would include.

The largest impacts of these changes will be on people who've written extensions to .NET Interactive that customize magic commands. We are attempting to minimize impacts on notebook users by maintining backwards compatibility to the greatest extent possible, but it's important to recognize that some breaking changes are unavoidable unless we want to bring the entirety of the System.CommandLine codebase into .NET Interactive, which has a high cost for maintainability and concept complexity.

So what's changing?

Options and arguments are now just parameters

The terms option and argument were chosen based on common Gnu/POSIX naming conventions for the command line. Separating the magic command API from the command line use case is an opportunity to choose the more commonly-recognized term parameter. Both named parameters (i.e. options) and unnamed parameters (i.e. arguments) will now use the same type, KernelDirectiveParameter.

What's being removed?

The following features of System.CommandLine are not currently planned to be reimplemented by the new magic command parser.

What's being added

Handling magic command invocation

As of System.CommandLine beta 4 (which is the most recent version that .NET Interactive depends on), the code that handles the execution of a magic command is specified using the Command.Handler property, which mainly does two things:

The Command.Handler API has been a major pain point in System.CommandLine and has seen a number of breaking changes over the last few years. The magic command API redesign is an opportunity to replace a couple of pieces with existing, stable APIs.

With all of that in mind, here's a simple example of the new API:

var fruitDirective = new KernelActionDirective("#!fruit");

kernel.AddDirective<ExampleCommand>(
    fruitDirective,
    async (ExampleCommand command, KernelInvocationContext context) =>
    {
        // magic command logic goes here
    });
marckruzik commented 2 weeks ago

I'm glad to see this being worked on! Bravo! 😃

jonsequitur commented 2 weeks ago

It's been on the back burner for a minute and is a very complex change. I'll be updating the details above as more of the design details are worked out. Please give us any thoughts or questions that occur to you.

IntegerMan commented 1 week ago

This is great to know about in this level of detail. This makes a lot of sense and I love the clarity on implicit parameters as well. I think this change makes the overall system more approachable with consistent terminology, in addition to being a necessary change due to the commandline dependency.