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

Proposal: Introduce <- operator for clean and composable transformations #4170

Closed
andpush opened this issue Nov 24, 2024 · 4 comments
Closed
Labels
feature Proposed language feature that solves one or more problems state-duplicate This issue or pull request already exists

Comments

@andpush
Copy link

andpush commented Nov 24, 2024

Abstract

This proposal suggests adding the <- operator to Dart to facilitate concise and readable functional transformations. The operator allows for left-to-right application of functions to values, enhancing code composability and readability without adding undue complexity.

Motivation

Dart lacks a concise syntax for chaining value transformations in a functional style. While the cascade operator (..) aids in object configuration (similar to Kotlin's apply), there is no equivalent for transforming values (like Kotlin's let). Introducing the <- operator addresses this gap, allowing developers to write more expressive and maintainable code.

Proposal Details

Syntax

expression <- function

Behavior

  • The left-hand expression is evaluated and passed as the first argument to the right-hand function.
  • The operator supports chaining, enabling multiple transformations in a left-to-right flow.

Examples

var result = someString <- toUpperCase;
// Equivalent to:
// var result = toUpperCase(someString);
// lambdas can be used for more complex applications:
var result = someString
    <- toUpperCase
    <- (s) => '$s is amazing'
    <- print;
// Equivalent to:
// print('$toUpperCase(someString) is amazing');
// Handling multiple arguments
var result = value <- (v) => someFunction(v, additionalArg);
// Equivalent to:
// var result = someFunction(value, additionalArg);
// Handling curried functions
int add(int x, int y) => x + y;
var curriedAdd = (x) => (y) => add(x, y);
var result = 5 <- curriedAdd(10); // Result: 15
// Equivalent to:
// var result = curriedAdd(10)(5);

Benefits

  • Improves readability by eliminating nested parentheses
  • Makes the transformation pipeline clear and linear
  • Improves composability
    Compare:
    print(appendExclamation(toUpperCase(someString)));
    vs
    someString <- toUpperCase <- appendExclamation <- print;

Choice of <-

  • Visually applying expression on the left hence the left <- arrow
  • Haskell pipeline operator |> looks not intuitive to me, << and >> may resemble bitwise operations

Precedence

  • The <- operator has lower precedence than method invocation (.) and function calls (), but higher than assignment
  • The operator is left-associative, ensuring expressions are evaluated from left to right

What do you think?
Thanks!

@andpush andpush added the feature Proposed language feature that solves one or more problems label Nov 24, 2024
@julemand101
Copy link

julemand101 commented Nov 24, 2024

While different suggested syntax, it might be related to / duplicate of: #1246

@lrhn
Copy link
Member

lrhn commented Nov 24, 2024

Agree, this is another infix reverse-order function application operator, aka "pipe". (So closing and deferring to the existing issue, which should now have a link back to here.)

The arrow direction feels wrong to me, probably because I think in "data flow" order. The value flows into the function, its result flows into the next function, etc. This arrow points in the opposite direction.

The operator <- is basically equivalent to a let (to ensure evaluation order):

e1 <- e2 <- e3

would be

let v1 = e1 in let v2 = e2(v1) in e3(v2)

(If Dart had a let.)

I personally prefer -> as the operator. Can't get used to |>, although the "pipe" part of it makes sense.

@lrhn lrhn closed this as not planned Won't fix, can't repro, duplicate, stale Nov 24, 2024
@lrhn lrhn added the state-duplicate This issue or pull request already exists label Nov 24, 2024
@andpush
Copy link
Author

andpush commented Nov 24, 2024

Thank you for reviewing, I agree, it looks duplicate.
The operator itself does not matter that much. For me -> would also be fine, though the opposite direction imo reflected the inversion of function and its argument. Also my variant avoided confusion with C++ arrow operator. Whatever the syntax hope that the functionality fits the Dart spirit and some day will be implemented.

@D-a-n-i-l-o
Copy link

D-a-n-i-l-o commented Nov 25, 2024

Just in case somebody is wondering, the following functional style is already possible:

  "someString"
    .toUpperCase()
    .map((s) => '$s is amazing')
    .print();

Minimal example:

import 'dart:core';
import 'dart:core' as core;

void main() {
  "someString"
    .print()
    .toUpperCase()
    .print()
    .map((s) => '$s is amazing')
    .print()
    .appendExclamation()
    .print();
}

extension on String {
  String appendExclamation() => this+"!";
  String map(String f(String input)) => f(this);
}

extension Printer<T> on T {
  T print() {
    core.print(this);
    return this;
  }
}

More examples:

import 'dart:core';
import 'dart:core' as core;

void main() {
  print( 1.plus(2).plus(3) );
  print( 2.5.plus(2.6) );
  
  1.plus(2).plus(3).print();
  2.5.plus(2.6).print();
  
  ("-"*30).print();
  
  "aBcDeFg".toUpperCase().appendExclamation().print();

  ("-"*30).print();

  100.print();
  1.1.print();
  (1,2).print();

  ("-"*30).print();
  
  (8,7).add().print();
  (1.5,2.5).add().print();
  
  ("-"*30).print();
  
  final numbers = [1, 5, 10, 100];
  final result1 = numbers.map((num) => num * 3);
  result1.print();
  
  [1, 5, 10, 100].map((num) => num * 4).print();

  ("-"*30).print();
  
  "someString"
    .toUpperCase()
    .map((s) => '$s is amazing')
    .appendExclamation()
    .print();

  "someString"
    .print()
    .toUpperCase()
    .print()
    .map((s) => '$s is amazing')
    .print()
    .appendExclamation()
    .print();

  ("-"*30).print();
  
  // Handling curried functions  
  var curriedAdd = (x) => (y) => add(x, y);
  var result2 = curriedAdd(6)(11); // Result: 17
  var result3 = 5.curriedAdd(10);  // Result: 15
  
  result2.print();
  result3.print();
}

num add(num x, num y) => x + y;

extension on num {
  num plus(num x) => this + x;
  num curriedAdd(y) => add(this, y);
}

extension on String {
  String appendExclamation() => this+"!";
  String map(String f(String input)) => f(this);
}

extension Printer<T> on T {
  T print() {
    core.print(this);
    return this;
  }
}

extension on (num,num) {
  num add() => this.$1 + this.$2;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems state-duplicate This issue or pull request already exists
Projects
None yet
Development

No branches or pull requests

4 participants