目錄
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 - 函數(shù)的結(jié)果只受函數(shù)參數(shù)影響, 且不影響外部變量 / 函數(shù)內(nèi)部不使用能被外部函數(shù)影響的變量
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開發(fā)中鏈式函數(shù)編程的典例:
make.centerY.equalTo(self.view).offset(100);
如何實現(xiàn)類似masonry的鏈式函數(shù)編程呢? (引自深入淺出-iOS函數(shù)式編程的實現(xiàn) && 響應(yīng)式編程概念)
有一個Person類, 它的兩個方法如下
- (void)run{
NSLog(@"run");
}
- (void)study {
NSLog(@"study")
}
要實現(xiàn)這樣的效果
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也是這么實現(xiàn)的
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
PromiseKit
我們來看看PromiseKit實現(xiàn)UI Animation的例子(更多例子, 請參考PromiseKit官網(wǎng))
[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是一門函數(shù)式編程語言(對于語言的討論可以參考Which Programming Languages Are Functional?), 但是相比于Objective-C來說Swift最大優(yōu)點就是
Swift為iOS編程世界引入了一個新的范式: 函數(shù)式范式
我們來看這樣一個例子(代碼的含義比較簡單, 就不解釋了)
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的使用非常簡單和函數(shù)式
// 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
其實響應(yīng)式這個概念很早就有了, 例如
當你操作鍵盤時, 電腦對按鍵進行中斷響應(yīng)
當你瀏覽網(wǎng)站時, 頁面對點擊頁面按鈕的響應(yīng)
那么響應(yīng)式編程的目的是什么呢? 我們來看這樣一個經(jīng)典的例子(引自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
而響應(yīng)式編程也是很早就有的了, 例如
iOS開發(fā)中的KVC
Android開發(fā)中的Broadcast
從中可以看出一點, 所謂的響應(yīng)式編程很多是基于event bus或observer的, 那么有沒有一種更友好, 更強大, 更智能的響應(yīng)式編程呢?
答案就是本文的主題: 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
由此, 函數(shù)響應(yīng)式編程集合了兩家之所長
那為什么是Functional Programming和Reactive Programming在"一起"了呢? 我認為因為他們兩者都最"合適"
函數(shù)式編程的這種輸入輸出特性, 本身就帶著些響應(yīng)式的味道
響應(yīng)式以往的實現(xiàn)都沒有函數(shù)式這樣的: 簡潔, 流暢(想想鏈式的絲滑)和高度統(tǒng)一(想想filter, map, 再對比下各種廣播觀察者event bus云云)
Functional Reactive Programming in iOS
ReactiveCocoa
在iOS開發(fā)中, 現(xiàn)在一說到函數(shù)響應(yīng)式或MVVM首先想到的應(yīng)該就是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);
}];
RxSwift
iOS開發(fā)中, 如果你已經(jīng)使用Swift語言開發(fā), 除了ReactiveCocoa還可以考慮RxJava
它的最大優(yōu)勢就是屬于ReactiveX旗下的Swift版本, 精通了RxSwift就可以無縫切換至RxJS, RxJava, RxAndroid,Rx.Net, RxRuby...
我們來看一個用RxSwift實現(xiàn)UITableView的例子(引自 【RxSwift系列】用RxSwift實現(xiàn)一個UITableView(一))
ViewModel的方法看起來應(yīng)該是這樣的
PS: 在真實應(yīng)用中, 數(shù)據(jù)應(yīng)該是通過網(wǎng)絡(luò)請求解析JSON數(shù)據(jù)而獲得來的
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
現(xiàn)在Android很火的RetroFit結(jié)合RxJava就可以寫出"動人"的函數(shù)響應(yīng)式實現(xiàn)來
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
更多文章, 請支持我的個人博客