An Introduction to Functional Reactive Programming(翻譯)

函數(shù)響應(yīng)式編程介紹

我今年發(fā)布了一個(gè)關(guān)于函數(shù)響應(yīng)式編程(Funcational Reactive Programming 簡稱FRP)的演講毕泌,試圖分解給它的名字還有你為什么要關(guān)心它的名字叠赦。這是關(guān)于那次演講的文章。

介紹

在過去幾年中搁料,函數(shù)響應(yīng)式編程非常流行熔脂。但是為什么是它融虽?你為什么需要關(guān)心眉睹。

即使是對(duì)于使用像RxJava這類響應(yīng)式框架的人荔茬,F(xiàn)RP背后的根本原因也可能是神秘的废膘。今天要打破這份神秘,把它分解成單獨(dú)的組件:響應(yīng)編程和函數(shù)編程慕蔚。

響應(yīng)編程

首先丐黄,讓我們來看看響應(yīng)代碼的意義。

我們從一個(gè)簡單的例子開始:一個(gè)開關(guān)和一個(gè)燈泡孔飒。當(dāng)你滑動(dòng)開關(guān)灌闺,燈泡打開或者關(guān)閉。

編碼方面坏瞄,兩個(gè)組件是耦合的桂对。通常你不會(huì)關(guān)心它們是怎么耦合的,但是讓我們深入挖掘惦积。

一種方法是讓開關(guān)修改燈泡的狀態(tài)。這種情況下猛频,開關(guān)是主動(dòng)的狮崩,將新的狀態(tài)推給燈泡;而燈泡是被動(dòng)的鹿寻,僅僅接收指令去改變它的狀態(tài)睦柴。

我們將通過在開關(guān)上放一個(gè)箭頭來表示這段關(guān)系——也就是說,連接兩個(gè)元件的是開關(guān)毡熏,不是燈泡坦敌。

這有一個(gè)主動(dòng)解決方案的草圖:這個(gè) Switch 包含一個(gè)LightBulb實(shí)例,當(dāng)它狀態(tài)發(fā)生變化時(shí)它會(huì)修改痢法。

另一種對(duì)這些組件進(jìn)行耦合的方法是讓燈泡監(jiān)聽開關(guān)的狀態(tài)然后響應(yīng)地修改自己狱窘。在這個(gè)模型中,燈泡是響應(yīng)的财搁,根據(jù)開關(guān)的狀態(tài)改變它的狀態(tài)蘸炸;而開關(guān)是可觀察的,其他的是可以觀察到他的狀態(tài)改變尖奔。

這里是被動(dòng)解決方案的一個(gè)示意圖:LightBulb接收到一個(gè) Switch 的監(jiān)聽事件,然后根據(jù)監(jiān)聽修改它自己的狀態(tài)。

最終對(duì)于用戶磨取,主動(dòng)和被動(dòng)的代碼都導(dǎo)致了相同的結(jié)果殿如。兩種方法有什么不同之處呢?

第一個(gè)區(qū)別是誰控制燈泡茴扁。在主動(dòng)模型中铃岔,它必須是調(diào)用LightBulb.power()的額外組件。燈泡具有響應(yīng)性峭火,燈泡自己控制它的亮度德撬。

第二個(gè)區(qū)別是誰決定Switch的控制铲咨。在主動(dòng)模型中,Swtich自己決定了它的控制是誰蜓洪。在響應(yīng)模型中纤勒,Switch不知道他的驅(qū)動(dòng)是什么,因?yàn)槠渌M件通過監(jiān)聽接入它隆檀。

你會(huì)覺得這兩種模型互為鏡像摇天。主動(dòng)和被動(dòng)的編碼之間有一種二重性。

然而恐仑,兩個(gè)組件的耦合緊密或者松散還有有一個(gè)細(xì)微的差別泉坐。在主動(dòng)模型中,模塊直接相互控制裳仆。在響應(yīng)模型中腕让,模塊控制他們自己,間接的相互連接歧斟。

讓我們來看看這是怎么樣在現(xiàn)實(shí)生活例子中體現(xiàn)出來的纯丸。這是Trello的主屏幕。它顯示了你從數(shù)據(jù)庫中得到的板静袖。如何體現(xiàn)和主動(dòng)或被動(dòng)模型的關(guān)系呢觉鼻?

