-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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 draft proposal for property expressions spec #4754
Conversation
Developed in #4715
Note that this is against the The purpose of this PR is for focused review of the design that was developed over in #4715. |
If I was designing style property values totally from scratch, I'd want one unified representation: property-name: property-value-expression, where the expression grammar includes:
In order to make this a superset of existing functions, we also need a way to express the existing |
I assume the top two deprecation's are replaced by the new data-driven expressions feature. I just wanted to say that the current zoom, property and zoom+property functions in Mapbox Studio are awesome and make working in Studio much nicer, so I hope this is an improvement upon the current state. |
👍 I was thinking
This is a great abstraction & syntax for unifying linear, exponential, step, color, etc. functions. I like that it affords a way to express zoom-dependent functions without a redundant concept while retaining the constraints we need. @jfirebaugh where do basic arithmetic functions (+ - * / ^) fit in here? While I think having higher level curves is useful, there are use cases that they either don't address or would make cumbersome. E.g.:
We also probably still want |
Oh, yeah, that wasn't a complete list of expression types. I think we want most of the other things listed in PR too. Other things I'm thinking about:
|
The issue wasn't with "data"/dynamic types as such, but with automatic coercion: having the arithmetic operators coerce strings to numbers seemed to lead to a place where there was no meaningful distinction between
I strongly suspect we'll end up adding them later even if we don't now. 👍 to general syntax... maybe "get" instead of "member"?
For expressions to subsume existing filters, we at least need to expose the geometry type. Coordinates... heh probably not 😱 |
How about ["literal", any JSON], i.e. allow strings, numbers, |
Sounds good. Could be used for both object member and array indexing. |
@jfirebaugh that looks great. This looks like it covers everything we currently support with functions. This seems like a good subset to dig more deeply into to work out typing and error handling. I'm not quite clear on how the easing/interpolation works. What do What are your thoughts on the typing?
How are errors handled? for example, failed coercions
|
Thinking ahead... if we do support this, we'll need to distinguish string literals from variable identifiers. Most languages use quotation marks as the lexical signifier for string literals, but ours does not have that luxury because we're using JSON strings as both identifiers (when in first position) and string constants (in other positions). But we can reverse the typical arrangement, and have a signifier for variable identifiers, e.g. ["var", name], evaluating to the value of the variable name. |
Good question. Thinking about this more, There are probably several possibilities for defining the semantics of interpolator as a function. I'm not sure it matters too much which one we pick until/unless we add support for first-class functions. Until then, ["step"], etc are just special expressions that can only be used in the second position of "curve". |
@jfirebaugh what's the advantage to quoting primitives? I think doing this would add quite a bit of verbosity that it would be nice to avoid if possible. |
* Introduce `curve` expression, use it to cover zoom function `stops`. * Introduce object and array literals * Remove explicitly typed property lookup
More proposals: Type systemGeneral principles:
The inhabitants of the type system are:
We want type inference to proceed primarily via recursive AST descent, i.e. to infer the types of expression arguments given the expression form and an expected result type. At the top of the tree, the expected result type is determined by the style property being evaluated. Looking at the expression forms proposed so far, we'll need to make some adjustments:
To minimize explicit annotations (e.g. infer Number in ["==", ["get", ["properties"], "foo"], 1]), we'd need to go beyond strictly recursive descent inference. Maybe not full Hindley–Milner though? Error handlingGeneral principles:
|
Actually, I'm not sure we need or want this. We want ["==" ["typeof", ["properties"], "foo"], "null"], sure, but is there a use for |
@jfirebaugh that looks great!
Sounds good. Using a enum argument to determine the return signature seems like a bit of an exception. Two other possibilities, each with their own drawbacks:
Both
Is this a problem? I think it should be fine, but wasn't completely sure. What are your thoughts on adding a way to specify arguments that need to be determinable at compile time? For example the keys in |
@jfirebaugh this looks excellent!
You're saying that no expression's "output" type would be defined as
@ansis I have been thinking of this as a syntax rule rather than a type rule, though we could also represent it with something like a
Let's play this out.... Intuitively, my hypothesis is that realistically, the cases where a Cases like: [==,
[
case, (some condition), [get, [properties], 'a'], [else], [get, [properties] 'b']
],
[
curve, [step], 42, 0, [get, [properties], 'x'], 10, [get, [properties], 'y']
]
] (let's say for the sake of argument that step curves can output strings) Would requiring annotations here provide any meaningful/helpful compile-time help to the user? Are there other plausible examples that are worse than this one (especially if we go a bit further than just recursive descent in our inference algorithm)? |
👍
Indistinguishable in the sense that there's no way to distinguish these cases within the expression language, or in the sense that they when encountered they'd produce identical diagnostic output? If the latter, I'm imagining the Error type to carry a string error message that can distinguish them. If the former, you can distinguish the second two cases from the first with a preflight
Agree with @anandthakker, I think this is best as a syntax rule rather than a type checking rule.
Yeah, you're right,
I think the benefit is mostly in runtime help. In the example you gave, required annotations could for example turn a mysteriously invisible layer into an actionable error message:
|
We should also add a "string length" function, which could be useful for selecting the correct highway shield icons based on the text. |
By "string length" did you mean "number of characters" or something like "rendered width"? At layout time we should have the information necessary for returning the second. |
@jfirebaugh okay I’m almost sold here, with one lingering question: the premise behind only requiring annotations when they can’t be inferred--rather than always requiring them--is that the latter would make authoring expressions too cumbersome (right?). Is this really true? My assumption is that users will only very rarely author expressions via the syntax tree, instead creating them using Studio or a text-based syntax. Studio would be able to generate annotations, so that's not an issue. How bad would it be to require them for the text-based case? |
@jfirebaugh Another thought following up on #4754 (comment): so far I've been imagining that the text-based syntax(es) would be totally isomorphic to the JSON AST structure... but we could say that every |
docs/style-spec/expressions.md
Outdated
- `[ "id" ]` returns the value of `feature.id`. | ||
|
||
**Decision:** | ||
- `["case", cond1, result1, cond2, result2, ..., ["else"], result_m]` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is an explicit "else" strictly necessary, given that "else if" is implied? Seems like we could simply interpret a dangling result_m as the else result. If so, then the "if" function becomes truly redundant to "case".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not necessary, but I personally think it (a) doesn't do much harm, (b) makes parsing slightly easier, (c) makes mistakes easier to spot. Guessing I'll be outvoted on this though.
"rendered width" would be cool to have, but it means that we'd need the glyph + shaping information available when evaluating the expression. Additionally it creates a circular dependency when you use it in a |
Is adding evaluation for the existence of sprite values still up for discussion? (#4715 (comment)). While an expression may be more cumbersome than implementing an image stack, it seems like expressions offer more flexibility. |
@nickidlugash Yeah, I think that's still an open discussion, but my feeling is that we should take that on as a follow-up, since, unless I'm misunderstanding, it would be an additive change. |
@anandthakker that sounds good. I wanted to confirm whether this was even logically and technically compatible with the arbitrary expressions schema. It would be great if we could look into adding this as a follow-up. |
] | ||
} | ||
} | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By only allowing one curve
value per zoom function, this seems to have on of the same limitations as our current zoom functions: if you have more than 2 stops you are forced to define the interpolation behavior between the pairs of stops in the same way even though that's usually not what you want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nickidlugash good point. Could you describe a couple/few use cases where a single interpolation base is non-ideal?
* Add draft proposal for property expressions spec Developed in #4715 * Update spec proposal * Introduce `curve` expression, use it to cover zoom function `stops`. * Introduce object and array literals * Remove explicitly typed property lookup * Describe type system * Remove ["else"] * Minor edits
* Add draft proposal for property expressions spec Developed in #4715 * Update spec proposal * Introduce `curve` expression, use it to cover zoom function `stops`. * Introduce object and array literals * Remove explicitly typed property lookup * Describe type system * Remove ["else"] * Minor edits
* Add draft proposal for property expressions spec Developed in #4715 * Update spec proposal * Introduce `curve` expression, use it to cover zoom function `stops`. * Introduce object and array literals * Remove explicitly typed property lookup * Describe type system * Remove ["else"] * Minor edits
* Add draft proposal for property expressions spec Developed in #4715 * Update spec proposal * Introduce `curve` expression, use it to cover zoom function `stops`. * Introduce object and array literals * Remove explicitly typed property lookup * Describe type system * Remove ["else"] * Minor edits
* Add draft proposal for property expressions spec Developed in #4715 * Update spec proposal * Introduce `curve` expression, use it to cover zoom function `stops`. * Introduce object and array literals * Remove explicitly typed property lookup * Describe type system * Remove ["else"] * Minor edits
* Add draft proposal for property expressions spec Developed in #4715 * Update spec proposal * Introduce `curve` expression, use it to cover zoom function `stops`. * Introduce object and array literals * Remove explicitly typed property lookup * Describe type system * Remove ["else"] * Minor edits
* Add draft proposal for property expressions spec Developed in #4715 * Update spec proposal * Introduce `curve` expression, use it to cover zoom function `stops`. * Introduce object and array literals * Remove explicitly typed property lookup * Describe type system * Remove ["else"] * Minor edits
* Add draft proposal for property expressions spec Developed in #4715 * Update spec proposal * Introduce `curve` expression, use it to cover zoom function `stops`. * Introduce object and array literals * Remove explicitly typed property lookup * Describe type system * Remove ["else"] * Minor edits
* Add draft proposal for property expressions spec Developed in #4715 * Update spec proposal * Introduce `curve` expression, use it to cover zoom function `stops`. * Introduce object and array literals * Remove explicitly typed property lookup * Describe type system * Remove ["else"] * Minor edits
Proposed plan for introducing arbitrary data-driven expressions into style functions:
keep stop-based zoom functionsallow data-driven expressions (a) as style property values, and (b) as output values for zoom function stops. (I.e., (a) replaces "property functions", and (b) replaces zoom+property functions.){ 'expression': ... }
{ 'expression': ['curve', 'interp', ['zoom'], ... ] }
{ 'expression': ['curve', 'interp', ['zoom'], 0, some, 10, another_expression, ... ] }
See
docs/style-spec/expressions.md
for the spec details.@mapbox/gl @mapbox/studio @mapbox/cartography-cats @mapbox/support