谈谈函数式响应式编程(Functional Reactive Programming)

目录

What is Functional Programming

第一部分概念较多, 如果没有耐心看下去的话, 可直接看重点标注的定义

What is side effects

Let's take a look at the first pair with this example

public int square(int x) {
    return x * x;
}

Here, the input you're used to thinking about is int x, and the output you're used to is also an int

public void processNext() {
    Message message = InboxQueue.popMessage();

    if (message != null) {
        process(message);
    }
}

The second piece of code has hidden inputs and outputs. It requires things, and causes things, but you could never guess what just by looking at the API

These hidden inputs and outputs have an official name: "side-effects"

Are Side Effects Bad?

we have to trust that the the hidden expectations of the original programmer were correct, and will remain correct as time marches on

Can we test this code? Not in isolation. Unlike a circuit board, we can't just plug into its inputs and check its outputs

What is a Pure Function

A function is called 'pure' if all its inputs are declared as inputs - none of them are hidden - and likewise all its outputs are declared as outputs

What is Functional Programming

Functional programming is about writing pure functions, about removing hidden inputs and outputs as far as we can, so that as much of our code as possible just describes a relationship between inputs and outputs

What is a Functional Programming Language

A functional programming language is one that supports and encourages programming without side-effects

Features of functional languages

  • First-Class Functions - Functional programming requires that functions are first-class, which means that they are treated like any other values and can be passed as arguments to other functions or be returned as a result of a function

  • Pure Function - 函数的结果只受函数参数影响, 且不影响外部变量 / 函数内部不使用能被外部函数影响的变量

  • Immutable Data - Purely functional programs typically operate on immutable data. Instead of altering existing values, altered copies are created and the original is preserved

  • Lazy Evaluation - Since pure computations are referentially transparent they can be performed at any time and still yield the same result. This makes it possible to defer the computation of values until they are needed, that is, to compute them lazily

  • Recursion

Recursion is heavily used in functional programming as it is the canonical and often the only way to iterate. Functional language implementations will often include tail call optimisation to ensure that heavy recursion does not consume excessive memory

Lazy evaluation avoids unnecessary computations and allows, for example, infinite data structures to be defined and used

In functional programming, programs are executed by evaluating expressions

In contrast with imperative programming where programs are composed of statements which change global state when executed. Functional programming typically avoids using mutable state

Benefits of functional programming

Functional programming is known to provide better support for structured programming than imperative programming. To make a program structured it is necessary to develop abstractions and split it into components which interface each other with those abstractions. Functional languages aid this by making it easy to create clean and simple abstractions. It is easy, for instance, to abstract out a recurring piece of code by creating a higher-order function, which will make the resulting code more declarative and comprehensible

Functional programs are often shorter and easier to understand than their imperative counterparts

Since various studies have shown that the average programmer's productivity in terms of lines of code is more or less the same for any programming language, this translates also to higher productivity

Functional Programming in iOS

Masonry

iOS开发中链式函数编程的典例:

make.centerY.equalTo(self.view).offset(100);

如何实现类似masonry的链式函数编程呢? (引自深入浅出-iOS函数式编程的实现 && 响应式编程概念)

有一个Person类, 它的两个方法如下

- (void)run{
    NSLog(@"run");
}

- (void)study {
    NSLog(@"study")
}

要实现这样的效果

person.run().study().run();

我们可以这样做

- (Person * (^)())run {  
    Person * (^block)() = ^() {            
        NSLog(@"run");           
        return self;        
    };       
    return block;
}

- (Person * (^)())study {
    Person * (^block)() = ^()  {            
        NSLog(@"study"); 
        return self;        
    };
    return block;
}

事实上masonry也是这么实现的

- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

PromiseKit

我们来看看PromiseKit实现UI Animation的例子(更多例子, 请参考PromiseKit官网)

[UIView promiseWithDuration:0.3 animations:^{
    view.frame = CGRectOffset(view.frame, 0, -100);
}].then(^{
    return [self doWork];
}).catch(^(NSError *error){
    [[UIAlertView …] show];
}).finally(^{
    view.frame = CGRectOffset(view.frame, 0, +100);
})

Swift

尽管我不认为Swift是一门函数式编程语言(对于语言的讨论可以参考Which Programming Languages Are Functional?), 但是相比于Objective-C来说Swift最大优点就是

Swift为iOS编程世界引入了一个新的范式: 函数式范式

我们来看这样一个例子(代码的含义比较简单, 就不解释了)

var evens = [Int]()
for i in 1...10 {
  if i % 2 == 0 {
    evens.append(i)
  }
}
println(evens)

