From: Gennady Date: 2004-02-04T01:00:25+09:00 Subject: Re: RCR draft for enhanced "case..when..else..end" syntax Guoliang Cao wrote: > Hi, > > I'm thinking of submitting a RCR. Here is the draft. Comments are welcome. > > Thanks, > Cao Do you know about this syntax currently available? a = "aaa" b = "bbb" case when a == "aaa", b == "bbb" puts "Lowercase detected" when a == "AAA", b == "BBB" puts "Uppercase detected" else puts "Detection failed" end > > ==================================================== > > RCR title: An enhanced case..when..else..end syntax > > This RCR involves (check all that apply): > ... a new feature x > ... a syntax change x > ... refactoring only > ... backwards incompatibility > > Abstract.... > This RCR proposes an enhanced case..when..else..end syntax. > > Problem.... > It's a very common task to group code based on a combination of values of > multiple variables. However, the current Ruby syntax only allows one > expression/variable being used in "case" clause. People have to use > if..else..end which requires more typing and is less intuitive. > > For example, suppose I'm generating a image, the color of each point is > defined based on its x and y coordinates. > > 0 ... 50...100...200 > 0 +----------------+ > | white | > 50 |----------------| > | | grey | > 100 |blue +----------| > | | black | > 150 |----------------| > | white | > 200 +----------------+ > > > Use if..else..end: > > if 0..50 === x || 150..200 === x > color = white > elsif 0..50 === y > color = blue > elsif 50..100 === x and 50..200 === y > color = grey > else > color = black > end > > Use new case..when..else..end: > > case x : y > when 0..50, 150..200 # x in 0..50 or 150..200 > color = white > when _ : 0..50 # y in 0..50 > color = blue > when 50..100 : 50..200 # x in 50..100 and y in 50..200 > color = grey > else # otherwise > color = black > end > > Note: If the color is only dependent on x, we can use the current "case" > syntax. However if it's dependent on more than one variable, "case" is > incapable of handling it. > > Proposal.... > The enhanced syntax looks like below: > > case expr1 : expr2 > when expr3 : _ : expr4 > do_something > when expr5 > do_different_thing > else > do_default_thing > end > > 1. "case" takes 0 or any number of expressions separated by ":"; > > If there's no expression, each clause separated by ":" in "when" is > evaluated to true or false and ":" is treated the same way as "and". > > 2. "when" takes 1 or more clauses separated by ":"; each clause by itself > can be separated by "," (this is supported in current syntax). > > If there are less clauses than the number of "case" expressions, the > rest of "case" expressions are not evaluated. > > If there are more clauses than the number of "case" expressions, the > rest of "when" clauses are evaluated to true or false. ":" is treated > the same way as "and" > > 3. "else" remains the same. > > 4. "_" means skipping evaluation of the corresponding "case" expression. > > > Analysis.... > What benefits do we get? > 1. It's a more general form, which means it can be used much wider than > what "case..when" is used for today; > 2. less typing; there's no need to type x, y, === in the when clause; > 3. this syntax change is backward compatible with Ruby 1.x > > Implementation.... > Because this request proposes syntax enhancement, it can't be achieved > without changing Ruby interpreter. > > The code below demonstrated the idea by extending Object class and > utilizing thread-local variables. It uses "ccase..cwhen..celse..end" to > simulate the proposed syntax. It uses "," to separate ccase/cwhen clauses > which are just arguments. It uses "true" instead of "_" to indicate > skipping evaluation. > > > Source: > > #!/usr/bin/ruby > # > # To utilize Ruby's existing functionality to demonstrate the idea, I'm doing > # this: > # add instance methods "ccase", "cwhen", "celse" to class Object which is the > # top one in the hierarchy so all classes inherit them. > # > # Its usage is like this: > # ccase a, b do > # cwhen [1,2], 1..3 do > # ... > # end > # cwhen 3, true do > # ... > # end > # celse do > # ... > # end > # end > # > # Problems so far: > # 1. how to let "cwhen" method body to access "ccase" arguments and > # intermediate objects; > # Solution: use thread-local variable > # > # 2. how to make it work in multi-threaded program; > # Solution: use thread-local variable > # > # 3. "do...end" or "{...}" is needed to pass block to ccase/cwhen/celse. > # Solution: ??? > # > # ccase = "customized case" > # cwhen = "customized when" > # celse = "customized else" > # > > class Object > > # customized case > def ccase arg0, *args > begin > args.unshift arg0 > Thread.current["in_ccase"] = true > Thread.current["ccase_args"] = args > Thread.current["hit_cwhen"] = nil > if block_given? > yield > end > ensure > Thread.current["in_ccase"] = nil > Thread.current["ccase_args"] = nil > Thread.current["hit_cwhen"] = nil > end > end > > # customized when > def cwhen arg0, *args > # skip if already matched > return if Thread.current["hit_cwhen"] > > args.unshift arg0 > hit = true # hit or not? > ccase_args = Thread.current["ccase_args"] > > if ccase_args and ccase_args.length > 0 > ccase_args.each_index do |idx| > ccase_arg = ccase_args[idx] > cwhen_arg = args[idx] > > # cwhen_arg is not present, hit = true > # cwhen_arg is true and ccase_arg is not false, hit = true > # ccase_arg is not present, hit = if cwhen_arg evaluates to true > # both ccase_arg and cwhen_arg are present, hit = if cwhen_arg === ccase_arg > if (not cwhen_arg) or (not ccase_arg and cwhen_arg) or (cwhen_arg == true and not ccase_arg == false) or (cwhen_arg === ccase_arg) > # hit = true > elsif cwhen_arg.is_a? Array > hit = false > cwhen_arg.each do |arg| > if arg === ccase_arg > hit = true > break > end > end > else > hit = false > end > s = if cwhen_arg.nil? then "nil" else cwhen_arg.to_s end > s1 = if ccase_arg.nil? then "nil" else ccase_arg.to_s end > print "#{s} === #{s1} : #{hit.to_s}\n" > > break if not hit > end > else > args.each {|arg| hit &= arg} > end > > return if not hit > Thread.current["hit_cwhen"] = true > > if block_given? > yield > end > end > > # customized else to be combined with ccase/cwhen > def celse > return if Thread.current["hit_cwhen"] > if block_given? > yield > end > end > > end > > if __FILE__ == $0 > def test a,b,c > ccase a,b,c do > cwhen [1, 3, 6], 1..2 do > # if a is in [1,3,6] and b === 1..2 > print "hit 1\n" > end > cwhen 4, 3, 1 do > # if a === 4 and b === 4 and c === 1 > print "hit 2\n" > end > cwhen 5, true, 7 do > # if a === 5 and c === 7 > print "hit 3\n" > end > celse do > # otherwise > print "no hit\n" > end > end > end > > test 1,2,3 # hit 1 > test 4,3,0 # no hit > test 4,3,1 # hit 2 > test 5,6,7 # hit 3 > > end > >