From: "austin (Austin Ziegler) via ruby-core" Date: 2024-10-01T17:19:42+00:00 Subject: [ruby-core:119385] [Ruby master Feature#20770] A *new* pipe operator proposal 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/