-
Notifications
You must be signed in to change notification settings - Fork 103
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
Sealed hierarchy for arbitrary precision dates and better static typing #177
Comments
First, some personal communication. @wkornewald, you've been very active with this project, and our conversations yielded some very insightful notions. Thank you a lot for this! I feel though that we need to establish some expectations about user contributions. You've been mostly trying to propose some complete technical decisions, and then I would go on to argue to get the rationale behind them. It must surely seem from your side that I'm thick-headed and unable to see the beauty of your proposals, but the reality is more subtle. In our internal decision-making, we consider a broad range of things, such as precedents for naming things, discoverability, the API shapes in the Kotlin ecosystem as a whole, ease of internalizing a whole library, the interconnection between different concepts, etc. Sometimes, one of us proposes a solution for some problem, but after long debates, we stumble upon a more elegant and clean solution for the very same things. So, the problems are the root of our decision-making. A complete, fleshed-out implementation is often much less useful to us than a thorough description of the real problems that it solves. You have a lot of domain knowledge that includes datetime things, you understand how to solve the problems you've faced, but these solutions are not useful to us on their own, as we can't just incorporate them without first searching for a simpler way, using our domain knowledge. Maybe it turns out the problem can be solved with the existing abstractions. Maybe the problem is just theoretical and a solution for it won't be useful to anyone ever. Maybe we recognize the problem, and in the end, will converge to something very similar to what you're proposing. Maybe we'll implement something entirely unlike it. No way to know beforehand. Therefore, when we argue about what may feel like minutia even though the abstraction is obviously logical and beneficial, it doesn't necessarily mean we disagree with it, it's just that we are capable of providing the abstractions ourselves, but we need to know to what end. Now, for technical things.
Three of the four things listed are for parsing and formatting, which does not necessarily require a separate class for each combination. Look at Python, or Swift: they're doing just fine, despite each having only one object to represent datetimes. The fourth thing is sorting "at least how humans would sort them", which, I think, is not a viable goal. Humans are very adaptable, computers are not. As a human, if I was tasked to find the earliest date in the list "June 1st, June 17th, June, June 2nd, June 28th", I would point to "June 1st", not "June", as there's obviously no date earlier than that. If I was tasked to find the latest date, I would point to "June 28th", assuming that it's unlikely that the date that's just "June" is later than that. If I was tasked with sorting these things, I would sort them by assuming that "June" means a non-existent "June 0th". So, no, I am wary about providing any one way of sorting these, as such sorting could be misapplied. Also, what are the use cases for sorting here? Which problem does it solve? Some specific examples, please, like "we use this mixed-data representation for X, which we have to output as Y, where the order is obviously Z".
This seems logical, but we're not going after beautiful abstractions at all costs, as overloading a programmer with too many entities is confusing when all they want to do is solve some common task. What task requires such casing? Does anyone do anything nontrivial with such data, that is, something besides parsing and showing? I saw in the PR you implying code like "the vaccination date is stored; if only the year Y is known and it's the first three months of the year Y+1, it's fine not to revaccinate; if the month is also known, then if half a year has not passed since these year + month, one also doesn't need to revaccinate; otherwise, suggest revaccination". Fair! Any other suggestions?
If we introduced
This is true. Do you have any particular use cases in mind? |
Thank you for the explanation. Maybe our discussion would be much easier if we did it on the phone. 😄
Well, Python's API is pretty old and it has multiple objects, too:
If you look at more modern APIs like the [Temporal](https://tc39.es/proposal-temporal/docs/] proposal, you also find Even if those objects are simple, it makes a lot of sense to provide a common type that can be used across different libraries without everyone reinventing his own abstraction and then translating between different objects. If someone builds a calendar picker and wants to allow varying precision levels then it's surely nicer if the communtiy can build on something official that is then compatible with other community libraries out of the box (see how Rust has handled async related APIs).
We have a list of medical data with varying date precisions. The user wants to see all of them as a timeline, sorted by date. So you have to somehow handle dates like "June 2016" and "2016". A pretty sensible default behavior (that can still be overridden for special cases) is to sort the objects like this:
Though, I can see if you find this too controversial.
Even showing the data can be non-trivial in that (real-world example) we have to treat ZonedDateTime specially because the UI requires showing date and time in separate widgets and/or different styles. Depending on the type you might even want to show "1 hour ago" or "sometime in 2016". Being able to use
I don't understand your Pair example. What is T there and how does that make it possible to define a Or how about a date picker where the doctor can enter different date precisions or modify an existing date? Also, Pair and other generic types have a big problem with readability and type erasure. How can I even safely do an |
BTW, this really is just a technical improvement suggestion to build more APIs around sealed (it often results in better APIs). It’s by far not my most important issue though. I find it much more important to have a proper ZonedDateTime like in my draft PR. Maybe secondary would be YearMonth and Year and MonthDay just to have good standard types that others can build upon. |
Originally suggested in PR #173 which contained both the sealed hierarchy and YearMonth. The YearMonth feature request is in #168. The implementation of YearMonth (and Year) is related to this proposal, but maybe for the discussion here we should focus on the sealed hierarchy aspect.
This is just a very quick example of how we could add support for an arbitrary precision date that can be a Year or YearMonth or LocalDate or ZonedDateTime (which could itself be a sealed class of OffsetDateTime and RegionDateTime - see #175).
At least in the medical space it's pretty common to have flexible date precisions in the official specifications, so you have to support at least these operations
You might also want to treat the data differently based on its type. Here it helps a lot to have a sealed interface, so you can easily match all possible cases (which is also useful for implementing the formatting and sorting etc. functions).
Using a sealed hierarchy would allow writing code in a more statically safe way because you can enforce with types that at least a certain precision level is provided. You can also exhaustively match on all subtypes, which makes working with arbitrary precision dates much more comfortable.
The latest example code is here: https://github.com/Kotlin/kotlinx-datetime/compare/master...wkornewald:feature/yearmonth-and-arbitraryprecisiondate?expand=1
To start the discussion, here's a copy of the code, which is a strict precision hierarchy ranging from Year to ZonedDateTime and it only contains the types that humans usually work with:
Of course one could also implement this outside of kotlinx.datetime, but having it here in this lib makes more sense because it works out of the box without any indirections, wrapping or custom types and it's helpful for everyone who must tackle this kind of problem.
The text was updated successfully, but these errors were encountered: