It's basically Object#try
which also works for Nil
with a descriptive yet still uninviting long name.
I don't really see the usecase..?
@RX14 Yes, I'd like to ask Ruby committer about usecase.
Not that I defend it, but could be used to create a variable, without creating a variable. To help keep calculations inline:
result = long.chained.computation(12).foo(1, 2, 3).yield_self {|list| list.sum * list.product }.to_s.size
RethinkDB (a database whose query language is mostly functional) has a similar operation, called do
: https://rethinkdb.com/api/ruby/do/.
It is like a map, but for the entire thing.
It's similar to .tap
in behavior and intent, expect it returns the block's value (not the tapped value).
I don't see much usage. Naming is verbose, and assigning to a value feels better:
list = long.chained.computation(12).foo(1, 2, 3)
result = (list.sum * list.product).to_s.size
@RX14 Why do you close this issue? (I can guess but...) If you could explain this, you should do it, or if you couldn't explain this, you shouldn't close this.
The consensus was that it wasn't really very useful. If anyone has a counterexample to discuss we can always reopen.
yield_self
is for those that want a "pipe operator" in Ruby: http://mlomnicki.com/yield-self-in-ruby-25/
Like others here, I don't think it looks nice, nor I think it's readable. And since we have try
(and you will probably never want to have a nil
in a chain), it can work as good as yield_self
.
I found very useful usecase of Object#yield_self
! See this simple fizz buzz example:
def fizz?(n)
n % 3 == 0
end
def buzz?(n)
n % 5 == 0
end
(1..100).each do |i|
if fizz?(i) && buzz?(i)
puts :FizzBuzz
elsif fizz?(i)
puts :Fizz
elsif buzz?(i)
puts :Buzz
else
puts i
end
end
When we have Object#yield_self
, we can rewrite this:
# `def fizz?` and `def buzz` is skipped
(1..100).each do |i|
case i
when .yield_self { |i| fizz?(i) && buzz?(i) }
puts :FizzBuzz
when .yield_self { |i| fizz?(i) }
puts :Fizz
when .yield_self { |i| buzz?(i) }
puts :Buzz
else
puts i
end
end
In other words, Object#yield_self
provides a potential to call any method in when
condition against case
value.
However, yield_self
is tooooooooo long to type. So, I suggest another name Object#let
(derived from Kotlin). What do you think?
@RX14 Please re-open this. I believe this feature makes Crystal more useful and we can discuss about this more.
But why would you want to do that? That's uglier than the if
version by far. If we just want a way to use arbitrary booleans in case
then we should discuss that without resorting to hacks.
Real world example, src/compiler/crystal/tools/doc/highlighter.cr
:
case token.type
when :NEWLINE
io.puts
# ...snip...
when :IDENT
if last_is_def
last_is_def = false
highlight token, "m", io
else
case token.value
when :def, :if, :else, :elsif, :end,
:class, :module, :include, :extend,
:while, :until, :do, :yield, :return, :unless, :next, :break, :begin,
:lib, :fun, :type, :struct, :union, :enum, :macro, :out, :require,
:case, :when, :then, :of, :abstract, :rescue, :ensure, :is_a?,
:alias, :pointerof, :sizeof, :instance_sizeof, :as, :typeof, :for, :in,
:undef, :with, :self, :super, :private, :protected, "new"
highlight token, "k", io
when :true, :false, :nil
highlight token, "n", io
else
io << token
end
end
when :"+", :"-", :"*", :"/", :"=", :"==", :"<", :"<=", :">", :">=", :"!", :"!=", :"=~", :"!~", :"&", :"|", :"^", :"~", :"**", :">>", :"<<", :"%", :"[]", :"[]?", :"[]=", :"<=>", :"==="
highlight token, "o", io
when :"}"
if break_on_rcurly
break
else
io << token
end
else
io << token
end
when
condition for keywords and operators are too long, so I want to refactor this with such constants:
KEYWORDS = Set{
:def, :if, :else, :elsif, :end,
:class, :module, :include, :extend,
:while, :until, :do, :yield, :return, :unless, :next, :break, :begin,
:lib, :fun, :type, :struct, :union, :enum, :macro, :out, :require,
:case, :when, :then, :of, :abstract, :rescue, :ensure, :is_a?,
:alias, :pointerof, :sizeof, :instance_sizeof, :as, :typeof, :for, :in,
:undef, :with, :self, :super, :private, :protected, "new"
}
OPERATORS = Set{
:"+", :"-", :"*", :"/", :"=", :"==", :"<", :"<=", :">", :">=", :"!",
:"!=", :"=~", :"!~", :"&", :"|", :"^", :"~", :"**", :">>", :"<<",
:"%", :"[]", :"[]?", :"[]=", :"<=>", :"==="
}
But I can't use these constants in when
condition because Set#===
is not specialized currently.
When there is Object#let
, this code can be rewritten:
case token.type
when :NEWLINE
io.puts
# ...snip...
when :IDENT
if last_is_def
last_is_def = false
highlight token, "m", io
else
case token.value
when .let { |v| KEYWORDS.includes? v }
highlight token, "k", io
when :true, :false, :nil
highlight token, "n", io
else
io << token
end
end
when .let { |t| OPERATORS.includes? t }
highlight token, "o", io
when :"}"
if break_on_rcurly
break
else
io << token
end
else
io << token
end
Of course Object#let
is not needed if #5269 is merged. However it is actual thing that Object#let
is the most generic way to call any method against case
value.
@RX14 Please imagine. Why do you hate this issue?
I think it makes sense. It would be nice to see which is faster, but I guess Set#includes?
is faster than a huge when
(I think this was concluded in a recent issue).
Benchmark is here and result is this:
old 358.88 ( 2.79ms) (卤 2.53%) fastest
new 337.75 ( 2.96ms) (卤 1.87%) 1.06脳 slower
Object#let
version is slow, but I feel it is a bit, also fast. It looks no problem to me.
/cc @asterite
And the most important point I think:
it is actual thing that
Object#let
is the most generic way to call any method againstcase
value.
It feels like an ugly hack, like there should be a concrete, real, syntax change for supporting this, not hacking it into the stdlib. Perhaps this could work:
```cr
case foo
when { func(foo) }
end
@RX14 that would require some lookahead in the parser to disambiguate from tuples I think. Besides the .foo { }
is using already existing constructs.
Maybe Object#bind
, Object#apply
or Object#itself(&block)
are short enough ?
The let
looks weird to me. expr.let { |var| S }
instead of let var = expr in S
everything seems twisted.
Another proposal which doesn't require a change:
if func(foo)
# do something
end
Well, {func(foo)}
is a valid tuple so clearly my syntax idea isn't sound. However, I maintain that if the only point of adding this method is for case
then we'd be better off fixing the root of the problem - case
. Can't think up of a good syntax though.
Object#into
is possible candidate. (or maybe Object#in
, but it is too short.) I think #apply
or #itself
is too long.
IMHO Object#yield_self
isn't bad, but perhaps #tap!
could be used?
#tap!
does not make sense. I think !
means mutable or danger, but this method is not mutable normally and safe. Additionally #tap
is used with mutable method sometimes.
Replacing with if
is good sometimes, on the other hand, it is not so good sometimes. I think highlighter.cr
is such an example. When normal when
value condition and other method call is mixed, this method is really useful.
And another benefit of this method is to call any method in method chaining. Any Crystal users dislike this because they are genius, so they can think the best variable name every time. However I can't do it every time. Naming is important, but writing executable program is more important, so I like this.
Re Object#let
, FYI there's a ruby gem with exactly that name, see rubygems object-let.
Disclaimer: I'm the author. Not that there's much code in there :)
I abuse #try
for that too. Most time it serves well, but sometimes feels like a hack: some_method.not_nil!.try...
.
Most helpful comment
It's similar to
.tap
in behavior and intent, expect it returns the block's value (not the tapped value).I don't see much usage. Naming is verbose, and assigning to a value feels better: