-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
[FEATURE beta] Composable Computed Properties #3696
Conversation
Honestly, this is really hard to read. I would be pretty angry if someone gave me this code. However, the intent is wonderful and I'm glad you guys are putting effort into it. I think creating a "wrapper" object of sorts (akin to _.chain from underscore/lodash) might help to separate the intent from the noise. Additionally, I would rewrite your example to use functions and vars to make it easier to grok. Your purpose is being lost in syntax and indentation. |
@stevekane I update the example, should be better now. |
currently example looks good to me. Im am insanely excited for this. |
👍 would love to get rid of a bunch of props that just exist to compose |
Agreed, after actually reading the comments this will be wicked. |
var sort = Ember.computed.sort,
filter = Ember.computed.filter,
filterBy = Ember.computed.filterBy,
mapBy = Ember.computed.mapBy;
var people = [
{name: "David", gender: "m", rank: 3, location: "Denver"},
{name: "Sally", rank: 2, gender: "f", location: "Denver"},
{name: "Franky", rank: 7, gender: "m", location: "Boston"},
{name: "Josie", rank: 8, gender: "f", location: "Chicago"},
{name: "Monica", rank: 1, gender: "f", location: "Denver"},
{name: "LeftEye", rank: 4, gender: "f", location: "Denver"},
{name: "Tboz", rank: 5, gender: "f", location: "Chicago"},
{name: "Neo", rank: 9, gender: "m", location: "San Francisco"},
{name: "Evangeline", rank: 6, gender: "f", location: "New York"},
{name: "Rocky", rank: 10, gender: "m", location: "Boston"},
];
//Filter by gender
//Sort by rank
//Take the first 5
function takeFirstFive = function (each, index) { return index < 5; }
var PeopleController = Ember.ArrayController.extend({
content: people,
//inline "LISP" style -- hard to read and reason about...
topFiveWomen: filter(sort(filterBy("gender", "f"), "rank"), takeFirstFive),
//same as above, but "grouped" by indendation -- still hard to reason about....
topFiveWomen:
filter(
sort(
filterBy("gender", "f"),
"rank"
),
takeFirstFive
),
topFiveWomen: Ember.computed.chain("content", [
filterBy("gender", "f"),
sort("rank"),
filter(takeFirstFive)
])
}); |
@stevekane maybe this is better? I wanted to get general feedback on the idea but topFiveWomen: filterBy("gender", "f").sort("rank").filter(takeFirstFive) |
The chaining is significantly easier to read. |
@@ -450,6 +450,7 @@ ReduceComputedPropertyInstanceMeta.prototype = { | |||
@constructor | |||
*/ | |||
function ReduceComputedProperty(options) { | |||
// TODO: ComputedProperty.apply(this, ?) |
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.
are we saving the dependent CPs? seems like we still need to do that.
@twokul , the style you suggest would be nice but attaching Ember.computed methods to the return values from Ember.computed methods would be required. I don't know how much chaos that might case. Using a chain wrapper sort of prevents this dirtiness from creeping into the system. |
@stevekane adding |
@twokul Either option has extreme appeal to me. I think inside-out FP is to be avoided in the "real world". It's just significantly harder to reason about "at a glance" and syntax errors are common. Thanks for doing this! Huge contributions. |
@stevekane fair enough. noted, thanks for your feedback! |
Checklist of things
Some notes:
We need a couple benchmarks to verify this doesn't do too much perf damage. There are two possible points of concern: the changes to As @twokul noted, chaining is easy to add. It's a question of choosing the syntax. The CP API is currently very light: almost everything is private. Because of this I suspect it's not much trouble to add all the stock macros to the prototype. However, if there are concerns, adding a On The Matter of String LiteralsSomething that's currently very annoying about CPs is that literal vs bound must be decided at macro-design time, rather than callsite-design time. One of the nice things about this approach is it makes it very easy to fix this. var equals = Em.computed.equals,
// "literal"
l = Em.computed.literal;
equals(l('Jaime Lannister'), l('Jaime Lannister')); // "Jaime Lannister" === "Jaime Lannister"
equals('name', l('Jaime Lannister')); // get("name") === "Jaime Lannister"
equals('name', 'otherName'); // get("name") === get("otherName") This would basically require changing all of the current |
Awesome! This cleans up a lot of intermediate properties which makes code hard to read, amazing work! Chaining of computed properties is also suggested in #3406 for implementing throttling and debouncing. Could that implementation take the same approach as described here? |
👍 This would allow me to clean up so much bloat in my models, I've been planning to try and tackle it myself. Also, IMO @hjdivad's idea to differentiate string literals from properties is important to include in any major addition to the computed property API. I can't count how many times I've wanted to use |
I would ❤️ chaining more than the style in the example. Also okay with
|
@mehulkar I like that array syntax a lot actually. Helps split out the chained pieces more visually with minimal additional overhead. |
@mehulkar its not just chaining, is composition. The current syntax feels correct. |
👍 nice and expressive, very little additional code. |
This is great work! I didn't think you'd be making enumerology obsolete so quickly :) |
@kselden would you mind looking at benchmarks to see if they make sense or not? |
name: 'Alex', | ||
state: 'happy' | ||
}); | ||
}); |
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.
This is not the comparison we want; we should be comparing object creation with CCP vs creation with explicit CPs
obj = Ember.Object.extend({
name: 'Alex',
state: 'happy',
equals_state_sleepy: equals('state', 'sleepy'),
not_equals_state_sleepy: not('equals_state_sleepy')
}).create({})
Notes from some benchmark observations.
|
+1 @hjdivad can you c/d this one? |
can you rebase? @hjdivad I know you also spent some time working on this, do you feel comfortable with the current implementation? I would really love to see this get in. |
@stefanpenner this mostly looks good. I have a few things to add
|
this commit is also labeled incorrectly. @rjackson can you provide details as to how to adjust the commit message. |
It should be |
This feature allows you to combine (compose) different computed properties together. So it gives you a really nice "functional programming" like syntax to deal with complex expressions. Example: ```javascript var sort = Ember.computed.sort, union = Ember.computed.union, equal = Ember.computed.equal, mapBy = Ember.computed.mapBy, not = Ember.computed.not, compare = Ember.compare, and = Ember.computed.and; var Person = Ember.Object.extend( napTime: and( equal('state', 'sleepy'), not('thirsty'), not('hungry') ) ); var person = Person.create({ state: 'sleepy', thirsty: true, hungry: false }); var Kitties = Ember.Object.extend( allKitties: sort( union( mapBy('whiteKitties', 'name'), mapBy('blackKitties', 'name') ), compare ) ); var kitties = Kitties.create({ whiteKitties: Ember.A([{ name: 'whiskeys' }, { name: 'bagels' }]), blackKitties: Ember.A([{ name: 'little boots' }]), }); person.get('napTime') => false person.set('thirsty', false) person.get('napTime') => true kitties.get('allKitties') => ['bagels', 'little boots', 'whiskeys'] ``` Kudos to brilliant @hjdivad for his help.
@rjackson this should be good to merge. I will write up a proposal for literal/property agnosticism and add it as a separate PR. |
[FEATURE beta] Composable Computed Properties
@hjdivad have the uncheked TODOs been handled on this? |
@trek I believe the unchecked ones are going to be a part of another PR. |
@twokul excellent. I think we'll be "go" on this when the times comes. |
Unfortunately, the core team has decided to no-go composable computed properties. We're not ready to make the internal computed property API public yet. We've got some interesting things in the works regarding stream composition and don't want to be constrained by this API. We'd love to see CCP be added to ember-cpm as we're sure many apps would benefit from it. |
This feature allows you to combine (compose) different computed
properties together. So it gives you a really nice "functional
programming" like syntax to deal with complex expressions.
Example:
Examples above use
Ember.Object
but Composable CPs work perfectly fine with POJOs:Kudos to brilliant @hjdivad for his help.
Update on two points mentioned below:
Both of them belong in a separate PR, so I'm going to remove them from the check list. It does seem reasonable to have something like that in ember, but I'm not sure what the best approach is to implement this kind of functionality and I don't want it to hold this PR.