使用主動(dòng)模型,當(dāng)數(shù)據(jù)庫變化時(shí)队橙,它將這些改變推給 UI坠陈。但是這沒有任何意義:為什么我的數(shù)據(jù)庫要關(guān)心UI?為什么它必須檢查主屏幕是否正在顯示捐康,并知道是否應(yīng)該推新數(shù)據(jù)給它仇矾。主動(dòng)模型在DB和UI之間創(chuàng)建了一個(gè)奇怪的緊密耦合。

相比之下解总,響應(yīng)式模型干凈很多∪粑矗現(xiàn)在我的UI監(jiān)聽數(shù)據(jù)的變化,有必要的時(shí)候更新自己倾鲫。數(shù)據(jù)庫只是提供一個(gè)監(jiān)聽的不說話的知識(shí)庫粗合。誰都能更新它,這些改變僅僅反映在需要的UI中乌昔。

這是好萊塢的原則:別大電弧給我們隙疚,我們會(huì)打電話給你。這對(duì)松散耦合代碼來說是很好的磕道,允許你封裝你的組件供屉。

我們現(xiàn)在能回答什么是 響應(yīng)式編程:當(dāng)你首先關(guān)注響應(yīng)式代碼,代替你默認(rèn)的主動(dòng)代碼。

如果你想默認(rèn)為響應(yīng)式伶丐,我的的簡單的監(jiān)聽就不是很好了悼做。它有以下幾個(gè)問題:

第一,每個(gè)監(jiān)聽是獨(dú)一無二的哗魂。我們有 Switch.OnFlipListener肛走,但是只適用于 Switch ÷急穑可以觀察到的每個(gè)模塊都必須實(shí)現(xiàn)它自己的偵聽器設(shè)置朽色。這不僅意味著成串的額外樣板代碼,還意味著你不能重用響應(yīng)模式组题,因?yàn)闃?gòu)建通用的框架葫男。

第二個(gè)問題是每個(gè)觀察者對(duì)可觀察組件有直接使用權(quán)。LightBulb必須有直接的 Switch 才能開始監(jiān)聽它崔列。這導(dǎo)致模塊之間的緊密耦合梢褐,破壞了我們的目標(biāo)。

我們真正想要的是赵讯,如果 Switch .flips()返回一些可以傳遞的通用類型盈咳。我們來看看能返回什么類型,滿足我們的需求瘦癌。

函數(shù)可以返回四個(gè)基本對(duì)象猪贪。子啊一個(gè)軸上返回item的數(shù)量:單個(gè)項(xiàng)或者多個(gè)項(xiàng)跷敬。另一個(gè)軸讯私,表示item是否立馬返回(sync),或者表示項(xiàng)是否稍后將交付的值(async)西傀。

同步返回很簡單斤寇。單個(gè)返回類型為T:任何類型。同樣的拥褂,多個(gè)項(xiàng)只是一個(gè)Iterable<T>娘锁。

使用同步代碼進(jìn)行編程很簡單,因?yàn)楫?dāng)你獲得它們時(shí)饺鹃,你能開始使用返回值莫秆,但是我們不在這個(gè)世界中。響應(yīng)式編碼被指上市異步的:無法知道可觀察組件何時(shí)會(huì)發(fā)出一個(gè)新的狀態(tài)悔详。

因此镊屎,讓我們研究一下異步返回。一個(gè)單一的異步項(xiàng)相當(dāng)于Future<T>茄螃。很好缝驳,但不是我們想要的——一個(gè)可觀察組件可能有多項(xiàng)(例如: Switch 可以打開/關(guān)閉很多項(xiàng))。

我們真正想要的是右下角。我們稱上一象限是一個(gè)Observable<T>用狱。一個(gè)Observable是所有響應(yīng)式框架的基礎(chǔ)运怖。

讓我們看看Observable<T>是怎么工作的。在我們的新代碼中夏伊, Switch .flips()返回一個(gè)Observable<Boolean>——即一個(gè)表示 Switch 狀態(tài)的true/false的序列∫≌梗現(xiàn)在我們的LightBulb,不是直接消費(fèi) Switch 署海,將會(huì)訂閱 Switch 提供的一個(gè)Observable<Boolean>吗购。

這段代碼的表現(xiàn)和我們沒有Observable的代碼是一樣的,但是修復(fù)了我前面提到的兩個(gè)問題砸狞。Observable<T>是一個(gè)廣義的類型捻勉,允許我們?cè)谒厦娼ⅰK梢员粋鬟f刀森,所以我們的組件也不是緊密耦合的了踱启。

