From: Guoliang Cao Date: 2004-02-03T23:35:25+09:00 Subject: RCR draft for enhanced "case..when..else..end" syntax Hi, I'm thinking of submitting a RCR. Here is the draft. Comments are welcome. Thanks, Cao ==================================================== 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