Project

General

Profile

Actions

Feature #20786

closed

Flow chaining with "then" keyword

Added by lpogic (Łukasz Pomietło) about 2 months ago. Updated 19 days ago.

Status:
Rejected
Assignee:
-
Target version:
-
[ruby-core:119469]

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 2 months ago

It looks to conflict inside if.

Updated by lpogic (Łukasz Pomietło) about 2 months 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:

  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:
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) 19 days 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.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0