讓我們鞏固一下Observable的基礎(chǔ)知識(shí)。Observable是隨著時(shí)間推移的item的集合研底。

這是我展示的一個(gè)彈珠圖埠偿。這條線表示時(shí)間,而圓表示的是Observable推給訂閱者的事件榜晦。

Observable可能導(dǎo)致兩種可能終端狀態(tài)之一:成功的完成和錯(cuò)誤冠蒋。

一個(gè)成功完成彈珠圖中的一條垂直線表示。不是所有集合都是無限的乾胶,也有必要能夠表示它抖剿。例如,你在Netflix放一個(gè)視頻识窿,視頻在某個(gè)時(shí)候會(huì)結(jié)束斩郎。

一個(gè)錯(cuò)誤是由X表示,是由于某種原因?qū)е聰?shù)據(jù)流的結(jié)果無效喻频。例如缩宜,有人拿錘子砸我們的開關(guān),那就值得告訴大家我們的開關(guān)不僅停止發(fā)出任何新的狀態(tài)而且也不能有效的監(jiān)聽更多甥温,因?yàn)樗呀?jīng)壞了锻煌。

函數(shù)式編程

讓我們先把響應(yīng)式編程放到一邊,然后跳到什么是函數(shù)式編程姻蚓。

函數(shù)式編程關(guān)注函數(shù)宋梧。對(duì)吧?我不是說任何簡單的老函數(shù):我們用純函數(shù)來教學(xué)史简。

讓我通過反例解釋下什么是純函數(shù)乃秀。

假設(shè)我們有一個(gè)完全合理的add()函數(shù)肛著,它將兩個(gè)數(shù)加在一起。但是等等跺讯,函數(shù)中所有的空空間是什么枢贿?

哦!看起來像add()發(fā)送文本到控制臺(tái)刀脏。這就是所謂的副作用局荚。add()的目的不是打印到控制臺(tái);它是兩個(gè)數(shù)相加愈污。然而它正在修改應(yīng)用程序的全局的狀態(tài)耀态。

但是等等,還有更多。

哎喲暂雹!它不僅打印到控制臺(tái)首装,還殺死程序。如果你只是看函數(shù)定義(兩個(gè)int輸入杭跪,一個(gè)int輸出),你就不會(huì)知道使用這個(gè)方法會(huì)對(duì)你的應(yīng)用程序造成什么樣的毀壞仙逻。

讓我們看另一個(gè)例子。

這里涧尿,我們?nèi)∫粋€(gè)列表看看所有元素的和和乘積是否一樣系奉。我認(rèn)為這對(duì)[1,2,3]是成立的,因?yàn)?+2+3==6和1*2*3==6.

然而姑廉,檢查sum()方法是如何實(shí)現(xiàn)缺亮。它不會(huì)影響我們app全局的狀態(tài),但是它修改我們的輸入桥言!這意味著代碼將會(huì)失敗萌踱,因?yàn)樵?code>product(numbers)執(zhí)行的時(shí)候,numbers是空的限书。像天方夜譚虫蝶,這類問題會(huì)一直出現(xiàn)在實(shí)際的不純的函數(shù)中章咧。

任何時(shí)候你在函數(shù)外面改變狀態(tài)都會(huì)產(chǎn)生副作用倦西。正如你所看到的,副作用可能使寫碼變的困難赁严。純函數(shù)不允許有任何的副作用扰柠。

有趣,這意味著純函數(shù)必須返回一個(gè)值疼约。返回類型是void的純函數(shù)什么都做不了卤档,因?yàn)椴荒苄薷妮斎牖蛘呷魏魏瘮?shù)外的狀態(tài)。

它也意味著你的函數(shù)輸入必須是不可變的程剥。我們不允許輸入可變劝枣,否則當(dāng)一個(gè)函數(shù)執(zhí)行時(shí)并發(fā)代碼可能改變輸入,打破了純潔性。順便說下舔腾,這也意味著輸出也應(yīng)該是不可變的(否則他們不能作為純函數(shù)的輸入)溪胶。

純函數(shù)有第二個(gè)方面,給相同的輸入稳诚,它們必須總是返回相同的輸出哗脖。換句話說,它們布恩那個(gè)依賴與任何函數(shù)外部的狀態(tài)扳还。

