ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
From: "austin (Austin Ziegler) via ruby-core" <ruby-core@ml.ruby-lang.org>
To: ruby-core@ml.ruby-lang.org
Cc: "austin (Austin Ziegler)" <noreply@ruby-lang.org>
Subject: [ruby-core:119385] [Ruby master Feature#20770] A *new* pipe operator proposal
Date: Tue, 01 Oct 2024 17:19:42 +0000 (UTC)	[thread overview]
Message-ID: <redmine.journal-110002.20241001171942.54143@ruby-lang.org> (raw)
In-Reply-To: <redmine.issue-20770.20240929175745.54143@ruby-lang.org>

Issue #20770 has been updated by austin (Austin Ziegler).


I think that this is one of the more interesting approaches to a pipeline operator in Ruby as it is just syntax sugar. As I am understanding it:

```ruby
foo
|> bar(_1, baz)
|> hoge(_1, quux)
```

would be treated by the parser to be the same as:

```ruby
foo
  .then { bar(_1, baz) }
  .then { hoge(_1, quux) }
```

It would be *nice* (given that there syntax sugaring happening here) that if `it` or `_1` is missing, it is implicitly inserted as the first parameter:

```ruby
foo
|> bar(baz)
|> hoge(quux)

  ==

foo
  .then { bar(_1, baz) }
  .then { hoge(_1, quux) }
```

This would enable the use of callables (procs and un/bound methods) as suggested by @dan0042 in #note-20.

I am *not* sure that without that implicit first parameter, the potential confusion introduced by the differently-shaped blocks is worthwhile. Regardless, as someone who maintains libraries that with deep compatibility, I won't be able to use this in those for another decade at least (I *still* haven't released versions of my most used libraries that are 3.x only), by which time I am hoping to have found someone else to maintain them.

vo.x (Vit Ondruch) wrote in #note-18:
> [the pipe operator] is IMHO mostly about type conversion

Having used Elixir heavily for the last seven years, I do not agree with this description. It *can* be, and the examples in question might be, but it's used equally in transformation (type conversion) and in context passing. `Plug` (more or less the Elixir equivalent to Rack) is composable because the first parameter to every plug function (whether a `function/2` or a module with `init/1` and `call/2`) is a `Plug.Conn` struct, allowing code like this:

```elixir
def call(conn, %Config{} = config) do
  {metadata, span_context} =
    start_span(:plug, %{conn: conn, options: Config.telemetry_context(config)})

  conn =
    register_before_send(conn, fn conn ->
      stop_span(span_context, Map.put(metadata, :conn, conn))
      conn
    end)

  results =
    conn
    |> verify_request_headers(config)
    |> Map.new()

  conn
  |> put_private(config.name, results)
  |> dispatch_results(config)
  |> dispatch_on_resolution(config.on_resolution)
end
```

This is no different than:

```elixir
def call(conn, %Config{} = config) do
  {metadata, span_context} =
    start_span(:plug, %{conn: conn, options: Config.telemetry_context(config)})

  conn =
    register_before_send(conn, fn conn ->
      stop_span(span_context, Map.put(metadata, :conn, conn))
      conn
    end)

  results = verify_request_headers(conn, config)
  results = Map.new(results)

  conn = put_private(conn, config.name, results)
  conn = dispatch_results(conn, config)
  dispatch_on_resolution(conn, config.on_resolution)
end
```

I find the former *much* more readable, because it's more data oriented and indicates that the data flows *through* the pipe — where it might be transformed (`conn |> verify_request_headers(…) |> Map.new()`) or it might just be modifying the input parameter (`conn |> put_private(…) |> dispatch_results(…) |> dispatch_on_resolution(…)`).

jeremyevans0 (Jeremy Evans) wrote in #note-10:
> We could expand the syntax to treat `.{}` as `.then{}`, similar to how `.()` is `.call()`.  With that, you could do:
> 
> ```ruby
> client_api_url
>   .{ URI.parse(it) }
>   .{ Net::HTTP.get(it) }
>   .{ JSON.parse(it).fetch(important_key) }
> ```
> 
> Which is almost as low of a syntatic overhead as you would want.
> 
> Note that we are still in a syntax moratorium, so it's probably better to wait until after that is over and we have crowned the one true parser before seriously considering new syntax.

This is … interesting. The biggest problem with it (from my perspective) is that it would privilege `{}` blocks with this form, because `do` *is* a valid method name, so `.do URI.parse(it) end` likely be a syntax error. That and the fact that it would be nearly a decade before it could be used by my libraries.

----------------------------------------
Feature #20770: A *new* pipe operator proposal
https://bugs.ruby-lang.org/issues/20770#change-110002

* Author: AlexandreMagro (Alexandre Magro)
* Status: Open
----------------------------------------
Hello,

This is my first contribution here. I have seen previous discussions around introducing a pipe operator, but it seems the community didn't reach a consensus. I would like to revisit this idea with a simpler approach, more of a syntactic sugar that aligns with how other languages implement the pipe operator, but without making significant changes to Ruby's syntax.

Currently, we often write code like this:

```ruby
value = half(square(add(value, 3)))
```

We can achieve the same result using the `then` method:

```ruby
value = value.then { add(_1, 3) }.then { square(_1) }.then { half(_1) }
```

While `then` helps with readability, we can simplify it further using the proposed pipe operator:

```ruby
value = add(value, 3) |> square(_1) |> half(_1)
```

