-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Value
to eventually replace ValueObject
#104
Add Value
to eventually replace ValueObject
#104
Conversation
@smoothdeveloper Sorry it took so long to follow-up on your comments in PR #100, but my availability to work on this project fell considerably over the last 3 months. Nevertheless, I took some more time to understand
The new
Agree and have thus have taken the approach to introduce |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like the direction that this is going. The accumulator interface looks super powerful. I've
knocked together an example of using it to aggregate into a class instance rather than a
dictionary over here: iwillspeak@117cbe2
return Apply(doc, new Tokens(Enumerable.Empty<string>(), typeof (DocoptInputErrorException)), accumulator); | ||
} | ||
|
||
internal T Apply<T>(string doc, ICollection<string> argv, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want this to be public? I can't see how a user would inject an accumulator otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think eventually. For now, I wanted to bring this as an internal design, refactoring and changes that can eventually be added to the public API.
Co-authored-by: Will Speak <[email protected]> Co-authored-by: Will Speak <[email protected]>
@iwillspeak Glad to hear and thanks for your thorough review!
Right, the idea was to put the user code in control and detach
Cool and really awesome to see another use case in action! I iterated another version where I removed some reflection, especially for setting the value. Instead, I went with quoted expressions, where one supplies a getter expression and an assignment/setter is dynamically inferred and compiled. One can then either provide the name-property mapping explicitly: ApplicationResultAccumulators.Create<TestArguments>(
("command" , args => args.Command ),
("<argument>" , args => args.ArgArgument ),
("--option" , args => args.FlagOption ),
("--max-degree-of-parallelism", args => args.FlagMaxDegreeOfParallelism)); or just list the property and have the kebabised name inferred from the property name based on prefix rules ( ApplicationResultAccumulators.Create<TestArguments>(
args => args.Command,
args => args.ArgArgument,
args => args.FlagOption,
args => args.FlagMaxDegreeOfParallelism); |
Yeah, all of those have their plus points. It seems like a nice abstraction. |
This PR is a follow-up from PR #100 and supersedes it. It's less provocative 😉 in the sense that it makes the same improvements without breaking any compatibility. The public API remains the same since
ValueObject
, unlike in PR #100, isn't being removed anymore. Instead, this PR changes & replaces all internal uses ofValueObject
with the new value typeValue
. The arguments for this are as follows (and remain pretty much the same as for PR #100):ValueObject
is aclass
and causes an additional allocation per value.Converting it to astruct
will require quite some review without much added benefits except saving on the wrapper allocation.Add
member ofValueObject
was used in just one place and could be in-lined at the single call site (during leaf pattern matching); the latter was chosen.Add
was the only reasonValueObject
was read-write.Equals
andGetHashCode
were never used or needed.It doesn't make the wrapped value any more strong-typed than using the bareobject
; instead itValueObject
does conversions in some cases that can fail, which isn't the case withValue
.ValueObject
reference that can be null or an instance with anull
forValue
adds confusing semantics at best.All tests still pass.
The new
Value
type is a read-onlystruct
so it doesn't cause any allocations to wrap the underlying type. It's a discriminated union of the values it permits:StringList
is also a new read-only type that holds one or more strings laid out like a cons list. It's stronger-typed than anArrayList
thatValueObject
holds, but it's immutable nature and internal implementation layout also means that it causes a cell allocation per entry. While it's a list, its native and most efficient operations make it act more like a stack. In other words, you can only prepend (push/cons
) items, remove the head item (pop/cdr
) or request the item at the head (peek/car
).StringList
also supports value equality by comparing its items for equality with those of anotherStringList
.While command/argument/option values are internally maintained as
Value
instances now, they are converted toValueObject
at the very end before returning to the user code. This is done through a new overload ofApply
that enables, what I'd like to call, bring your own types (BYOT). That is, the new overload doesn't care about or enforce any representation for values asValueObject
or options asIDictionary<string, ValueObject>
. The overload has the following generic signature:There is a new abstraction in there called
IApplicationResultAccumulator<>
that's defined as:The parsed options and their values are fed through an implementation of the above accumulator interface that then accumulates/folds to some state type
T
. I have added an implementation (seeValueObjectDictionaryAccumulator
) that accumulates commands, arguments and options as keyedValueObject
instances toIDictionary<string, ValueObject>
, thus yielding full backwards compatibility. A second implementation (ValueDictionaryAccumulator
) does that same but encodes values asValue
instead ofValueObject
. This second implementation is only used in tests in this PR, but the main motivation for its addition is for PR #77 (we can eventually think about exposing it publicly too). While it was not necessary, the tests were updated to useValue
rather thanValueObject
since the bulk cases were just initializing values rather than using any features fromValueObject
and the code reads simpler now.