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/
next prev 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).