JacquesCarette / Drasil

Generate all the things (focusing on research software)
https://jacquescarette.github.io/Drasil
BSD 2-Clause "Simplified" License
136 stars 25 forks source link

Redesigning `Choices` to enable new input patterns #3781

Open B-rando1 opened 3 weeks ago

B-rando1 commented 3 weeks ago

As discussed in #3752, we want to add new input patterns to Drasil. From looking at drasil-code it seems that we need to change Choices in order to allow users to express the input patterns they would like. I have a design, but it has flaws so I would like some advice.

Changes to Choices

Choices already has a couple of relevant properties:

I created a new datatype data LogicLoc = InCons | InMthd | InFunc. I then added two fields of this type to Choices: parseLoc and validateLoc. (I'm not too sure about any of these names, feel free to suggest better ones 😄). The idea is that we can specify whether parsing and validation are done directly in the constructor, in a method to be called by the constructor, or in an external function.

Notes

It's possible I'm overanalyzing this, but it just seems like a better design of Choices would lead to fewer edge-cases to deal with.

B-rando1 commented 3 weeks ago

@smiths had a good idea in related discussion #3780. Rather than giving the user full control over every aspect of the code, we could create a curated list of decisions that fit well together, and just offer those. This would simplify the structure of Choices, and would also remove the chance of having a lot of edge cases.

B-rando1 commented 3 weeks ago

One change to Choices that I think makes sense is getting rid of the part of Architecture that deals with separate vs combined input methods. @smiths mentioned in #3752 that parsing and validation were originally separated in order to separate the 'secrets' of input format and input constraints. He went on to say that they later decided that since parsing and validation are only ever done together, they might as well be in the same module with the meta-secret of "how to provide valid input parameters". I agree with this, and I also think there's an argument that Parnas' idea of a "Module" doesn't have to refer to files - it could refer to functions or methods that hold a secret.

The other reason is that the currently available design choices that involve Separate (e.g. GlassBR) generally have more issues with them.

We should probably wait to make changes until we have an overall direction, but I just thought I should mention this piece here.

JacquesCarette commented 3 weeks ago

So one way to handle "dependent choices" is to have the data-structures direct them.

Let me take Architecture as the example (even though it might go away); Unbundled would stay as is (and imply InFunc) while Bundled would contain a LogicLoc field that further specifies that variability. (This needs to be adjusted for the actual design, but hopefully illustrates the idea.)

B-rando1 commented 3 weeks ago

@JacquesCarette I've been thinking a bit about your comment, and this is currently the set of choices that makes sense to me:

Dependent choices hierarchy

I think that if we want to allow the full set of choices, this is (hopefully) on the right track. I need to think some more if there's a good way of preventing complexity (#3780), but if this is the way we want to go then I think we might be able to figure that part out.

B-rando1 commented 3 weeks ago

A few notes from yesterday's meeting:

smiths commented 3 weeks ago

Thank you for the summary @B-rando1. That is a great habit!

B-rando1 commented 1 week ago

I'm having some technical difficulties with getting Interleaved to work. A couple weeks ago I got something in the right direction, where if Unbundled Interleaved is chosen, it puts all of the input-related code inside get_input. Here is the function I added to do that: https://github.com/JacquesCarette/Drasil/blob/8ae2f82dfae96af251df6c309d486a524f551e6e/code/drasil-code/lib/Language/Drasil/Code/Imperative/Modules.hs#L410-L435

The issue is that, as you might expect, interleaving the parsing and validation is not as straightforward.

From reverse-engineering the code a bit, formatBod, derivedBod, and constraintsBod are all of type GenState [MSBlock], where each element of the list seems to refer to one specific action, e.g. opening the file, defining a single variable from a file, or checking a constraint on a set of variables.

Based on that, what makes the most sense to me is to change the types of these variables form GenState [MSBlock] to GenState [(Set Label, MSBlock)], where the Set is the set of all variables involved in that MSBlock.

If we could get things to that point, then we could build up a Set of variables that have been defined, and check after each input block if there are any input constraints that can now be added to the code.

Does this sound like a smart way of doing it?

I'm hoping that this way is doable, but there are a lot of parts to the code that I don't understand yet. I'll have to become friends with the quantVar, mkVar, and readData functions in particular it looks like.

B-rando1 commented 1 week ago

I may have found a problem with interleaved constraints. As I described in my last comment, we need some way of keeping track of which variables have been defined, and which variables each input constraint depends on. The first one seems just about possible (I got it working for derived inputs, and in theory parsed inputs should be similar).

The problem is that, as far as I can tell, each constraint is tied to a single variable, even if it depends on multiple variables. Take, for example, this example from Unitals.hs in PD Controller:

Because ipSimTime is put into the constructor for Bounded in this way, the constraint loses sight of it and just sees it as 'some expression' (no different than the frac 1 1000 for the lower bound). The constraint knows it's 'owned' by ipStepTime, but that's all it knows.

If we want to have interleaved parsing and constraint-checking, we'll need a better system of keeping track of all variables involved with constraints. It might be possible to reason it out under this current system, but I do wonder it would be better to make constraints their own thing (rather than a part of ConstrConcept as they currently are). I'm feeling a little out of my depth out here in drasil-lang, so I'd appreciate any ideas @balacij @JacquesCarette.

JacquesCarette commented 1 week ago

Having constraints as a separate thing that explicitly lists the variables that it depends on is an excellent idea.

Then we could do a topological sort and output things in the dependency order.

B-rando1 commented 1 week ago

I was talking with @balacij and @NoahCardoso about this, and we found a function eNamesRI that finds all UIDs involved in a RealInterval. We should be able to use this to get the necessary information to sort the code expressions.