[ 
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)

Reply via email to