-
Notifications
You must be signed in to change notification settings - Fork 178
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
Neo date formatter: Options<R> vs R::Options and same for .format #5269
Comments
Discussed with Shane yesterday. In general I'm in favor of having the intermediate type with the user calling Both of these add some indirection, but they're more easily documented over a pile of traits. IMO functions like Between having an intermediate type or trait, I find that having a type tends to be less messy, and probably better for monomorphization. |
In addition to hour cycle and era display, I also need an option for fractional second digits, unless that gets modeled as part of the field set. |
Would like comments from @robertbastian and @zbraniecki |
I'm in favour of |
Discussion on constructors, also with implications on NeoOptions: use icu::datetime::fieldsets::YMD;
use icu::datetime::DateComponents;
icu::datetime::Formatter::<YMD>::try_new(locale, options)
icu::datetime::Formatter::try_new_with_components(locale, DateComponents::YearMonth, options)
// Zibi prefers:
icu::datetime::Formatter::try_new(locale, YMD, options)
// Shane: It can also be written as:
icu::datetime::Formatter::<YMD>::try_new(locale, YMD, options)
// Robert: This would give gnarly errors
icu::datetime::Formatter::<YMDT>::try_new(locale, YMD, options)
// Robert: This actually works if the type can be inferred, i.e. it's declared in a field or argument
let formatter: Formatter<YMD> =
icu::datetime::Formatter::try_new(locale, option)
// Shane: can we keep the old signature around?
icu::datetime::Formatter::<YMD>::try_new_bikeshed(locale, options)
// Shane: If we go with NeoOptions<R>, we could make it so that you write something like
icu::datetime::Formatter::try_new(locale, YMD.with_length(Length::Long) /* NeoOptions<YMD> */)
Constructor signature proposal:
LGTM: @Manishearth @sffc New proposal:
LGTM: @Manishearth @sffc Need input from @robertbastian @zbraniecki |
The main thing that we discussed in the meeting after people left was the "else" block there, which proposes a way to decouple the ctor discussion from the options discussion, however it constrains the options discussion by giving DateOptions (etc) a generic in the "we have five options types" model |
I want to explore the field set being the options type. We would get call sites like this: icu::datetime::Formatter::try_new(locale, YMD::with_length(Length::Medium)) The function signature is impl Formatter<FSet> {
pub fn try_new(Preferences, FSet)
} And the field set would be defined like this (most likely expanded from a macro) #[non_exhaustive]
pub struct YMD {
pub length: Length,
pub era_display: EraDisplay,
} For runtime components, it would be #[non_exhaustive]
pub struct DateSkeleton {
pub field_set: DateFieldSet,
pub length: Length,
pub era_display: EraDisplay,
} Assuming it works, I can agree to this. |
This would produce good documentation, so I'm onboard. |
We haven't reached consensus on the input types. Currently, we have: impl<C: CldrCalendar, R: DateTimeMarkers> TypedNeoFormatter<C, R>
where
R::D: DateInputMarkers,
R::T: TimeMarkers,
R::Z: ZoneMarkers,
{
pub fn format<I>(&self, input: &I) -> FormattedNeoDateTime
where
I: ?Sized + IsInCalendar<C> + AllInputMarkers<R>,
{
let input = ExtractedInput::extract_from_neo_input::<R::D, R::T, R::Z, I>(input);
FormattedNeoDateTime {
pattern: self.selection.select(&input),
input,
names: self.names.as_borrowed(),
}
}
}
pub trait AllInputMarkers<R: DateTimeMarkers>:
NeoGetField<<R::D as DateInputMarkers>::YearInput>
+ NeoGetField<<R::D as DateInputMarkers>::MonthInput>
+ NeoGetField<<R::D as DateInputMarkers>::DayOfMonthInput>
+ NeoGetField<<R::D as DateInputMarkers>::DayOfWeekInput>
+ NeoGetField<<R::D as DateInputMarkers>::DayOfYearInput>
+ NeoGetField<<R::D as DateInputMarkers>::AnyCalendarKindInput>
+ NeoGetField<<R::T as TimeMarkers>::HourInput>
+ NeoGetField<<R::T as TimeMarkers>::MinuteInput>
+ NeoGetField<<R::T as TimeMarkers>::SecondInput>
+ NeoGetField<<R::T as TimeMarkers>::NanoSecondInput>
+ NeoGetField<<R::Z as ZoneMarkers>::TimeZoneOffsetInput>
+ NeoGetField<<R::Z as ZoneMarkers>::TimeZoneIdInput>
+ NeoGetField<<R::Z as ZoneMarkers>::TimeZoneMetazoneInput>
+ NeoGetField<<R::Z as ZoneMarkers>::TimeZoneVariantInput>
where
R::D: DateInputMarkers,
R::T: TimeMarkers,
R::Z: ZoneMarkers,
{
} Problems with this design:
I would instead like to explore the following design: pub struct NeoInput<R: DateTimeMarkers>
where
R::D: DateInputMarkers,
R::T: TimeMarkers,
R::Z: ZoneMarkers,
{
pub year: <R::D as DateInputMarkers>::YearInput,
pub month: <R::D as DateInputMarkers>::MonthInput,
// ...
} This is reminiscent of Pros and cons:
I put "fields are eagerly evaluated" as a pro and con. An advantage is that it has a more clean separation of responsibilities: field resolution happens above the ICU4X formatter membrane. A disadvantage is that it means we can't in the future refactor the code to lazy-evaluate the inputs. I want to emphasize that the actual fields being read are determined by the field set
I guess it is possible that we could have lazy extraction in |
I discussed this a bit with @robertbastian and @Manishearth today. I didn't take notes, but here were some of my takeaways: Creating I noted above that third-party datetime types can't really in practice implement #[non_exhaustive]
pub struct YearInfo {
pub extended_year: i32,
pub kind: YearKind,
}
#[non_exhaustive]
pub enum YearKind {
Era(EraYear),
Cyclic(CyclicYear),
}
#[non_exhaustive]
pub struct EraYear {
pub formatting_era: Era,
pub standard_era: Era,
pub era_year: i32,
}
#[non_exhaustive]
pub struct CyclicYear {
pub year: NonZero<u8>,
pub related_iso: i32,
} There are similar cross-field invariants between Time Zone and Metazone, between MonthInfo and DayOfMonth, between a future WeekOfYear and DayOfYear, etc. A convenience function could be added to construct a One possible resolution, which I resisted for a long time but am warming up to, is to simply accept the concrete
In both cases, we lose the ability to format, for example, a MonthDay. Thoughts? @Manishearth @zbraniecki |
I'd rather still retain the ability to integrate with other crates, and the ability for people to make their own MonthDay types.
This is going to still be a trait mess since we only have one formatter type so this would need to be |
I think when we first designed this crate I thought the DateTimeInput trait was overkill and wanted to use our types. Since then I think I've moved on to liking it and thinking it helps keep the API small. ICU4X |
|
If we're restricting inputs to a fixed set, we might as well use different methods. That way we can document each one properly, and the client sees which concrete type they have to construct from the signature. I don't see |
List of formattable types:
More discussion:
Options:
Discussion on the specific options:
Decision: Option 2 LGTM: @robertbastian @sffc @Manishearth |
New WG discussion: Options:
Discussion:
break
pub struct Formatter<FSet>(FSet);
pub struct YMD;
struct Date;
impl<FSet> Formatter<FSet> {
pub fn format() {
}
}
// Stretch 1
impl Formatter<YMD> {
/// See aliases module for more information
pub fn format_date(x: Date) {}
}
// Stretch 2
type YMDFormatter = Formatter<YMD>; Stretch goals can be done post 2.0 if desired.
Agreed: @sffc @Manishearth @robertbastian |
I split the stretch goal to #5861. The rest of this issue is done. |
Currently in #5248 I implemented
where
R::LengthOption
is set according to the field set. It is usuallyNeoSkeletonLength
but it is a zero-sized type for field sets that don't need the length.I intend to add at least two more options:
R::HourCycleOption
for runtime hour cycle choicesR::EraDisplayOption
for runtime era display choicesAnother approach would be to instead create separate options bags for each combinations of possible options. For example:
and then
R::Options
would choose the correct struct.Pros and cons:
NeoOptions<R>
makes it possible/easier to add more options in the future in a non-breaking wayNeoOptions<R>
is arguably more consistent with the neo formatter design which has aimed at lots of markers/traits but very few concrete typesR::Options
is probably more clear in documentation because you are constructing a real struct with specific fields, rather than a weird struct whose fields depend onR
I also wanted to raise the possibility of applying a similar treatment to the
format<I>(&self, input: &I)
function. Currently we have a trait that looks like this:which is blanked-implemented on all T satisfying its bounds. We could instead have
Pros and cons:
pub trait AllInputMarkers<R>
allows users to pass foreign types directly into the format function so long as they implement the traitpub struct Input<R>
makes the format function less generic (instead of being generic in bothR
andI
, now it is generic only inR
), probably resulting in smaller code sizepub struct Input<R>
allows clients to more easily drive the formatter even without usingicu_calendar::Date
or one of the other official date typesThoughts?
@Manishearth @robertbastian @zbraniecki
The text was updated successfully, but these errors were encountered: