4월 초 베이징에 갔을 때 동급생 Xu Hao가 우리 회사 동료들이 쓴 기사가 너무 단순하고 세부 사항에 너무 많은 관심을 기울였다고 말했습니다. 그리고 수박을 잃어버려서 다시 블로그 업데이트를 하지 않았습니다(사실 프로젝트가 너무 바쁜 것이 근본 원인입니다). 지난 주에는 동료 여러 명과 함께 "Martin Fowler Shenzhen Tour" 이벤트에 참가했습니다. 동료 Tashi와 저는 Yang Yun(Jianghu의 Big Devil으로 알려짐)과 함께 "FullStack Language JavaScript"를 기고했습니다. ) )의 주제는 " 기능적 프로그래밍 및 제어 시스템 복잡성 마스터하기"이고, Li Xin(Jianghu에서는 Xin Ye로 알려짐)의 주제는 "동시성: 과거와 사후 세계"입니다.
다른 동료들과 리허설을 하다가 갑자기 제가 이야기한 부분이 이벤트 기반 동시성 메커니즘과 함수형 프로그래밍과도 관련이 있다는 것을 깨달았습니다. 잘 생각해 보면 JavaScript 자체의 특성과 관련이 있어야 합니다.
Event-Based Node.js는 동시성에서 매우 일반적인 모델입니다.
함수형 프로그래밍으로 자연스럽게 콜백을 지원하므로 비동기/이벤트 메커니즘에 매우 적합합니다.
함수형 프로그래밍으로 글쓰기에 매우 적합합니다. DSL
회의 다음날 갑자기 프로젝트 코드에서 함수형 프로그래밍을 사용하여 집계 모델을 다시 작성하고 싶었는데 그 아이디어가 막연하게 관련되어 있는 것으로 나타났습니다. NoSQL을 사용하면서 더 나아가 나에게 많은 단점이 있다는 것을 발견했습니다.
다음 예는 실제 프로젝트의 한 장면에서 나온 것인데, 도메인이 전환되었지만 그 뒤에 있는 메커니즘을 읽고 이해하는 데에는 전혀 영향을 미치지 않습니다.
사용자가 구독한 RSS 목록을 볼 수 있는 애플리케이션을 상상해 보세요. 목록(피드라고 함)의 각 항목에는 id
, 기사 제목 title
및 기사 링크 url
가 포함되어 있습니다.
데이터 모델은 다음과 같습니다.
var feeds = [ { 'id': 1, 'url': 'http://abruzzi.github.com/2015/03/list-comprehension-in-python/', 'title': 'Python中的 list comprehension 以及 generator' }, { 'id': 2, 'url': 'http://abruzzi.github.com/2015/03/build-monitor-script-based-on-inotify/', 'title': '使用inotify/fswatch构建自动监控脚本' }, { 'id': 3, 'url': 'http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/', 'title': '使用underscore.js构建前端应用' } ];
이 간단한 애플리케이션에 사용자 관련 정보가 없으면 모델은 매우 간단합니다. 하지만 곧 독립 실행형 버전에서 웹 버전으로 애플리케이션을 확장해야 했습니다. 즉, 사용자라는 개념을 도입했습니다. 모든 사용자는 그러한 목록을 하나 볼 수 있습니다. 또한 사용자는 피드를 수집할 수도 있습니다. 물론 수집 후 사용자는 수집된 피드 목록을 볼 수도 있습니다.
각 사용자가 여러 개의 피드를 수집할 수 있고, 각 피드는 여러 사용자가 수집할 수도 있으므로 이들 사이의 다대다 관계는 위 그림과 같습니다. .
$ curl //m.sbmmt.com/:9999/user/1/feeds
를 사용하여 1
사용자의 feed
을 모두 가져오는 등의 작업을 생각할 수도 있지만 실제 문제는 모든 피드를 얻은 후 UI, 각 피드에 속성makred
을 추가해야 합니다. 이 속성은 피드가 수집되었는지 여부를 나타내는 데 사용됩니다. 인터페이스에 따라 노란색 별일 수도 있고 빨간색 하트일 수도 있습니다.
관계형 데이터베이스의 한계로 인해 서버측에서 집계를 수행해야 합니다. 개체 를 래핑하여 FeedWrapper
:
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; } }
와 같은 개체를 생성한 다음 FeedService
:
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); } } })); }
와 같은 서비스 개체를 정의합니다. 좋습니다. 통과 가능한 구현으로 간주될 수 있지만 정적강력한 유형의 Java는 이를 수행하는 것을 약간 꺼려하며 일단 새로운 변경 사항이 발생하면(거의 확실하게 발생함) 이 로직 부분을 JavaScript에 넣습니다. , 이 프로세스가 어떻게 단순화되는지 살펴보겠습니다.
본 기사에서는 lodash
를 함수형 프로그래밍 라이브러리로 사용하여 코드 작성을 단순화하겠습니다. JavaScript는 동적으로 약한 유형의 언어이므로 언제든지 객체에 속성을 추가할 수 있습니다. 이러한 방식으로 간단한 map
작업으로 위의 Java 해당 코드인
_.map(feeds, function(item) { return _.extend(item, {marked: isMarked(item.id)}); });
를 완성할 수 있습니다. > isMarked
var userMarkedIds = [1, 2]; function isMarked(id) { return _.includes(userMarkedIds, id); }
목록에 있는지 확인합니다. 이 목록은 다음 요청으로 얻을 수 있습니다. All of userMarkedIds
$ curl //m.sbmmt.com/:9999/user/1/marked-feed-ids
을 요청한 다음 로컬에서 /marked-feeds
을 수행하여 모든 _.pluck(feeds, 'id')
속성을 추출할 수도 있습니다. id
嗯,代码是精简了许多。但是如果仅仅能做到这一步的话,也没有多大的好处嘛。现在需求又有了变化,我们需要在另一个页面上展示当前用户的收藏夹(用以展示用户所有收藏的feed)。作为程序员,我们可不愿意重新写一套界面,如果能复用同一套逻辑当然最好了。
比如对于上面这个列表,我们已经有了对应的模板:
{{#each feeds}} <li class="list-item"> <p 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}}"> <p class="detail"> <h3>{{this.title}}</h3> </p> </a> </p> </li> {{/each}}
事实上,这段代码在收藏夹页面上完全可以复用,我们只需要把所有的marked
属性都设置为true就行了!简单,很快我们就可以写出对应的代码:
_.map(feeds, function(item) { return _.extend(item, {marked: true}); });
漂亮!而且重要的是,它还可以如正常工作!但是作为程序员,你很快就发现了两处代码的相似性:
_.map(feeds, function(item) { return _.extend(item, {marked: isMarked(item.id)}); }); _.map(feeds, function(item) { return _.extend(item, {marked: true}); });
消除重复是一个有追求的程序员的基本素养,不过要消除这两处貌似有点困难:位于marked:
后边的,一个是函数调用,另一个是值!如果要简化,我们不得不做一个匿名函数,然后以回调的方式来简化:
function wrapFeeds(feeds, predicate) { return _.map(feeds, function(item) { return _.extend(item, {marked: predicate(item.id)}); }); }
对于feed列表,我们要调用:
wrapFeeds(feeds, isMarked);
而对于收藏夹,则需要传入一个匿名函数:
wrapFeeds(feeds, function(item) {return true});
在lodash
中,这样的匿名函数可以用_.wrap
来简化:
wrapFeeds(feeds, _.wrap(true));
好了,目前来看,简化的还不错,代码缩减了,而且也好读了一些(当然前提是你已经熟悉了函数式编程的读法)。
如果仔细审视isMarked
函数,会发现它对外部的依赖不是很漂亮(而且这个外部依赖是从网络异步请求来的),也就是说,我们需要在请求到markedIds
的地方才能定义isMarked
函数,这样就把函数定义绑定
到了一个固定的地方,如果该函数的逻辑比较复杂,那么势必会影响代码的可维护性(或者更糟糕的是,多出维护)。
要将这部分代码隔离出去,我们需要将ids
作为参数传递出去,并得到一个可以当做谓词(判断一个id是否在列表中的谓词)的函数。
简而言之,我们需要:
var predicate = createFunc(ids); wrapFeeds(feeds, predicate);
这里的createFunc
函数接受一个列表作为参数,并返回了一个谓词函数。而这个谓词函数就是上边说的isMarked
。这个神奇的过程被称为柯里化currying
,或者偏函数partial
。在lodash
中,这个很容易实现:
function isMarkedIn(ids) { return _.partial(_.includes, ids); }
这个函数会将ids
保存起来,当被调用时,它会被展开为:_.includes(ids, <id>)
。只不过这个<id>
会在实际迭代的时候才传入:
$('/marked-feed-ids').done(function(ids) { var wrappedFeeds = wrapFeeds(feeds, isMarkedIn(ids)); console.log(wrappedFeeds); });
这样我们的代码就被简化成了:
$('/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})); });
위 내용은 JavaScript 함수형 프로그래밍 튜토리얼에 대한 간략한 토론(그림)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!