What's especially nice about this, is that it can be explained as a more or less syntactic transformation. You don't even need a special keyword, just a rule that method bodies beginning with `in PATTERN` are wrapped in an implicit `case [*args, kwargs.any? ? kwargs : nil].compact ... end` expression.
Your example method is a bit tricky, because it rebinds an argument and then presumably uses it in the elided bit (the "..."). If the case statement was the entire method body, given proposed syntactic rule it could be written as:
def for_environment
in String => code unless code in UUID_RE
none # no support for filtering via environment codes
in UUID_RE => id
Environment.find_by(id:) || none
in Environment => env
env
in nil
nil
end
To me this "rhymes" nicely with the ability to write `rescue` blocks at the method level:
def foo(params)
send_request(params)
rescue => e
raise if critical_error?(e)
end
---
That is of course a pretty rough approximation, and the real rules would probably be a bit more complex[0], but it definitely seems achievable and possibly worth doing even without taking the type-annotation aspect into account.
[0]: for a start, patterns would need to be extended to allow binding the block argument in a manner equivalent to `def foo(&block)`
I think introducing defp, with similar semantics to Elixir -- i.e. with overloading -- would actually be quite beneficial, while still feeling like Ruby.
For example, with overloading,
defp for_environment(Environment => environment)
case
when environment.isolated?
where(environment:)
when environment.shared?
where(environment: [nil, *environment])
else
none
end
end
defp for_environment(nil)
where(environment: nil)
end
defp for_environment(String => code unless code in UUID_RE)
none # no support for filtering via environment codes
end
defp for_environment(UUID_RE => id)
return none unless environment = Environment.find_by(id:)
# calls the pattern matched method for Environment
for_environment(environment)
end
Which would be equivalent to (using syntax from OP),
defp for_environment
in String => code unless code in UUID_RE
none # no support for filtering via environment codes
in UUID_RE => id
return none unless environment = Environment.find_by(id:)
case
when environment.nil?
where(environment: nil)
when environment.isolated?
where(environment:)
when environment.shared?
where(environment: [nil, *environment])
else
none
end
in Environment => environment
case
when environment.isolated?
where(environment:)
when environment.shared?
where(environment: [nil, *environment])
else
none
end
in nil
where(environment: nil)
end
Which would be equivalent to,
def for_environment(environment)
environment =
case environment
in String => code unless code in UUID_RE
return none # no support for filtering via environment codes
in UUID_RE => id
return none unless env = Environment.find_by(id:)
env
in Environment => env
env
in nil
nil
end
case
when environment.nil?
where(environment: nil)
when environment.isolated?
where(environment:)
when environment.shared?
where(environment: [nil, *environment])
else
none
end
end
In this case, I feel like defp with overloading could really clean up the code (defp without overloading, not so much). You no longer have to add the input transformation to your mental stack when groking the method.
I also don't really see a problem with allowing &block like a normal def.
Your example method is a bit tricky, because it rebinds an argument and then presumably uses it in the elided bit (the "..."). If the case statement was the entire method body, given proposed syntactic rule it could be written as:
To me this "rhymes" nicely with the ability to write `rescue` blocks at the method level: ---That is of course a pretty rough approximation, and the real rules would probably be a bit more complex[0], but it definitely seems achievable and possibly worth doing even without taking the type-annotation aspect into account.
[0]: for a start, patterns would need to be extended to allow binding the block argument in a manner equivalent to `def foo(&block)`