[
https://issues.apache.org/jira/browse/MESOS-2348?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Niklas Quarfot Nielsen updated MESOS-2348:
------------------------------------------
Sprint: Mesosphere Q1 Sprint 3 - 2/20, Mesosphere Q1 Sprint 3 - 3/6 (was:
Mesosphere Q1 Sprint 3 - 2/20)
> Introduce a new filter abstraction for Resources.
> -------------------------------------------------
>
> Key: MESOS-2348
> URL: https://issues.apache.org/jira/browse/MESOS-2348
> Project: Mesos
> Issue Type: Task
> Components: general
> Reporter: Michael Park
> Assignee: Michael Park
> Labels: mesosphere
>
> h1. Motivation
> The main motivation here is to improve the design of the filter abstraction
> for {{Resources}}. With the new design we gain:
> 1. No duplicate code.
> 2. No need to introduce a new class for every filter.
> 3. Safer code with less work.
> 4. Ability to compose filters.
> h2. Overview of the Current Design.
> I think I'll need to start here to provide a clear motivation. Here's the
> current design in short:
> {code}
> class Filter {
> public:
> virtual Resources apply(const Resources& resources) const = 0;
> };
> class FooFilter : public Filter {
> public:
> virtual Resources apply(const Resource& resources) const {
> Resources result;
> foreach (const Resource& resource, resources) {
> if (/* resource is Foo. */) {
> result += resource;
> }
> }
> return result;
> }
> };
> class BarFilter : public Filter {
> public:
> virtual Resources apply(const Resource& resources) const {
> Resources result;
> foreach (const Resource& resource, resources) {
> if (/* resource is Bar. */) {
> result += resource;
> }
> }
> return result;
> }
> };
> {code}
> h3. Disadvantages
> 1. Duplicate code.
> Every derived {{Filter}} will have duplicate code, in specific:
> {code}
> Resources result;
> foreach (const Resource& resource, resources) {
> if (/* resource satisfies some predicate. */) {
> result += resource;
> }
> }
> return result;
> {code}
> 2. Need to introduce a new class definition for every new {{Filter}}.
> We should be able to create new filters inline and use them in cases where
> the filter is only needed once and would only hurt readability at the global
> level. If the filter is useful in many contexts, by all means give it a name
> and put it somewhere.
> This is equivalent to lambda expressions which allow us to create new
> functions inline in cases where the function is only useful in this specific
> context but would only hurt readability at the global level. If the lambda is
> useful in many contexts, we give it a name and put it somewhere.
> 3. The constraints are too weak.
> A {{Filter}} must return a subset of the original {{Resources}}. It need not
> be a strict subset, but it must not be a strict superset. With the pure
> virtual apply, the only constraint we put on a new {{Filter}} definition is
> that we take {{Resources}} and return {{Resources}}. We should strive for
> code that prevents preventable bugs.
> 4. Inability to compose filters.
> I've defined 2 filters above, {{FooFilter}} and {{BarFilter}}. We should be
> able to give rise to a new filter with a composition of the filters above.
> For example, if I wanted to {{AND}} the filters, I shouldn't have to
> introduce {{FooAndBarFilter}}.
> This is equivalent to if we had predicates for these. Suppose we have
> {{isFoo(resource)}} and {{isBar(resource)}}. Would we introduce {{isNotFoo}},
> {{isNotBar}}, {{isFooAndBar}}, {{isFooOrBar}}, {{isFooNotBar}}, etc? If
> {{FooAndBar}} is a common concept we use all the time, sure. But in general,
> we would simply compose our predicates: {{!isFoo(resource)}},
> {{!isBar(resource)}}, {{isFoo(resource) && isBar(resource)}},
> {{isFoo(resource) || isBar(resource)}}, {{isFoo(resource) &&
> !isBar(resource)}}.
> h2. Overview of the New Design
> {code}
> class Filter {
> public:
> typedef lambda::function<bool(const Resource&)> Predicate;
> Filter(const Predicate& _predicate)
> : predicate(_predicate) {}
> Resources operator()(const Resources& resources) const {
> Resources result;
> foreach (const Resource& resource, resources) {
> if (predicate(resource)) {
> result += resource;
> }
> }
> return result;
> }
> Filter operator ! ();
> private:
> Filter operator && (
> const Filter &lhs,
> const Filter &rhs);
> Filter operator || (
> const Filter &lhs,
> const Filter &rhs);
> Predicate predicate;
> };
> bool isFoo(const Resource& resource) {
> return /* resource is Foo. */
> }
> Filter FooFilter = Filter(isFoo);
> Filter BarFilter = Filter([](const Resource& resource) {
> return /* resource is Bar. */
> });
> {code}
> h3. Addressing the Disadvantages
> 1. No duplicate code.
> We've removed the duplicate code by making the predicate the customization
> point.
> 2. No need to introduce a new class definition.
> We can certainly introduce named filters if they're useful in many contexts
> such as {{FooFilter}} and {{BarFilter}} above, but there's no *need* to.
> 3. The constraints are strong.
> The creator of a new {{Filter}} simply specifies the predicate. The
> constraint, "filter returns a subset of the original Resources" cannot be
> violated.
> 4. Composability.
> Using the overloaded logical operators (We can change this to be named
> functions if people prefer), we can compose existing filters.
> {{Filter FooAndBarFilter = FooFilter && BarFilter;}} gives us a composed
> filter that requires both filters to be satisfied.
--
This message was sent by Atlassian JIRA
(v6.3.4#6332)