Feature #20786
closedFlow chaining with "then" keyword
Description
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:¶
# original code source: https://github.com/ruby/ruby/blob/a6383fbe1628bdaa9ec6a30b3baa60dd7430b461/lib/ipaddr.rb#L181C1-L185C6
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:¶
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:
# 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
  
        
          
          Updated by nobu (Nobuyoshi Nakada) about 1 year ago
          
          
        
        
      
      It looks to conflict inside if.
        
          
          Updated by lpogic (Łukasz Pomietło) about 1 year ago
          
          
        
        
      
      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:
foo then bar end
The "bar" is evaluated only if "foo" is true, so it does quiet the same what:
if foo then bar end
does (except that first form result is "false" if "foo" returns "false").
Another examples:
# 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:
if foo then bar then baz end end
So I see 2 solutions:
- If "if" is used, the first "then" belongs to another flow, so the case is the same as one above.
 - "if" can also be divided into steps, so the case is as follows:
 
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:
begin
  foo
then
  bar
then
  baz
  ready?
then
  p "ok"
end
instead of this:
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:
# 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.
        
          
          Updated by matz (Yukihiro Matsumoto) 12 months ago
          
          
        
        
      
      - 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.