dart-lang / language

Design of the Dart language

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add the concept of Functional Interface to adopt the concept of Higher Order Function

Ing-Brayan-Martinez opened this issue · comments

Greetings, today I bring you a proposal to bring functional characteristics to the syntax of the Dart language, for this we must explain some things to get into context.

What is a functional interface?

A functional interface is a concept invented for Java 8 that allowed the introduction of Lambda expressions to the language, something fundamental to adopt functional programming.

The concept of a functional interface is that any interface that has a single abstract method can be considered a functional interface that allows it to be declared as a higher order type similar to the typedef to which a lambda function can be assigned. I will show you an example in code.

The jdk provides several functional interfaces in our case we will only talk about Comsumer

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

Use the functional interface

import java.util.ArrayList;
import java.util.function.Consumer;

public class Main {
  public static void main(String[] args) {
    ArrayList<Integer> numbers = new ArrayList<Integer>();
    numbers.add(5);
    numbers.add(9);
    numbers.add(8);
    numbers.add(1);
    Consumer<Integer> method = (n) -> { System.out.println(n); };
    numbers.forEach( method );
  }
}

What is a Higher Order Function?

It is the ability of a programming language that supports functional programming to declare functions as variables, they can also be passed as a parameter to another function or that a function returns another function. You can research more about this on the internet.

What problem do we want to solve?

The introduction of functional interfaces will allow the Dart language to introduce higher order functions in the Dart language, as an alternative to the typedef, something fundamental for functional programming.

Another effect that functional interfaces bring is to create a balance between paradigms since it will allow cohesion between object-oriented programming and functional programming in a consistent manner, which is the ideal balance point.

The concept of functional interface is a non-intrusive concept because it was designed for Java, a class-based, inflexible, object-oriented language. This allows it to be consistent and usable for the Dart language that shares some characteristics, for example in Flutter and any designed project. with an object-oriented design it can be used.

What is a functional interface in Dart?

Currently Dart allows an approach to the creation of higher order functions thanks to the reserved word typedef that allows creating a higher order type that can be used if there are problems, let's see some examples.

This is a real example of a StreamBuilder in Flutter

/// Signature for strategies that build widgets based on asynchronous
/// interaction.
///
/// See also:
///
///  * [StreamBuilder], which delegates to an [AsyncWidgetBuilder] to build
///    itself based on a snapshot from interacting with a [Stream].
///  * [FutureBuilder], which delegates to an [AsyncWidgetBuilder] to build
///    itself based on a snapshot from interacting with a [Future].
typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnapshot<T> snapshot);

To define a higher order function like Consumer we must do it this way

typedef Consumer<T> = void Function(T value);
typedef Calculate<T> = void Function(List<T> list, Consumer<T> value);

Calculate<int> fn = (List<int> list, Consumer<int> fn) {
   for (int result in list) {
     fn(result);
   }
};

final fn2 = (List<int> list, Consumer<int> fn) {
  for (int result in list) {
    fn(result);
  }
};

void main() {
  final List<int> list = [1,2,3,4,5,6];

  fn(list, (n) => print(n));
  fn2(list, (n) => print(n));
}

To define a Consumer with the alternative syntax based on functional interfaces

@functionalInterface
abstract interface class Consumer<T> {
  void apply(T value);
}

@functionalInterface
abstract interface class Calculate<T> {
  void apply(List<T> list, Consumer<T> value);
}

Calculate<int> fn = (List<int> list, Consumer<int> fn) {
  for (int result in list) {
    fn(result);
  }
};

final fn2 = (List<int> list, Consumer<int> fn) {
  for (int result in list) {
    fn(result);
  }
};

void main() {
  final List<int> list = [1,2,3,4,5,6];

  fn(list, (n) => print(n));
  fn2(list, (n) => print(n));
}

The @functionalInterface annotation is necessary so that the compiler can more easily identify the functional interfaces. Something very similar happens with the @override annotation for method overloading in class inheritance. I think I don't need to explain further. The idea is simple.

About innovation in the syntax of the Dart language

Personally, I think that the syntax of the language is at a fairly high point of maturity and finding room to improve and optimize has become a real challenge.

I think that this proposal could be rejected because typedef already exists and I think that it is good, the point of the proposal is to have alternative ways to reach the same result that allow us to enrich the experience of using the language

I think they should return to the idea of ​​Optional since it is one of those features that are really innovative. We will see if functional interfaces are accepted as an alternative to typedef

I think that this proposal could be rejected because typedef already exists and I think that it is good, the point of the proposal is to have alternative ways to reach the same result that allow us to enrich the experience of using the language

Not that I'm qualified to make a decision here, but wouldn't we want to have as few ways as possible to express the exact same idea? If functional interfaces introduced new features over typedef, that could warrant a new syntax, but why spend time implementing and documenting another way that is longer and less direct than typedef? My concern is that people writing and reading code day-to-day will be confused over which to choose, or what are the differences between them.

@Levi-Lesches Greetings friend, the point is that typedef are not the same as a functional interface, you have to understand some fundamentals

If we review the reason why the typedef and extension were created, it was to give programmers a way to introduce features to the language in a dynamic way to satisfy the requirements of a project. Both features are a kind of wildcard to make the syntax more flexible for project development, and it is perfect

The reason why Functional Interfaces are proposed is to have an explicit syntax that manages to satisfy the creation of higher order functions in a manner consistent with the object-oriented paradigms and the functional programming paradigm, creating a balance point so as not to resort to the wildcard of typedef so that the language has a design that manages to satisfy all needs and all paradigms

This is how in Typescript programmers use the Any type to solve problems. The ideal is to create the type that satisfies the use case so as not to resort to the Any type. This is a small comparison.

We need to implement solutions like these to improve performance, also so that projects developed in Dart have greater robust consistency and can age in a good way like Java or C# projects that are more than 15 years old, and that continue to work to date, This also allows the SOLID principles to be permanently implemented. Thanks to this we can implement abstractions through interfaces that are durable over time.

I think the oversimplification of syntax does not generate anything positive. I think it generates ambiguities and poor maintainability.

I think that if this is approved there will be no need to choose between one or the other because functional interfaces could be the recommended practice

You're asking for a Java feature which was created to mimic a feature Dart properly supports.

will allow the Dart language to introduce higher order functions in the Dart language

Dart already have higher order functions as first-class values, so there is no introduction.

Currently Dart allows an approach to the creation of higher order functions thanks to the reserved word typedef that allows creating a higher order type that can be used if there are problems

I don't think typedef has any relevance. Dart has first-class higher order function types as well.
You can also write, without any typedef:

void Function(List<int>, void Function(int)) fn = (list, fn) {
  // ...
}
void fn2(List<int> list, void Function(int) fn) {
  // ...
}

As I read this, the only value proposition is to allow function values to be assignable to interface types with a single abstract method, if that methd has a compatible function type. The benefit is that the interface type could have other methods too.

That is, a functional interface like:

functional interface class Foo {   
   void log(String text);
   void logNamed(String name String text) { log("$name: $text"); }
}

would be assignable from a function type like void Function(String). Possibly assignable to it too.

It would be like the class had an implicit constructor, and field and implementation of the abstract function like:

abstract interface class Foo {
   final void Function(String) _$log;
   /*implicit*/ _Foo$fromFunction(this._$log);
   void log(String text) => _$log(text);

   void logNamed(String name String text) { log("$name: $text"); }
}

