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

Optimize the DateTime type for functional programming #56021

Closed
Ing-Brayan-Martinez opened this issue Jun 16, 2024 · 7 comments
Closed

Optimize the DateTime type for functional programming #56021

Ing-Brayan-Martinez opened this issue Jun 16, 2024 · 7 comments
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug

Comments

@Ing-Brayan-Martinez
Copy link

Greetings, today I want to talk to you about an update of the DateTime data type to make it compatible with the functional programming paradigm, to have a reference point I have decided to be inspired by the LocalDateTime type of the JDK, because this class is already designed to work with this paradigm

The main reason why this change is necessary is because we must make the DateTime type use the singlenton pattern so that if a date is defined it is stored within the type and can only be modified through the various operators, this is important to use dates in math calculations

We all know that calculating dates is very complicated, that is why we must simplify the way of operating dates mathematically, functional programming helps with this

We should stop using the DateTime(int year, [int month = 1, int day = 1, int hour = 0, int minute = 0, int second = 0, int millisecond = 0, int microsecond = 0]) constructor To use the of() operator to be faithful to the functional programming paradigm, this would look like this

void main() {
  DateTime date = DateTime.of(2024, 06, 12);
  print(date.toString());
 
 DateTime date2 = DateTime.now();
 print(date2.toString());
}
@lrhn lrhn transferred this issue from dart-lang/language Jun 16, 2024
@lrhn lrhn added area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug labels Jun 16, 2024
@lrhn
Copy link
Member

lrhn commented Jun 16, 2024

Can you explain which concrete changes you're asking for, how they will differ from what is there today, and which use-cases would be improved by that?

Renaming the DateTime(...) constructor to DateTime.of(...) doesn't seem like a change.

@lrhn lrhn added the needs-info We need additional information from the issue author (auto-closed after 14 days if no response) label Jun 16, 2024
@Ing-Brayan-Martinez
Copy link
Author

Ing-Brayan-Martinez commented Jun 16, 2024

Regarding this, the idea is to implement the concept of Funtor and Monad to be able to operate with dates mathematically.

Funtor

To represent a functor we need the operator of(int year, [int month = 1, int day = 1, int hour = 0, int minute = 0, int second = 0, int millisecond = 0, int microsecond = 0]) which has the purpose of initializing a Monad by taking a value of type int and wrapping it to be able to operate on it later

Other possible functors are: now(), parse(), ofEpochSecond()

Monad

To represent a Monad it is necessary to have an object that uses the singlenton pattern that I have a simple wrapped type, and through operators that are actually functions that transform the wrapped value

Some operators that could be implemented are: addHours(), addDay(), with(), among many more.

If the DateTime type incorporates all these new features, it will be usable within the functional programming paradigm.

@julemand101
Copy link
Contributor

The main reason why this change is necessary is because we must make the DateTime type use the singlenton pattern so that if a date is defined it is stored within the type and can only be modified through the various operators, this is important to use dates in math calculations

Which part of DateTime are not immutable today?

@github-actions github-actions bot removed the needs-info We need additional information from the issue author (auto-closed after 14 days if no response) label Jun 17, 2024
@simolus3
Copy link
Contributor

simolus3 commented Jun 17, 2024

To represent a functor we need the operator of

We don't, you can also tear off unnamed constructors into functions:

void main() {
  final DateTime Function(int) dateTimeFromYear = DateTime.new;
  print(dateTimeFromYear(2024));
}

@eernstg
Copy link
Member

eernstg commented Jun 17, 2024

I don't think it's going to be very useful to discuss things like monad laws in the context of a language like Dart where mutable state is everywhere. Dart isn't going to mutate (no pun intended, of course ;-) into Haskell with a different syntax. However, I think there's a way to approach these ideas in a way which is a better fit for Dart.

Some operators that could be implemented are: addHours(), addDay(), with(), among many more

@Ing-Brayan-Martinez, how well would it fit your needs if DateTime were expressed using a notion of data classes that included automatic support for a copyWith method?

People do not agree, but in this context I'll consider a 'data class' to be the same thing as a 'value class', that is, a class whose instances are immutable. There would be some additional properties, of course. In particular, data classes would have some automatically generated members (like operator ==, hashCode, and copyWith). The method copyWith would take named arguments, one for each instance variable, and it would return a fresh instance of the data class whose instance variables are as specified using the named arguments, defaulting to the instance variable value of the receiver for those instance variables where no new value was specified.

