4 ms·
As a huge proponent of Ruby's new pattern matching syntax (it has changed the way I write Ruby), I really, really like the idea of defp. So many times I wish I
by ezekg 3y ago
As a huge proponent of Ruby's new pattern matching syntax (it has changed the way I write Ruby), I really, really like the idea of defp. So many times I wish I could overload a method, dispatching by pattern. Instead, I have to pattern match in the method body, which causes the method definition to become convoluted:
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
...
end
I think defp looks and feels very Ruby-like compared to the alternatives (which have the problems you discussed).
And Elixir is great, so I can appreciate the inspiration.
- ckolkey 3y agoI completely agree. That really stood out from the article
- grncdr 3y agoWhat'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)`
- ezekg 3y agoI 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.
- deleted 3y ago[deleted]
- deleted 3y ago[deleted]