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) 3 months ago
It looks to conflict inside if
.
Updated by lpogic (Łukasz Pomietło) 3 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:
- 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) about 1 month 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.