A simple JavaScript Functional Programming Tutorial
Preface
When I was in Beijing in early April, Xu Hao said that the articles written by my colleagues in our company were too simple, too detailed, and then I picked up sesame seeds and lost watermelon, the root cause is that the project is too busy ). Last week, I joined the "Martin Fowler Shenzhen tour" event with several other colleagues. My colleague zaxi and I contributed a "FullStack Language JavaScript", along with Yang yunjianghu's "big devil) the topic is "Mastering functional programming and Controlling System Complexity", and Li xinjianghu is called Xin ye.)
During rehearsals with other colleagues, we suddenly found that our topics were more or less related. I also talked about the event-based concurrency mechanism and functional programming. If you think about it, it should be unrelated to the features of JavaScript itself:
On the second day after the meeting, I suddenly wanted to rewrite an aggregate model in the project code in the form of functional programming. The results showed that the idea had a slight relationship with NoSQL, I further found myself many shortcomings.
The following example is from the actual project scenario. However, Domain switching does not affect the reading and understanding of the underlying mechanism.
A bookmarks Application
Imagine an application where you can see a list of subscribed RSS feeds. Each item in the list is called a Feed.id, The title of an articletitleLink to an articleurl.
The data model looks like this:
- Var feeds = [
- {
- 'Id': 1,
- 'Url': 'http: // abruzzi.github.com/2015/03/list-comprehension-in-python /',
- 'Title': 'list comprehension and generator' in Python'
- },
- {
- 'Id': 2,
- 'Url': 'http: // abruzzi.github.com/2015/03/build-monitor-script-based-on-inotify /',
- 'Title': 'build an automatic monitoring script using inotify/fswatch'
- },
- {
- 'Id': 3,
- 'Url': 'http: // abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery /',
- 'Title': 'Use underscore. js to build front-end applications'
- }
- ];
When this simple application does not have any user-related information, the model is very simple. But soon, the application needs to be extended from the standalone version to the Web version. That is to say, we have introduced the user concept. Each user can see such a list. In addition, you can add a Feed to favorites. Of course, after favorites, you can also view the list of favorite feeds.
Since each user can add multiple feeds to favorites and each Feed can be added to favorites by multiple users, shows the many-to-many relationship between them. You may think
- $ curl http://localhost:9999/user/1/feeds
To get users1AllfeedBut these are not important. The real problem is that after you get all the feeds, you need to add an attribute for each Feed on the UI.makred. This attribute indicates whether the feed has been added to favorites. Corresponding to the interface, it may be a yellow star or a red heart.
Server Aggregation
Due to the limitations of relational databases, You need to perform an aggregation on the server side. For example, wrap the feed object to generateFeedWrapperObjects such:
- public class FeedWrapper {
- private Feed feed;
- private boolean marked;
-
- public boolean isMarked() {
- return marked;
- }
-
- public void setMarked(boolean marked) {
- this.marked = marked;
- }
-
- public FeedWrapper(Feed feed, boolean marked) {
- this.feed = feed;
- this.marked = marked;
- }
- }
Then defineFeedServiceSuch:
- public ArrayList<FeedWrapper> wrapFeed(List<Feed> markedFeeds, List<Feed> feeds) {
- return newArrayList(transform(feeds, new Function<Feed, FeedWrapper>() {
- @Override
- public FeedWrapper apply(Feed feed) {
- if (markedFeeds.contains(feed)) {
- return new FeedWrapper(feed, true);
- } else {
- return new FeedWrapper(feed, false);
- }
- }
- }));
- }
Well, this is also a complementary implementation, but static and strong-type Java is barely doing this, and new changes will almost certainly happen ), we still put this part of logic in JavaScript to see how it simplifies this process.
Client Aggregation
The topic is coming soon. We will use this articlelodashIt is used as a library for functional programming to simplify code writing. Because JavaScript is a dynamic and weak language, we can add attributes to an object at any time.mapYou can complete the above Java code:
- _. Map (feeds, function (item ){
- Return _. extend (item, {marked: isMarked (item. id )});
- });
-
- The function isMarked will do the following:
-
- Var userMarkedIds = [1, 2];
- Function isMarked (id ){
- Return _. includes (userMarkedIds, id );
- }
Check whether the input parameters are in a list.userMarkedIds, This list may be obtained by the following requests:
$ Curl http: /localhost: 9999/user/1/marked-feed-ids
To reduce the data size transmitted over the network, you can also/marked-feedsAll requests are sent, and then done locally_.pluck(feeds, 'id')To extract allidAttribute.
Well, the code is much simpler. However, if this step can only be achieved, it will not be of much benefit. Now the demand has changed. We need to display the favorites of the current user on another page to show all the favorite feeds of the user ). As programmers, we don't want to write a new set of interfaces. If we can reuse the same logic, it would be better.
For example, for the above list, we already have the corresponding template:
- {{#each feeds}}
- <li class="list-item">
- <div class="section" data-feed-id="{{this.id}}">
- {{#if this.marked}}
- <span class="marked icon-favorite"></span>
- {{else}}
- <span class="unmarked icon-favorite"></span>
- {{/if}}
- <a href="/feeds/{{this.url}}">
- <div class="detail">
-
- </div>
- </a>
- </div>
- </li>
- {{/each}}
In fact, this code can be reused on the favorites page.markedSet all attributes to true! Simple. Soon we can write the corresponding code:
- _.map(feeds, function(item) {
- return _.extend(item, {marked: true});
- });
Pretty! What's more, it can work normally! But as a programmer, you soon discovered the similarities between the two codes:
- _. Map (feeds, function (item ){
- Return _. extend (item, {marked: isMarked (item. id )});
- });
-
- _. Map (feeds, function (item ){
- Return _. extend (item, {marked: true });
- });
-
- Eliminating duplication is a basic literacy of a programmer. However, it seems a little difficult to eliminate these two points: Behind marked: function call and value! To simplify the process, we have to create an anonymous function and then simplify it in callback mode:
-
- Function wrapFeeds (feeds, predicate ){
- Return _. map (feeds, function (item ){
- Return _. extend (item, {marked: predicate (item. id )});
- });
- }
For the feed list, we need to call:
WrapFeeds (feeds, isMarked );
For favorites, You need to input an anonymous function:
WrapFeeds (feeds, function (item) {return true });
Inlodash._.wrapTo simplify:
WrapFeeds (feeds, _. wrap (true ));
Well, now we can see that the simplification is good, the code is reduced, and you can also read some of it, on the premise that you are familiar with functional programming reading ).
Further steps
If you carefully examineisMarkedFunction, it will find that its external dependency is not very beautiful and this external dependency comes from the asynchronous request of the Network), that is, we needmarkedIdsCan be definedisMarkedFunction to define the function.BindTo a fixed point, if the logic of the function is complex, it will inevitably affect the maintainability of the code or, worse, more maintenance ).
To isolate this part of code, we needidsPass it out as a parameter and get a function that can be used as a predicate to determine whether an id is a predicate in the list.
In short, we need:
- var predicate = createFunc(ids);
- wrapFeeds(feeds, predicate);
HerecreateFuncThe function accepts a list as a parameter and returns a predicate function. This predicate function is the one mentioned above.isMarked. This magical process is called kerihuacurrying, Or partial functionpartial. Inlodash, This is easy to implement:
- function isMarkedIn(ids) {
- return _.partial(_.includes, ids);
- }
This function willidsWhen called, it is expanded:_.includes(ids, <id>). But this<id>Will be passed in during actual iteration:
- $('/marked-feed-ids').done(function(ids) {
- var wrappedFeeds = wrapFeeds(feeds, isMarkedIn(ids));
- console.log(wrappedFeeds);
- });
In this way, our code is simplified:
- $('/marked-feed-ids').done(function(ids) {
- var wrappedFeeds = wrapFeeds(feeds, isMarkedIn(ids));
- var markedFeeds = wrapFeeds(feeds, _.wrap(true));
-
- allFeedList.html(template({feeds: wrappedFeeds}));
- markedFeedList.html(template({feeds: markedFeeds}));
- });