Coding 2022-12-31

By Max Woerner Chase

All right, starting the entry a little early today, because I just came up with a breakthrough, and I need to document it to get it all the way fleshed out.

Basically, I've got a provisional name replacement for flex: artifact. Once I had that, I started looking over the various bits defined in the module, and I realized something about some of the helper classes I wrote: they're not so much misnamed, as they maybe shouldn't exist at all.

This turns out to be trickier than I thought it was before I started typing. Let's have a look at two Part definitions that illustrate what I need to work through.

Here's how they look currently:

HTML_DIR = _parametric_command.Extra(
    _flex.FlexOut[str](
        _reports.accumulator("coverage", suffix="index.html"),
        str,
        _parametric.no_parameters(("mypy",)),
        HTML_LABEL,
        _flex.ArgParent("--html-report"),
    ),
)

...

JUNIT_XML = _parametric_command.Argument(
    _flex.FlexOut[pathlib.Path](
        _reports.accumulator(suffix="junit.xml"),
        pathlib.Path,
        _parametric.no_parameters(()),
        JUNIT_LABEL,
        _flex.ExtraParent(),
    ),
    "--junit-xml",
)

And then let's update the names in those and make a few other tweaks:

HTML_DIR = _parametric_command.Implicit(
    _artifact.Output(
        _reports.accumulator("coverage", suffix="index.html"),
        str,
        _parametric.no_parameters(("mypy",)),
        HTML_LABEL,
        _artifact.ArgParent("--html-report"),
    ),
)

...

JUNIT_XML = _parametric_command.Argument(
    _artifact.Output(
        _reports.accumulator(suffix="junit.xml"),
        pathlib.Path,
        _parametric.no_parameters(()),
        JUNIT_LABEL,
        _artifact.ImplicitParent(),
    ),
    "--junit-xml",
)

Some things that come to mind are relatively minor, such as that Argument should probably have an alternative constructor called something like prefixed.

But the big issue that I need to figure out is those ArgParent and ImplicitParent classes. What I hope to do is to essentially turn the HTML_DIR definition "inside-out", so that the description of how the command is actually constructed lives at the top level. I think technically this changes the semantics because the Artifact instance has less control over how it's "rendered", but I also think that doesn't matter, because the Output instance should only appear in one context anyway.

(Also of note, there's another Artifact implementation that corresponds to output, and it should also have access to whatever the main one does in this area; currently, it does not.)

Comparing these usages, the HTML_DIR is saying "if we ensure that this directory exists, we can predict the file that will be written to it". The JUNIT_XML is saying "let's write a file to this path, and we had better make sure the parent exists". In the former case, the child is implicit in the constructed command. In the latter case, the parent is implicit.

Let's try changing things up modestly.

HTML_DIR = _parametric_command.Argument.prefixed(
    "--html-report"
    _artifact.Output(
        _reports.accumulator("coverage", suffix=""),
        str,
        _parametric.no_parameters(("mypy",)),
        HTML_LABEL,
        _artifact.ImplicitChild("index.html"),
    ),
)

...

JUNIT_XML = _parametric_command.Argument.prefixed(
    "--junit-xml",
    _artifact.Output(
        _reports.accumulator(suffix="junit.xml"),
        pathlib.Path,
        _parametric.no_parameters(()),
        JUNIT_LABEL,
        _artifact.ImplicitParent(),
    ),
)

The relevant changes to the interface look really small from this perspective, but they represent a pretty involved change to the implementation. Let's skip over the prefixed stuff, because that's trivial.

Including line-numbers for some reference to myself later...

It's hard to say how these changes should relate to lines 265-7, because the map fields aren't typically doing anything interesting. When we get to 268, there's the Input creation, which needs to be modified in the case of an ImplicitChild, but left alone if there is nothing or an ImplicitParent. The expression from 269-78 would similarly need to use the possibly-updated value. Lines 280-2 apply the outer specification, so should use the parametric from before the ImplicitChild is applied. In the event that ImplicitParent is passed, the block starting at line 284 should be entered, but the method calls to the object can be inlined.

Let's now have a look at OutputFromInput. The current implementation is... aggressively golfed in terms of pure statement count, so it's possible I'll need to break it up if I want to replicate this logic. OutputFromInput.convert starts by pulling the Parametric from the wrapped input. This should correspond to line 264. Note that the OutputFromInput expects the Parametric to have been updated beforehand. I might need to change this, because I can't use these new Implicit ideas if I don't have a PathStr to apply them to, which is not guaranteed. Alternatively, the OutputFromInput could require the caller to pass a PathStr, and then have a separate map step... So, assume the wrapped Input must have a compatible type, and then operate on that in the same general way as Output. It looks like 265 onward could work the same, and maybe be a common function. Question: should OutputFromInput accept the injected values? I forget why I had it discard that argument... For what it's worth, it also happens with Input. I'm also looking at the way I'm putting this together and thinking it it would make sense to just move the inject_registry calls off of the "accumulator" classes, to reduce the API surface. However, doing that might break things... Anyway, I believe that OutputFromInput would need to keep some of the logic that's in Input. I think the logic common to OutputFromInput and Output is from 176-86, which replaces lines 118-20 in Input.

I think this is all of the information I need to fix this stuff up. As of this writing, there's time tonight for me to attempt this, but I'd rather focus on continuing to plan.

First thing I'd like to try: removing the inject_registry wrappers and try doing the injection after the parametric is extracted. I kind of want to try that before any more planning, then get into planning seriously.


It's later now. I'm going to try a quick rewrite and see what breaks, if anything.


All right, it looks like that was clean.

I'm not going to put in much more work on this tonight, so let's switch to seeing which files I want to hit next.

I'll try looking at input_accumulator, then installer, then command. For now, though, I'm going to wrap up.

Good night.