Closed kavinphan closed 2 years ago
@kphan32, thank you for the PR! Can you please provide a bit of context on where you think this reduce_when
would be handy? I'd like to understand the use case a bit better.
@adomokos I was working on ingesting data from multiple sources that needed to be marshaled into a common data source. A majority required the same transformation, while others required special treatment. This resulted in the following similar to the original example:
reduce_when(
:data_source,
{
:source_one => [Resources::TransformFromSourceOne],
:source_two => [Resources::TransformFromSourceTwo],
:source_three => [Resources::TransformFromSourceThree]
# [...]
},
:els => [Resources::TransformGeneric]
)
Sweet, I like it, will merge it. I am not sure about reduce_when
name. I was expecting the first argument to be a predicate, then a truthy and falsy case, it took me a while to figure out that the first argument is the key, and the other is the dictionary of key and actions, and then the third argument is the case not found.
It seems you are trying to find a "processor" based on the key you provided. Could we find a better name for this? Do you have other options?
Also, you might want to rebase from main
, I bumped the Ruby versions last night.
WDYT of having the second parameter be named as is
? That way it could be read as reduce_when :key is ___ els ___
?
I took inspiration mainly from using Kotlin previously, which had a when
expression.
This helps. I like Kotlin's when
, it's a decent-enough pattern matching attempt from JetBrains.
WDYT of having the second parameter be named as is? That way it could be read as
reduce_when :key is ___ els ___
?
This could work. Can you show me what you mean by a working prototype?
Lovely :) 🙏🏻
Just my 2 cents about what I'd mainly expect from the interface:
reduce_case(
:key,
when: {
one: [TestDoubles::AddsOneAction],
two: [TestDoubles::AddsTwoAction]
},
els: [TestDoubles::FailureAction]
)
because it would really quack like https://ruby-doc.org/core-3.1.1/doc/syntax/control_expressions_rdoc.html#label-case+Expression. Given the ruby expression is called case
, I'd opt to name the reducer with the same word.
But I'll be obviuosly grateful for any decision <3
I do like what @pioneerskies suggested here:
reduce_case(
:key,
when: {
one: [TestDoubles::AddsOneAction],
two: [TestDoubles::AddsTwoAction]
},
els: [TestDoubles::FailureAction]
)
I also like the idea of making the matching more flexible (goes against strongly typed languages, but we are talking Ruby here).
What do you think about making those changes @kphan32?
@adomokos Those sound good to me, will do :+1:
Though, when
is a keyword so it'll end up being wen
. Not sure how I feel about that, WDYT?
Though, when is a keyword so it'll end up being wen. Not sure how I feel about that, WDYT?
Yeah, wen
is awkward.
What do you think about this?
reduce_case(
:key,
:cases => {
one: [TestDoubles::AddsOneAction],
two: [TestDoubles::AddsTwoAction]
},
:els => [TestDoubles::FailureAction]
)
Let me know if @pioneerskies (or anybody else) has better ideas.
On the fly the only alternative ringing in my brain is a - possibly awkward - whens
.
This is what I'd do in order to achive the most expressive interface bypassing the problem of having keywords
module LightService
module Organizer
class ReduceWhen
extend ScopedReducable
class Arguments
attr_reader :value, :when, :else
def initialize(**args)
validate_arguments(**args)
@value = args[:value]
@when = args[:when]
@else = args[:else]
end
private
def validate_arguments(**args)
raise(
ArgumentError,
"Expected keyword arguments: [:value, :when, :else]. Given: #{args.keys}"
) unless args.keys.intersection(mandatory_arguments).count == mandatory_arguments.count
end
def mandatory_arguments
%i[value when else]
end
end
def self.run(organizer, **args)
arguments = Arguments.new(**args)
lambda do |ctx|
return ctx if ctx.stop_processing?
matched_case = arguments.when.keys.find { |k| k.eql?(ctx[arguments.value]) }
steps = arguments.when[matched_case] || arguments.else
ctx = scoped_reduce(organizer, ctx, steps)
ctx
end
end
end
end
end
This way the example of use would be
reduce_case(
value: :foo,
when: {
'Foo' => [MyAction],
bar: [MyOtherAction]
},
else: [AnotherAction]
)
Don't know if it fits, if it's worth the complexity, etc.
Hope it someway helps :)
@pioneerskies definitely a fan of this, and I don't mind the complexity.
This is looking good, I'll merge it. Can you, @kphan32 add README docs to it?
@adomokos Updated :+1:
Thank you @kphan32 for this great addition to LS! 💯
Was looking for a when-else reducer but couldn't find one, so I made this.
reduce_when
takes in:els
(else is reserved) that is an array of stepsExample