例如才避,檢查和用戶打招呼的函數(shù)。它是沒有任何副作用的氨距,但是它隨機(jī)返回兩個(gè)問候中的一個(gè)桑逝。隨即形式通過一個(gè)外部的靜態(tài)函數(shù)還提供的。

這讓寫碼更加困難俏让,有兩個(gè)原因肢娘。首先,不管的輸入該方法有不一致的結(jié)果舆驶。如果你知道給函數(shù)相同的輸入結(jié)果是相同的輸出橱健,那寫碼就更容易了。其次沙廉,你現(xiàn)在有一個(gè)有外部依賴項(xiàng)的行數(shù)拘荡;如果外部依賴被任何方式改變,函數(shù)運(yùn)行的表現(xiàn)可能會(huì)不同撬陵。

對(duì)于面向?qū)ο箝_發(fā)者來說是困惑的珊皿,這意味著純函數(shù)甚至不能訪問它包含在其中的類的狀態(tài)。例如:Random的方法本質(zhì)是不純的巨税,因?yàn)檎{(diào)用它們根據(jù)Random的內(nèi)部狀態(tài)返回新的值蟋定。

簡單地說,函數(shù)式編程是基于純函數(shù)的草添。純函數(shù)是一個(gè)不能消費(fèi)或者改變外部狀態(tài)的函數(shù)——他們完全依賴于輸入來獲取輸出驶兜。

有一點(diǎn)困惑,經(jīng)常碰到人介紹FP:你是如何改變的远寸?例如抄淑,如果我想要獲取一個(gè)整數(shù)列表并將它們的所有值都加倍?當(dāng)然你必須改變列表,對(duì)吧?

嗯驰后,不完全是肆资。你能用純函數(shù)轉(zhuǎn)換你的列表。這是一個(gè)使列表中的值加倍的純函數(shù)灶芝。沒有副作用郑原,沒有外部狀態(tài)唉韭,也沒有輸入/輸出的改變。這個(gè)函數(shù)提供了改變工作犯犁,所以你不必做纽哥。

然而,我們寫的函數(shù)靈活度不高栖秕。它能做是將數(shù)組中每個(gè)數(shù)加倍春塌,但是我能想象我們對(duì)一個(gè)整型數(shù)組做更多的操作:所有值三倍,所有值減半...想法是無限的簇捍。

讓我們寫一個(gè)通用的整數(shù)操作器只壳。我們從一個(gè)Function接口開始,這允許我們定義我們想要的如何才做每個(gè)整數(shù)暑塑。

然后吼句,我們將寫一個(gè)map()行數(shù),同時(shí)接收整數(shù)數(shù)組和一個(gè)Function事格。每一個(gè)數(shù)組中的整數(shù)惕艳,我們都能應(yīng)用Function

瞧!有了一些額外的代碼驹愚,我們現(xiàn)在可以將任何整數(shù)數(shù)組映射到另一個(gè)整數(shù)數(shù)組远搪。

我們更深入的看這個(gè)例子:為什么不使用泛型,這樣我們對(duì)于任何列表都可以從一種類型轉(zhuǎn)換為另一種類型逢捺。修改之前的代碼不是那么難谁鳍。

現(xiàn)在,我們可以將任何List<T>映射到List<R>中劫瞳。例如倘潜,我們能獲取一個(gè)字符串列表轉(zhuǎn)換為每個(gè)字符串長度的列表。

我們的map()被稱為高階函數(shù)志于,因?yàn)樗邮找粋€(gè)函數(shù)作為參數(shù)涮因。能夠傳遞和使用函數(shù)的去昂達(dá)工具因?yàn)樗试S代碼更加靈活。而不是寫重復(fù)的伺绽、特定的函數(shù)养泡,你可以寫像map()這樣的通用函數(shù),這函數(shù)很多情況下都是可重用的憔恳。

除了由于沒有外部狀態(tài)工作更容易以外瓤荔,純函數(shù)也讓編寫函數(shù)更容易净蚤。如果你一個(gè)函數(shù)是A->B而另一個(gè)是B->C钥组,那么我們能把這兩個(gè)函數(shù)結(jié)合起來創(chuàng)建一個(gè)A->C。

當(dāng)你便攜不純函數(shù)的時(shí)候今瀑,經(jīng)常出現(xiàn)不想要的副作用程梦,這意味著很難知道組合函數(shù)是否能正常工作点把。只有純函數(shù)能保證程序員的組合是安全的。

讓我們來看一個(gè)合成的例子屿附。這是另一個(gè)通常的FP函數(shù)——filter()郎逃。他使我們所有列表中的項(xiàng)。現(xiàn)在挺份,我們可以通過組合兩個(gè)函數(shù)在轉(zhuǎn)換之前過濾我們的列表褒翰。

我們現(xiàn)在有了一對(duì)小但是功能強(qiáng)大的轉(zhuǎn)換函數(shù),它們通過允許我們組合它們來增強(qiáng)它們的功能匀泊。

函數(shù)式編程的內(nèi)容遠(yuǎn)比我這里介紹的多优训,但這一速成課程足以理解“FRP”部分的“FP”部分。

函數(shù)響應(yīng)式編程

讓我們看看函數(shù)式編程如何增強(qiáng)響應(yīng)式代碼各聘。

假設(shè)我們的 Switch 揣非,提供的不是Observable<Boolean>,而是提供一個(gè)有枚舉基礎(chǔ)的Observable<State>躲因。

現(xiàn)在表面上看來我們沒法 SwitchLightBulb因?yàn)槲覀儾患嫒莘盒驮缇础5怯幸粋€(gè)很明顯的方法Observable<State>模仿Observable<Boolean>——如果我們能將一種類型的流轉(zhuǎn)換成另一種類型,那怎么做大脉?

還記得我們?cè)贔P中看到的map()函數(shù)么搞监?它將一個(gè)類型的同步集合轉(zhuǎn)換成另一種類型。如果我們將相同的想法應(yīng)用到一個(gè)異步集合上呢镰矿?像Observable腺逛。

Ta-da:這是map(),但是是為了ObservableObservable.map()是所謂的操作符衡怀。操作符讓你以任何方式轉(zhuǎn)換一個(gè)Observable流棍矛。

這是一個(gè)操作符的彈珠圖,比我們之前看到的要復(fù)雜抛杨。讓我們來分解一下:

上面的一行表示輸入流:一系列彩色的圓圈够委。

中間框表示操作符:將圓形轉(zhuǎn)換為正方形。

下面的一行表示輸出流:一系列彩色的方塊怖现。

本質(zhì)上茁帽,它是輸入流中的每個(gè)條目的1:1轉(zhuǎn)換。

讓我們把它應(yīng)用到我們的開關(guān)問題屈嗤。我們從Observable<State>開始潘拨。然后我們用map()這樣每當(dāng)一個(gè)新狀態(tài)被釋放時(shí),它就會(huì)被轉(zhuǎn)換為一個(gè)布爾值饶号;因此map()返回Observable<Boolean>√罚現(xiàn)在我們有了正確的類型,我們能構(gòu)造我們的LightBulb了茫船。

好了琅束,這就是有用的扭屁。但是這和純函數(shù)有什么關(guān)系呢?你不能在map()內(nèi)部寫任何東西涩禀,包括副作用料滥?當(dāng)然性穿,你可以...但是疾嗅,你的代碼很難和它一起工作竭钝。另外肩钠,你錯(cuò)過了一些無副作用的操作符組合牡彻。

想象我們的State枚舉在兩個(gè)以上吴裤,但是我們只關(guān)心開啟/關(guān)閉狀態(tài)突硝。這種情況下设拟,我我們希望過濾掉中間的狀態(tài)雁社≡【看,在FRP中還有一個(gè)filter()操作符霉撵;我們可以和map()組合起來獲得我們想要的結(jié)果磺浙。

如果你將FRP的代碼和FP的代碼進(jìn)行對(duì)比,你會(huì)看到相似之處徒坡。唯一的區(qū)別就是FP代碼是處理同步集合撕氧,而FRP菜嗎處理的是異步集合。

FRP中有大量的運(yùn)算符喇完,覆蓋了許多用于流處理的常見情況伦泥,這些操作可以應(yīng)用和組合在一起。讓我們來看一個(gè)真實(shí)的例子锦溪。

我之前展示的Trello主屏幕非常簡單——它有一個(gè)從數(shù)據(jù)庫到UI的大箭頭不脯。但實(shí)際上,我們的主屏幕使用了大量的數(shù)據(jù)來源刻诊。

