Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add section about keyword interaction mechanics being illustrative #1445

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

gregsdennis
Copy link
Member

@gregsdennis gregsdennis commented Sep 20, 2023

Holding this PR based on the outcome of https://github.com/orgs/json-schema-org/discussions/491

Related to #1444

@gregsdennis gregsdennis added this to the stable-release milestone Sep 20, 2023
jsonschema-core.md Outdated Show resolved Hide resolved
Copy link
Member

@jdesrosiers jdesrosiers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. I just left one minor suggestion.

jsonschema-core.md Outdated Show resolved Hide resolved
jsonschema-core.md Outdated Show resolved Hide resolved
jsonschema-core.md Outdated Show resolved Hide resolved
jsonschema-core.md Outdated Show resolved Hide resolved
@gregsdennis gregsdennis self-assigned this Sep 30, 2023
jsonschema-core.md Outdated Show resolved Hide resolved
@gregsdennis
Copy link
Member Author

@jdesrosiers @Julian

Based on my ramblings here, what do you think of this (before I add it as a commit)?

Keyword Interactions

Keywords MAY modify their behavior based on the presence, absence, or value of another keyword in the same schema object. Such keywords MUST NOT result in a circular dependency.

Unless otherwise specified, keywords act independently.

Supplementary specifications are encouraged to specify any dependencies as part of the dependent keyword (i.e. the keyword whose behavior is modified).

Within this document, keyword dependencies are expressed using one of the following mechanics:

  • Static dependencies, in which the dependency relies on the presence or contents of another keyword.
  • Dynamic dependencies, in which the dependency relies on the evaluation of another keyword against an instance. This dependency may be on either the annotations produced by the keyword or the validation result of its subschema.

These mechanics are used merely to describe dependencies; they are for illustrative purposes and not prescriptive. Implementations MAY use whatever mechanisms makes sense given the needs of their architecture and language in order to achieve the specified behaviors.

Annotations as an Interaction Mechanism

@Julian
Copy link
Member

Julian commented Oct 4, 2023

That looks pretty good to me! Though the words "in the same schema object" at the beginning make me double take a bit (maybe those words were there before -- it seems so) -- unevaluatedProperties changes its behavior not just based on keywords in the same schema object but also whenever properties and friends appear potentially in subschema resources does it not?

But the rest certainly seems better than before to me, I have no suggestions for further improvements there. (Though I have to reread your other comment on the discussion)

@gregsdennis
Copy link
Member Author

unevaluatedProperties changes its behavior not just based on keywords in the same schema object but also whenever properties and friends appear potentially in subschema resources does it not?

I think it's argued that "subschemas" is included in "values."

@gregsdennis gregsdennis changed the title add section about annotation-based keyword interaction being illustrative add section about keyword interaction mechanics being illustrative Oct 4, 2023
@gregsdennis
Copy link
Member Author

