dart-lang / language

Design of the Dart language
Other
2.65k stars 202 forks source link

Add Tuples to the Dart language #3994

Closed Ing-Brayan-Martinez closed 1 month ago

Ing-Brayan-Martinez commented 1 month ago

Introduction

Greetings, today I have come with a proposal that would be worth sharing, I have had to do a lot of research to talk about a topic that is relevant to you, I am going to propose it as an achievable goal in the medium term, perhaps in the short term, that depends on you without further ado. preamble let's start

What is a Tuple?

A tuple is a data structure that functions as a list of objects of different types, which can be accessed through an index. These objects are not connected to each other but have meaning when we consider them collectively.

What is not a tuple?

It is necessary to explain that it is not a Tuple to avoid confusion with existing characteristics in the language, let's see the following:

How to represent a Tuple

Now that the concepts have been defined, let's now move on to the syntax. My proposal defines the tuples as follows:

void main() {

 Tuple<int> t1a = [1];
 Tuple<int> t1b = Tuple.of(1);

 Tuple2<int, int> t2a = [1,2];
 Tuple2<int, int> t2b = Tuple.of(1,2);

 Tuple3<int, int, int> t3a = [1,2,3];
 Tuple3<int, int, int> t3b = Tuple.of(1,2,3);

 Tuple4<int, int, int, int> t4a = [...];
 Tuple4<int, int, int, int> t4b = Tuple.of(...);

 Tuple5<int, int, int, int, int> t5a = [...];
 Tuple5<int, int, int, int, int> t5b = Tuple.of(...);

 Tuple6<int, int, int, int, int, int> t6a = [...];
 Tuple6<int, int, int, int, int, int> t6b = Tuple.of(...);

 Tuple7<int, int, int, int, int, int, int> t7a = [...];
 Tuple7<int, int, int, int, int, int, int> t7b = Tuple.of(...);

 Tuple8<int, int, int, int, int, int, int, int> t8a = [...];
 Tuple8<int, int, int, int, int, int, int, int> t8b = Tuple.of(...);

 Tuple9<int, int, int, int, int, int, int, int, int> t9a = [...];
 Tuple9<int, int, int, int, int, int, int, int, int> t9b = Tuple.of(...);

 Tuple10<int, int, int, int, int, int, int, int, int, int> t10a = [...];
 Tuple10<int, int, int, int, int, int, int, int, int, int> t10b = Tuple.of(...);

}

As you can see, tuples have many variants depending on the number of types they are going to handle, this is ideal for a very precise object-oriented representation with a syntax that is easy to understand and use, this syntax is an idea borrowed from the Java ecosystem that is easily adaptable to the syntax of the Dart language, we must remember that this language is the combination of Java and JavaScript that is why this syntax exists List<int> list = [1,2,3,4]; that combines the syntax of Java and JavaScript in a single expression

Regarding the scalability of this design based on variants you will not have any problem, because if you need a Tuple with more than 10 data types, it is a clear sign that you already have a bad design for the algorithm you are trying to build and you should use another way of representing your data with a responsible object-oriented design

How to use a Tuple?

The use of tuples will depend on the design of your algorithm, however I am going to make a basic example to give a perspective.

Example 1, create a tuple and use it with Stream

class Person {
 String name;
 int age;
 Person(this.name,this.age);
}

void main() {
 // Declare tuple representing people
 Tuple2<int, String> tuple1 = ['ana',16,'yoel',30,'juan',26,'tifany',21,'Marie',43];

 // Separate data based on its data type
 List<String> names = tuple1.ofType<String>();

 List<int> ages = tuple1.ofType<int>();

 // Create people
 List<Person> people = Stream.zip([names, ages])
   .map(record => Person(record.$1, record.$2))
   .toList();

 // Recreate the tuple
 Tuple2<int, String> tuple2 = Stream.zip([names, ages])
   .toTuple2<int, String>();
}

Example 2, convert a map into a tuple and display on the screen

void main() {
 // Declare Map representing people
 Map<String, int> map = {
  'ana': 16,
  'yoel': 30,
  'juan': 26,
  'tifany': 21,
  'Marie': 43};

 // Convert to a tuple
 Tuple2<int, String> tuple = map.toTuple2<int, String>();

  // Iterate and display on screen, the result will be random
  for (dynamic value in tuple) {
    print(value);
  }
}

Example 3: Convert a tuple to a list

void main() {
 // Declare tuple representing people
 Tuple2<int, String> tuple = ['ana',16,'yoel',30,'juan',26,'tifany',21,'Marie',43];

 // Convert to a list
 List<dynamic> list = tuple.toList();

  // Iterate and display on screen, the result will be random
  for (dynamic value in list) {
    print(value);
  }
}

Example 4: Using tuples with pattern matching

bool isMyTuple(dynamic tuple) {
  return switch (tuple) {
     Tuple2<String, bool> => True,
     Tuple2<String, int> => True,
     Tuple2<String, double> => True,
     _ => False
  }
}

void main() {
 // Declare tuple
 Tuple2<int, String> tuple = ['ana',16,'yoel',30,'juan',26,'tifany',21,'Marie',43];

 // Evaluate tuple
 bool result = isMyTuple(tuple);

  // Show message on screen
  if (result) {
    print('yess.....');
  }
}

As you can see the concept of Tuple can be compatible with other features of the language that already works very well, I am just trying to give a broad perspective so that you can evaluate the scope of this proposal and the possible incompatibilities.