特別是防楷,我們有團(tuán)隊(duì)的來源,每個(gè)團(tuán)隊(duì)內(nèi)部都有多個(gè)董事會(huì)则涯。我們要確保我們能同步接收到這些數(shù)據(jù);我們不希望有不匹配的數(shù)據(jù)复局,比如沒有它的父團(tuán)隊(duì)的董事會(huì)。

為了解決這個(gè)問題粟判,我們可以使用combineLatest()操作符亿昏,它接收多個(gè)流并將他們組合成一個(gè)復(fù)合流。特別有用的档礁,每次它的輸入流更新時(shí)它也會(huì)更新角钩,因此我們可以確保我們發(fā)送到UI的數(shù)據(jù)是完整的、最新的。

在FRP方面確實(shí)有大量的操作符彤断。這里有幾個(gè)有用的...通常野舶,當(dāng)人們第一次接觸到FRP時(shí)易迹,他們看到操作符的列表就頭暈了宰衙。

然而這些操作符的目的不是要壓倒一切,而是在應(yīng)用程序中對(duì)典型的數(shù)據(jù)流驚醒建模睹欲。他們是你的朋友供炼,而不是你的敵人。

我的建議是窘疮,每次都要一步一步來袋哼。不要一下子記住所有的元素安撫,相反闸衫,只需要意識(shí)到一個(gè)操作符可能已經(jīng)存在于你想要做的事情中涛贯。當(dāng)你需要的時(shí)候去找他們,再練習(xí)之后你會(huì)習(xí)慣的蔚出。

題外話

我試著回答“什么是函數(shù)響應(yīng)式編程?”“我們現(xiàn)在有了一個(gè)答案:它是響應(yīng)式的流弟翘,結(jié)合了函數(shù)運(yùn)算符。

但是你為什么要嘗試使用FRP呢?

反應(yīng)流允許您通過標(biāo)準(zhǔn)化組件間通信的方法來編寫模塊化代碼骄酗∠∮啵活性數(shù)據(jù)流也允許這些組件之間的松散耦合。

響應(yīng)流本身也是異步的趋翻。也許您的工作完全是同步的睛琳,但是我所使用的大多數(shù)應(yīng)用程序都依賴于異步用戶輸入和并發(fā)操作。使用異步代碼設(shè)計(jì)的框架比嘗試編寫自己的并發(fā)解決方案更容易踏烙。

FRP的函數(shù)部件是有用的师骗,因?yàn)樗茏屇阋砸环N合理的方式處理流。函數(shù)操作符允許你控制流的交互方式讨惩。它還提供了用于復(fù)制一個(gè)邏輯的通用邏輯工具丧凤。

函數(shù)響應(yīng)式編程不是很直觀。大多數(shù)人都是主動(dòng)和不純的編碼步脓,包括自己愿待。你做的時(shí)間足夠長,你的腦海會(huì)開始固化靴患,不純的編碼示威的解決方案仍侥。打破這種思維模式可以讓你通過函數(shù)響應(yīng)式編程編寫更有效的代碼。

資源

我想感謝/指出我為這次演講所做的一些資源鸳君。

原文鏈接:An Introduction to Functional Reactive Programming

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末晓猛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子隐解,更是在濱河造成了極大的恐慌鞍帝,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件煞茫,死亡現(xiàn)場離奇詭異帕涌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)续徽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門蚓曼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人钦扭,你說我怎么就攤上這事纫版。” “怎么了客情?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵其弊,是天一觀的道長。 經(jīng)常有香客問我膀斋,道長梭伐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任仰担,我火速辦了婚禮糊识,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己赂苗,他們只是感情好愉耙,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拌滋,像睡著了一般朴沿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鸠真,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天悯仙,我揣著相機(jī)與錄音龄毡,去河邊找鬼吠卷。 笑死,一個(gè)胖子當(dāng)著我的面吹牛沦零,可吹牛的內(nèi)容都是我干的祭隔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼路操,長吁一口氣:“原來是場噩夢啊……” “哼疾渴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起屯仗,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤搞坝,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后魁袜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桩撮,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年峰弹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了店量。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鞠呈,死狀恐怖融师,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蚁吝,我是刑警寧澤旱爆,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站窘茁,受9級(jí)特大地震影響怀伦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜庙曙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一空镜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦吴攒、人聲如沸张抄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽署惯。三九已至,卻和暖如春镣隶,著一層夾襖步出監(jiān)牢的瞬間极谊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來泰國打工安岂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留轻猖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓域那,卻偏偏與公主長得像咙边,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子次员,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容