Skip to content

Instantly share code, notes, and snippets.

@estum
Last active August 8, 2023 05:52
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save estum/d44add3eba05f5c0c3e8 to your computer and use it in GitHub Desktop.
Save estum/d44add3eba05f5c0c3e8 to your computer and use it in GitHub Desktop.
Ruby alternative switch-case syntax.
# = Kernel#switch
# Provides alternative switch-case syntax.
#
# # support methods:
# value = []
# switch value do
# on empty?: -> { "none" }
# on one?: -> { "one" }
# on many?: -> { "many" }
# end
#
# # More:
# switch value do
# on Hash => proc { value }
# on Array => lambda { Hash[value] }
# on Post, Comment do
# value.as_json
# end
# default { fail "¯\_(ツ)_/¯" }
# end
#
# # ...and more:
# value = "false"
# switch value do
# on /^(\d+)$/, ->{ Regexp.last_matches[1].to_i } # Sorry, no $N-vars :'(
# on /^(\d+\.?\d*)$/, ->{ value.to_f }
# on /^true|false$/i, ->{ value =~ "true" }
# default { value }
# end
class SwitchOn
class MultipleDefaultCaseError < SyntaxError; end
class MissingStatementBlockError < SyntaxError; end
class Case
def initialize(*args, &block)
if block_given?
proc = block.to_proc
else
if args.one? && args[0].is_a?(Hash)
*args, proc = args[0].to_a[0]
elsif args.many?
proc = args.pop
end
end
raise MissingStatementBlockError unless proc.is_a? Proc
@matches, @proc = args, proc
end
def call
@proc.call
end
def match?(object)
@matches.each {|m| break true if perform_match(object, m) } === true
end
private
def perform_match(object, match)
return method_match(object, match) if match.is_a?(Symbol) && object.respond_to?(match)
match === object
end
def method_match(object, match)
result = object.send(match) rescue nil
[true, false].include?(result) ? result : (match === object)
end
end
def initialize(&block)
@cases = []
@found = false
instance_eval(&block)
end
def on(*args, &block)
@cases << Case.new(*args, &block)
end
def default(&block)
raise MultipleDefaultCaseError unless @default.nil?
@default = block.to_proc
end
def call(object)
result = @cases.each do |kase|
found! if kase.match? object
break kase.call if found?
end
found? ? result : default_or_nil
end
private
def default_or_nil
@default.call if @default.is_a? Proc
end
def found!
@found = true
end
def found?
@found === true
end
end
module Kernel
def switch(object, &block)
SwitchOn.new(&block).call(object)
end
end
@estum
Copy link
Author

estum commented Aug 25, 2014

Examples:

# support methods:
value = []
switch value do
  on empty?: -> { "none" }
  on one?:   -> { "one" }
  on many?:  -> { "many" }
end

# More:
switch value do
  on Hash  => proc { value }
  on Array => lambda { Hash[value] }
  on Post, Comment do
    value.as_json
  end
  default { fail \_(ツ)_/¯" }
end

# ...and more:
value = "false"     
switch value do
  on /^(\d+)$/,       ->{ Regexp.last_matches[1].to_i }    # Sorry, no $N-vars :'(
  on /^(\d+\.?\d*)$/, ->{ value.to_f }
  on /^true|false$/i, ->{ value =~ "true" }
  default { value }
end

@focused
Copy link

focused commented Aug 26, 2016

Nice, similar to pattern matching

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment