Comments (13)
One important limitation here is that so far, I've carefully kept Askama's template grammar separate from the Rust grammar. Both if let
and full match
syntax would probably require relinquishing that, making the parser more complex and also require changes to stay up to date with Rust.
I do think if foo is some
with that compiling to the if let
as you write is a pretty good fit. It could expand to some
, none
, ok
or err
to cover your base cases. Still wondering if there should be some explicit naming of the "unpacking" in the syntax (maybe if foo is some as bar
?).
from askama.
Your grammar philosophy makes sense to me. I've been reflecting on your comment for a bit and I can't think of a better alternative for explicit unpacking, though I'd slightly prefer the as bar
to be optional.
The trickier case is Result
...
{% if foo is ok %}
Sucess: {{ foo }}
{% else as err %}
Error: {{ err }}
{% end %}
or would you have to use {% else if foo is err as err %}
? (note: I don't currently have any use for Result
, I'm just trying to think through how this syntax might extend cleanly)
from askama.
Hmm, yeah, that makes it pretty ugly. On the other hand, how would it work if you access an attribute or method that returns Option
? I don't think shadowing the original expression works out well in that case. I think that makes naming it a requirement?
from askama.
ah.. good catch on the shadowing problem for attributes/methods. You'd certainly have to identify names separate from more complex expressions, which is a good argument for requiring explicit names. And even though I still like the implicit shadow form, even if let Some(foo) = foo
requires redundantly stating the shadow name, so I'm persuaded.
I'm curious though what you think the else
unpacking for Result
should look like? I feel like I've been thinking about this all day and my brain isn't coming up with any clear winner.
from askama.
Throwing in my own $.02, I'd like to say that if some sort of is
construct was adopted, I'd personally greatly prefer it if it was a generic construct akin to what Jinja does โย semantically boolean checks. I've always found them to be very pleasant to use in Jinja. The actual implementation wouldn't have to return a literal bool
though, could return an enum with an attached value, for example, something like Either with one value being true
and the other false
, to facilitate the type of unpacking you're discussing.
from askama.
I thought of another problem: current match
(at least until this RFC lands) is pretty picky about exact typing, for example in terms of how many references. From what we've seen so far, Askama is pretty reliant on coercion to solve these problems, so a dumbed down syntax for match probably won't work so well.
As for unpacking Err
, I haven't come up with anything better than else if foo is err as err
either. I do think there are more design directions to explore though: specialized blocks for Option
and Result
, for example, as in {% option %}
.
As a general principle, I think one the other principles/goals for Askama (next to the "stay away from full Rust syntax") is it should be easy to see how it translates into Rust code. I think else if foo is err as err
does okay on that, at least, as it is quite similar to an if let
construct. If we think about it, the main win is terms of conciseness is really something match
-like such that you don't have to repeat the foo
part.
from askama.
@despawnerer - I don't fully follow your suggestion. Can you provide an example of returning an "enum with attached value"?
@djc:
I think you can still achieve coersion with tricks like (&foo).as_ref()
, since I believe the method call should auto-deref up to the type needed by the method, and as_ref
should then just return &Option
if foo
was Option
, &Option
, or &&&&&Option
.
I've chewed a lot on more generic match
ideas, and finally found one that I don't hate. Here's an idea that could be generalized to a lot of enums
:
{% match foo %}
{% when some val %}
Found {{ val }}
{% when none %}
Not found
{% end %}
Notes/Variations:
match
grammar ismatch EXPR [as TYPE]
- I imagine type being optional for types where variants are already in the prelude (e.g.Result
andOption
), but if you have anenum Color { Red, ... }
, you'd usematch foo as color when red
. If you just want to start with a minimal solution forOption
andResult
, you could omit the[as TYPE]
and it'd be backward compatible to add later to support other types.when
grammar iswhen VARIANT [ARG[, ARG]*][ or VARIANT [ARG[, ARG]*]]*
where each arg unpacks a tuple variant's next arg. Thenor
can be used to capture multiple variants in the same arm (i.e. Rust's|
). You could get extra complex and allow match guards, likewhen ok num if num > 32
, but probably good to start simple and skip that. You could also skip theor
clause.- you might allow combining the first lines into
{% match foo when some val %}
- I suggested
camel_case
enum and variant types because that's consistent with our conversation up to this point. If supporting justOption
andResult
, these keywords can be hard-coded. If supporting more enums, this would involve auto-converting the type and variant toPascalCase
, though that would prevent using enums that don't follow standard naming conventions (probably fine?). The alternative is to just use the casing that is consistent with rust (e.g.match foo as Option when Some val
) - You could switch match grammar to
match [TYPE] EXPR
. That was my original idea sinceas
feels suggestive of coersion, but in some cases theas type
feels less ambiguous. (e.g.match color as color
vsmatch color color
) else
could be the_ =>
catch all variant.- this doesn't work well for the non-exhaustive
if let
case. You could have an emptyelse
block, or add some keyword somewhere that implies non-exhaustive matching, or also include a syntax likeif foo is some val
to handle that case.
Here's a more complex example:
enum Color {
Rgb(u32, u32, u32),
Rgba(u32, u32, u32, u32),
GrayScale(u32),
Cmyk(u32, u32, u32, u32)
}
{% match item.get_color() as color %}
{% when rgb red, green, blue or rbga red, green, blue %}
Colorful ({{red}}, {{green}}, {{blue}}) (alpha ignored)
{% when gray_scale val %}
Gray ({{val}}, {{val}}, {{val}})
{% else %}
Sorry, we don't yet support this color system
{% end %}
from askama.
I don't think AsRef
is a generic solution to the problem, it requires the user's types to actually implement AsRef
. So it may solve your problem in this case, but it's not a general solution to resolving the issue we have in Askama with coercion especially for dereferencing. I believe this is a significant open problem for this issue.
Based on your proposal, I have a counter proposal, which looks like this:
{% match item.get_color() %}
{% arm Rgb -> (red, green, blue) | Rgba -> (red, green, blue) %}
Colorful ({{red}}, {{green}}, {{blue}}) (alpha ignored)
{% arm GrayScale -> val %}
Gray ({{val}}, {{val}}, {{val}})
{% else %}
Sorry, we don't yet support this color system
{% end %}
Non-exhaustive version:
{% if match Rgb -> (red, green, blue) = item.get_color() %}
Colorful ({{red}}, {{green}}, {{blue}}) (alpha ignored)
{% else if match GrayScale -> val %}
Gray ({{ val }}, {{ val }}, {{ val }})
{% else %}
Unsupported
{% endif %}
Responding to your notes:
- I don't understand the need that your
as
onmatch
fulfills. Rust'smatch
keyword doesn't need anas
, right? If this is in response to my remarks earlier about shadowing, as far as I understand that's only needed for the variant arguments, not the matched-against expression. when
is unfamiliar, whereasarm
is used in Rust vocabulary to describe the arms. It's nice to have something more familiar for this.when
just reminds me of XSLT... Next, I really want some operator in there to connect the variant to the arguments instead of having them just separated by a space. I think->
works nicely for this, what do you think? In Rust it has a somewhat different meaning, but the context is different enough that I hope it's not confusing.=>
could work but is used differently in a very similar context in Rust, which makes it a lesser choice IMO. The challenge for me is that there's two somewhat counteracting forces going on: (1) that Rust's syntax acts as if the arguments are stored "in" the variant, (2) that the variant argument values are assigned to the local names. There's a fine line here about having a clear match to Rust's syntax, but not aping it too closely as to prevent confusion. I think I'd also want to keep Rust's|
foror
, similar to what I've done so far with the other operators.- Do you see this as the non-exhaustive (
if let
) syntax, or some third way? - If we special-case
Option
andResult
we can name them differently to make it fit in with other syntax, but for the general case I really don't want to case-convert names. So maybe we should stay away from translating names in general (at least for this). - Not sure what this is about, exactly.
- Using
else
for_
sounds good. - I think
else
as non-exhaustive is exactly right, then you addif VARIANT -> ARG
again if you want to do more matching.
One possibly interesting alternative is to introduce another block type, like this:
{% match item.get_color() %}
{= Rgb -> (red, green, blue) | Rgba -> (red, green, blue) =}
Colorful ({{red}}, {{green}}, {{blue}}) (alpha ignored)
{= GrayScale -> val =}
Gray ({{val}}, {{val}}, {{val}})
{= else =}
Sorry, we don't yet support this color system
{% end %}
It is a bit more concise, but I'm not sure it's easier to understand.
from askama.
Re: Generic solution: I think Borrow
should work as it's implemented for basically everything. That said, it sounds like the match bindings mode stuff is very close to landing in nightly, so it could just wait for that (maybe get implemented behind a nightly feature flag).
Re: arm
vs when
vs whatever: when
reads very natural to me having worked enough with ruby's case and coffeescript's switch. case
from Scala's match also reads naturally to me. Rust's match arms don't have a keyword with them so I just picked the word that read most naturally as English to me. arm
sounds fine too, though "when" feels grammatically more similar to "else" (when-else vs arm-else). {= ... =}
feels conflicting for me: it reminds me of ERB's interpolation syntax, and makes me wonder if the =
is for comparison or assignment. Other choices include {@ ... @}
and {%@ ... %}
since @
already implies "pattern binding". But in general, my preference isn't strong enough for me to really try and defend one of these; I could happily get used to any of these. It's your bike shed. ;-)
Jumping into the numbered points:
-
I proposed the
as
keyword as a way to figure out the type being matched for enums NOT in thestd::prelude
.arm Rgb -> (r,g,b)
needs to generate the arm:Color::Rgb(r,g,b) =>
(unless you already haveColor::*
in scope which doesn't seem to be common in Rust). So you can either prefix the type with every variant{% arm Color::Rgb -> (r,g,b) %}
, or have a singleas Color
that specifies the type that Askama can prepend to all the variants. This doesn't apply when matchingOption
,Result
and non-enum types like strings and numbers. -
At first I thought
->
was a typo because I expected to see=>
. Then I realized that it was actually doing something very different. IMO, it feels too unlike anything else in Askama's grammar, too unlike Rust, and reuses an existing rust symbol with a different meaning (in Rust, I read->
as "returns"). I feel like you should either go more rust-like withRgb(red, green, blue)
(but this naturally means my mental model will think that I can use full Rust syntax in the arm, like_
) or find something that feels more like other Askama/Jinja constructs. I could bikeshed this all day long, so here's a table comparing some ideas:
Rgb red, blue, green Some foo // my previous suggestion
Rgb -> (red, blue, green) Some -> (foo) // your suggestion
Rgb(red, blue, green) Some(foo) // rust-like
Rgb with red, blue, green Some with foo
Rgb with (red, blue, green) Some with (foo)
Rgb yield red, blue, green Some yield foo
Rgb yield (red, blue, green) Some yield (foo)
(red, blue, green) in Rgb foo in Some
(red, blue, green) @ Rgb foo @ Some // also guilty of changing the meaning of a rust symbol
-
This was just a way to collapse 2
{% %}
blocks into one line. Definitely not necessary. And I thinkmatch
should always be exhaustive... Something likeif let
orif match
should handle the non-exhaustive case. I think we're basically in agreement there from your examples. -
Either approach seems fine. My only vote here would be consistency. (
GrayScale
andSome
) or (gray_scale
andsome
) -
I can see how this wouldn't have made sense if you didn't see the need for
as Type
. But this was just an alternative syntax for the optionalas Type
.
6/7. Nothing to add.
from askama.
I haven't worked with Borrow
, and the documentation doesn't say much about how it applies to this use case (abstracting over arbitrary number of references). We'd need some good tests.
I think you've convinced me that when
is better than arm
, so let's go with that for now.
-
I understand the idea now and it makes sense to me, but I'm not sure yet about the
as
keyword. It feels too different from howas
is used in Rust. Maybefrom
is a better fit? We have to connect expression to type to variant. Also, how does it work when matching a tuple? I guess we could defer this question for now to say that the variant can be a full path (likeColor::Rgb
). -
Alright, so not my suggestion. I feel that the variant should probably be named before the arguments, agree? I think
Rgb with (red, blue, green)
looks okay, though then I kind of want to bikeshed the keyword more, maybefrom
as well? The more I look at alternatives, the more I want to stay withwith
, though. I think parentheses should be mandatory except in the single-argument case. -
Alright, clear.
-
Right.
-
See 1.
from askama.
In trying to create a quick example of how the Borrow
auto-deref it would work, I realized that the compiler won't auto-deref when resolving trait implementations, so impl Trait<T> for &T
doesn't imply impl Trait<T> for &&&&T
, so trying to use method-calling for arbitrary auto-deref wouldn't work for AsRef
or Borrow
. So I think we're stuck with a limited form of match
and/or waiting for match modes to land (which sounds like it could be in nightly very soon).
with
is also my current favorite inside match arms. I think your idea of mandatory parentheses except for single argument is compelling. ๐
I agree that as
suggests value coercion or type renaming. IMO, from
suffers a similar problem of implying conversion (though less so since it's not a keyword). Other ideas include:
match foo : Color // like type ascription
match foo use Color // hints at bringing Color's variants in scope
match foo using Color // similar to above, but doesn't change the meaning of use
match foo with Color // this might conflate the meaning of 'with' in the match arms
Anyhow, I agree that deferring this decision is a good choice. It's not needed for the common cases of Option
or Result
, and it could be be add backward compatibly in the future if wanted.
So I think these examples basically summarize the current state of our design discussion:
// Basic Option
{% match get_item() %}
{% when Some with val %}
Found {{ val }}
{% when None %}
Not found
{% end %}
// Advanced enum
{% match item.get_color() %}
{% when Color::Rgb with (red, green, blue) | Color::Rgba with (red, green, blue) %}
Colorful ({{red}}, {{green}}, {{blue}}) (alpha ignored)
{% when Color::GrayScale with val %}
Gray ({{val}}, {{val}}, {{val}})
{% else %}
Sorry, we don't yet support this color system
{% end %}
from askama.
Nice! So, do you want to take a stab at implementing it? ๐
from askama.
This has long been done.
from askama.
Related Issues (20)
- Do we need a built-in yaml filter? HOT 4
- Template compilation infinite loop / failed cycle detection HOT 4
- Add display_some filter and friends HOT 14
- Float literals are not completely supported HOT 1
- Add support for single quote strings? HOT 7
- Allow output of not prettified JSON data
- Compilation error when using nested template blocks with in a single template file HOT 3
- HELP: about askama_axum::Template HOT 2
- Nested comments causes parser to timeout HOT 2
- Publish all crate versions books HOT 7
- if let chain has a weird behaviour HOT 2
- `include` is not very efficient HOT 3
- 0.13.0 release planning HOT 2
- Add tests for generated code HOT 2
- When using askama_axum, the automatic import of IntoResponse will preferentially import from askama_axum instead of axum::response. HOT 4
- Deny all / more reserved keywords as macro names
- Using block fragments as part of larger template HOT 4
- Unnecessary fields required when using blocks for partial rendering HOT 3
- Add `elif` keyword HOT 2
- Axum documentation states `askama_axum` import is required, but it's not? HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. ๐๐๐
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google โค๏ธ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from askama.