[ https://issues.apache.org/jira/browse/MESOS-2348?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=14319981#comment-14319981 ]
Alexander Rukletsov commented on MESOS-2348: -------------------------------------------- https://reviews.apache.org/r/30911/ > 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)