If we like this, then I need to create a follow-up PR that removes annotation language from all of the static dependency keywords (that's gonna be a lot).

Also #1451 will need to be closed and those sections possibly re-evaluated. I'm not sure I like the way it's set up, and I think it could be improved. We'll see.

@jdesrosiers
Copy link
Member

I think this is fantastic. I have one suggestion, but I'll save it for when you actually push the change. I'm 100% on board with the direction.

Copy link
Member

@jdesrosiers jdesrosiers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a couple small notes. In addition to that, I think it would be good to also include a recommendation to strongly prefer static dependencies when designing keywords with interactions.

jsonschema-core.md Outdated Show resolved Hide resolved
Comment on lines +545 to +548
These mechanics are used merely to describe dependencies; they are for
illustrative purposes and not prescriptive. Implementations MAY use whatever
mechanisms make sense given the needs of their architecture and language in
order to achieve the specified behaviors.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this paragraph is necessary anymore. These are more classifications of behavioral properties than they are mechanisms. They're certainly not implementation details. It seems redundant to state that implementations can implement them however they want because we haven't said anything that constrains how they would implement them. We've only defined their external behaviors.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it hurts to be explicit here.

The original concern about annotations being the description mechanism was that it would lead implementors into thinking they had to use annotations to drive these dependencies. I don't think the other changes address that concern, but this paragraph does so explicitly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep it, it needs to be made more clear what it's trying to say because I can't make sense of it. The way I understand it, the things you're calling "mechanisms" now are not mechanisms in the same way that annotation are a mechanism. If they were, everything would make sense. Before, we were telling implementations how to get the right outcome using annotations. Now, we're going to tell implementations what the outcome should be and let implementations figure out how to get to that outcome on their own. Since the new way already isn't telling implementations how to get the outcome, it doesn't make sense to me that we're still telling them they can get to that outcome however they need to. We're no longer giving them a "how", so telling them that the "how" is just illustrative doesn't make sense.

I'm sure there's some piece of this that you understand differently that's leading us to different places.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's unclear about it? We invent some mechanics to describe behavior and then say they're only descriptive and implementations can do what they want as long as the behavior is met.

We're no longer giving them a "how"

We are giving them a "how." It's just the "how" isn't always annotations. Now it can also be "this keyword looks at that keyword."

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way I understand it, the things you're calling "mechanisms" now are not mechanisms in the same way that annotation are a mechanism.

I disagree. "Dynamic dependencies" even explicitly includes annotations as one of the sub-parts that may be used for determinatioin.

In computer science, a mechanisum is any function that produces an output. We define two classes of mechanisms, but that doesn't negate the fact that there are mechanisms.

Besides, if you take unevaluatedProperties as an example, it still defines using annotations as the mechanism for determining its assertion result.

Before, we were telling implementations how to get the right outcome using annotations.

Aren't we still doing that?

I'm really struggling to reach any other conclusion. Can you help me understand how you reached yours @jdesrosiers?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are giving them a "how." It's just the "how" isn't always annotations. Now it can also be "this keyword looks at that keyword."

This is the part I'm not following. "This keyword looks at that keyword" doesn't say what mechanism is used to do the looking. That statement describes the behavior, not the mechanism by which the behavior is achieved. It's like saying "Greg goes to the store". The mechanism by which he gets to the store is implicitly up to him. That statement doesn't describe the mechanism he should use to get to the store. He might drive, walk, ride a bike, take a bus, etc.

We're saying that there are two categories of keyword dependencies. One category is where the keyword depends on the value of the dependent keyword. The other category is where where the keyword depends on the value of the dependent keyword as well as an instance. Those are statements of dependency. I don't see how they can be interpreted as mechanisms (or imply a mechanism) for how that dependency is implemented.

Before, we were telling implementations how to get the right outcome using annotations.

Aren't we still doing that?

No, the plan is to undo that except possibly in the case of unevaluated* whose definition is dependent on annotations.

if you take unevaluatedProperties as an example, it still defines using annotations as the mechanism for determining its assertion result.

Right, but that's a different context. This section defines two "mechanisms": static keyword dependencies and dynamic keyword dependencies. Those are the "mechanisms" that are the subject of this passage. Annotations are no longer the subject. Is the point of that passage to say that keywords may describe their behavior in terms of some mechanism, but if they do implementations don't need to use that mechanism? If so, that makes perfect sense. That needs to be said either in this section or by the keyword definition using a mechanism to define behavior. But, that's not what this passage currently says.

I'm really struggling to reach any other conclusion. Can you help me understand how you reached yours @jdesrosiers?

I too am trying very hard to understand this from another perspective. I specifically need help understanding how a statement of dependencies can be interpreted as a mechanism. I've laid out how I have reached by conclusions in this and the previous comments in this thread. I don't know how else to explain it. The only explanation for this misunderstanding is that there is some underlying assumption that we're making differently and not realizing. We are all coming to logical conclusions based on our assumption, but end up with different conclusions because our assumptions are different. I don't know how to tease out that fundamental difference of understanding.

Ultimately, I don't want this to become another big argument. I'm simply trying to understand what that passage is trying to say. The way I understand the passage, it's meaningless, so I'm not concerned about it doing harm. I just want to understand it's intent or remove it if it doesn't make sense.

Co-authored-by: Jason Desrosiers <jdesrosi@gmail.com>
@gregsdennis
Copy link
Member Author

gregsdennis commented Oct 5, 2023

I think it would be good to also include a recommendation to strongly prefer static dependencies when designing keywords with interactions.

I'm iffy about this being in the spec. I think it would be good as (advanced) user guidance on the website.

This is a bit different from recommending that dependencies are defined as part of the dependent keyword, where the spec is more-or-less defining that one keyword can't actively modify other keyword's behavior; rather the second keyword modifies its own behavior based on the first.

@jdesrosiers
Copy link
Member

I think it would be good to also include a recommendation to strongly prefer static dependencies when designing keywords with interactions.

I'm iffy about this being in the spec. I think it would be good as (advanced) user guidance on the website.

Well, I don't disagree with that, but I do think it's inconsistent with our recent decisions to include guidance in other places in the spec including this section.

This is a bit different from recommending that dependencies are defined as part of the dependent keyword, where the spec is more-or-less defining that one keyword can't actively modify other keyword's behavior; rather the second keyword modifies its own behavior based on the first.

I disagree. We established that the dependency directionality is a matter of perspective and the recommendation is merely an authoring style choice. Warning people off of dynamic keywords that are likely to be more difficult to implement and slower to evaluate than static keywords seems like more valuable guidance to include.

@gregsdennis
Copy link
Member Author

Warning people off of dynamic keywords that are likely to be more difficult to implement and slower to evaluate than static keywords seems like more valuable guidance to include.

I disagree. Warning people off dynamic keywords limits the keywords that people propose. Dynamic keywords aren't inherently more difficult to implement or slower to evaluate. They may be in your implementation (and perhaps others), but that's not universal.

@jdesrosiers
Copy link
Member

Dynamic keywords aren't inherently more difficult to implement or slower to evaluate.

I disagree. I wasn't making a personal statement here. I believe that dynamic keywords are in general more difficult to implement and slower to evaluate. I certainly make unevaluated* harder due to my architectural choices, but I'm that's not what if talking about.

Dynamic keywords inherently require either maintaining state and managing order of evaluation or duplicating work. Both of those options carry complexity and performance costs. Implementing state management and managing order of operations adds complexity to your architecture and there is a performance cost associated with maintaining state that I've found to be significant. Duplicating validation can incur complexity in a different way and has a performance impact because evaluations are repeated. I acknowledge that it may be relatively easy to implement unevaluated* if you already have state management and order of evaluation as part of your architecture, but you still pay the cost of the added architectural complexity and performance to have those things.

From an anecdotal perspective, I think the current state of the ecosystem shows that implementers find dynamic dependencies more difficult to implement as they are the least likely to be implemented in implementations that claim partial support of 2019-09 and 2020-12 and more likely to be complained about by implementers trying to support the later versions. I would say the only thing in JSON Schema that gives implementers as much trouble as dynamic keywords is $id.

None of this is particularly relevant, but I wanted to respond to the accusation that my position is based on self-interest. The real issue here is whether or not we put guidance in the spec. Generally, I think no. I would hope that when we do, it's limited to times where we the guidance is universally agreed on. That's not the case here, but it's also not been the case for other guidance we recently decided should be in the spec, so it seems reasonable to consider if we're being consistent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

None yet

4 participants