使用Functional Filtering可以写成这样

func isEven(number: Int) -> Bool {
  return number % 2 == 0
}
evens = Array(1...10).filter(isEven)
println(evens)

运用Swift的closures以上代码还可以简化成

evens = Array(1...10).filter { (number) in number % 2 == 0 }
println(evens)

更甚

evens = Array(1...10).filter { $0 % 2 == 0 }
println(evens)

Functional Programming in Android

Glide

Glide作为新生代Android图片库的代表, 和Fresco都是值得推荐的

Glide, Fresco与Picasso的比较可以参考[ Android ] Fresco 与 Picasso 、Glide 的比较Picasso&Glide&Fresco比较

Glide的使用非常简单和函数式

// For a simple view:
@Override public void onCreate(Bundle savedInstanceState) {
  ...
  ImageView imageView = (ImageView) findViewById(R.id.my_image_view);

  Glide.with(this).load("http://goo.gl/gEgYUd").into(imageView);
}

// For a simple image list:
@Override public View getView(int position, View recycled, ViewGroup container) {
  final ImageView myImageView;
  if (recycled == null) {
    myImageView = (ImageView) inflater.inflate(R.layout.my_image_view, container, false);
  } else {
    myImageView = (ImageView) recycled;
  }

  String url = myUrls.get(position);

  Glide
    .with(myFragment)
    .load(url)
    .centerCrop()
    .placeholder(R.drawable.loading_spinner)
    .crossFade()
    .into(myImageView);

  return myImageView;
}

What is Reactive Programming

以下是Wiki Reactive Programming的定义

reactive programming is a programming paradigm oriented around data flows and the propagation of change

其实响应式这个概念很早就有了, 例如

  • 当你操作键盘时, 电脑对按键进行中断响应

  • 当你浏览网站时, 页面对点击页面按钮的响应

那么响应式编程的目的是什么呢? 我们来看这样一个经典的例子(引自Wiki Reactive Programming)

For example, in an imperative programming setting, a b c

a = b + c would mean that a is being assigned the result of b+c in the instant the expression is evaluated

and later, the values of b and c can be changed with no effect on the value of a

However, in reactive programming, the value of a would be automatically updated based on the new values

而响应式编程也是很早就有的了, 例如

  • iOS开发中的KVC

  • Android开发中的Broadcast

从中可以看出一点, 所谓的响应式编程很多是基于event bus或observer的, 那么有没有一种更友好, 更强大, 更智能的响应式编程呢?

答案就是本文的主题: Functional Reactive Programming

What is Functional Reactive Programming

按照"国际惯例", 先看下Wiki FRP的定义

Functional reactive programming (FRP) is a programming paradigm for reactive programming (asynchronous dataflow programming) using the building blocks of functional programming (e.g. map, reduce, filter)

简单来说

Functional Reactive Programming = Functional Programming + Reactive Programming

由此, 函数响应式编程集合了两家之所长

那为什么是Functional Programming和Reactive Programming在"一起"了呢? 我认为因为他们两者都最"合适"

  • 函数式编程的这种输入输出特性, 本身就带着些响应式的味道

  • 响应式以往的实现都没有函数式这样的: 简洁, 流畅(想想链式的丝滑)和高度统一(想想filter, map, 再对比下各种广播观察者event bus云云)

Functional Reactive Programming in iOS

ReactiveCocoa

在iOS开发中, 现在一说到函数响应式或MVVM首先想到的应该就是ReactiveCocoa了

我们来看一个ReactiveCocoa的例子(引自ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2)