What problem am I trying to solve?

Answering this question is complicated due to the context of the proposal. The answer could be ambiguous because Tuples are intended to be one more tool than the Dart language already has to create algorithms that can solve problems.

Tuples are used to group data of different types in a context that is somehow disordered and then we intend to group and order in more complex types of data, the use of tuples will be conditioned to the context of where they are used, the same thing happens for example to lists or any data structure

If I'm honest, I don't know how to answer this question clearly. I only know that tuples are a useful tool that is used to create algorithms.

Conclusions

This proposal allows a Tuple design that can be differentiated between List, Map, Set and Record to be a data structure that adds value, the objective is to have tools that enhance the development experience with more and better tools to build algorithms

I have been doing a lot of research, to make a good proposal I politely ask you to consider it, at this moment I don't know if you have other priorities, consider it in the future

If you have any questions or suggestions, please comment. I will be attentive to give you an answer.

I ask you to use a list of questions format in case of any doubt to carry out an effective translation.

mateusfccp commented 1 month ago

A Dart record is a tuple.

I'm failing to understand why you think it's not.

munificent commented 1 month ago

Dart already has tuples (we just call them "records" because they also allow named fields).

Wdestroier commented 1 month ago

Programming languages without tuples have classes such as Tuple2 and Tuple3 or Pair and Triple to simulate tuples or unnamed compound data structures. Dart properly supports tuples, such that these (annoying to write and sometimes costly at runtime) wrapper classes are unnecessary.

Ing-Brayan-Martinez commented 1 month ago

Thanks for the clarification, this is an attempt to bring something new, have a great day

munificent commented 1 month ago

this is an attempt to bring something new

I appreciate the attempt but I also want to point out that every time you file an issue, multiple members of the Dart team spend time reading, understanding, and responding to that issue. That's time we're not spending designing language features, talking to users, responding to other issues, etc.

If you want Dart to bring new features to users, one of the most important things you personally can do is make sure you are using your and our time well. I am sure it took you a lot of time to file this 1,000 word issue and it definitely took me time to read it. That time would have been better spent doing research first and discovering that we already have tuples and it was one of the highest-profile features added in Dart 3.0.

Ing-Brayan-Martinez commented 1 month ago

I just wanted to apologize for my ignorance of some topics, I am a person who is constantly training and studying, something I have learned in life is to learn to fail, I learned this with the book The Lean Startup the phrase that changed my life is:

If you're going to fail, do it fast and cheap

My mistake was ignoring that a Tuple is the same concept as a Record, I thought they were different concepts.

I want to know if there is interest in this proposal to open a new issue where I will rename it as the multiple list concept, which proposes a list that can have multiple data, this is a concept that does not exist.

class Person {
 String name;
 int age;
 Person(this.name,this.age);
}

void main() {
 // Declare list representing people
 ListMultiple<int, String> list1 = ['ana',16,'yoel',30,'juan',26,'tifany',21,'Marie',43];

 // Separate data based on its data type
 List<String> names = list1.ofType<String>();

 List<int> ages = list1.ofType<int>();

 // Create people
 List<Person> people = Stream.zip([names, ages])
   .map(record => Person(record.$1, record.$2))
   .toList();

 // Recreate the tuple
 ListMultiple<int, String> list2 = Stream.zip([names, ages])
   .toListMultiple2<int, String>();
}

The multi-list is the same as a conventional list, the difference is that it can hold more than one type of data

If the Dart language team is not interested in this, please close this issue permanently to avoid inappropriate comments from other people.

mateusfccp commented 1 month ago

@Ing-Brayan-Martinez Your usecase would be better solved by the general untagged union feature.

See #83.

For instance, you would be able to do (strawman syntax):

final class Person {
  final String name;
  final int age;

  const Person(this.name,this.age);
}

void main() {
  // Declare list representing people
  final list1 = <int | String>['ana', 16, 'yoel', 30, 'juan', 26, 'tifany', 21, 'Marie', 43];

  // Separate data based on its data type
  final names = [...list1.whereType<String>()];
  final ages = [...list1.whereType<int>()];

  // For the logic below to work, we assume that we have the same number of names and ages
  assert(names.length == ages.length);

  // Create people
  final people = [
    for (int i = 0; i < names.length; i++)
      Person(names[i], ages[i]),
  ];

  // Recreate the list
  final list2 = [
    for (final person in people)
      ...[person.name, person.age],
  ];
}

Currently, you can already do it with Object, but you may want to do more checks and assertions at runtime, which is not as good as having static assertions:

final class Person {
  final String name;
  final int age;

  const Person(this.name,this.age);
}

void main() {
  // Declare list representing people
  final list1 = ['ana', 16, 'yoel', 30, 'juan', 26, 'tifany', 21, 'Marie', 43]; // Inferred as List<Object>

  assert(list1.every((item) => item is String || item is int));

  // Separate data based on its data type
  final names = [...list1.whereType<String>()];
  final ages = [...list1.whereType<int>()];

  // For the logic below to work, we assume that we have the same number of names and ages
  assert(names.length == ages.length);

  // Create people
  final people = [
    for (int i = 0; i < names.length; i++)
      Person(names[i], ages[i]),
  ];

  // Recreate the list, inferred as List<Object>
  final list2 = [
    for (final person in people)
      ...[person.name, person.age],
  ];
}