Moreover, with the upcoming `it` feature in Ruby 3.4 (#18980), the code could look even cleaner:

```ruby
value = add(value, 3) |> square(it) |> half(it)
```

This proposal uses the anonymous block argument `(_1)`, and with `it`, it simplifies the code without introducing complex syntax changes. It would allow us to achieve the same results as in other languages that support pipe operators, but in a way that feels natural to Ruby, using existing constructs like `then` underneath.

I believe this operator would enhance code readability and maintainability, especially in cases where multiple operations are chained together.

Thank you for considering this proposal!






-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/

  parent reply	other threads:[~2024-10-01 17:19 UTC|newest]

Thread overview: 56+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-09-29 17:57 [ruby-core:119335] [Ruby master Bug#20770] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-09-30  1:15 ` [ruby-core:119336] [Ruby master Feature#20770] " nobu (Nobuyoshi Nakada) via ruby-core
2024-09-30  2:24 ` [ruby-core:119340] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-09-30  4:27 ` [ruby-core:119341] " shuber (Sean Huber) via ruby-core
2024-09-30 14:15 ` [ruby-core:119362] " bkuhlmann (Brooke Kuhlmann) via ruby-core
2024-09-30 19:02 ` [ruby-core:119364] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-09-30 20:32 ` [ruby-core:119365] " vo.x (Vit Ondruch) via ruby-core
2024-09-30 21:23 ` [ruby-core:119366] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-09-30 22:15 ` [ruby-core:119367] " ufuk (Ufuk Kayserilioglu) via ruby-core
2024-09-30 22:30 ` [ruby-core:119368] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-09-30 22:55 ` [ruby-core:119369] " jeremyevans0 (Jeremy Evans) via ruby-core
2024-09-30 23:58 ` [ruby-core:119370] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-10-01  7:11 ` [ruby-core:119372] " ko1 (Koichi Sasada) via ruby-core
2024-10-01  7:11 ` [ruby-core:119373] " mame (Yusuke Endoh) via ruby-core
2024-10-01  7:11 ` [ruby-core:119375] " vo.x (Vit Ondruch) via ruby-core
2024-10-01  7:11 ` [ruby-core:119376] " vo.x (Vit Ondruch) via ruby-core
2024-10-01  7:55 ` [ruby-core:119377] " zverok (Victor Shepelev) via ruby-core
2024-10-01  8:24 ` [ruby-core:119378] " zverok (Victor Shepelev) via ruby-core
2024-10-01  9:55 ` [ruby-core:119379] " vo.x (Vit Ondruch) via ruby-core
2024-10-01 13:08 ` [ruby-core:119381] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-10-01 16:13 ` [ruby-core:119383] " Dan0042 (Daniel DeLorme) via ruby-core
2024-10-01 17:09 ` [ruby-core:119384] " ufuk (Ufuk Kayserilioglu) via ruby-core
2024-10-01 17:19 ` austin (Austin Ziegler) via ruby-core [this message]
2024-10-01 17:47 ` [ruby-core:119386] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-10-01 18:18 ` [ruby-core:119387] " Eregon (Benoit Daloze) via ruby-core
2024-10-01 19:02 ` [ruby-core:119389] " zverok (Victor Shepelev) via ruby-core
2024-10-01 19:43 ` [ruby-core:119391] " eightbitraptor (Matthew Valentine-House) via ruby-core
2024-10-01 22:46 ` [ruby-core:119392] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-10-02  6:34 ` [ruby-core:119396] " zverok (Victor Shepelev) via ruby-core
2024-10-02 15:19 ` [ruby-core:119405] " shuber (Sean Huber) via ruby-core
2024-10-02 16:14 ` [ruby-core:119408] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-10-02 17:03 ` [ruby-core:119409] " zverok (Victor Shepelev) via ruby-core
2024-10-03 21:55 ` [ruby-core:119436] " lpogic via ruby-core
2024-10-05 19:46 ` [ruby-core:119466] " nevans (Nicholas Evans) via ruby-core
2024-10-15 10:01 ` [ruby-core:119529] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-11-06 21:12 ` [ruby-core:119781] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-11-08 12:40 ` [ruby-core:119842] " lpogic via ruby-core
2024-11-08 19:29 ` [ruby-core:119850] " austin (Austin Ziegler) via ruby-core
2024-11-08 20:28 ` [ruby-core:119851] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-11-08 23:38 ` [ruby-core:119853] " lpogic via ruby-core
2024-11-09  2:54 ` [ruby-core:119858] " austin (Austin Ziegler) via ruby-core
2024-11-09  3:21 ` [ruby-core:119859] " baweaver (Brandon Weaver) via ruby-core
2024-11-09 11:37 ` [ruby-core:119863] " lpogic via ruby-core
2024-11-09 11:57 ` [ruby-core:119864] " lpogic via ruby-core
2024-11-09 21:23 ` [ruby-core:119866] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-11-09 21:42 ` [ruby-core:119867] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-11-12 11:50 ` [ruby-core:119893] " lpogic via ruby-core
2024-11-12 19:02 ` [ruby-core:119904] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-11-17  9:44 ` [ruby-core:119951] " zverok (Victor Shepelev) via ruby-core
2024-11-29 15:52 ` [ruby-core:120057] " lpogic via ruby-core
2024-11-29 17:27 ` [ruby-core:120060] " austin (Austin Ziegler) via ruby-core
2024-11-29 17:55 ` [ruby-core:120061] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-11-29 18:19 ` [ruby-core:120062] " AlexandreMagro (Alexandre Magro) via ruby-core
2024-11-29 19:25 ` [ruby-core:120063] " austin (Austin Ziegler) via ruby-core
2024-11-29 23:53 ` [ruby-core:120064] " AlexandreMagro (Alexandre Magro) via ruby-core
2025-01-05  6:32 ` [ruby-core:120482] " rubyFeedback (robert heiler) via ruby-core

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=redmine.journal-110002.20241001171942.54143@ruby-lang.org \
    --to=ruby-core@ml.ruby-lang.org \
    --cc=noreply@ruby-lang.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).