[ruby-core:119469] [Ruby master Feature#20786] Flow chaining with "then" keyword

Issue #20786 has been reported by lpogic (Łukasz Pomietło). ---------------------------------------- Feature #20786: Flow chaining with "then" keyword https://bugs.ruby-lang.org/issues/20786 * Author: lpogic (Łukasz Pomietło) * Status: Open ---------------------------------------- Hi, I would like to propose using the *"then"* keyword to create chained flows. ### Background: Original idea: https://bugs.ruby-lang.org/issues/20770#change-110056 However, the concept has changed slightly since then. I now assume that rhs is only calculated when lhs is true, which seems more compatible with the current usage of *"then"* keyword. I would also like to move the discussion here as it may cause unnecessary chaos there. ### Basics: In the canonical version, *"then"* is used in a *"begin..end"* block to split it into steps. When the result of the last expression before *"then"* is not *"false"* or *"nil"*, it is passed as an argument to the next step. Otherwise, all subsequent steps are skipped. The result of the *"begin..end"* block is the result of the last expression, or *"false"*/*"nil"* if any step was skipped. ### Example: ``` ruby # original code source: https://github.com/ruby/ruby/blob/a6383fbe1628bdaa9ec6a30b3baa60dd7430b461/l... def include?(other) other = coerce_other(other) return false unless other.family == family begin_addr <= other.begin_addr && end_addr >= other.end_addr end # with chained flow def include?(other) begin coerce_other(other) then => coerced # then => name is my proposition of argument naming syntax return false unless coerced.family == family coerced then => it begin_addr <= it.begin_addr && end_addr >= it.end_addr end end ``` Please treat the canonical form as a starting point. As further improvements I would see: - implicit block argument name - implicit begin - beginless *"then"*, when lhs is just expression ### Example with futher improvements: ``` ruby def include?(other) coerce_other(other) then it.family == family ? it : false then begin_addr <= it.begin_addr && end_addr >= it.end_addr end # or def include?(other) coerce_other other then return false unless it.family == family begin_addr <= it.begin_addr && end_addr >= it.end_addr end end ``` ### *"then"* keyword as the pipeline operator: Under certain conditions, "then" could offer similar capabilities to the pipeline operator of functional languages: ``` ruby # assuming that "foo" and "bar" never return "false" or "nil" # instead of writing this: baz("string", bar(foo(), 2), 5) # you could write this: begin foo() then bar(it, 2) then baz("string", it, 5) end ``` -- https://bugs.ruby-lang.org/

Issue #20786 has been updated by nobu (Nobuyoshi Nakada). It looks to conflict inside `if`. ---------------------------------------- Feature #20786: Flow chaining with "then" keyword https://bugs.ruby-lang.org/issues/20786#change-110088 * Author: lpogic (Łukasz Pomietło) * Status: Open ---------------------------------------- Hi, I would like to propose using the *"then"* keyword to create chained flows. ### Background: Original idea: https://bugs.ruby-lang.org/issues/20770#change-110056 However, the concept has changed slightly since then. I now assume that rhs is only calculated when lhs is true, which seems more compatible with the current usage of *"then"* keyword. I would also like to move the discussion here as it may cause unnecessary chaos there. ### Basics: In the canonical version, *"then"* is used in a *"begin..end"* block to split it into steps. When the result of the last expression before *"then"* is not *"false"* or *"nil"*, it is passed as an argument to the next step. Otherwise, all subsequent steps are skipped. The result of the *"begin..end"* block is the result of the last expression, or *"false"*/*"nil"* if any step was skipped. ### Example: ``` ruby # original code source: https://github.com/ruby/ruby/blob/a6383fbe1628bdaa9ec6a30b3baa60dd7430b461/l... def include?(other) other = coerce_other(other) return false unless other.family == family begin_addr <= other.begin_addr && end_addr >= other.end_addr end # with chained flow def include?(other) begin coerce_other(other) then => coerced # then => name is my proposition of argument naming syntax return false unless coerced.family == family coerced then => it begin_addr <= it.begin_addr && end_addr >= it.end_addr end end ``` Please treat the canonical form as a starting point. As further improvements I would see: - implicit block argument name - implicit begin - beginless *"then"*, when lhs is just expression ### Example with futher improvements: ``` ruby def include?(other) coerce_other(other) then it.family == family ? it : false then begin_addr <= it.begin_addr && end_addr >= it.end_addr end # or def include?(other) coerce_other other then return false unless it.family == family begin_addr <= it.begin_addr && end_addr >= it.end_addr end end ``` ### *"then"* keyword as the pipeline operator: Under certain conditions, "then" could offer similar capabilities to the pipeline operator of functional languages: ``` ruby # assuming that "foo" and "bar" never return "false" or "nil" # instead of writing this: baz("string", bar(foo(), 2), 5) # you could write this: begin foo() then bar(it, 2) then baz("string", it, 5) end ``` -- https://bugs.ruby-lang.org/

Issue #20786 has been updated by lpogic (Łukasz Pomietło). You're right. Thats why I proposed to evaluate rhs conditionally. The canonical form has a clear *beginning* and *end*, so I assume your comment refers to the beginningless form. Let's look the following example: ``` ruby foo then bar end ``` The "bar" is evaluated only if "foo" is true, so it does quiet the same what: ``` ruby if foo then bar end ``` does (except that first form result is *"false"* if *"foo"* returns *"false"*). Another examples: ``` ruby # chained flow in condition: if (foo then baz end) then bar end # chained flow in statement: if foo bar then baz end # please note that in the beginless form the expression is required on the left side of the first "then" end ``` I think the most questionable case is this: ``` ruby if foo then bar then baz end end ``` So I see 2 solutions: 1) If "if" is used, the first "then" belongs to another flow, so the case is the same as one above. 2) "if" can also be divided into steps, so the case is as follows: ``` ruby if foo then bar then baz end # please note that the second "end" has been removed ``` I'm not sure which one would be better. In any case, the chain flow would allow for a notation like this: ``` ruby begin foo then bar then baz ready? then p "ok" end ``` instead of this: ``` ruby if foo if bar baz if ready? p "ok" end end end ``` There is also the issue of the "else" branch. Can be an alternative path for the last "then" branch or for all: ``` ruby # the case: if foo then bar then baz ready? then p "ok" else p "fail" end # "else" as an alternative to the last branch: if foo if bar baz if ready? p "ok" else p "fail" end end end # "else" as an alternative to all branches: if foo if bar baz if ready? p "ok" else p "fail" end else p "fail" end else p "fail" end ``` Finally, I'm not sure if "then" rhs should be evaluated conditionally, as this could sometimes cause problems in applications like the pipeline operator. Maybe use a different keyword for conditionality, e.g. "andthen"? I believe it should be carefully analyzed. ---------------------------------------- Feature #20786: Flow chaining with "then" keyword https://bugs.ruby-lang.org/issues/20786#change-110091 * Author: lpogic (Łukasz Pomietło) * Status: Open ---------------------------------------- Hi, I would like to propose using the *"then"* keyword to create chained flows. ### Background: Original idea: https://bugs.ruby-lang.org/issues/20770#change-110056 However, the concept has changed slightly since then. I now assume that rhs is only calculated when lhs is true, which seems more compatible with the current usage of *"then"* keyword. I would also like to move the discussion here as it may cause unnecessary chaos there. ### Basics: In the canonical version, *"then"* is used in a *"begin..end"* block to split it into steps. When the result of the last expression before *"then"* is not *"false"* or *"nil"*, it is passed as an argument to the next step. Otherwise, all subsequent steps are skipped. The result of the *"begin..end"* block is the result of the last expression, or *"false"*/*"nil"* if any step was skipped. ### Example: ``` ruby # original code source: https://github.com/ruby/ruby/blob/a6383fbe1628bdaa9ec6a30b3baa60dd7430b461/l... def include?(other) other = coerce_other(other) return false unless other.family == family begin_addr <= other.begin_addr && end_addr >= other.end_addr end # with chained flow def include?(other) begin coerce_other(other) then => coerced # then => name is my proposition of argument naming syntax return false unless coerced.family == family coerced then => it begin_addr <= it.begin_addr && end_addr >= it.end_addr end end ``` Please treat the canonical form as a starting point. As further improvements I would see: - implicit block argument name - implicit begin - beginless *"then"*, when lhs is just expression ### Example with futher improvements: ``` ruby def include?(other) coerce_other(other) then it.family == family ? it : false then begin_addr <= it.begin_addr && end_addr >= it.end_addr end # or def include?(other) coerce_other other then return false unless it.family == family begin_addr <= it.begin_addr && end_addr >= it.end_addr end end ``` ### *"then"* keyword as the pipeline operator: Under certain conditions, "then" could offer similar capabilities to the pipeline operator of functional languages: ``` ruby # assuming that "foo" and "bar" never return "false" or "nil" # instead of writing this: baz("string", bar(foo(), 2), 5) # you could write this: begin foo() then bar(it, 2) then baz("string", it, 5) end ``` -- https://bugs.ruby-lang.org/

Issue #20786 has been updated by matz (Yukihiro Matsumoto). Status changed from Open to Rejected We are discussing pipeline operator in #20770. IMO, this proposal does not make the code clearer nor more concise. Matz. ---------------------------------------- Feature #20786: Flow chaining with "then" keyword https://bugs.ruby-lang.org/issues/20786#change-110478 * Author: lpogic (Łukasz Pomietło) * Status: Rejected ---------------------------------------- Hi, I would like to propose using the *"then"* keyword to create chained flows. ### Background: Original idea: https://bugs.ruby-lang.org/issues/20770#change-110056 However, the concept has changed slightly since then. I now assume that rhs is only calculated when lhs is true, which seems more compatible with the current usage of *"then"* keyword. I would also like to move the discussion here as it may cause unnecessary chaos there. ### Basics: In the canonical version, *"then"* is used in a *"begin..end"* block to split it into steps. When the result of the last expression before *"then"* is not *"false"* or *"nil"*, it is passed as an argument to the next step. Otherwise, all subsequent steps are skipped. The result of the *"begin..end"* block is the result of the last expression, or *"false"*/*"nil"* if any step was skipped. ### Example: ``` ruby # original code source: https://github.com/ruby/ruby/blob/a6383fbe1628bdaa9ec6a30b3baa60dd7430b461/l... def include?(other) other = coerce_other(other) return false unless other.family == family begin_addr <= other.begin_addr && end_addr >= other.end_addr end # with chained flow def include?(other) begin coerce_other(other) then => coerced # then => name is my proposition of argument naming syntax return false unless coerced.family == family coerced then => it begin_addr <= it.begin_addr && end_addr >= it.end_addr end end ``` Please treat the canonical form as a starting point. As further improvements I would see: - implicit block argument name - implicit begin - beginless *"then"*, when lhs is just expression ### Example with futher improvements: ``` ruby def include?(other) coerce_other(other) then it.family == family ? it : false then begin_addr <= it.begin_addr && end_addr >= it.end_addr end # or def include?(other) coerce_other other then return false unless it.family == family begin_addr <= it.begin_addr && end_addr >= it.end_addr end end ``` ### *"then"* keyword as the pipeline operator: Under certain conditions, "then" could offer similar capabilities to the pipeline operator of functional languages: ``` ruby # assuming that "foo" and "bar" never return "false" or "nil" # instead of writing this: baz("string", bar(foo(), 2), 5) # you could write this: begin foo() then bar(it, 2) then baz("string", it, 5) end ``` -- https://bugs.ruby-lang.org/
participants (3)
-
lpogic
-
matz (Yukihiro Matsumoto)
-
nobu (Nobuyoshi Nakada)