[[[self.usernameTextField.rac_textSignal
    map:^id(NSString *text) {
        return @(text.length);
    }]
    filter:^BOOL(NSNumber *length) {
        return [length integerValue] > 3;
    }]
    subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
FilterAndMapPipeline.png

RxSwift

iOS开发中, 如果你已经使用Swift语言开发, 除了ReactiveCocoa还可以考虑RxJava

它的最大优势就是属于ReactiveX旗下的Swift版本, 精通了RxSwift就可以无缝切换至RxJS, RxJava, RxAndroid,Rx.Net, RxRuby...

我们来看一个用RxSwift实现UITableView的例子(引自 【RxSwift系列】用RxSwift实现一个UITableView(一))

ViewModel的方法看起来应该是这样的

PS: 在真实应用中, 数据应该是通过网络请求解析JSON数据而获得来的

import Foundation
import RxSwift
import RxDataSources

class ViewModel: NSObject {

    func getUsers() -> Observable<[SectionModel<String, User>]> {
    return Observable.create { (observer) -> Disposable in
        let users = [User(followersCount: 19_901_990, followingCount: 1990, screenName: "Marco Sun"),
            User(followersCount: 19_890_000, followingCount: 1989, screenName: "Taylor Swift"),
            User(followersCount: 250_000, followingCount: 25, screenName: "Rihanna"),
            User(followersCount: 13_000_000_000, followingCount: 13, screenName: "Jolin Tsai"),
            User(followersCount: 25_000_000, followingCount: 25, screenName: "Adele")]
        let section = [SectionModel(model: "", items: users)]
        observer.onNext(section)
        observer.onCompleted()
        return AnonymousDisposable{}
    }
}

viewDidLoad()方法是这样的

override func viewDidLoad() {

    super.viewDidLoad()
    view.addSubview(tableView)
    tableView.registerClass(TableViewCell.self, forCellReuseIdentifier: reuseIdentifier)

    viewModel.getUsers()
        .bindTo(tableView.rx_itemsWithDataSource(dataSource))
        .addDisposableTo(disposeBag)
}

Functional Reactive Programming in Android

RxJava

The example below shows what you may already be familiar with:

It calls to the web service, uses a callbacks interface to pass the successful result to the next web service call, define another success callback, and then moves on to the next web service request

As you can see, this results in two nested callbacks

Old Style

// The "Nested Callbacks" Way
    public void fetchUserDetails() {
        // first, request the users...
        mService.requestUsers(new Callback<GithubUsersResponse>() {
            @Override
            public void success(final GithubUsersResponse githubUsersResponse,
                                final Response response) {
                Timber.i(TAG, "Request Users request completed");
                final List<GithubUserDetail> githubUserDetails = new ArrayList<GithubUserDetail>();
                // next, loop over each item in the response
                for (GithubUserDetail githubUserDetail : githubUsersResponse) {
                    // request a detail object for that user
                    mService.requestUserDetails(githubUserDetail.mLogin,
                                                new Callback<GithubUserDetail>() {
                        @Override
                        public void success(GithubUserDetail githubUserDetail,
                                            Response response) {
                            Log.i("User Detail request completed for user : " + githubUserDetail.mLogin);
                            githubUserDetails.add(githubUserDetail);
                            if (githubUserDetails.size() == githubUsersResponse.mGithubUsers.size()) {
                                // we've downloaded'em all - notify all who are interested!
                                mBus.post(new UserDetailsLoadedCompleteEvent(githubUserDetails));
                            }
                        }

                        @Override
                        public void failure(RetrofitError error) {
                            Log.e(TAG, "Request User Detail Failed!!!!", error);
                        }
                    });
                }
            }

            @Override
            public void failure(RetrofitError error) {
                Log.e(TAG, "Request User Failed!!!!", error);
            }
        });
    }

Now, let’s look at the same functionality written with RxJava

public void rxFetchUserDetails() {
        //request the users
        mService.rxRequestUsers().concatMap(Observable::from)
        .concatMap((GithubUser githubUser) ->
                        //request the details for each user
                        mService.rxRequestUserDetails(githubUser.mLogin)
        )
        //accumulate them as a list
        .toList()
        //define which threads information will be passed on
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread())
        //post them on an eventbus
        .subscribe(githubUserDetails -> {
            EventBus.getDefault().post(new UserDetailsLoadedCompleteEvent(githubUserDetails));
        });
    }

RetroFit

现在Android很火的RetroFit结合RxJava就可以写出"动人"的函数响应式实现来

NetworkUtil.getTestObjectRxApi().getTestObjects()
        .doOnNext(new Action1<Map<String, List<TestObjectBean>>>() {
            @Override
            public void call(Map<String, List<TestObjectBean>> stringListMap) {
                Logger.e(Thread.currentThread().getName());
                TestObjectBean testObjectBean = stringListMap.get("results").get(0);
                Logger.d("objectId = " + testObjectBean.getOb());
                testObjectBean.setOb("new id");
            }
        })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Observer<Map<String, List<TestObjectBean>>>() {
            @Override
            public void onCompleted() {
                Logger.e(Thread.currentThread().getName());
                Logger.d("completed");
            }

            @Override
            public void onError(Throwable e) {
                Logger.e(e.getMessage());
            }

            @Override
            public void onNext(Map<String, List<TestObjectBean>> stringListMap) {
                Logger.e(Thread.currentThread().getName());
                for (TestObjectBean testObjectBean : stringListMap.get("results")) {
                    Logger.d(testObjectBean);
                }
            }
        });

References

更多文章, 请支持我的个人博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容