that got used to wrap a function when it was assigned to the interface type.
(Which would be implementable directly in the language if it had implicit coercing constructors, #108).

Maybe the interface can be assigned to a function type too, implicitly tearing off the log method, or extracting the _$log.

Not seeing a big value-add here.

Today you could also use an extension type:

extension type Foo(void Function(String) _log) {
   void log(String name) { _log(name); }
   void logNamed(String name String text) { log("$name: $text"); }
}

which would allow easy wrapping, Foo(fn), and unwrapping, foo._log or foo as void Function(String).

I'm not seeing any actual problem that exists today being solved here.

It's not introducing higher order functions. Dart has had those since day 1.

Another effect that functional interfaces bring is to create a balance between paradigms since it will allow cohesion between object-oriented programming and functional programming in a consistent manner, which is the ideal balance point.

That's ... words.

Would this feature be solving any concrete problem that you can describe?

And how will it be better than what Dart has today, or could have with features that have a similar implementation cost to this one?

the point is that typedef are not the same as a functional interface

This is a fine way to start a comment, but then you didn't bring up any actual differences between the two!

If we review the reason why the typedef and extension were created,

I don't know, I don't think comparing the "reasons" why a feature was created to be a very useful exercise compared to looking at the feature as it exists today.

it was to give programmers a way to introduce features to the language in a dynamic way to satisfy the requirements of a project. Both features are a kind of wildcard to make the syntax more flexible for project development, and it is perfect

I'm not sure what you mean when you say "dynamic" here -- dynamic already has a meaning in Dart, usually associated with runtime behavior. Neither typedefs nor extensions exist at runtime:

typedef ListOfNumbers = List<num>;
void main() {
  final ListOfNumbers nums = [1, 2, 3];
  print(nums.runtimeType);  // List<num>
}

And they're not wildcards either -- you have to explicitly declare exactly what your types or extension methods are before using them. They're both very specific features you have to work with at compile-time. I don't see how functional interfaces aren't the same.

The reason why Functional Interfaces are proposed is to have an explicit syntax that manages to satisfy the creation of higher order functions in a manner consistent with the object-oriented paradigms and the functional programming paradigm, creating a balance point so as not to resort to the wildcard of typedef so that the language has a design that manages to satisfy all needs and all paradigms

Again, you haven't really explained why they're different. As lrhn says, you can do everything you're describing today, and typedefs aren't like an Any type. Rather, they give a useful name to a specific type that already exists.

I think in general with the way you're using Any, "wildcard", and "ambiguities", perhaps you may not be thinking of typedefs in the way others do. Can you point to a specific problem that can't, or shouldn't, be written in Dart without functional interfaces today and why? Your Calculate example at the top doesn't really illustrate why today's Dart can't solve your problem. All you did was replace one line of typedef with four lines of functional interface.

As I read this, the only value proposition is to allow function values to be assignable to interface types with a single abstract method, if that methd has a compatible function type. The benefit is that the interface type could have other methods too.

@lrhn This is exactly this, it is having the ability to assign a function to an interface of a single abstract method that has exactly the same type signature. This would allow us to standardize some functions that exist in the base SDK with the concepts of Consumer, Predicate BiConsumer, among others. others. This allows you to avoid repeated code because you can reuse the same functional interface that maintains the same type signature, avoiding declaring the same thing with a different name.

In addition, they will have other functions with a body that serve as support to generate composition of functions or any other functionality that is useful for the functional interface.

All this allows us to move from the functional world to the world of objects in a transparent way, that is the balance I am talking about.

Returning to the question of what problem am I solving? I could be solving a problem of:

  • Repeated code declaration of functions with the same type signature but with a different name
  • Have the ability to assign functions to objects (interface of a single abstract method) that comply with the same type signature
  • Have some methods with a body in the functional interface that allows it to be supported, for example, allowing composition of functions
  • Simplify the maintainability of contracts within the Dart language base SDK
  • Standardization of Functions and Callback used in the base SDK of the Dart language

This is all about type signature compatibility between functions and objects, something fundamental to using the functional programming paradigm.

Today you could also use an extension type:

extension type Foo(void Function(String) _log) {
   void log(String name) { _log(name); }
   void logNamed(String name String text) { log("$name: $text"); }
}

which would allow easy wrapping, Foo(fn), and unwrapping, foo._log or foo as void Function(String).

@lrhn If extension types on Function types could implement the representation Function type, then there wouldn't be a need for an extra step like unwrapping or casting. See #3839.

@lrhn @mmcdon20 Greetings on this new day, I think what you are proposing is interesting and I believe that both proposals are necessary and are not mutually exclusive for the following reasons:

For the functional interfaces, these will allow a better object-oriented design of the base SDK of the Dart language and allow an explicit syntax to be able to assign functions to objects that comply with the same type signature as the function, all this will generally help to The entire base SDK is a significant advance for the platform

In the event that the existing object-oriented design in the base SDK with the functional interfaces does not satisfy the needs of a user to create a project, it is in this case where the extension can be used to extend the functional interfaces with some method with a body that the user believes is necessary for his project, both characteristics are necessary

an explicit syntax to be able to assign functions to objects that comply with the same type signature as the function

The direct assignability, and probably the implicit implementation, is the new thing.
The operation is implementable today, simply by having a field with the function type, a member forwarding to that function, and a constructor to set it. It just takes more code and more syntax.

I'm not absolutely sure that syntax is a bad thing.

So, let's assume we introduce the feature, with a syntax reminiscent of extension types.

functional class Foo(this.print) extendsSuper {
  void print(String text);
  // Other members
}

which desugars to (using the macro-related augment feature):

class Foo {
  void Function(String) _$print; 
  Foo(void Function(String) print) : _$print = print;  // Implicit constructor.
  void print(String text);
  // members
  augment void print(String text) => _$print(text);
}

and with implicit assignment between Foo and void Function(String) where:

Foo foo = print;
void Function(String) func = foo;

effectively desugars to:

Foo foo = Foo(print);
void Function(String) func = foo._$print;

Why only one function, though.

If you could similarly define:

functional class Bar(this.log, [this.namedLog]) {
  void log(String text);
  void namedLog(String name, String text) => log("$name: $text");
}

and that effectively desugars to:

class Bar(this.log, [this.namedLog]) {
  final void Function(String) _$log;
  final void Function(String, String)? _$namedLog;
  Bar(void Function(String) log , [void Function(String, String)? _$namedLog])
     : _$log = log, _$namedLog = namedLog;
  void log(String text);
  void namedLog(String name, String text) => log("$name: $text");
  sugment void log(String text) => _$log(text);
  augment void namedLog(String name, String text) => 
      _$namedLog == null ? augmented(name, text) : _$namedLog(name, text);
}

That is, it's a general way to allow abstract signatures to be implemented using functions.

The assignability is less obvious here, though.
At least one of the types is nullable and its argumnet optional, which probably means we should ignore it (it can't be assigned to a function type if it's null), so maybe this class is assignable to and from void Function(String) too.

Something like functional class Baz(this.lessThan, this.greaterThan) { ... } would not be assignable to or from a single function type, but could be assignable to and from a pair of function types.

But again, I'm not sure the assignability is selling itself to me.
If we add a call method to Foo above, with the same behavior as the function, then
then it is assignable to void Function(String), the implicit coercion will tear off the call method.
Then all we need is implicit coercion in the other direction, which could be implicit constructors #108, and macros.

@functional
class Foo {
  void print(String text);
  // members, with no constructors and no `call` method.
}
// Generated by `@functional` macro:
augment class Foo {
  void Function(String) _$print; 
  implicit Foo(void Function(String) print) : _$print = print;  // Implicit constructor.
  augment void print(String text) => _$print(text);
  void call(String text) => _$print(text);
}

That is, all you need for this is macros and implicit constructors, then you can create everything needed, the macros for the implicit code generation, the .call-tear-off for assignability in one direction and implicit constructors for assignability in the other direction.

Can with for an extension type too, since this is all about an object whose entire state is defined by a function (or potentially more functions):

@functional
extension type Qux._(void Function(String) _) {
  void log(String text);
  // members
}

generates:

augment extension type Qux {
  implict Qux(void Function(String text)  log) : this._(log);
  augment void log(String text) => _(text);
  void call(String text) => _(text);
}

The entire concept of a "functional interface" isn't needed here. It's the operational behavior which matters:
A value whose state is defined by a function, and which is assignable to/from function type.

Will have to figure out how generics work, but that's mainly for assignment to the type, in which case it's just a problem that implicit constructors need to handle in general.

(There is no chance that a class type would be a subtype of a function type. It's not impossible that we could allow extension types to "implement" function types and record types, but that will require some twiddling with the upper-bound algorithm first, maybe a generic variance feature too to really make it useful.)

From what I see it is trying to replicate this change through a macro which could simulate the behavior, I have come to the conclusion that dart is not prepared for this because we need other concepts to achieve functional interfaces.

We need the ability to create anonymous classes. This is common in Java. Let's see an example:

public interface Runnable {
    void run();
}

public class Example {

    public static void main(String[] args) {
        String s = "Hello world!";

        // declarate
        Runnable r = new Runnable() {
            //Assumes an implicit constructor with no arguments "Runnable()"
            @Override
            public void run() {
                System.out.println(s);
            }
        };
        // call
        r.run();
    }
}

This syntax is simplified using a Lambda expression

public interface Runnable {
    void run();
}

public class Example {

    public static void main(String[] args) {
        String s = "Hello world!";

        // declarate
        Runnable r = () -> System.out.println(s);

        // call
        r.run();
    }
}

If we try to replicate this in the Dart language syntax it would look like this

abstract interface class Runnable {
  void run();
}

void main() {
  String s = "Hello world!";

  // declarate
  Runnable r = Runnable() {
    //Assumes an implicit constructor with no arguments "Runnable()"
    @override
    void run() {
      print(s);
    }
  }

  // call
  r.run();
}

This syntax could be simplified like this

abstract interface class Runnable {
  void run();
}

void main() {
  String s = "Hello world!";

  // declarate
  Runnable r = () => print(s);

  // call
  r.run();
}

This leads me to the conclusion that Dart is not prepared for this because to represent higher order functions the type Function is used, which satisfies the syntax of functions according to the proposed form, there would be no need for a type Function because any object that has an abstract method would already behave the same as the Function type because the class and object system has become 'functional' because an object could behave like a function that would eliminate the need to have a Function type

To resolve this issue we must first decide whether to open another issue to introduce the anonymous classes and then return here or simply close it and not implement this or any alternative that you propose

I think that functional interfaces are a very necessary feature in the Dart language, we must assume the functional programming paradigm is what all the competition is doing and they have been trying for years and many languages ​​already have these characteristics

According to the proposed form we could have a generic functional interface that represents any existing anonymous function or lambda expression or single input parameter callback. This would eliminate the need to use the "Function" type. This "functional interface" could be represented as follows:

abstract interface class Function<T, S> {
  S apply(T value);
}

void main() {

  // declarate
  Function<int, int> fn = (n) => n + 2;

  // call
  int result = fn.apply(10);

  // result
  print(result);  //print: 12
}

According to the proposed form we could have a generic functional interface that represents any existing function. This would eliminate the need to have a Function type. This functional interface could be represented as follows:

abstract interface class Function<T, S> {
  S apply(T value);
}

void main() {

  // declarate
  Function<int, int> fn = (n) => n + 2;

  // call
  int result = fn.apply(10);

  // result
  print(result);  //print: 12
}

I don't see how this can represent any existing function. What about functions with more than one argument? What about functions with named arguments? The existing Function type can already represent any type of function.

int add(int a, int b) => a + b;
void greet({String name = 'World'}) => print('Hello $name!');

void main() {
  int Function(int, int) function1 = add;
  void Function({String name}) function2 = greet;  
}

@mmcdon20 I think you are right, I must be more specific, this represents all anonymous functions or lambda expressions with a single parameter that have the following form:

(n) => print(n + 2);

(n) {
  print(n + 2);
}

To represent any other type of function, the type Function would be needed, especially for typedef and the dart:mirrors API because the other features are not part of the functional paradigm and must be represented in some way.

I am only referring to anonymous functions or callbacks, the functional interfaces are limited to this context for the rest of the characteristics if the Function type is necessary.

Something important that I have not mentioned is that functional interfaces, in addition to representing anonymous functions, can also be implemented as a traditional interface. We can see this with an example:

abstract interface class Runnable {
  void run();
}

class Example implements Runnable {
  void run() {
      print("hello dart");
  }
}

void main() {
  // Example 1
  Runnable fn = Example();
  fn.run();  //print: hello dart

  // Example 1
  Runnable fn2 = () => print("hello flutter");
  fn2.run();  //print: hello flutter
}

The flexibility of this new feature is essential for a good development experience

@mmcdon20 I think you are right, I must be more specific, this represents all anonymous functions or lambda expressions with a single parameter that have the following form:

(n) => print(n + 2);

(n) {
  print(n + 2);
}

To represent any other type of function, the type Function would be needed, especially for typedef and the dart:mirrors API because the other features are not part of the functional paradigm and must be represented in some way.

I am only referring to anonymous functions or callbacks, the functional interfaces are limited to this context for the rest of the characteristics if the Function type is necessary.

You can already represent this subset of Function types as follows:

typedef Callback<T, S> = S Function(T);

dart already has some support for treating objects like functions in the form of callable objects. This allows you to call an object like a Function and also pass an object to a variable of a Function type. The only thing missing is the ability to assign a function to the callable class type, but this could be solved with implicit constructors as @lrhn has already mentioned.

class Counter {
  int _count = 0;
  int call() => ++_count;
}

void main() {
  // you can call an object with a call method
  Counter c = Counter();
  print(c());
  print(c());
  // you can assign a callable object to a function type
  int Function() f = Counter(); // implicit tear-off on .call
  print(f());
  print(f());
  // you can not assign a function type to a callable class
  int count = 0;
  Counter x = () => ++count; // error
}

I think that this debate has become very extensive, I think that the team already has enough information for them to make a decision or if they have any questions they can ask, we should think about how to close the thread

Regarding callable objects, it is a way that exists, the point is the lack of standardization with a good object-oriented design to access a callable object should be done through an interface

Interfaces are necessary for a good object-oriented design and long-term maintainability. This simplifies the work of the maintenance team and allows greater scalability, eliminating technical debt among other benefits.

I think I'm already taking up your valuable time in tribal conversations, greetings and grateful for everything.

No plans to implement this.