(Today, data classes are most likely going to be supported by a macro, which means that anybody who needs a slightly different set of auto-generated members can create their own macro that fits the bill.)

I'm guessing that with would be just another name for copyWith. We can't use with as a member name in Dart because that's a reserved word, so I'll stick to copyWith.

With that, addHours, addDay, etc. would just be special cases of copyWith. I'll just use an extension to define them.

extension on DateTime {
  // This would be implicitly induced into the class `DateTime` if we had
  // data classes and `DateTime` were declared to be a data class.
  DateTime copyWith({
    int? year,
    int? month,
    int? day,
    int? hour,
    int? minute,
    int? second,
  }) =>
      DateTime(
        year ?? this.year,
        month ?? this.month,
        day ?? this.day,
        hour ?? this.hour,
        minute ?? this.minute,
        second ?? this.second,
      );

  // Add various requested methods to `DateTime`.
  DateTime addYear(int year) => copyWith(year: year);
  DateTime addMonth(int month) => copyWith(month: month);
  DateTime addDay(int day) => copyWith(day: day);
  DateTime addHour(int hour) => copyWith(hour: hour);
  // ... etc.
}

void main() {
  DateTime date = DateTime(2024, 06, 12);
  date = DateTime(2024).addMonth(6).addDay(12); // Same thing.
  print(date);
}

Is it fair to say that you want support for automatically generating these members?

One consideration about types could be quite interesting: The number of combinations is large if we wish to maintain in the static type of each expression that we have a proto-DateTime instance where no instance variables have been initialized, or one where year has been initialized (and none of the others), etc., for all subsets of {year, month, day, hour, minute, second}. We would need 2^6 different types to keep track of each of those subsets, that is, 64 types.

The point would be that we would be able to specify the instance variable values one by one, in any order, and it would be a compile-time error to forget any of them, and also a compile-time error to specify any of them twice.

In order to avoid having 64 types for this, I've just modeled the situation using the same approach as DateTime uses today: Anything which is not specified will default to a given value (in this case, zero), and every expression in the method invocation chain that builds a given DateTime has the type DateTime. We will not get any compile-time errors if we forget to specify a value for some of the instance variables, and we will not get an error if we specify any of them twice.

Is there a nice way to get those errors?

@Ing-Brayan-Martinez
Copy link
Author

Ing-Brayan-Martinez commented Jun 17, 2024

I don't think it's going to be very useful to discuss things like monad laws in the context of a language like Dart where mutable state is everywhere. Dart isn't going to mutate (no pun intended, of course ;-) into Haskell with a different syntax. However, I think there's a way to approach these ideas in a way which is a better fit for Dart.

@eernstg You are right, I totally agree, I think it is necessary to say that I do not intend to convert Dart into Haskell, I just want to adapt some useful concepts, as they were done in Java 8 in 2014, the idea is to adapt the language to support functional programming of practical, minimalist and easy way to understand by human beings

One consideration about types could be quite interesting: The number of combinations is large if we wish to maintain in the static type of each expression that we have a proto-DateTime instance where no instance variables have been initialized, or one where year has been initialized (and none of the others), etc., for all subsets of {year, month, day, hour, minute, second}. We would need 2^6 different types to keep track of each of those subsets, that is, 64 types.

My main priority is the of() operator to initialize a DateTime so that it is understood within the paradigm, the of() operator could behave the same as the DateTime() constructor this will avoid type variety complications that you mention

My second priority is that the DateTime type inherits from the Monad type that I have proposed to create to group all types that have a behavior similar to a Monad in the same family.

The rest of the operators are your choice if you want to include them, the idea is to add operators like addDay() or substraDay() and if you want to make the decision to change the name of the with() operator to copyWith() to solve a design problem I agree

The external dart_date library aims to add all the necessary operators through extension, so I say that my priority is the of() and ofEpochSecond() operator and the inheritance of the Monad type.

The only operator that we do need to add is map() since it will be needed to use it with the dart:time api

@mraleph
Copy link
Member

mraleph commented Jun 17, 2024

As explained above there is no reason to introduce DateTime.of.

Also DateTime is by no definition a monad.

@mraleph mraleph closed this as completed Jun 17, 2024
@mraleph mraleph closed this as not planned Won't fix, can't repro, duplicate, stale Jun 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

6 participants